mirror of
https://github.com/facebook/rocksdb.git
synced 2026-07-07 14:47:40 +08:00
Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bd35a845ca | |||
| befd813e95 | |||
| 5c04610b9c | |||
| 8f0de0554f | |||
| d0a0c91017 | |||
| 354588fea4 | |||
| 99ad5553eb | |||
| 1aedd57a62 | |||
| b8a1b8f361 | |||
| ecd3693770 | |||
| 27ec9bc869 | |||
| abe8a29a37 | |||
| 1e0b641048 | |||
| 7c22a2c549 | |||
| 03384bc9d2 | |||
| c2c7d5e916 | |||
| 2760c2aef8 | |||
| 69c986825e | |||
| 6bab3a34e9 | |||
| cc1c3ee54e | |||
| d65d7d657d | |||
| 01e460d538 | |||
| f0fca2b1d5 | |||
| e85d8a6517 | |||
| 09a9ec3ac0 | |||
| 47b424f4bd | |||
| 314352761f | |||
| 4985cea141 | |||
| 596e9008e4 | |||
| 09df74c540 | |||
| 95f6add746 | |||
| 2e5de5a2c3 | |||
| 905dd17b35 | |||
| 1b99947e99 | |||
| a89740fbc6 | |||
| a345b4d60d | |||
| a376c22066 | |||
| 0c6e4674a6 | |||
| b0e20194ea | |||
| c377c2ba15 | |||
| 531a5f88a1 | |||
| 10196d7edc | |||
| 90e245697f | |||
| c871142988 | |||
| 4c41e51c07 | |||
| 00803d619c | |||
| 83031e7343 | |||
| 296b47db25 | |||
| b1f62be10e | |||
| d89483098f | |||
| c861fb390d | |||
| fd00f39f97 | |||
| bb75092574 | |||
| 8972dd1ffa | |||
| 139778dfb3 | |||
| 728e5f5750 | |||
| 48cd7a3aae | |||
| 0be89e87fd | |||
| e295344ae3 | |||
| b42d4a8ad4 | |||
| 8015fc9871 | |||
| b2c302597d | |||
| 2db4e48211 | |||
| 1b0a1abafa | |||
| c1ae1f143e | |||
| 9cc94bfbd3 | |||
| 33970a392c | |||
| 5732c039a4 | |||
| 0ae90f8e28 | |||
| 5a165416ac |
@@ -572,6 +572,58 @@ jobs:
|
||||
gtest-parallel $(</tmp/test_list) --output_dir=/tmp | cat # pipe to cat to continuously output status on circleci UI. Otherwise, no status will be printed while the job is running.
|
||||
- post-steps
|
||||
|
||||
build-linux-arm-test-full:
|
||||
machine:
|
||||
image: ubuntu-2004:202101-01
|
||||
resource_class: arm.large
|
||||
steps:
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- run: make V=1 J=4 -j4 check | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-arm:
|
||||
machine:
|
||||
image: ubuntu-2004:202101-01
|
||||
resource_class: arm.large
|
||||
steps:
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- run: ROCKSDBTESTS_PLATFORM_DEPENDENT=only make V=1 J=4 -j4 all_but_some_tests check_some | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-arm-cmake-no_test_run:
|
||||
machine:
|
||||
image: ubuntu-2004:202101-01
|
||||
resource_class: arm.large
|
||||
environment:
|
||||
JAVA_HOME: /usr/lib/jvm/java-8-openjdk-arm64
|
||||
steps:
|
||||
- pre-steps
|
||||
- run:
|
||||
name: "Set Java Environment"
|
||||
command: |
|
||||
echo "JAVA_HOME=${JAVA_HOME}"
|
||||
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> $BASH_ENV
|
||||
which java && java -version
|
||||
which javac && javac -version
|
||||
- run:
|
||||
name: "Build with cmake"
|
||||
command: |
|
||||
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
|
||||
- run:
|
||||
name: "Build Java with cmake"
|
||||
command: |
|
||||
rm -rf build
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DJNI=1 -DCMAKE_BUILD_TYPE=Release -DWITH_GFLAGS=0 ..
|
||||
make -j4 rocksdb rocksdbjni
|
||||
- post-steps
|
||||
|
||||
build-format-compatible:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
@@ -682,6 +734,7 @@ workflows:
|
||||
- build-linux-gcc-8-no_test_run
|
||||
- build-linux-gcc-9-no_test_run
|
||||
- build-linux-gcc-10-cxx20-no_test_run
|
||||
- build-linux-arm-cmake-no_test_run
|
||||
build-macos:
|
||||
jobs:
|
||||
- build-macos
|
||||
@@ -691,6 +744,9 @@ workflows:
|
||||
build-cmake-mingw:
|
||||
jobs:
|
||||
- build-cmake-mingw
|
||||
build-linux-arm:
|
||||
jobs:
|
||||
- build-linux-arm
|
||||
nightly:
|
||||
triggers:
|
||||
- schedule:
|
||||
@@ -701,3 +757,4 @@ workflows:
|
||||
- master
|
||||
jobs:
|
||||
- build-format-compatible
|
||||
- build-linux-arm-test-full
|
||||
|
||||
@@ -61,6 +61,10 @@ matrix:
|
||||
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
|
||||
@@ -93,6 +97,10 @@ matrix:
|
||||
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
|
||||
|
||||
+35
-2
@@ -296,6 +296,7 @@ else()
|
||||
endif()
|
||||
|
||||
include(CheckCXXSourceCompiles)
|
||||
set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
|
||||
if(NOT MSVC)
|
||||
set(CMAKE_REQUIRED_FLAGS "-msse4.2 -mpclmul")
|
||||
endif()
|
||||
@@ -312,7 +313,6 @@ int main() {
|
||||
auto d = _mm_cvtsi128_si64(c);
|
||||
}
|
||||
" HAVE_SSE42)
|
||||
unset(CMAKE_REQUIRED_FLAGS)
|
||||
if(HAVE_SSE42)
|
||||
add_definitions(-DHAVE_SSE42)
|
||||
add_definitions(-DHAVE_PCLMUL)
|
||||
@@ -320,18 +320,50 @@ 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++11")
|
||||
CHECK_CXX_SOURCE_COMPILES("
|
||||
#include <atomic>
|
||||
std::atomic<uint64_t> x(0);
|
||||
int main() {
|
||||
uint64_t i = x.load(std::memory_order_relaxed);
|
||||
bool b = x.is_lock_free();
|
||||
return 0;
|
||||
}
|
||||
" BUILTIN_ATOMIC)
|
||||
if (NOT BUILTIN_ATOMIC)
|
||||
#TODO: Check if -latomic exists
|
||||
list(APPEND THIRDPARTY_LIBS atomic)
|
||||
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)
|
||||
endif()
|
||||
|
||||
option(WITH_PERF_CONTEXT "Enable perf context" ON)
|
||||
if (NOT WITH_PERF_CONTEXT)
|
||||
add_definitions(-DNPERF_CONTEXT)
|
||||
endif()
|
||||
|
||||
option(FAIL_ON_WARNINGS "Treat compile warnings as errors" ON)
|
||||
if(FAIL_ON_WARNINGS)
|
||||
if(MSVC)
|
||||
@@ -1316,7 +1348,8 @@ if(WITH_BENCHMARK_TOOLS)
|
||||
${ROCKSDB_LIB} ${THIRDPARTY_LIBS})
|
||||
|
||||
add_executable(cache_bench${ARTIFACT_SUFFIX}
|
||||
cache/cache_bench.cc)
|
||||
cache/cache_bench.cc
|
||||
cache/cache_bench_tool.cc)
|
||||
target_link_libraries(cache_bench${ARTIFACT_SUFFIX}
|
||||
${ROCKSDB_LIB} ${GFLAGS_LIB})
|
||||
|
||||
|
||||
+22
-1
@@ -1,9 +1,23 @@
|
||||
# Rocksdb Change Log
|
||||
## Unreleased
|
||||
### Bug Fixes
|
||||
* Fixed a bug in handling file rename error in distributed/network file systems when the server succeeds but client returns error. The bug can cause CURRENT file to point to non-existing MANIFEST file, thus DB cannot be opened.
|
||||
* Fixed a bug where ingested files were written with incorrect boundary key metadata. In rare cases this could have led to a level's files being wrongly ordered and queries for the boundary keys returning wrong results.
|
||||
* Fixed a data race between insertion into memtables and the retrieval of the DB properties `rocksdb.cur-size-active-mem-table`, `rocksdb.cur-size-all-mem-tables`, and `rocksdb.size-all-mem-tables`.
|
||||
* Fixed the false-positive alert when recovering from the WAL file. Avoid reporting "SST file is ahead of WAL" on a newly created empty column family, if the previous WAL file is corrupted.
|
||||
|
||||
### Behavior Changes
|
||||
* Due to the fix of false-postive alert of "SST file is ahead of WAL", all the CFs with no SST file (CF empty) will bypass the consistency check. We fixed a false-positive, but introduced a very rare true-negative which will be triggered in the following conditions: A CF with some delete operations in the last a few queries which will result in an empty CF (those are flushed to SST file and a compaction triggered which combines this file and all other SST files and generates an empty CF, or there is another reason to write a manifest entry for this CF after a flush that generates no SST file from an empty CF). The deletion entries are logged in a WAL and this WAL was corrupted, while the CF's log number points to the next WAL (due to the flush). Therefore, the DB can only recover to the point without these trailing deletions and cause the inconsistent DB status.
|
||||
|
||||
### New Features
|
||||
* Add new option allow_stall passed during instance creation of WriteBufferManager. When allow_stall is set, WriteBufferManager will stall all writers shared across multiple DBs and columns if memory usage goes beyond specified WriteBufferManager::buffer_size (soft limit). Stall will be cleared when memory is freed after flush and memory usage goes down below buffer_size.
|
||||
|
||||
## 6.20.0 (04/16/2021)
|
||||
### Behavior Changes
|
||||
* `ColumnFamilyOptions::sample_for_compression` now takes effect for creation of all block-based tables. Previously it only took effect for block-based tables created by flush.
|
||||
* `CompactFiles()` can no longer compact files from lower level to up level, which has the risk to corrupt DB (details: #8063). The validation is also added to all compactions.
|
||||
* Fixed some cases in which DB::OpenForReadOnly() could write to the filesystem. If you want a Logger with a read-only DB, you must now set DBOptions::info_log yourself, such as using CreateLoggerFromOptions().
|
||||
* get_iostats_context() will never return nullptr. If thread-local support is not available, and user does not opt-out iostats context, then compilation will fail. The same applies to perf context as well.
|
||||
|
||||
### Bug Fixes
|
||||
* Use thread-safe `strerror_r()` to get error messages.
|
||||
@@ -11,6 +25,7 @@
|
||||
* Made BackupEngine thread-safe and added documentation comments to clarify what is safe for multiple BackupEngine objects accessing the same backup directory.
|
||||
* Fixed crash (divide by zero) when compression dictionary is applied to a file containing only range tombstones.
|
||||
* Fixed a backward iteration bug with partitioned filter enabled: not including the prefix of the last key of the previous filter partition in current filter partition can cause wrong iteration result.
|
||||
* Fixed a bug that allowed `DBOptions::max_open_files` to be set with a non-negative integer with `ColumnFamilyOptions::compaction_style = kCompactionStyleFIFO`.
|
||||
|
||||
### Performance Improvements
|
||||
* On ARM platform, use `yield` instead of `wfe` to relax cpu to gain better performance.
|
||||
@@ -18,10 +33,16 @@
|
||||
### Public API change
|
||||
* Added `TableProperties::slow_compression_estimated_data_size` and `TableProperties::fast_compression_estimated_data_size`. When `ColumnFamilyOptions::sample_for_compression > 0`, they estimate what `TableProperties::data_size` would have been if the "fast" or "slow" (see `ColumnFamilyOptions::sample_for_compression` API doc for definitions) compression had been used instead.
|
||||
* Update DB::StartIOTrace and remove Env object from the arguments as its redundant and DB already has Env object that is passed down to IOTracer::StartIOTrace
|
||||
* For new integrated BlobDB, add support for blob files for backup/restore like table files. Because of current limitations, blob files always use the kLegacyCrc32cAndFileSize naming scheme, and incremental backups must read and checksum all blob files in a DB, even for files that are already backed up.
|
||||
* Added `FlushReason::kWalFull`, which is reported when a memtable is flushed due to the WAL reaching its size limit; those flushes were previously reported as `FlushReason::kWriteBufferManager`. Also, changed the reason for flushes triggered by the write buffer manager to `FlushReason::kWriteBufferManager`; they were previously reported as `FlushReason::kWriteBufferFull`.
|
||||
* Extend file_checksum_dump ldb command and DB::GetLiveFilesChecksumInfo API for IntegratedBlobDB and get checksum of blob files along with SST files.
|
||||
|
||||
### New Features
|
||||
* Added the ability to open BackupEngine backups as read-only DBs, using BackupInfo::name_for_open and env_for_open provided by BackupEngine::GetBackupInfo() with include_file_details=true.
|
||||
* Added BackupEngine support for integrated BlobDB, with blob files shared between backups when table files are shared. Because of current limitations, blob files always use the kLegacyCrc32cAndFileSize naming scheme, and incremental backups must read and checksum all blob files in a DB, even for files that are already backed up.
|
||||
* Added an optional output parameter to BackupEngine::CreateNewBackup(WithMetadata) to return the BackupID of the new backup.
|
||||
* Added BackupEngine::GetBackupInfo / GetLatestBackupInfo for querying individual backups.
|
||||
* Made the Ribbon filter a long-term supported feature in terms of the SST schema(compatible with version >= 6.15.0) though the API for enabling it is expected to change.
|
||||
|
||||
|
||||
## 6.19.0 (03/21/2021)
|
||||
### Bug Fixes
|
||||
|
||||
@@ -55,50 +55,25 @@ DEBUG_LEVEL?=1
|
||||
# Set the default LIB_MODE to static
|
||||
LIB_MODE?=static
|
||||
|
||||
ifeq ($(MAKECMDGOALS),dbg)
|
||||
# OBJ_DIR is where the object files reside. Default to the current directory
|
||||
OBJ_DIR?=.
|
||||
|
||||
# Check the MAKECMDGOALS to set the DEBUG_LEVEL and LIB_MODE appropriately
|
||||
|
||||
ifneq ($(filter clean release install, $(MAKECMDGOALS)),)
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
ifneq ($(filter dbg, $(MAKECMDGOALS)),)
|
||||
DEBUG_LEVEL=2
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS),clean)
|
||||
else ifneq ($(filter shared_lib install-shared, $(MAKECMDGOALS)),)
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS),release)
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS),shared_lib)
|
||||
LIB_MODE=shared
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS),install-shared)
|
||||
LIB_MODE=shared
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS),static_lib)
|
||||
else ifneq ($(filter static_lib install-static, $(MAKECMDGOALS)),)
|
||||
DEBUG_LEVEL=0
|
||||
LIB_MODE=static
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS),install-static)
|
||||
DEBUG_LEVEL=0
|
||||
LIB_MODE=static
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS),install)
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
|
||||
|
||||
ifneq ($(findstring jtest, $(MAKECMDGOALS)),)
|
||||
else ifneq ($(filter jtest rocksdbjava%, $(MAKECMDGOALS)),)
|
||||
OBJ_DIR=jl
|
||||
LIB_MODE=shared
|
||||
endif
|
||||
|
||||
ifneq ($(findstring rocksdbjava, $(MAKECMDGOALS)),)
|
||||
LIB_MODE=shared
|
||||
ifneq ($(findstring rocksdbjavastatic, $(MAKECMDGOALS)),)
|
||||
OBJ_DIR=jls
|
||||
ifneq ($(DEBUG_LEVEL),2)
|
||||
@@ -107,8 +82,6 @@ ifneq ($(findstring rocksdbjava, $(MAKECMDGOALS)),)
|
||||
ifeq ($(MAKECMDGOALS),rocksdbjavastaticpublish)
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
else
|
||||
OBJ_DIR=jl
|
||||
endif
|
||||
endif
|
||||
|
||||
@@ -491,7 +464,6 @@ CXXFLAGS += $(WARNING_FLAGS) -I. -I./include $(PLATFORM_CXXFLAGS) $(OPT) -Woverl
|
||||
|
||||
LDFLAGS += $(PLATFORM_LDFLAGS)
|
||||
|
||||
OBJ_DIR?=.
|
||||
LIB_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(LIB_SOURCES))
|
||||
LIB_OBJECTS += $(patsubst %.cc, $(OBJ_DIR)/%.o, $(ROCKSDB_PLUGIN_SOURCES))
|
||||
ifeq ($(HAVE_POWER8),1)
|
||||
@@ -519,13 +491,14 @@ VALGRIND_OPTS = --error-exitcode=$(VALGRIND_ERROR) --leak-check=full
|
||||
|
||||
TEST_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(TEST_LIB_SOURCES) $(MOCK_LIB_SOURCES)) $(GTEST)
|
||||
BENCH_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(BENCH_LIB_SOURCES))
|
||||
CACHE_BENCH_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(CACHE_BENCH_LIB_SOURCES))
|
||||
TOOL_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(TOOL_LIB_SOURCES))
|
||||
ANALYZE_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(ANALYZER_LIB_SOURCES))
|
||||
STRESS_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(STRESS_LIB_SOURCES))
|
||||
|
||||
# Exclude build_version.cc -- a generated source file -- from all sources. Not needed for dependencies
|
||||
ALL_SOURCES = $(filter-out util/build_version.cc, $(LIB_SOURCES)) $(TEST_LIB_SOURCES) $(MOCK_LIB_SOURCES) $(GTEST_DIR)/gtest/gtest-all.cc
|
||||
ALL_SOURCES += $(TOOL_LIB_SOURCES) $(BENCH_LIB_SOURCES) $(ANALYZER_LIB_SOURCES) $(STRESS_LIB_SOURCES)
|
||||
ALL_SOURCES = $(LIB_SOURCES) $(TEST_LIB_SOURCES) $(MOCK_LIB_SOURCES) $(GTEST_DIR)/gtest/gtest-all.cc
|
||||
ALL_SOURCES += $(TOOL_LIB_SOURCES) $(BENCH_LIB_SOURCES) $(CACHE_BENCH_LIB_SOURCES) $(ANALYZER_LIB_SOURCES) $(STRESS_LIB_SOURCES)
|
||||
ALL_SOURCES += $(TEST_MAIN_SOURCES) $(TOOL_MAIN_SOURCES) $(BENCH_MAIN_SOURCES)
|
||||
|
||||
TESTS = $(patsubst %.cc, %, $(notdir $(TEST_MAIN_SOURCES)))
|
||||
@@ -651,8 +624,8 @@ else
|
||||
LIBRARY=$(STATIC_LIBRARY)
|
||||
TEST_LIBRARY=$(STATIC_TEST_LIBRARY)
|
||||
TOOLS_LIBRARY=$(STATIC_TOOLS_LIBRARY)
|
||||
STRESS_LIBRARY=$(STATIC_STRESS_LIBRARY)
|
||||
endif
|
||||
STRESS_LIBRARY=$(STATIC_STRESS_LIBRARY)
|
||||
|
||||
ROCKSDB_MAJOR = $(shell egrep "ROCKSDB_MAJOR.[0-9]" include/rocksdb/version.h | cut -d ' ' -f 3)
|
||||
ROCKSDB_MINOR = $(shell egrep "ROCKSDB_MINOR.[0-9]" include/rocksdb/version.h | cut -d ' ' -f 3)
|
||||
@@ -1246,7 +1219,7 @@ $(STATIC_TOOLS_LIBRARY): $(TOOL_OBJECTS)
|
||||
$(AM_V_AR)rm -f $@ $(SHARED_TOOLS_LIBRARY)
|
||||
$(AM_V_at)$(AR) $(ARFLAGS) $@ $^
|
||||
|
||||
$(STATIC_STRESS_LIBRARY): $(ANALYZE_OBJECTS) $(STRESS_OBJECTS)
|
||||
$(STATIC_STRESS_LIBRARY): $(ANALYZE_OBJECTS) $(STRESS_OBJECTS) $(TESTUTIL)
|
||||
$(AM_V_AR)rm -f $@ $(SHARED_STRESS_LIBRARY)
|
||||
$(AM_V_at)$(AR) $(ARFLAGS) $@ $^
|
||||
|
||||
@@ -1258,7 +1231,7 @@ $(SHARED_TOOLS_LIBRARY): $(TOOL_OBJECTS) $(SHARED1)
|
||||
$(AM_V_AR)rm -f $@ $(STATIC_TOOLS_LIBRARY)
|
||||
$(AM_SHARE)
|
||||
|
||||
$(SHARED_STRESS_LIBRARY): $(ANALYZE_OBJECTS) $(STRESS_OBJECTS) $(SHARED_TOOLS_LIBRARY) $(SHARED1)
|
||||
$(SHARED_STRESS_LIBRARY): $(ANALYZE_OBJECTS) $(STRESS_OBJECTS) $(TESTUTIL) $(SHARED_TOOLS_LIBRARY) $(SHARED1)
|
||||
$(AM_V_AR)rm -f $@ $(STATIC_STRESS_LIBRARY)
|
||||
$(AM_SHARE)
|
||||
|
||||
@@ -1280,7 +1253,7 @@ folly_synchronization_distributed_mutex_test: $(OBJ_DIR)/third-party/folly/folly
|
||||
$(AM_LINK)
|
||||
endif
|
||||
|
||||
cache_bench: $(OBJ_DIR)/cache/cache_bench.o $(LIBRARY)
|
||||
cache_bench: $(OBJ_DIR)/cache/cache_bench.o $(CACHE_BENCH_OBJECTS) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
persistent_cache_bench: $(OBJ_DIR)/utilities/persistent_cache/persistent_cache_bench.o $(LIBRARY)
|
||||
@@ -1292,7 +1265,7 @@ memtablerep_bench: $(OBJ_DIR)/memtable/memtablerep_bench.o $(LIBRARY)
|
||||
filter_bench: $(OBJ_DIR)/util/filter_bench.o $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
db_stress: $(OBJ_DIR)/db_stress_tool/db_stress.o $(STRESS_LIBRARY) $(TOOLS_LIBRARY) $(TESTUTIL) $(LIBRARY)
|
||||
db_stress: $(OBJ_DIR)/db_stress_tool/db_stress.o $(STRESS_LIBRARY) $(TOOLS_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
write_stress: $(OBJ_DIR)/tools/write_stress.o $(LIBRARY)
|
||||
@@ -1887,6 +1860,9 @@ io_tracer_parser: $(OBJ_DIR)/tools/io_tracer_parser.o $(TOOLS_LIBRARY) $(LIBRARY
|
||||
|
||||
db_blob_corruption_test: $(OBJ_DIR)/db/blob/db_blob_corruption_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
db_write_buffer_manager_test: $(OBJ_DIR)/db/db_write_buffer_manager_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
#-------------------------------------------------
|
||||
# make install related stuff
|
||||
PREFIX ?= /usr/local
|
||||
|
||||
@@ -1,3 +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
|
||||
* [ZenFS](https://github.com/westerndigitalcorporation/zenfs): a file system for zoned block devices
|
||||
|
||||
@@ -340,6 +340,7 @@ cpp_library(
|
||||
"util/compression_context_cache.cc",
|
||||
"util/concurrent_task_limiter_impl.cc",
|
||||
"util/crc32c.cc",
|
||||
"util/crc32c_arm64.cc",
|
||||
"util/dynamic_bloom.cc",
|
||||
"util/file_checksum_helper.cc",
|
||||
"util/hash.cc",
|
||||
@@ -649,6 +650,7 @@ cpp_library(
|
||||
"util/compression_context_cache.cc",
|
||||
"util/concurrent_task_limiter_impl.cc",
|
||||
"util/crc32c.cc",
|
||||
"util/crc32c_arm64.cc",
|
||||
"util/dynamic_bloom.cc",
|
||||
"util/file_checksum_helper.cc",
|
||||
"util/hash.cc",
|
||||
@@ -792,6 +794,21 @@ cpp_library(
|
||||
link_whole = False,
|
||||
)
|
||||
|
||||
cpp_library(
|
||||
name = "rocksdb_cache_bench_tools_lib",
|
||||
srcs = ["cache/cache_bench_tool.cc"],
|
||||
auto_headers = AutoHeaders.RECURSIVE_GLOB,
|
||||
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"],
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
link_whole = False,
|
||||
)
|
||||
|
||||
cpp_library(
|
||||
name = "rocksdb_stress_lib",
|
||||
srcs = [
|
||||
@@ -1409,6 +1426,13 @@ ROCKS_TESTS = [
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"db_write_buffer_manager_test",
|
||||
"db/db_write_buffer_manager_test.cc",
|
||||
"parallel",
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"db_write_test",
|
||||
"db/db_write_test.cc",
|
||||
|
||||
@@ -173,6 +173,11 @@ def generate_targets(repo_path, deps_map):
|
||||
src_mk.get("ANALYZER_LIB_SOURCES", []) +
|
||||
["test_util/testutil.cc"],
|
||||
[":rocksdb_lib"])
|
||||
# rocksdb_cache_bench_tools_lib
|
||||
TARGETS.add_library(
|
||||
"rocksdb_cache_bench_tools_lib",
|
||||
src_mk.get("CACHE_BENCH_LIB_SOURCES", []),
|
||||
[":rocksdb_lib"])
|
||||
# rocksdb_stress_lib
|
||||
TARGETS.add_rocksdb_library(
|
||||
"rocksdb_stress_lib",
|
||||
|
||||
Vendored
+7
-370
@@ -1,7 +1,11 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// Copyright (c) 2013-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.
|
||||
|
||||
#ifndef GFLAGS
|
||||
#include <cstdio>
|
||||
@@ -10,375 +14,8 @@ int main() {
|
||||
return 1;
|
||||
}
|
||||
#else
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/system_clock.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/gflags_compat.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/random.h"
|
||||
|
||||
using GFLAGS_NAMESPACE::ParseCommandLineFlags;
|
||||
|
||||
static constexpr uint32_t KiB = uint32_t{1} << 10;
|
||||
static constexpr uint32_t MiB = KiB << 10;
|
||||
static constexpr uint64_t GiB = MiB << 10;
|
||||
|
||||
DEFINE_uint32(threads, 16, "Number of concurrent threads to run.");
|
||||
DEFINE_uint64(cache_size, 1 * GiB,
|
||||
"Number of bytes to use as a cache of uncompressed data.");
|
||||
DEFINE_uint32(num_shard_bits, 6, "shard_bits.");
|
||||
|
||||
DEFINE_double(resident_ratio, 0.25,
|
||||
"Ratio of keys fitting in cache to keyspace.");
|
||||
DEFINE_uint64(ops_per_thread, 0,
|
||||
"Number of operations per thread. (Default: 5 * keyspace size)");
|
||||
DEFINE_uint32(value_bytes, 8 * KiB, "Size of each value added.");
|
||||
|
||||
DEFINE_uint32(skew, 5, "Degree of skew in key selection");
|
||||
DEFINE_bool(populate_cache, true, "Populate cache before operations");
|
||||
|
||||
DEFINE_uint32(lookup_insert_percent, 87,
|
||||
"Ratio of lookup (+ insert on not found) to total workload "
|
||||
"(expressed as a percentage)");
|
||||
DEFINE_uint32(insert_percent, 2,
|
||||
"Ratio of insert to total workload (expressed as a percentage)");
|
||||
DEFINE_uint32(lookup_percent, 10,
|
||||
"Ratio of lookup to total workload (expressed as a percentage)");
|
||||
DEFINE_uint32(erase_percent, 1,
|
||||
"Ratio of erase to total workload (expressed as a percentage)");
|
||||
|
||||
DEFINE_bool(use_clock_cache, false, "");
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class CacheBench;
|
||||
namespace {
|
||||
// State shared by all concurrent executions of the same benchmark.
|
||||
class SharedState {
|
||||
public:
|
||||
explicit SharedState(CacheBench* cache_bench)
|
||||
: cv_(&mu_),
|
||||
num_initialized_(0),
|
||||
start_(false),
|
||||
num_done_(0),
|
||||
cache_bench_(cache_bench) {}
|
||||
|
||||
~SharedState() {}
|
||||
|
||||
port::Mutex* GetMutex() {
|
||||
return &mu_;
|
||||
}
|
||||
|
||||
port::CondVar* GetCondVar() {
|
||||
return &cv_;
|
||||
}
|
||||
|
||||
CacheBench* GetCacheBench() const {
|
||||
return cache_bench_;
|
||||
}
|
||||
|
||||
void IncInitialized() {
|
||||
num_initialized_++;
|
||||
}
|
||||
|
||||
void IncDone() {
|
||||
num_done_++;
|
||||
}
|
||||
|
||||
bool AllInitialized() const { return num_initialized_ >= FLAGS_threads; }
|
||||
|
||||
bool AllDone() const { return num_done_ >= FLAGS_threads; }
|
||||
|
||||
void SetStart() {
|
||||
start_ = true;
|
||||
}
|
||||
|
||||
bool Started() const {
|
||||
return start_;
|
||||
}
|
||||
|
||||
private:
|
||||
port::Mutex mu_;
|
||||
port::CondVar cv_;
|
||||
|
||||
uint64_t num_initialized_;
|
||||
bool start_;
|
||||
uint64_t num_done_;
|
||||
|
||||
CacheBench* cache_bench_;
|
||||
};
|
||||
|
||||
// Per-thread state for concurrent executions of the same benchmark.
|
||||
struct ThreadState {
|
||||
uint32_t tid;
|
||||
Random64 rnd;
|
||||
SharedState* shared;
|
||||
|
||||
ThreadState(uint32_t index, SharedState* _shared)
|
||||
: tid(index), rnd(1000 + index), shared(_shared) {}
|
||||
};
|
||||
|
||||
struct KeyGen {
|
||||
char key_data[27];
|
||||
|
||||
Slice GetRand(Random64& rnd, uint64_t max_key) {
|
||||
uint64_t raw = rnd.Next();
|
||||
// Skew according to setting
|
||||
for (uint32_t i = 0; i < FLAGS_skew; ++i) {
|
||||
raw = std::min(raw, rnd.Next());
|
||||
}
|
||||
uint64_t key = FastRange64(raw, max_key);
|
||||
// Variable size and alignment
|
||||
size_t off = key % 8;
|
||||
key_data[0] = char{42};
|
||||
EncodeFixed64(key_data + 1, key);
|
||||
key_data[9] = char{11};
|
||||
EncodeFixed64(key_data + 10, key);
|
||||
key_data[18] = char{4};
|
||||
EncodeFixed64(key_data + 19, key);
|
||||
return Slice(&key_data[off], sizeof(key_data) - off);
|
||||
}
|
||||
};
|
||||
|
||||
char* createValue(Random64& rnd) {
|
||||
char* rv = new char[FLAGS_value_bytes];
|
||||
// Fill with some filler data, and take some CPU time
|
||||
for (uint32_t i = 0; i < FLAGS_value_bytes; i += 8) {
|
||||
EncodeFixed64(rv + i, rnd.Next());
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
void deleter(const Slice& /*key*/, void* value) {
|
||||
delete[] static_cast<char*>(value);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
class CacheBench {
|
||||
static constexpr uint64_t kHundredthUint64 =
|
||||
std::numeric_limits<uint64_t>::max() / 100U;
|
||||
|
||||
public:
|
||||
CacheBench()
|
||||
: max_key_(static_cast<uint64_t>(FLAGS_cache_size / FLAGS_resident_ratio /
|
||||
FLAGS_value_bytes)),
|
||||
lookup_insert_threshold_(kHundredthUint64 *
|
||||
FLAGS_lookup_insert_percent),
|
||||
insert_threshold_(lookup_insert_threshold_ +
|
||||
kHundredthUint64 * FLAGS_insert_percent),
|
||||
lookup_threshold_(insert_threshold_ +
|
||||
kHundredthUint64 * FLAGS_lookup_percent),
|
||||
erase_threshold_(lookup_threshold_ +
|
||||
kHundredthUint64 * FLAGS_erase_percent) {
|
||||
if (erase_threshold_ != 100U * kHundredthUint64) {
|
||||
fprintf(stderr, "Percentages must add to 100.\n");
|
||||
exit(1);
|
||||
}
|
||||
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 {
|
||||
cache_ = NewLRUCache(FLAGS_cache_size, FLAGS_num_shard_bits);
|
||||
}
|
||||
if (FLAGS_ops_per_thread == 0) {
|
||||
FLAGS_ops_per_thread = 5 * max_key_;
|
||||
}
|
||||
}
|
||||
|
||||
~CacheBench() {}
|
||||
|
||||
void PopulateCache() {
|
||||
Random64 rnd(1);
|
||||
KeyGen keygen;
|
||||
for (uint64_t i = 0; i < 2 * FLAGS_cache_size; i += FLAGS_value_bytes) {
|
||||
cache_->Insert(keygen.GetRand(rnd, max_key_), createValue(rnd),
|
||||
FLAGS_value_bytes, &deleter);
|
||||
}
|
||||
}
|
||||
|
||||
bool Run() {
|
||||
ROCKSDB_NAMESPACE::Env* env = ROCKSDB_NAMESPACE::Env::Default();
|
||||
const auto& clock = env->GetSystemClock();
|
||||
|
||||
PrintEnv();
|
||||
SharedState shared(this);
|
||||
std::vector<std::unique_ptr<ThreadState> > threads(FLAGS_threads);
|
||||
for (uint32_t i = 0; i < FLAGS_threads; i++) {
|
||||
threads[i].reset(new ThreadState(i, &shared));
|
||||
env->StartThread(ThreadBody, threads[i].get());
|
||||
}
|
||||
{
|
||||
MutexLock l(shared.GetMutex());
|
||||
while (!shared.AllInitialized()) {
|
||||
shared.GetCondVar()->Wait();
|
||||
}
|
||||
// Record start time
|
||||
uint64_t start_time = clock->NowMicros();
|
||||
|
||||
// Start all threads
|
||||
shared.SetStart();
|
||||
shared.GetCondVar()->SignalAll();
|
||||
|
||||
// Wait threads to complete
|
||||
while (!shared.AllDone()) {
|
||||
shared.GetCondVar()->Wait();
|
||||
}
|
||||
|
||||
// Record end time
|
||||
uint64_t end_time = clock->NowMicros();
|
||||
double elapsed = static_cast<double>(end_time - start_time) * 1e-6;
|
||||
uint32_t qps = static_cast<uint32_t>(
|
||||
static_cast<double>(FLAGS_threads * FLAGS_ops_per_thread) / elapsed);
|
||||
fprintf(stdout, "Complete in %.3f s; QPS = %u\n", elapsed, qps);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Cache> cache_;
|
||||
const uint64_t max_key_;
|
||||
// Cumulative thresholds in the space of a random uint64_t
|
||||
const uint64_t lookup_insert_threshold_;
|
||||
const uint64_t insert_threshold_;
|
||||
const uint64_t lookup_threshold_;
|
||||
const uint64_t erase_threshold_;
|
||||
|
||||
static void ThreadBody(void* v) {
|
||||
ThreadState* thread = static_cast<ThreadState*>(v);
|
||||
SharedState* shared = thread->shared;
|
||||
|
||||
{
|
||||
MutexLock l(shared->GetMutex());
|
||||
shared->IncInitialized();
|
||||
if (shared->AllInitialized()) {
|
||||
shared->GetCondVar()->SignalAll();
|
||||
}
|
||||
while (!shared->Started()) {
|
||||
shared->GetCondVar()->Wait();
|
||||
}
|
||||
}
|
||||
thread->shared->GetCacheBench()->OperateCache(thread);
|
||||
|
||||
{
|
||||
MutexLock l(shared->GetMutex());
|
||||
shared->IncDone();
|
||||
if (shared->AllDone()) {
|
||||
shared->GetCondVar()->SignalAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OperateCache(ThreadState* thread) {
|
||||
// To use looked-up values
|
||||
uint64_t result = 0;
|
||||
// To hold handles for a non-trivial amount of time
|
||||
Cache::Handle* handle = nullptr;
|
||||
KeyGen gen;
|
||||
for (uint64_t i = 0; i < FLAGS_ops_per_thread; i++) {
|
||||
Slice key = gen.GetRand(thread->rnd, max_key_);
|
||||
uint64_t random_op = thread->rnd.Next();
|
||||
if (random_op < lookup_insert_threshold_) {
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
// do lookup
|
||||
handle = cache_->Lookup(key);
|
||||
if (handle) {
|
||||
// do something with the data
|
||||
result += NPHash64(static_cast<char*>(cache_->Value(handle)),
|
||||
FLAGS_value_bytes);
|
||||
} else {
|
||||
// do insert
|
||||
cache_->Insert(key, createValue(thread->rnd), FLAGS_value_bytes,
|
||||
&deleter, &handle);
|
||||
}
|
||||
} else if (random_op < insert_threshold_) {
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
// do insert
|
||||
cache_->Insert(key, createValue(thread->rnd), FLAGS_value_bytes,
|
||||
&deleter, &handle);
|
||||
} else if (random_op < lookup_threshold_) {
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
// do lookup
|
||||
handle = cache_->Lookup(key);
|
||||
if (handle) {
|
||||
// do something with the data
|
||||
result += NPHash64(static_cast<char*>(cache_->Value(handle)),
|
||||
FLAGS_value_bytes);
|
||||
}
|
||||
} else if (random_op < erase_threshold_) {
|
||||
// do erase
|
||||
cache_->Erase(key);
|
||||
} else {
|
||||
// Should be extremely unlikely (noop)
|
||||
assert(random_op >= kHundredthUint64 * 100U);
|
||||
}
|
||||
}
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void PrintEnv() const {
|
||||
printf("RocksDB version : %d.%d\n", kMajorVersion, kMinorVersion);
|
||||
printf("Number of threads : %u\n", FLAGS_threads);
|
||||
printf("Ops per thread : %" PRIu64 "\n", FLAGS_ops_per_thread);
|
||||
printf("Cache size : %" PRIu64 "\n", FLAGS_cache_size);
|
||||
printf("Num shard bits : %u\n", FLAGS_num_shard_bits);
|
||||
printf("Max key : %" PRIu64 "\n", max_key_);
|
||||
printf("Resident ratio : %g\n", FLAGS_resident_ratio);
|
||||
printf("Skew degree : %u\n", FLAGS_skew);
|
||||
printf("Populate cache : %d\n", int{FLAGS_populate_cache});
|
||||
printf("Lookup+Insert pct : %u%%\n", FLAGS_lookup_insert_percent);
|
||||
printf("Insert percentage : %u%%\n", FLAGS_insert_percent);
|
||||
printf("Lookup percentage : %u%%\n", FLAGS_lookup_percent);
|
||||
printf("Erase percentage : %u%%\n", FLAGS_erase_percent);
|
||||
printf("----------------------------\n");
|
||||
}
|
||||
};
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#include <rocksdb/cache_bench_tool.h>
|
||||
int main(int argc, char** argv) {
|
||||
ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
if (FLAGS_threads <= 0) {
|
||||
fprintf(stderr, "threads number <= 0\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::CacheBench bench;
|
||||
if (FLAGS_populate_cache) {
|
||||
bench.PopulateCache();
|
||||
printf("Population complete\n");
|
||||
printf("----------------------------\n");
|
||||
}
|
||||
if (bench.Run()) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
return ROCKSDB_NAMESPACE::cache_bench_tool(argc, argv);
|
||||
}
|
||||
|
||||
#endif // GFLAGS
|
||||
|
||||
Vendored
+504
@@ -0,0 +1,504 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#ifdef GFLAGS
|
||||
#include "rocksdb/cache_bench_tool.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
|
||||
#include "options/configurable_helper.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/system_clock.h"
|
||||
#include "rocksdb/tiered_cache.h"
|
||||
#include "rocksdb/utilities/object_registry.h"
|
||||
#include "rocksdb/utilities/options_type.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/gflags_compat.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/random.h"
|
||||
#include "util/stop_watch.h"
|
||||
|
||||
using GFLAGS_NAMESPACE::ParseCommandLineFlags;
|
||||
|
||||
static constexpr uint32_t KiB = uint32_t{1} << 10;
|
||||
static constexpr uint32_t MiB = KiB << 10;
|
||||
static constexpr uint64_t GiB = MiB << 10;
|
||||
|
||||
DEFINE_uint32(threads, 16, "Number of concurrent threads to run.");
|
||||
DEFINE_uint64(cache_size, 1 * GiB,
|
||||
"Number of bytes to use as a cache of uncompressed data.");
|
||||
DEFINE_uint32(num_shard_bits, 6, "shard_bits.");
|
||||
|
||||
DEFINE_double(resident_ratio, 0.25,
|
||||
"Ratio of keys fitting in cache to keyspace.");
|
||||
DEFINE_uint64(ops_per_thread, 0,
|
||||
"Number of operations per thread. (Default: 5 * keyspace size)");
|
||||
DEFINE_uint32(value_bytes, 8 * KiB, "Size of each value added.");
|
||||
|
||||
DEFINE_uint32(skew, 5, "Degree of skew in key selection");
|
||||
DEFINE_bool(populate_cache, true, "Populate cache before operations");
|
||||
|
||||
DEFINE_uint32(lookup_insert_percent, 87,
|
||||
"Ratio of lookup (+ insert on not found) to total workload "
|
||||
"(expressed as a percentage)");
|
||||
DEFINE_uint32(insert_percent, 2,
|
||||
"Ratio of insert to total workload (expressed as a percentage)");
|
||||
DEFINE_uint32(lookup_percent, 10,
|
||||
"Ratio of lookup to total workload (expressed as a percentage)");
|
||||
DEFINE_uint32(erase_percent, 1,
|
||||
"Ratio of erase to total workload (expressed as a percentage)");
|
||||
|
||||
DEFINE_bool(use_clock_cache, false, "");
|
||||
|
||||
DEFINE_bool(skewed, false, "If true, skew the key access distribution");
|
||||
#ifndef ROCKSDB_LITE
|
||||
DEFINE_string(tiered_cache_uri, "",
|
||||
"Full URI for creating a custom NVM cache object");
|
||||
static class std::shared_ptr<ROCKSDB_NAMESPACE::TieredCache> tiered_cache;
|
||||
#endif
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
class CacheBench;
|
||||
|
||||
namespace {
|
||||
// State shared by all concurrent executions of the same benchmark.
|
||||
class SharedState {
|
||||
public:
|
||||
explicit SharedState(CacheBench* cache_bench)
|
||||
: cv_(&mu_),
|
||||
num_initialized_(0),
|
||||
start_(false),
|
||||
num_done_(0),
|
||||
cache_bench_(cache_bench) {}
|
||||
|
||||
~SharedState() {}
|
||||
|
||||
port::Mutex* GetMutex() { return &mu_; }
|
||||
|
||||
port::CondVar* GetCondVar() { return &cv_; }
|
||||
|
||||
CacheBench* GetCacheBench() const { return cache_bench_; }
|
||||
|
||||
void IncInitialized() { num_initialized_++; }
|
||||
|
||||
void IncDone() { num_done_++; }
|
||||
|
||||
bool AllInitialized() const { return num_initialized_ >= FLAGS_threads; }
|
||||
|
||||
bool AllDone() const { return num_done_ >= FLAGS_threads; }
|
||||
|
||||
void SetStart() { start_ = true; }
|
||||
|
||||
bool Started() const { return start_; }
|
||||
|
||||
private:
|
||||
port::Mutex mu_;
|
||||
port::CondVar cv_;
|
||||
|
||||
uint64_t num_initialized_;
|
||||
bool start_;
|
||||
uint64_t num_done_;
|
||||
|
||||
CacheBench* cache_bench_;
|
||||
};
|
||||
|
||||
class Stats {
|
||||
private:
|
||||
uint64_t hits;
|
||||
uint64_t misses;
|
||||
uint64_t lookup_us;
|
||||
uint64_t insert_us;
|
||||
|
||||
public:
|
||||
Stats() : hits(0), misses(0), lookup_us(0), insert_us(0) {}
|
||||
|
||||
void AddHits(uint64_t inc) { hits += inc; }
|
||||
void AddMisses(uint64_t inc) { misses += inc; }
|
||||
void AddLookupUs(uint64_t lookup) { lookup_us += lookup; }
|
||||
void AddInsertUs(uint64_t insert) { insert_us += insert; }
|
||||
void Merge(const Stats& other) {
|
||||
hits += other.hits;
|
||||
misses += other.misses;
|
||||
lookup_us += other.lookup_us;
|
||||
insert_us += other.insert_us;
|
||||
}
|
||||
void Report() {
|
||||
fprintf(stdout, "%llu hits, %llu misses, %llu lookup_us, %llu insert_us\n",
|
||||
(unsigned long long)hits, (unsigned long long)misses,
|
||||
(unsigned long long)lookup_us, (unsigned long long)insert_us);
|
||||
fflush(stdout);
|
||||
}
|
||||
};
|
||||
|
||||
// Per-thread state for concurrent executions of the same benchmark.
|
||||
struct ThreadState {
|
||||
uint32_t tid;
|
||||
Random64 rnd;
|
||||
Stats stats;
|
||||
SharedState* shared;
|
||||
|
||||
ThreadState(uint32_t index, SharedState* _shared)
|
||||
: tid(index), rnd(1000 + index), shared(_shared) {}
|
||||
};
|
||||
|
||||
struct KeyGen {
|
||||
char key_data[27];
|
||||
|
||||
Slice GetRand(Random64& rnd, uint64_t max_key, int max_log) {
|
||||
uint64_t key = 0;
|
||||
if (!FLAGS_skewed) {
|
||||
uint64_t raw = rnd.Next();
|
||||
// Skew according to setting
|
||||
for (uint32_t i = 0; i < FLAGS_skew; ++i) {
|
||||
raw = std::min(raw, rnd.Next());
|
||||
}
|
||||
key = FastRange64(raw, max_key);
|
||||
} else {
|
||||
key = rnd.Skewed(max_log);
|
||||
if (key > max_key) {
|
||||
key -= max_key;
|
||||
}
|
||||
}
|
||||
// Variable size and alignment
|
||||
size_t off = key % 8;
|
||||
key_data[0] = char{42};
|
||||
EncodeFixed64(key_data + 1, key);
|
||||
key_data[9] = char{11};
|
||||
EncodeFixed64(key_data + 10, key);
|
||||
key_data[18] = char{4};
|
||||
EncodeFixed64(key_data + 19, key);
|
||||
return Slice(&key_data[off], sizeof(key_data) - off);
|
||||
}
|
||||
};
|
||||
|
||||
char* createValue(Random64& rnd) {
|
||||
char* rv = new char[FLAGS_value_bytes];
|
||||
// Fill with some filler data, and take some CPU time
|
||||
for (uint32_t i = 0; i < FLAGS_value_bytes; i += 8) {
|
||||
EncodeFixed64(rv + i, rnd.Next());
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
void helperCallback(Cache::SizeCallback* size_cb,
|
||||
Cache::SaveToCallback* save_cb,
|
||||
Cache::DeletionCallback* del_cb) {
|
||||
if (size_cb) {
|
||||
*size_cb = [](void* /*obj*/) -> size_t { return FLAGS_value_bytes; };
|
||||
}
|
||||
if (save_cb) {
|
||||
*save_cb = [](void* obj, size_t /*offset*/, size_t size,
|
||||
void* out) -> Status {
|
||||
memcpy(out, obj, size);
|
||||
return Status::OK();
|
||||
};
|
||||
}
|
||||
if (del_cb) {
|
||||
*del_cb = [](const Slice& /*key*/, void* obj) -> void {
|
||||
delete[] static_cast<char*>(obj);
|
||||
};
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
class CacheBench {
|
||||
static constexpr uint64_t kHundredthUint64 =
|
||||
std::numeric_limits<uint64_t>::max() / 100U;
|
||||
|
||||
public:
|
||||
CacheBench()
|
||||
: max_key_(static_cast<uint64_t>(FLAGS_cache_size / FLAGS_resident_ratio /
|
||||
FLAGS_value_bytes)),
|
||||
lookup_insert_threshold_(kHundredthUint64 *
|
||||
FLAGS_lookup_insert_percent),
|
||||
insert_threshold_(lookup_insert_threshold_ +
|
||||
kHundredthUint64 * FLAGS_insert_percent),
|
||||
lookup_threshold_(insert_threshold_ +
|
||||
kHundredthUint64 * FLAGS_lookup_percent),
|
||||
erase_threshold_(lookup_threshold_ +
|
||||
kHundredthUint64 * FLAGS_erase_percent),
|
||||
skewed_(FLAGS_skewed) {
|
||||
if (erase_threshold_ != 100U * kHundredthUint64) {
|
||||
fprintf(stderr, "Percentages must add to 100.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
max_log_ = 0;
|
||||
if (skewed_) {
|
||||
uint64_t max_key = max_key_;
|
||||
while (max_key >>= 1) max_log_++;
|
||||
if (max_key > (1u << max_log_)) max_log_++;
|
||||
}
|
||||
|
||||
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_tiered_cache_uri.empty()) {
|
||||
Status s = ObjectRegistry::NewInstance()->NewSharedObject<TieredCache>(
|
||||
FLAGS_tiered_cache_uri, &tiered_cache);
|
||||
if (tiered_cache == nullptr) {
|
||||
fprintf(stderr,
|
||||
"No tiered cache registered matching string: %s status=%s\n",
|
||||
FLAGS_tiered_cache_uri.c_str(), s.ToString().c_str());
|
||||
exit(1);
|
||||
}
|
||||
opts.tiered_cache = tiered_cache;
|
||||
}
|
||||
#endif
|
||||
|
||||
cache_ = NewLRUCache(opts);
|
||||
}
|
||||
if (FLAGS_ops_per_thread == 0) {
|
||||
FLAGS_ops_per_thread = 5 * max_key_;
|
||||
}
|
||||
}
|
||||
|
||||
~CacheBench() {}
|
||||
|
||||
void PopulateCache() {
|
||||
Random64 rnd(1);
|
||||
KeyGen keygen;
|
||||
for (uint64_t i = 0; i < 10 * FLAGS_cache_size; i += FLAGS_value_bytes) {
|
||||
cache_->Insert(keygen.GetRand(rnd, max_key_, max_log_), createValue(rnd),
|
||||
helperCallback, FLAGS_value_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
bool Run() {
|
||||
ROCKSDB_NAMESPACE::Env* env = ROCKSDB_NAMESPACE::Env::Default();
|
||||
const auto& clock = env->GetSystemClock();
|
||||
|
||||
PrintEnv();
|
||||
SharedState shared(this);
|
||||
std::vector<std::unique_ptr<ThreadState> > threads(FLAGS_threads);
|
||||
for (uint32_t i = 0; i < FLAGS_threads; i++) {
|
||||
threads[i].reset(new ThreadState(i, &shared));
|
||||
env->StartThread(ThreadBody, threads[i].get());
|
||||
}
|
||||
{
|
||||
MutexLock l(shared.GetMutex());
|
||||
while (!shared.AllInitialized()) {
|
||||
shared.GetCondVar()->Wait();
|
||||
}
|
||||
// Record start time
|
||||
uint64_t start_time = clock->NowMicros();
|
||||
|
||||
// Start all threads
|
||||
shared.SetStart();
|
||||
shared.GetCondVar()->SignalAll();
|
||||
|
||||
// Wait threads to complete
|
||||
while (!shared.AllDone()) {
|
||||
shared.GetCondVar()->Wait();
|
||||
}
|
||||
|
||||
// Record end time
|
||||
uint64_t end_time = clock->NowMicros();
|
||||
double elapsed = static_cast<double>(end_time - start_time) * 1e-6;
|
||||
uint32_t qps = static_cast<uint32_t>(
|
||||
static_cast<double>(FLAGS_threads * FLAGS_ops_per_thread) / elapsed);
|
||||
fprintf(stdout, "Complete in %.3f s; QPS = %u\n", elapsed, qps);
|
||||
|
||||
Stats merge_stats;
|
||||
for (uint32_t i = 0; i < FLAGS_threads; ++i) {
|
||||
merge_stats.Merge(threads[i]->stats);
|
||||
}
|
||||
merge_stats.Report();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Cache> cache_;
|
||||
const uint64_t max_key_;
|
||||
// Cumulative thresholds in the space of a random uint64_t
|
||||
const uint64_t lookup_insert_threshold_;
|
||||
const uint64_t insert_threshold_;
|
||||
const uint64_t lookup_threshold_;
|
||||
const uint64_t erase_threshold_;
|
||||
const bool skewed_;
|
||||
int max_log_;
|
||||
|
||||
static void ThreadBody(void* v) {
|
||||
ThreadState* thread = static_cast<ThreadState*>(v);
|
||||
SharedState* shared = thread->shared;
|
||||
|
||||
{
|
||||
MutexLock l(shared->GetMutex());
|
||||
shared->IncInitialized();
|
||||
if (shared->AllInitialized()) {
|
||||
shared->GetCondVar()->SignalAll();
|
||||
}
|
||||
while (!shared->Started()) {
|
||||
shared->GetCondVar()->Wait();
|
||||
}
|
||||
}
|
||||
thread->shared->GetCacheBench()->OperateCache(thread);
|
||||
|
||||
{
|
||||
MutexLock l(shared->GetMutex());
|
||||
shared->IncDone();
|
||||
if (shared->AllDone()) {
|
||||
shared->GetCondVar()->SignalAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OperateCache(ThreadState* thread) {
|
||||
// To use looked-up values
|
||||
uint64_t result = 0;
|
||||
// To hold handles for a non-trivial amount of time
|
||||
Cache::Handle* handle = nullptr;
|
||||
KeyGen gen;
|
||||
ROCKSDB_NAMESPACE::Env* env = ROCKSDB_NAMESPACE::Env::Default();
|
||||
SystemClock* clock = env->GetSystemClock().get();
|
||||
for (uint64_t i = 0; i < FLAGS_ops_per_thread; i++) {
|
||||
Slice key = gen.GetRand(thread->rnd, max_key_, max_log_);
|
||||
uint64_t random_op = thread->rnd.Next();
|
||||
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();
|
||||
};
|
||||
|
||||
if (random_op < lookup_insert_threshold_) {
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
// do lookup
|
||||
{
|
||||
uint64_t elapsed = 0;
|
||||
{
|
||||
StopWatch sw(clock, nullptr, 0, &elapsed);
|
||||
handle = cache_->Lookup(key, helperCallback, create_cb,
|
||||
Cache::Priority::LOW, true);
|
||||
}
|
||||
thread->stats.AddLookupUs(elapsed);
|
||||
}
|
||||
if (handle) {
|
||||
thread->stats.AddHits(1);
|
||||
// do something with the data
|
||||
result += NPHash64(static_cast<char*>(cache_->Value(handle)),
|
||||
FLAGS_value_bytes);
|
||||
} else {
|
||||
thread->stats.AddMisses(1);
|
||||
// do insert
|
||||
{
|
||||
uint64_t elapsed = 0;
|
||||
{
|
||||
StopWatch sw(clock, nullptr, 0, &elapsed);
|
||||
cache_->Insert(key, createValue(thread->rnd), helperCallback,
|
||||
FLAGS_value_bytes, &handle);
|
||||
}
|
||||
thread->stats.AddInsertUs(elapsed);
|
||||
}
|
||||
}
|
||||
} else if (random_op < insert_threshold_) {
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
// do insert
|
||||
{
|
||||
uint64_t elapsed = 0;
|
||||
{
|
||||
StopWatch sw(clock, nullptr, 0, &elapsed);
|
||||
cache_->Insert(key, createValue(thread->rnd), helperCallback,
|
||||
FLAGS_value_bytes, &handle);
|
||||
}
|
||||
thread->stats.AddInsertUs(elapsed);
|
||||
}
|
||||
} else if (random_op < lookup_threshold_) {
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
// do lookup
|
||||
{
|
||||
uint64_t elapsed = 0;
|
||||
{
|
||||
StopWatch sw(clock, nullptr, 0, &elapsed);
|
||||
handle = cache_->Lookup(key, helperCallback, create_cb,
|
||||
Cache::Priority::LOW, true);
|
||||
}
|
||||
thread->stats.AddLookupUs(elapsed);
|
||||
}
|
||||
if (handle) {
|
||||
thread->stats.AddHits(1);
|
||||
// do something with the data
|
||||
result += NPHash64(static_cast<char*>(cache_->Value(handle)),
|
||||
FLAGS_value_bytes);
|
||||
}
|
||||
} else if (random_op < erase_threshold_) {
|
||||
// do erase
|
||||
cache_->Erase(key);
|
||||
} else {
|
||||
// Should be extremely unlikely (noop)
|
||||
assert(random_op >= kHundredthUint64 * 100U);
|
||||
}
|
||||
}
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void PrintEnv() const {
|
||||
printf("RocksDB version : %d.%d\n", kMajorVersion, kMinorVersion);
|
||||
printf("Number of threads : %u\n", FLAGS_threads);
|
||||
printf("Ops per thread : %" PRIu64 "\n", FLAGS_ops_per_thread);
|
||||
printf("Cache size : %" PRIu64 "\n", FLAGS_cache_size);
|
||||
printf("Num shard bits : %u\n", FLAGS_num_shard_bits);
|
||||
printf("Max key : %" PRIu64 "\n", max_key_);
|
||||
printf("Resident ratio : %g\n", FLAGS_resident_ratio);
|
||||
printf("Skew degree : %u\n", FLAGS_skew);
|
||||
printf("Populate cache : %d\n", int{FLAGS_populate_cache});
|
||||
printf("Lookup+Insert pct : %u%%\n", FLAGS_lookup_insert_percent);
|
||||
printf("Insert percentage : %u%%\n", FLAGS_insert_percent);
|
||||
printf("Lookup percentage : %u%%\n", FLAGS_lookup_percent);
|
||||
printf("Erase percentage : %u%%\n", FLAGS_erase_percent);
|
||||
printf("----------------------------\n");
|
||||
}
|
||||
};
|
||||
|
||||
int cache_bench_tool(int argc, char** argv) {
|
||||
ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
if (FLAGS_threads <= 0) {
|
||||
fprintf(stderr, "threads number <= 0\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::CacheBench bench;
|
||||
if (FLAGS_populate_cache) {
|
||||
bench.PopulateCache();
|
||||
printf("Population complete\n");
|
||||
printf("----------------------------\n");
|
||||
}
|
||||
if (bench.Run()) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
#endif
|
||||
Vendored
+22
@@ -271,7 +271,27 @@ class ClockCacheShard final : public CacheShard {
|
||||
Status Insert(const Slice& key, uint32_t hash, void* value, size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
Cache::Handle** handle, Cache::Priority priority) override;
|
||||
Status Insert(const Slice& key, uint32_t hash, void* value,
|
||||
Cache::CacheItemHelperCallback helper_cb, size_t charge,
|
||||
Cache::Handle** handle, Cache::Priority priority) override {
|
||||
Cache::DeletionCallback delete_cb;
|
||||
(*helper_cb)(nullptr, nullptr, &delete_cb);
|
||||
return Insert(key, hash, value, charge, delete_cb, handle, priority);
|
||||
}
|
||||
Cache::Handle* Lookup(const Slice& key, uint32_t hash) override;
|
||||
Cache::Handle* Lookup(const Slice& key, uint32_t hash,
|
||||
Cache::CacheItemHelperCallback /*helper_cb*/,
|
||||
const Cache::CreateCallback& /*create_cb*/,
|
||||
Cache::Priority /*priority*/, bool /*wait*/) override {
|
||||
return Lookup(key, hash);
|
||||
}
|
||||
bool Release(Cache::Handle* handle, bool /*useful*/,
|
||||
bool force_erase) override {
|
||||
return Release(handle, force_erase);
|
||||
}
|
||||
bool isReady(Cache::Handle* /*handle*/) override { return true; }
|
||||
void Wait(Cache::Handle* /*handle*/) override {}
|
||||
|
||||
// If the entry in in cache, increase reference count and return true.
|
||||
// Return false otherwise.
|
||||
//
|
||||
@@ -748,6 +768,8 @@ class ClockCache final : public ShardedCache {
|
||||
|
||||
void DisownData() override { shards_ = nullptr; }
|
||||
|
||||
void WaitAll(std::vector<Handle*>& /*handles*/) override {}
|
||||
|
||||
private:
|
||||
ClockCacheShard* shards_;
|
||||
};
|
||||
|
||||
Vendored
+162
-82
@@ -97,7 +97,8 @@ void LRUHandleTable::Resize() {
|
||||
LRUCacheShard::LRUCacheShard(size_t capacity, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio,
|
||||
bool use_adaptive_mutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy)
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
const std::shared_ptr<TieredCache>& tiered_cache)
|
||||
: capacity_(0),
|
||||
high_pri_pool_usage_(0),
|
||||
strict_capacity_limit_(strict_capacity_limit),
|
||||
@@ -105,7 +106,8 @@ LRUCacheShard::LRUCacheShard(size_t capacity, bool strict_capacity_limit,
|
||||
high_pri_pool_capacity_(0),
|
||||
usage_(0),
|
||||
lru_usage_(0),
|
||||
mutex_(use_adaptive_mutex) {
|
||||
mutex_(use_adaptive_mutex),
|
||||
tiered_cache_(tiered_cache) {
|
||||
set_metadata_charge_policy(metadata_charge_policy);
|
||||
// Make empty circular linked list
|
||||
lru_.next = &lru_;
|
||||
@@ -256,8 +258,14 @@ void LRUCacheShard::SetCapacity(size_t capacity) {
|
||||
EvictFromLRU(0, &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 (tiered_cache_ && entry->IsTieredCacheCompatible() &&
|
||||
!entry->IsPromoted()) {
|
||||
tiered_cache_->Insert(entry->key(), entry->value, entry->info_.helper_cb)
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
entry->Free();
|
||||
}
|
||||
}
|
||||
@@ -267,17 +275,126 @@ void LRUCacheShard::SetStrictCapacityLimit(bool strict_capacity_limit) {
|
||||
strict_capacity_limit_ = strict_capacity_limit;
|
||||
}
|
||||
|
||||
Cache::Handle* LRUCacheShard::Lookup(const Slice& key, uint32_t hash) {
|
||||
MutexLock l(&mutex_);
|
||||
LRUHandle* e = table_.Lookup(key, hash);
|
||||
if (e != nullptr) {
|
||||
assert(e->InCache());
|
||||
if (!e->HasRefs()) {
|
||||
// The entry is in LRU since it's in hash and has no external references
|
||||
LRU_Remove(e);
|
||||
Status LRUCacheShard::InsertItem(LRUHandle* e, Cache::Handle** handle) {
|
||||
Status s = Status::OK();
|
||||
autovector<LRUHandle*> last_reference_list;
|
||||
size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
|
||||
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
|
||||
// Free the space following strict LRU policy until enough space
|
||||
// is freed or the lru list is empty
|
||||
EvictFromLRU(total_charge, &last_reference_list);
|
||||
|
||||
if ((usage_ + total_charge) > capacity_ &&
|
||||
(strict_capacity_limit_ || handle == nullptr)) {
|
||||
if (handle == nullptr) {
|
||||
// Don't insert the entry but still return ok, as if the entry inserted
|
||||
// into cache and get evicted immediately.
|
||||
e->SetInCache(false);
|
||||
last_reference_list.push_back(e);
|
||||
} else {
|
||||
delete[] reinterpret_cast<char*>(e);
|
||||
*handle = nullptr;
|
||||
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_ += 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
|
||||
LRU_Remove(old);
|
||||
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 {
|
||||
e->Ref();
|
||||
*handle = reinterpret_cast<Cache::Handle*>(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to insert the evicted entries into NVM cache
|
||||
// Free the entries here outside of mutex for performance reasons
|
||||
for (auto entry : last_reference_list) {
|
||||
if (tiered_cache_ && entry->IsTieredCacheCompatible() &&
|
||||
!entry->IsPromoted()) {
|
||||
tiered_cache_->Insert(entry->key(), entry->value, entry->info_.helper_cb)
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
entry->Free();
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Cache::Handle* LRUCacheShard::Lookup(
|
||||
const Slice& key, uint32_t hash,
|
||||
ShardedCache::CacheItemHelperCallback helper_cb,
|
||||
const ShardedCache::CreateCallback& create_cb, Cache::Priority priority,
|
||||
bool wait) {
|
||||
LRUHandle* e = nullptr;
|
||||
{
|
||||
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
|
||||
LRU_Remove(e);
|
||||
}
|
||||
e->Ref();
|
||||
e->SetHit();
|
||||
}
|
||||
}
|
||||
|
||||
// If handle table lookup failed, then allocate a handle outside the
|
||||
// mutex if we're going to lookup in the NVM cache
|
||||
// Only support synchronous for now
|
||||
// TODO: Support asynchronous lookup in NVM cache
|
||||
if (!e && tiered_cache_ && helper_cb && wait) {
|
||||
assert(create_cb);
|
||||
std::unique_ptr<TieredCacheHandle> tiered_handle =
|
||||
tiered_cache_->Lookup(key, create_cb, wait);
|
||||
if (tiered_handle != nullptr) {
|
||||
e = reinterpret_cast<LRUHandle*>(
|
||||
new char[sizeof(LRUHandle) - 1 + key.size()]);
|
||||
|
||||
e->flags = 0;
|
||||
e->SetPromoted(true);
|
||||
e->SetTieredCacheCompatible(true);
|
||||
e->info_.helper_cb = helper_cb;
|
||||
e->key_length = key.size();
|
||||
e->hash = hash;
|
||||
e->refs = 0;
|
||||
e->next = e->prev = nullptr;
|
||||
e->SetInCache(true);
|
||||
e->SetPriority(priority);
|
||||
memcpy(e->key_data, key.data(), key.size());
|
||||
|
||||
e->value = tiered_handle->Value();
|
||||
e->charge = tiered_handle->Size();
|
||||
|
||||
// This call could nullify e if the cache is over capacity and
|
||||
// strict_capacity_limit_ is true. In such a case, the caller will try
|
||||
// to insert later, which might again fail, but its ok as this should
|
||||
// not be common
|
||||
InsertItem(e, reinterpret_cast<Cache::Handle**>(&e))
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
e->Ref();
|
||||
e->SetHit();
|
||||
}
|
||||
return reinterpret_cast<Cache::Handle*>(e);
|
||||
}
|
||||
@@ -338,81 +455,32 @@ bool LRUCacheShard::Release(Cache::Handle* handle, bool force_erase) {
|
||||
Status LRUCacheShard::Insert(const Slice& key, uint32_t hash, void* value,
|
||||
size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
Cache::CacheItemHelperCallback helper_cb,
|
||||
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
|
||||
// It shouldn't happen very often though.
|
||||
LRUHandle* e = reinterpret_cast<LRUHandle*>(
|
||||
new char[sizeof(LRUHandle) - 1 + key.size()]);
|
||||
Status s = Status::OK();
|
||||
autovector<LRUHandle*> last_reference_list;
|
||||
|
||||
e->value = value;
|
||||
e->deleter = deleter;
|
||||
e->flags = 0;
|
||||
if (helper_cb) {
|
||||
e->SetTieredCacheCompatible(true);
|
||||
e->info_.helper_cb = helper_cb;
|
||||
} else {
|
||||
e->info_.deleter = deleter;
|
||||
}
|
||||
e->charge = charge;
|
||||
e->key_length = key.size();
|
||||
e->flags = 0;
|
||||
e->hash = hash;
|
||||
e->refs = 0;
|
||||
e->next = e->prev = nullptr;
|
||||
e->SetInCache(true);
|
||||
e->SetPriority(priority);
|
||||
memcpy(e->key_data, key.data(), key.size());
|
||||
size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
|
||||
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
|
||||
// Free the space following strict LRU policy until enough space
|
||||
// is freed or the lru list is empty
|
||||
EvictFromLRU(total_charge, &last_reference_list);
|
||||
|
||||
if ((usage_ + total_charge) > capacity_ &&
|
||||
(strict_capacity_limit_ || handle == nullptr)) {
|
||||
if (handle == nullptr) {
|
||||
// Don't insert the entry but still return ok, as if the entry inserted
|
||||
// into cache and get evicted immediately.
|
||||
e->SetInCache(false);
|
||||
last_reference_list.push_back(e);
|
||||
} else {
|
||||
delete[] reinterpret_cast<char*>(e);
|
||||
*handle = nullptr;
|
||||
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_ += 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
|
||||
LRU_Remove(old);
|
||||
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 {
|
||||
e->Ref();
|
||||
*handle = reinterpret_cast<Cache::Handle*>(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Free the entries here outside of mutex for performance reasons
|
||||
for (auto entry : last_reference_list) {
|
||||
entry->Free();
|
||||
}
|
||||
|
||||
return s;
|
||||
return InsertItem(e, handle);
|
||||
}
|
||||
|
||||
void LRUCacheShard::Erase(const Slice& key, uint32_t hash) {
|
||||
@@ -468,7 +536,8 @@ LRUCache::LRUCache(size_t capacity, int num_shard_bits,
|
||||
bool strict_capacity_limit, double high_pri_pool_ratio,
|
||||
std::shared_ptr<MemoryAllocator> allocator,
|
||||
bool use_adaptive_mutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy)
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
const std::shared_ptr<TieredCache>& tiered_cache)
|
||||
: ShardedCache(capacity, num_shard_bits, strict_capacity_limit,
|
||||
std::move(allocator)) {
|
||||
num_shards_ = 1 << num_shard_bits;
|
||||
@@ -478,7 +547,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,
|
||||
use_adaptive_mutex, metadata_charge_policy);
|
||||
use_adaptive_mutex, metadata_charge_policy, tiered_cache);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -543,19 +612,12 @@ double LRUCache::GetHighPriPoolRatio() {
|
||||
return result;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
const std::shared_ptr<TieredCache>& tiered_cache) {
|
||||
if (num_shard_bits >= 20) {
|
||||
return nullptr; // the cache cannot be sharded into too many fine pieces
|
||||
}
|
||||
@@ -568,7 +630,25 @@ std::shared_ptr<Cache> NewLRUCache(
|
||||
}
|
||||
return std::make_shared<LRUCache>(
|
||||
capacity, num_shard_bits, strict_capacity_limit, high_pri_pool_ratio,
|
||||
std::move(memory_allocator), use_adaptive_mutex, metadata_charge_policy);
|
||||
std::move(memory_allocator), use_adaptive_mutex, metadata_charge_policy,
|
||||
tiered_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.tiered_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) {
|
||||
return NewLRUCache(capacity, num_shard_bits, strict_capacity_limit,
|
||||
high_pri_pool_ratio, memory_allocator, use_adaptive_mutex,
|
||||
metadata_charge_policy, nullptr);
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
Vendored
+88
-8
@@ -11,9 +11,9 @@
|
||||
#include <string>
|
||||
|
||||
#include "cache/sharded_cache.h"
|
||||
|
||||
#include "port/malloc.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/tiered_cache.h"
|
||||
#include "util/autovector.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -49,7 +49,14 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
struct LRUHandle {
|
||||
void* value;
|
||||
void (*deleter)(const Slice&, void* value);
|
||||
union Info {
|
||||
Info() {}
|
||||
~Info() {}
|
||||
void (*deleter)(const Slice&, void* value);
|
||||
ShardedCache::CacheItemHelperCallback helper_cb;
|
||||
// This needs to be explicitly constructed and destructed
|
||||
std::unique_ptr<TieredCacheHandle> tiered_handle;
|
||||
} info_;
|
||||
LRUHandle* next_hash;
|
||||
LRUHandle* next;
|
||||
LRUHandle* prev;
|
||||
@@ -69,6 +76,12 @@ 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 tiered cache
|
||||
IS_TIERED_CACHE_COMPATIBLE = (1 << 4),
|
||||
// Is the handle still being read from a lower tier
|
||||
IS_PENDING = (1 << 5),
|
||||
// Has the item been promoted from a lower tier
|
||||
IS_PROMOTED = (1 << 6),
|
||||
};
|
||||
|
||||
uint8_t flags;
|
||||
@@ -95,6 +108,11 @@ struct LRUHandle {
|
||||
bool IsHighPri() const { return flags & IS_HIGH_PRI; }
|
||||
bool InHighPriPool() const { return flags & IN_HIGH_PRI_POOL; }
|
||||
bool HasHit() const { return flags & HAS_HIT; }
|
||||
bool IsTieredCacheCompatible() const {
|
||||
return flags & IS_TIERED_CACHE_COMPATIBLE;
|
||||
}
|
||||
bool IsPending() const { return flags & IS_PENDING; }
|
||||
bool IsPromoted() const { return flags & IS_PROMOTED; }
|
||||
|
||||
void SetInCache(bool in_cache) {
|
||||
if (in_cache) {
|
||||
@@ -122,10 +140,38 @@ struct LRUHandle {
|
||||
|
||||
void SetHit() { flags |= HAS_HIT; }
|
||||
|
||||
void SetTieredCacheCompatible(bool tiered) {
|
||||
if (tiered) {
|
||||
flags |= IS_TIERED_CACHE_COMPATIBLE;
|
||||
} else {
|
||||
flags &= ~IS_TIERED_CACHE_COMPATIBLE;
|
||||
}
|
||||
}
|
||||
|
||||
void SetIncomplete(bool incomp) {
|
||||
if (incomp) {
|
||||
flags |= IS_PENDING;
|
||||
} else {
|
||||
flags &= ~IS_PENDING;
|
||||
}
|
||||
}
|
||||
|
||||
void SetPromoted(bool promoted) {
|
||||
if (promoted) {
|
||||
flags |= IS_PROMOTED;
|
||||
} else {
|
||||
flags &= ~IS_PROMOTED;
|
||||
}
|
||||
}
|
||||
|
||||
void Free() {
|
||||
assert(refs == 0);
|
||||
if (deleter) {
|
||||
(*deleter)(key(), value);
|
||||
if (!IsTieredCacheCompatible() && info_.deleter) {
|
||||
(*info_.deleter)(key(), value);
|
||||
} else if (IsTieredCacheCompatible()) {
|
||||
ShardedCache::DeletionCallback del_cb;
|
||||
(*info_.helper_cb)(nullptr, nullptr, &del_cb);
|
||||
(*del_cb)(key(), value);
|
||||
}
|
||||
delete[] reinterpret_cast<char*>(this);
|
||||
}
|
||||
@@ -193,7 +239,8 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
public:
|
||||
LRUCacheShard(size_t capacity, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio, bool use_adaptive_mutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy);
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
const std::shared_ptr<TieredCache>& tiered_cache);
|
||||
virtual ~LRUCacheShard() override = default;
|
||||
|
||||
// Separate from constructor so caller can easily make an array of LRUCache
|
||||
@@ -212,8 +259,32 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
Cache::Handle** handle,
|
||||
Cache::Priority priority) override;
|
||||
virtual Cache::Handle* Lookup(const Slice& key, uint32_t hash) override;
|
||||
Cache::Priority priority) override {
|
||||
return Insert(key, hash, value, charge, deleter, nullptr, handle, priority);
|
||||
}
|
||||
virtual Status Insert(const Slice& key, uint32_t hash, void* value,
|
||||
Cache::CacheItemHelperCallback helper_cb, size_t charge,
|
||||
Cache::Handle** handle,
|
||||
Cache::Priority priority) override {
|
||||
return Insert(key, hash, value, charge, nullptr, helper_cb, handle,
|
||||
priority);
|
||||
}
|
||||
// If helper_cb is null, the values of the following arguments don't
|
||||
// matter
|
||||
virtual Cache::Handle* Lookup(const Slice& key, uint32_t hash,
|
||||
ShardedCache::CacheItemHelperCallback helper_cb,
|
||||
const ShardedCache::CreateCallback& create_cb,
|
||||
ShardedCache::Priority priority,
|
||||
bool wait) override;
|
||||
virtual Cache::Handle* Lookup(const Slice& key, uint32_t hash) override {
|
||||
return Lookup(key, hash, nullptr, nullptr, Cache::Priority::LOW, true);
|
||||
}
|
||||
virtual bool Release(Cache::Handle* handle, bool /*useful*/,
|
||||
bool force_erase) override {
|
||||
return Release(handle, force_erase);
|
||||
}
|
||||
virtual bool isReady(Cache::Handle* /*handle*/) override { return true; }
|
||||
virtual void Wait(Cache::Handle* /*handle*/) override {}
|
||||
virtual bool Ref(Cache::Handle* handle) override;
|
||||
virtual bool Release(Cache::Handle* handle,
|
||||
bool force_erase = false) override;
|
||||
@@ -243,6 +314,11 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
double GetHighPriPoolRatio();
|
||||
|
||||
private:
|
||||
Status InsertItem(LRUHandle* item, Cache::Handle** handle);
|
||||
Status Insert(const Slice& key, uint32_t hash, void* value, size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
Cache::CacheItemHelperCallback helper_cb,
|
||||
Cache::Handle** handle, Cache::Priority priority);
|
||||
void LRU_Remove(LRUHandle* e);
|
||||
void LRU_Insert(LRUHandle* e);
|
||||
|
||||
@@ -303,6 +379,8 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
// We don't count mutex_ as the cache's internal state so semantically we
|
||||
// don't mind mutex_ invoking the non-const actions.
|
||||
mutable port::Mutex mutex_;
|
||||
|
||||
std::shared_ptr<TieredCache> tiered_cache_;
|
||||
};
|
||||
|
||||
class LRUCache
|
||||
@@ -316,7 +394,8 @@ class LRUCache
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator = nullptr,
|
||||
bool use_adaptive_mutex = kDefaultToAdaptiveMutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy =
|
||||
kDontChargeCacheMetadata);
|
||||
kDontChargeCacheMetadata,
|
||||
const std::shared_ptr<TieredCache>& tiered_cache = nullptr);
|
||||
virtual ~LRUCache();
|
||||
virtual const char* Name() const override { return "LRUCache"; }
|
||||
virtual CacheShard* GetShard(int shard) override;
|
||||
@@ -325,6 +404,7 @@ class LRUCache
|
||||
virtual size_t GetCharge(Handle* handle) const override;
|
||||
virtual uint32_t GetHash(Handle* handle) const override;
|
||||
virtual void DisownData() override;
|
||||
virtual void WaitAll(std::vector<Handle*>& /*handles*/) override {}
|
||||
|
||||
// Retrieves number of elements in LRU, for unit test purpose only
|
||||
size_t TEST_GetLRUSize();
|
||||
|
||||
Vendored
+176
-3
@@ -7,8 +7,12 @@
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/random.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -30,9 +34,9 @@ class LRUCacheTest : public testing::Test {
|
||||
DeleteCache();
|
||||
cache_ = reinterpret_cast<LRUCacheShard*>(
|
||||
port::cacheline_aligned_alloc(sizeof(LRUCacheShard)));
|
||||
new (cache_) LRUCacheShard(capacity, false /*strict_capacity_limit*/,
|
||||
high_pri_pool_ratio, use_adaptive_mutex,
|
||||
kDontChargeCacheMetadata);
|
||||
new (cache_) LRUCacheShard(
|
||||
capacity, false /*strict_capcity_limit*/, high_pri_pool_ratio,
|
||||
use_adaptive_mutex, kDontChargeCacheMetadata, nullptr /*tiered_cache*/);
|
||||
}
|
||||
|
||||
void Insert(const std::string& key,
|
||||
@@ -191,6 +195,175 @@ TEST_F(LRUCacheTest, EntriesWithPriority) {
|
||||
ValidateLRUList({"e", "f", "g", "Z", "d"}, 2);
|
||||
}
|
||||
|
||||
class TestTieredCache : public TieredCache {
|
||||
public:
|
||||
TestTieredCache(size_t capacity) : num_inserts_(0), num_lookups_(0) {
|
||||
cache_ = NewLRUCache(capacity, 0, false, 0.5, nullptr,
|
||||
kDefaultToAdaptiveMutex, kDontChargeCacheMetadata);
|
||||
}
|
||||
~TestTieredCache() { cache_.reset(); }
|
||||
|
||||
std::string Name() override { return "TestTieredCache"; }
|
||||
|
||||
Status Insert(const Slice& key, void* value,
|
||||
Cache::CacheItemHelperCallback helper_cb) override {
|
||||
Cache::SizeCallback size_cb;
|
||||
Cache::SaveToCallback save_cb;
|
||||
size_t size;
|
||||
char* buf;
|
||||
Status s;
|
||||
|
||||
num_inserts_++;
|
||||
(*helper_cb)(&size_cb, &save_cb, nullptr);
|
||||
size = (*size_cb)(value);
|
||||
buf = new char[size + sizeof(uint64_t)];
|
||||
EncodeFixed64(buf, size);
|
||||
s = (*save_cb)(value, 0, size, buf + sizeof(uint64_t));
|
||||
EXPECT_OK(s);
|
||||
return cache_->Insert(key, buf, size,
|
||||
[](const Slice& /*key*/, void* val) -> void {
|
||||
delete[] reinterpret_cast<char*>(val);
|
||||
});
|
||||
}
|
||||
|
||||
std::unique_ptr<TieredCacheHandle> Lookup(
|
||||
const Slice& key, const Cache::CreateCallback& create_cb,
|
||||
bool /*wait*/) override {
|
||||
std::unique_ptr<TieredCacheHandle> tiered_handle;
|
||||
Cache::Handle* handle = cache_->Lookup(key);
|
||||
num_lookups_++;
|
||||
if (handle) {
|
||||
void* value;
|
||||
size_t charge;
|
||||
char* ptr = (char*)cache_->Value(handle);
|
||||
size_t size = DecodeFixed64(ptr);
|
||||
ptr += sizeof(uint64_t);
|
||||
Status s = create_cb(ptr, size, &value, &charge);
|
||||
EXPECT_OK(s);
|
||||
tiered_handle.reset(
|
||||
new TestTieredCacheHandle(cache_.get(), handle, value, charge));
|
||||
}
|
||||
return tiered_handle;
|
||||
}
|
||||
|
||||
void Erase(const Slice& /*key*/) override {}
|
||||
|
||||
void WaitAll(std::vector<TieredCacheHandle*> /*handles*/) override {}
|
||||
|
||||
std::string GetPrintableOptions() const override { return ""; }
|
||||
|
||||
uint32_t num_inserts() { return num_inserts_; }
|
||||
|
||||
uint32_t num_lookups() { return num_lookups_; }
|
||||
|
||||
private:
|
||||
class TestTieredCacheHandle : public TieredCacheHandle {
|
||||
public:
|
||||
TestTieredCacheHandle(Cache* cache, Cache::Handle* handle, void* value,
|
||||
size_t size)
|
||||
: cache_(cache), handle_(handle), value_(value), size_(size) {}
|
||||
~TestTieredCacheHandle() { cache_->Release(handle_); }
|
||||
|
||||
bool isReady() override { return true; }
|
||||
|
||||
void Wait() override {}
|
||||
|
||||
void* Value() override { return value_; }
|
||||
|
||||
size_t Size() override { return size_; }
|
||||
|
||||
private:
|
||||
Cache* cache_;
|
||||
Cache::Handle* handle_;
|
||||
void* value_;
|
||||
size_t size_;
|
||||
};
|
||||
|
||||
std::shared_ptr<Cache> cache_;
|
||||
uint32_t num_inserts_;
|
||||
uint32_t num_lookups_;
|
||||
};
|
||||
|
||||
TEST_F(LRUCacheTest, TestTieredCache) {
|
||||
LRUCacheOptions opts(1024, 0, false, 0.5, nullptr, kDefaultToAdaptiveMutex,
|
||||
kDontChargeCacheMetadata);
|
||||
std::shared_ptr<TestTieredCache> tiered_cache(new TestTieredCache(2048));
|
||||
opts.tiered_cache = tiered_cache;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(opts);
|
||||
|
||||
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_;
|
||||
};
|
||||
|
||||
Cache::CacheItemHelperCallback helper_cb =
|
||||
[](Cache::SizeCallback* size_cb, Cache::SaveToCallback* saveto_cb,
|
||||
Cache::DeletionCallback* del_cb) -> void {
|
||||
if (size_cb) {
|
||||
*size_cb = [](void* obj) -> size_t {
|
||||
return reinterpret_cast<TestItem*>(obj)->Size();
|
||||
};
|
||||
}
|
||||
if (saveto_cb) {
|
||||
*saveto_cb = [](void* obj, size_t offset, size_t size,
|
||||
void* out) -> Status {
|
||||
TestItem* item = reinterpret_cast<TestItem*>(obj);
|
||||
char* buf = item->Buf();
|
||||
EXPECT_EQ(size, item->Size());
|
||||
EXPECT_EQ(offset, 0);
|
||||
memcpy(out, buf, size);
|
||||
return Status::OK();
|
||||
};
|
||||
}
|
||||
if (del_cb) {
|
||||
*del_cb = [](const Slice& /*key*/, void* obj) -> void {
|
||||
delete reinterpret_cast<TestItem*>(obj);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
int create_count = 0;
|
||||
Cache::CreateCallback test_item_creator =
|
||||
[&create_count](void* buf, size_t size, void** out_obj,
|
||||
size_t* charge) -> Status {
|
||||
create_count++;
|
||||
*out_obj = reinterpret_cast<void*>(new TestItem((char*)buf, size));
|
||||
*charge = size;
|
||||
return Status::OK();
|
||||
};
|
||||
|
||||
Random rnd(301);
|
||||
std::string str1 = rnd.RandomString(1020);
|
||||
TestItem* item1 = new TestItem(str1.data(), str1.length());
|
||||
ASSERT_OK(cache->Insert("k1", item1, helper_cb, str1.length()));
|
||||
std::string str2 = rnd.RandomString(1020);
|
||||
TestItem* item2 = new TestItem(str2.data(), str2.length());
|
||||
// k2 should be demoted to NVM
|
||||
ASSERT_OK(cache->Insert("k2", item2, helper_cb, str2.length()));
|
||||
|
||||
Cache::Handle* handle;
|
||||
handle = cache->Lookup("k2", helper_cb, test_item_creator,
|
||||
Cache::Priority::LOW, true);
|
||||
ASSERT_NE(handle, nullptr);
|
||||
cache->Release(handle);
|
||||
// This lookup should promote k1 and demote k2
|
||||
handle = cache->Lookup("k1", helper_cb, test_item_creator,
|
||||
Cache::Priority::LOW, true);
|
||||
ASSERT_NE(handle, nullptr);
|
||||
cache->Release(handle);
|
||||
ASSERT_EQ(tiered_cache->num_inserts(), 2u);
|
||||
ASSERT_EQ(tiered_cache->num_lookups(), 1u);
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
Vendored
+33
@@ -51,11 +51,39 @@ Status ShardedCache::Insert(const Slice& key, void* value, size_t charge,
|
||||
->Insert(key, hash, value, charge, deleter, handle, priority);
|
||||
}
|
||||
|
||||
Status ShardedCache::Insert(const Slice& key, void* value,
|
||||
CacheItemHelperCallback helper_cb, size_t charge,
|
||||
Handle** handle, Priority priority) {
|
||||
uint32_t hash = HashSlice(key);
|
||||
return GetShard(Shard(hash))
|
||||
->Insert(key, hash, value, helper_cb, charge, handle, priority);
|
||||
}
|
||||
|
||||
Cache::Handle* ShardedCache::Lookup(const Slice& key, Statistics* /*stats*/) {
|
||||
uint32_t hash = HashSlice(key);
|
||||
return GetShard(Shard(hash))->Lookup(key, hash);
|
||||
}
|
||||
|
||||
Cache::Handle* ShardedCache::Lookup(const Slice& key,
|
||||
CacheItemHelperCallback helper_cb,
|
||||
const CreateCallback& create_cb,
|
||||
Priority priority, bool wait,
|
||||
Statistics* /*stats*/) {
|
||||
uint32_t hash = HashSlice(key);
|
||||
return GetShard(Shard(hash))
|
||||
->Lookup(key, hash, helper_cb, create_cb, priority, wait);
|
||||
}
|
||||
|
||||
bool ShardedCache::isReady(Handle* handle) {
|
||||
uint32_t hash = GetHash(handle);
|
||||
return GetShard(Shard(hash))->isReady(handle);
|
||||
}
|
||||
|
||||
void ShardedCache::Wait(Handle* handle) {
|
||||
uint32_t hash = GetHash(handle);
|
||||
GetShard(Shard(hash))->Wait(handle);
|
||||
}
|
||||
|
||||
bool ShardedCache::Ref(Handle* handle) {
|
||||
uint32_t hash = GetHash(handle);
|
||||
return GetShard(Shard(hash))->Ref(handle);
|
||||
@@ -66,6 +94,11 @@ bool ShardedCache::Release(Handle* handle, bool force_erase) {
|
||||
return GetShard(Shard(hash))->Release(handle, force_erase);
|
||||
}
|
||||
|
||||
bool ShardedCache::Release(Handle* handle, bool useful, bool force_erase) {
|
||||
uint32_t hash = GetHash(handle);
|
||||
return GetShard(Shard(hash))->Release(handle, useful, force_erase);
|
||||
}
|
||||
|
||||
void ShardedCache::Erase(const Slice& key) {
|
||||
uint32_t hash = HashSlice(key);
|
||||
GetShard(Shard(hash))->Erase(key, hash);
|
||||
|
||||
Vendored
+24
-1
@@ -28,9 +28,20 @@ class CacheShard {
|
||||
size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
Cache::Handle** handle, Cache::Priority priority) = 0;
|
||||
virtual Status Insert(const Slice& key, uint32_t hash, void* value,
|
||||
Cache::CacheItemHelperCallback helper_cb, size_t charge,
|
||||
Cache::Handle** handle, Cache::Priority priority) = 0;
|
||||
virtual Cache::Handle* Lookup(const Slice& key, uint32_t hash) = 0;
|
||||
virtual Cache::Handle* Lookup(const Slice& key, uint32_t hash,
|
||||
Cache::CacheItemHelperCallback helper_cb,
|
||||
const Cache::CreateCallback& create_cb,
|
||||
Cache::Priority priority, bool wait) = 0;
|
||||
virtual bool Release(Cache::Handle* handle, bool useful,
|
||||
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 force_erase = false) = 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;
|
||||
@@ -62,6 +73,7 @@ class ShardedCache : public Cache {
|
||||
virtual const CacheShard* GetShard(int shard) const = 0;
|
||||
virtual void* Value(Handle* handle) override = 0;
|
||||
virtual size_t GetCharge(Handle* handle) const override = 0;
|
||||
virtual void WaitAll(std::vector<Handle*>& handles) override = 0;
|
||||
|
||||
virtual uint32_t GetHash(Handle* handle) const = 0;
|
||||
virtual void DisownData() override = 0;
|
||||
@@ -72,7 +84,18 @@ class ShardedCache : public Cache {
|
||||
virtual Status Insert(const Slice& key, void* value, size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
Handle** handle, Priority priority) override;
|
||||
virtual Status Insert(const Slice& key, void* value,
|
||||
CacheItemHelperCallback helper_cb, size_t chargge,
|
||||
Handle** handle = nullptr,
|
||||
Priority priority = Priority::LOW) override;
|
||||
virtual Handle* Lookup(const Slice& key, Statistics* stats) override;
|
||||
virtual Handle* Lookup(const Slice& key, CacheItemHelperCallback helper_cb,
|
||||
const CreateCallback& create_cb, Priority priority,
|
||||
bool wait, Statistics* stats = nullptr) override;
|
||||
virtual bool Release(Handle* handle, bool useful,
|
||||
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 force_erase = false) override;
|
||||
virtual void Erase(const Slice& key) override;
|
||||
|
||||
@@ -190,7 +190,7 @@ Status BlobFileBuilder::OpenBlobFileIfNeeded() {
|
||||
std::move(file), blob_file_paths_->back(), *file_options_,
|
||||
immutable_cf_options_->clock, io_tracer_, statistics,
|
||||
immutable_cf_options_->listeners,
|
||||
immutable_cf_options_->file_checksum_gen_factory,
|
||||
immutable_cf_options_->file_checksum_gen_factory.get(),
|
||||
tmp_set.Contains(FileType::kBlobFile)));
|
||||
|
||||
constexpr bool do_flush = false;
|
||||
@@ -306,7 +306,6 @@ Status BlobFileBuilder::CloseBlobFile() {
|
||||
" total blobs, %" PRIu64 " total bytes",
|
||||
column_family_name_.c_str(), job_id_, blob_file_number,
|
||||
blob_count_, blob_bytes_);
|
||||
|
||||
if (blob_callback_) {
|
||||
s = blob_callback_->OnBlobFileCompleted(blob_file_paths_->back());
|
||||
}
|
||||
|
||||
@@ -16,6 +16,14 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class BlobFileCompletionCallback {
|
||||
public:
|
||||
#ifdef ROCKSDB_LITE
|
||||
BlobFileCompletionCallback(SstFileManager* /*sst_file_manager*/,
|
||||
InstrumentedMutex* /*mutex*/,
|
||||
ErrorHandler* /*error_handler*/) {}
|
||||
Status OnBlobFileCompleted(const std::string& /*file_name*/) {
|
||||
return Status::OK();
|
||||
}
|
||||
#else
|
||||
BlobFileCompletionCallback(SstFileManager* sst_file_manager,
|
||||
InstrumentedMutex* mutex,
|
||||
ErrorHandler* error_handler)
|
||||
@@ -25,8 +33,6 @@ class BlobFileCompletionCallback {
|
||||
|
||||
Status OnBlobFileCompleted(const std::string& file_name) {
|
||||
Status s;
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(sst_file_manager_);
|
||||
if (sfm) {
|
||||
// Report new blob files to SstFileManagerImpl
|
||||
@@ -39,9 +45,6 @@ class BlobFileCompletionCallback {
|
||||
error_handler_->SetBGError(s, BackgroundErrorReason::kFlush);
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)file_name;
|
||||
#endif // ROCKSDB_LITE
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -49,5 +52,6 @@ class BlobFileCompletionCallback {
|
||||
SstFileManager* sst_file_manager_;
|
||||
InstrumentedMutex* mutex_;
|
||||
ErrorHandler* error_handler_;
|
||||
#endif // ROCKSDB_LITE
|
||||
};
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -120,7 +120,7 @@ Status BlobFileReader::OpenFile(
|
||||
file_reader->reset(new RandomAccessFileReader(
|
||||
std::move(file), blob_file_path, immutable_cf_options.clock, io_tracer,
|
||||
immutable_cf_options.statistics, BLOB_DB_BLOB_FILE_READ_MICROS,
|
||||
blob_file_read_hist, immutable_cf_options.rate_limiter,
|
||||
blob_file_read_hist, immutable_cf_options.rate_limiter.get(),
|
||||
immutable_cf_options.listeners));
|
||||
|
||||
return Status::OK();
|
||||
|
||||
@@ -267,6 +267,49 @@ TEST_F(DBBlobBasicTest, GenerateIOTracing) {
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBBlobBasicTest, BestEffortsRecovery_MissingNewestBlobFile) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
options.create_if_missing = true;
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(dbfull()->DisableFileDeletions());
|
||||
constexpr int kNumTableFiles = 2;
|
||||
for (int i = 0; i < kNumTableFiles; ++i) {
|
||||
for (char ch = 'a'; ch != 'c'; ++ch) {
|
||||
std::string key(1, ch);
|
||||
ASSERT_OK(Put(key, "value" + std::to_string(i)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
Close();
|
||||
|
||||
std::vector<std::string> files;
|
||||
ASSERT_OK(env_->GetChildren(dbname_, &files));
|
||||
std::string blob_file_path;
|
||||
uint64_t max_blob_file_num = kInvalidBlobFileNumber;
|
||||
for (const auto& fname : files) {
|
||||
uint64_t file_num = 0;
|
||||
FileType type;
|
||||
if (ParseFileName(fname, &file_num, /*info_log_name_prefix=*/"", &type) &&
|
||||
type == kBlobFile) {
|
||||
if (file_num > max_blob_file_num) {
|
||||
max_blob_file_num = file_num;
|
||||
blob_file_path = dbname_ + "/" + fname;
|
||||
}
|
||||
}
|
||||
}
|
||||
ASSERT_OK(env_->DeleteFile(blob_file_path));
|
||||
|
||||
options.best_efforts_recovery = true;
|
||||
Reopen(options);
|
||||
std::string value;
|
||||
ASSERT_OK(db_->Get(ReadOptions(), "a", &value));
|
||||
ASSERT_EQ("value" + std::to_string(kNumTableFiles - 2), value);
|
||||
}
|
||||
|
||||
class DBBlobBasicIOErrorTest : public DBBlobBasicTest,
|
||||
public testing::WithParamInterface<std::string> {
|
||||
protected:
|
||||
|
||||
+2
-2
@@ -156,7 +156,7 @@ Status BuildTable(
|
||||
file_writer.reset(new WritableFileWriter(
|
||||
std::move(file), fname, file_options, ioptions.clock, io_tracer,
|
||||
ioptions.statistics, ioptions.listeners,
|
||||
ioptions.file_checksum_gen_factory,
|
||||
ioptions.file_checksum_gen_factory.get(),
|
||||
tmp_set.Contains(FileType::kTableFile)));
|
||||
|
||||
builder = NewTableBuilder(
|
||||
@@ -168,7 +168,7 @@ Status BuildTable(
|
||||
}
|
||||
|
||||
MergeHelper merge(env, internal_comparator.user_comparator(),
|
||||
ioptions.merge_operator, nullptr, ioptions.info_log,
|
||||
ioptions.merge_operator.get(), nullptr, ioptions.info_log,
|
||||
true /* internal key corruption is not ok */,
|
||||
snapshots.empty() ? 0 : snapshots.back(),
|
||||
snapshot_checker);
|
||||
|
||||
@@ -79,6 +79,8 @@ using ROCKSDB_NAMESPACE::IngestExternalFileOptions;
|
||||
using ROCKSDB_NAMESPACE::Iterator;
|
||||
using ROCKSDB_NAMESPACE::LiveFileMetaData;
|
||||
using ROCKSDB_NAMESPACE::Logger;
|
||||
using ROCKSDB_NAMESPACE::LRUCacheOptions;
|
||||
using ROCKSDB_NAMESPACE::MemoryAllocator;
|
||||
using ROCKSDB_NAMESPACE::MemoryUtil;
|
||||
using ROCKSDB_NAMESPACE::MergeOperator;
|
||||
using ROCKSDB_NAMESPACE::NewBloomFilterPolicy;
|
||||
@@ -150,6 +152,12 @@ struct rocksdb_filelock_t { FileLock* rep; };
|
||||
struct rocksdb_logger_t {
|
||||
std::shared_ptr<Logger> rep;
|
||||
};
|
||||
struct rocksdb_lru_cache_options_t {
|
||||
LRUCacheOptions rep;
|
||||
};
|
||||
struct rocksdb_memory_allocator_t {
|
||||
std::shared_ptr<MemoryAllocator> rep;
|
||||
};
|
||||
struct rocksdb_cache_t {
|
||||
std::shared_ptr<Cache> rep;
|
||||
};
|
||||
@@ -2682,6 +2690,59 @@ unsigned char rocksdb_options_get_skip_checking_sst_file_sizes_on_db_open(
|
||||
return opt->rep.skip_checking_sst_file_sizes_on_db_open;
|
||||
}
|
||||
|
||||
/* Blob Options Settings */
|
||||
void rocksdb_options_set_enable_blob_files(rocksdb_options_t* opt,
|
||||
unsigned char val) {
|
||||
opt->rep.enable_blob_files = val;
|
||||
}
|
||||
extern ROCKSDB_LIBRARY_API unsigned char rocksdb_options_get_enable_blob_files(
|
||||
rocksdb_options_t* opt) {
|
||||
return opt->rep.enable_blob_files;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_min_blob_size(rocksdb_options_t* opt, uint64_t val) {
|
||||
opt->rep.min_blob_size = val;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_options_get_min_blob_size(rocksdb_options_t* opt) {
|
||||
return opt->rep.min_blob_size;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_blob_file_size(rocksdb_options_t* opt, uint64_t val) {
|
||||
opt->rep.blob_file_size = val;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_options_get_blob_file_size(rocksdb_options_t* opt) {
|
||||
return opt->rep.blob_file_size;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_blob_compression_type(rocksdb_options_t* opt,
|
||||
int val) {
|
||||
opt->rep.blob_compression_type = static_cast<CompressionType>(val);
|
||||
}
|
||||
|
||||
int rocksdb_options_get_blob_compression_type(rocksdb_options_t* opt) {
|
||||
return opt->rep.blob_compression_type;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_enable_blob_gc(rocksdb_options_t* opt,
|
||||
unsigned char val) {
|
||||
opt->rep.enable_blob_garbage_collection = val;
|
||||
}
|
||||
|
||||
unsigned char rocksdb_options_get_enable_blob_gc(rocksdb_options_t* opt) {
|
||||
return opt->rep.enable_blob_garbage_collection;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_blob_gc_age_cutoff(rocksdb_options_t* opt,
|
||||
double val) {
|
||||
opt->rep.blob_garbage_collection_age_cutoff = val;
|
||||
}
|
||||
|
||||
double rocksdb_options_get_blob_gc_age_cutoff(rocksdb_options_t* opt) {
|
||||
return opt->rep.blob_garbage_collection_age_cutoff;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_num_levels(rocksdb_options_t* opt, int n) {
|
||||
opt->rep.num_levels = n;
|
||||
}
|
||||
@@ -4102,16 +4163,58 @@ unsigned char rocksdb_flushoptions_get_wait(rocksdb_flushoptions_t* opt) {
|
||||
return opt->rep.wait;
|
||||
}
|
||||
|
||||
rocksdb_memory_allocator_t* rocksdb_jemalloc_nodump_allocator_create(
|
||||
char** errptr) {
|
||||
rocksdb_memory_allocator_t* allocator = new rocksdb_memory_allocator_t;
|
||||
ROCKSDB_NAMESPACE::JemallocAllocatorOptions options;
|
||||
SaveError(errptr, ROCKSDB_NAMESPACE::NewJemallocNodumpAllocator(
|
||||
options, &allocator->rep));
|
||||
return allocator;
|
||||
}
|
||||
|
||||
void rocksdb_memory_allocator_destroy(rocksdb_memory_allocator_t* allocator) {
|
||||
delete allocator;
|
||||
}
|
||||
|
||||
rocksdb_lru_cache_options_t* rocksdb_lru_cache_options_create() {
|
||||
return new rocksdb_lru_cache_options_t;
|
||||
}
|
||||
|
||||
void rocksdb_lru_cache_options_destroy(rocksdb_lru_cache_options_t* opt) {
|
||||
delete opt;
|
||||
}
|
||||
|
||||
void rocksdb_lru_cache_options_set_capacity(rocksdb_lru_cache_options_t* opt,
|
||||
size_t capacity) {
|
||||
opt->rep.capacity = capacity;
|
||||
}
|
||||
|
||||
void rocksdb_lru_cache_options_set_memory_allocator(
|
||||
rocksdb_lru_cache_options_t* opt, rocksdb_memory_allocator_t* allocator) {
|
||||
opt->rep.memory_allocator = allocator->rep;
|
||||
}
|
||||
|
||||
rocksdb_cache_t* rocksdb_cache_create_lru(size_t capacity) {
|
||||
rocksdb_cache_t* c = new rocksdb_cache_t;
|
||||
c->rep = NewLRUCache(capacity);
|
||||
return c;
|
||||
}
|
||||
|
||||
rocksdb_cache_t* rocksdb_cache_create_lru_opts(
|
||||
rocksdb_lru_cache_options_t* opt) {
|
||||
rocksdb_cache_t* c = new rocksdb_cache_t;
|
||||
c->rep = NewLRUCache(opt->rep);
|
||||
return c;
|
||||
}
|
||||
|
||||
void rocksdb_cache_destroy(rocksdb_cache_t* cache) {
|
||||
delete cache;
|
||||
}
|
||||
|
||||
void rocksdb_cache_disown_data(rocksdb_cache_t* cache) {
|
||||
cache->rep->DisownData();
|
||||
}
|
||||
|
||||
void rocksdb_cache_set_capacity(rocksdb_cache_t* cache, size_t capacity) {
|
||||
cache->rep->SetCapacity(capacity);
|
||||
}
|
||||
|
||||
+51
@@ -1759,6 +1759,25 @@ int main(int argc, char** argv) {
|
||||
rocksdb_options_set_atomic_flush(o, 1);
|
||||
CheckCondition(1 == rocksdb_options_get_atomic_flush(o));
|
||||
|
||||
/* Blob Options */
|
||||
rocksdb_options_set_enable_blob_files(o, 1);
|
||||
CheckCondition(1 == rocksdb_options_get_enable_blob_files(o));
|
||||
|
||||
rocksdb_options_set_min_blob_size(o, 29);
|
||||
CheckCondition(29 == rocksdb_options_get_min_blob_size(o));
|
||||
|
||||
rocksdb_options_set_blob_file_size(o, 30);
|
||||
CheckCondition(30 == rocksdb_options_get_blob_file_size(o));
|
||||
|
||||
rocksdb_options_set_blob_compression_type(o, 4);
|
||||
CheckCondition(4 == rocksdb_options_get_blob_compression_type(o));
|
||||
|
||||
rocksdb_options_set_enable_blob_gc(o, 1);
|
||||
CheckCondition(1 == rocksdb_options_get_enable_blob_gc(o));
|
||||
|
||||
rocksdb_options_set_blob_gc_age_cutoff(o, 0.75);
|
||||
CheckCondition(0.75 == rocksdb_options_get_blob_gc_age_cutoff(o));
|
||||
|
||||
// Create a copy that should be equal to the original.
|
||||
rocksdb_options_t* copy;
|
||||
copy = rocksdb_options_create_copy(o);
|
||||
@@ -2362,6 +2381,37 @@ int main(int argc, char** argv) {
|
||||
rocksdb_cache_destroy(co);
|
||||
}
|
||||
|
||||
StartPhase("jemalloc_nodump_allocator");
|
||||
{
|
||||
rocksdb_memory_allocator_t* allocator;
|
||||
allocator = rocksdb_jemalloc_nodump_allocator_create(&err);
|
||||
if (err != NULL) {
|
||||
// not supported on all platforms, allow unsupported error
|
||||
const char* ni = "Not implemented: ";
|
||||
size_t ni_len = strlen(ni);
|
||||
size_t err_len = strlen(err);
|
||||
|
||||
CheckCondition(err_len >= ni_len);
|
||||
CheckCondition(memcmp(ni, err, ni_len) == 0);
|
||||
Free(&err);
|
||||
} else {
|
||||
rocksdb_cache_t* co;
|
||||
rocksdb_lru_cache_options_t* copts;
|
||||
|
||||
copts = rocksdb_lru_cache_options_create();
|
||||
|
||||
rocksdb_lru_cache_options_set_capacity(copts, 100);
|
||||
rocksdb_lru_cache_options_set_memory_allocator(copts, allocator);
|
||||
|
||||
co = rocksdb_cache_create_lru_opts(copts);
|
||||
CheckCondition(100 == rocksdb_cache_get_capacity(co));
|
||||
|
||||
rocksdb_cache_destroy(co);
|
||||
rocksdb_lru_cache_options_destroy(copts);
|
||||
}
|
||||
rocksdb_memory_allocator_destroy(allocator);
|
||||
}
|
||||
|
||||
StartPhase("env");
|
||||
{
|
||||
rocksdb_env_t* e;
|
||||
@@ -2869,6 +2919,7 @@ int main(int argc, char** argv) {
|
||||
rocksdb_readoptions_destroy(roptions);
|
||||
rocksdb_writeoptions_destroy(woptions);
|
||||
rocksdb_compactoptions_destroy(coptions);
|
||||
rocksdb_cache_disown_data(cache);
|
||||
rocksdb_cache_destroy(cache);
|
||||
rocksdb_comparator_destroy(cmp);
|
||||
rocksdb_dbpath_destroy(dbpath);
|
||||
|
||||
@@ -1355,6 +1355,12 @@ Status ColumnFamilyData::ValidateOptions(
|
||||
"[0.0, 1.0].");
|
||||
}
|
||||
|
||||
if (cf_options.compaction_style == kCompactionStyleFIFO &&
|
||||
db_options.max_open_files != -1 && cf_options.ttl > 0) {
|
||||
return Status::NotSupported(
|
||||
"FIFO compaction only supported with max_open_files = -1.");
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
+140
-16
@@ -312,14 +312,19 @@ CompactionJob::CompactionJob(
|
||||
const std::atomic<int>* manual_compaction_paused, const std::string& db_id,
|
||||
const std::string& db_session_id, std::string full_history_ts_low,
|
||||
BlobFileCompletionCallback* blob_callback)
|
||||
: job_id_(job_id),
|
||||
compact_(new CompactionState(compaction)),
|
||||
compaction_job_stats_(compaction_job_stats),
|
||||
: compact_(new CompactionState(compaction)),
|
||||
compaction_stats_(compaction->compaction_reason(), 1),
|
||||
db_options_(db_options),
|
||||
log_buffer_(log_buffer),
|
||||
output_directory_(output_directory),
|
||||
stats_(stats),
|
||||
bottommost_level_(false),
|
||||
write_hint_(Env::WLTH_NOT_SET),
|
||||
job_id_(job_id),
|
||||
compaction_job_stats_(compaction_job_stats),
|
||||
dbname_(dbname),
|
||||
db_id_(db_id),
|
||||
db_session_id_(db_session_id),
|
||||
db_options_(db_options),
|
||||
file_options_(file_options),
|
||||
env_(db_options.env),
|
||||
io_tracer_(io_tracer),
|
||||
@@ -330,11 +335,8 @@ CompactionJob::CompactionJob(
|
||||
shutting_down_(shutting_down),
|
||||
manual_compaction_paused_(manual_compaction_paused),
|
||||
preserve_deletes_seqnum_(preserve_deletes_seqnum),
|
||||
log_buffer_(log_buffer),
|
||||
db_directory_(db_directory),
|
||||
output_directory_(output_directory),
|
||||
blob_output_directory_(blob_output_directory),
|
||||
stats_(stats),
|
||||
db_mutex_(db_mutex),
|
||||
db_error_handler_(db_error_handler),
|
||||
existing_snapshots_(std::move(existing_snapshots)),
|
||||
@@ -342,10 +344,8 @@ CompactionJob::CompactionJob(
|
||||
snapshot_checker_(snapshot_checker),
|
||||
table_cache_(std::move(table_cache)),
|
||||
event_logger_(event_logger),
|
||||
bottommost_level_(false),
|
||||
paranoid_file_checks_(paranoid_file_checks),
|
||||
measure_io_stats_(measure_io_stats),
|
||||
write_hint_(Env::WLTH_NOT_SET),
|
||||
thread_pri_(thread_pri),
|
||||
full_history_ts_low_(std::move(full_history_ts_low)),
|
||||
blob_callback_(blob_callback) {
|
||||
@@ -965,7 +965,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
}
|
||||
|
||||
MergeHelper merge(
|
||||
env_, cfd->user_comparator(), cfd->ioptions()->merge_operator,
|
||||
env_, cfd->user_comparator(), cfd->ioptions()->merge_operator.get(),
|
||||
compaction_filter, db_options_.info_log.get(),
|
||||
false /* internal key corruption is expected */,
|
||||
existing_snapshots_.empty() ? 0 : existing_snapshots_.back(),
|
||||
@@ -1550,9 +1550,7 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
FileDescriptor output_fd;
|
||||
uint64_t oldest_blob_file_number = kInvalidBlobFileNumber;
|
||||
if (meta != nullptr) {
|
||||
fname =
|
||||
TableFileName(sub_compact->compaction->immutable_cf_options()->cf_paths,
|
||||
meta->fd.GetNumber(), meta->fd.GetPathId());
|
||||
fname = GetTableFileName(meta->fd.GetNumber());
|
||||
output_fd = meta->fd;
|
||||
oldest_blob_file_number = meta->oldest_blob_file_number;
|
||||
} else {
|
||||
@@ -1672,9 +1670,7 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
assert(sub_compact->builder == nullptr);
|
||||
// no need to lock because VersionSet::next_file_number_ is atomic
|
||||
uint64_t file_number = versions_->NewFileNumber();
|
||||
std::string fname =
|
||||
TableFileName(sub_compact->compaction->immutable_cf_options()->cf_paths,
|
||||
file_number, sub_compact->compaction->output_path_id());
|
||||
std::string fname = GetTableFileName(file_number);
|
||||
// Fire events.
|
||||
ColumnFamilyData* cfd = sub_compact->compaction->column_family_data();
|
||||
#ifndef ROCKSDB_LITE
|
||||
@@ -1937,4 +1933,132 @@ void CompactionJob::LogCompaction() {
|
||||
}
|
||||
}
|
||||
|
||||
std::string CompactionJob::GetTableFileName(uint64_t file_number) {
|
||||
return TableFileName(compact_->compaction->immutable_cf_options()->cf_paths,
|
||||
file_number, compact_->compaction->output_path_id());
|
||||
}
|
||||
|
||||
std::string CompactionServiceCompactionJob::GetTableFileName(
|
||||
uint64_t file_number) {
|
||||
return MakeTableFileName(output_path_, file_number);
|
||||
}
|
||||
|
||||
CompactionServiceCompactionJob::CompactionServiceCompactionJob(
|
||||
int job_id, Compaction* compaction, const ImmutableDBOptions& db_options,
|
||||
const FileOptions& file_options, VersionSet* versions,
|
||||
const std::atomic<bool>* shutting_down, LogBuffer* log_buffer,
|
||||
FSDirectory* output_directory, Statistics* stats,
|
||||
InstrumentedMutex* db_mutex, ErrorHandler* db_error_handler,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
std::shared_ptr<Cache> table_cache, EventLogger* event_logger,
|
||||
const std::string& dbname, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::string& db_id, const std::string& db_session_id,
|
||||
const std::string& output_path,
|
||||
const CompactionServiceInput& compaction_service_input,
|
||||
CompactionServiceResult* compaction_service_result)
|
||||
: CompactionJob(
|
||||
job_id, compaction, db_options, file_options, versions, shutting_down,
|
||||
0, log_buffer, nullptr, output_directory, nullptr, stats, db_mutex,
|
||||
db_error_handler, existing_snapshots, kMaxSequenceNumber, nullptr,
|
||||
table_cache, event_logger,
|
||||
compaction->mutable_cf_options()->paranoid_file_checks,
|
||||
compaction->mutable_cf_options()->report_bg_io_stats, dbname,
|
||||
&(compaction_service_result->stats), Env::Priority::USER, io_tracer,
|
||||
nullptr, db_id, db_session_id,
|
||||
compaction->column_family_data()->GetFullHistoryTsLow()),
|
||||
output_path_(output_path),
|
||||
compaction_input_(compaction_service_input),
|
||||
compaction_result_(compaction_service_result) {}
|
||||
|
||||
Status CompactionServiceCompactionJob::Run() {
|
||||
AutoThreadOperationStageUpdater stage_updater(
|
||||
ThreadStatus::STAGE_COMPACTION_RUN);
|
||||
|
||||
auto* c = compact_->compaction;
|
||||
assert(c->column_family_data() != nullptr);
|
||||
assert(c->column_family_data()->current()->storage_info()->NumLevelFiles(
|
||||
compact_->compaction->level()) > 0);
|
||||
|
||||
write_hint_ =
|
||||
c->column_family_data()->CalculateSSTWriteHint(c->output_level());
|
||||
bottommost_level_ = c->bottommost_level();
|
||||
|
||||
compact_->sub_compact_states.emplace_back(c, compaction_input_.begin,
|
||||
compaction_input_.end,
|
||||
compaction_input_.approx_size);
|
||||
|
||||
log_buffer_->FlushBufferToLog();
|
||||
LogCompaction();
|
||||
const uint64_t start_micros = db_options_.clock->NowMicros();
|
||||
// Pick the only sub-compaction we should have
|
||||
assert(compact_->sub_compact_states.size() == 1);
|
||||
SubcompactionState* sub_compact = compact_->sub_compact_states.data();
|
||||
|
||||
ProcessKeyValueCompaction(sub_compact);
|
||||
|
||||
compaction_stats_.micros = db_options_.clock->NowMicros() - start_micros;
|
||||
compaction_stats_.cpu_micros = sub_compact->compaction_job_stats.cpu_micros;
|
||||
|
||||
RecordTimeToHistogram(stats_, COMPACTION_TIME, compaction_stats_.micros);
|
||||
RecordTimeToHistogram(stats_, COMPACTION_CPU_TIME,
|
||||
compaction_stats_.cpu_micros);
|
||||
|
||||
Status status = sub_compact->status;
|
||||
IOStatus io_s = sub_compact->io_status;
|
||||
|
||||
if (io_status_.ok()) {
|
||||
io_status_ = io_s;
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
constexpr IODebugContext* dbg = nullptr;
|
||||
|
||||
if (output_directory_) {
|
||||
io_s = output_directory_->Fsync(IOOptions(), dbg);
|
||||
}
|
||||
}
|
||||
if (io_status_.ok()) {
|
||||
io_status_ = io_s;
|
||||
}
|
||||
if (status.ok()) {
|
||||
status = io_s;
|
||||
}
|
||||
if (status.ok()) {
|
||||
// TODO: Add verify_table() and VerifyCompactionFileConsistency()
|
||||
}
|
||||
|
||||
// Finish up all book-keeping to unify the subcompaction results
|
||||
AggregateStatistics();
|
||||
UpdateCompactionStats();
|
||||
|
||||
compaction_result_->bytes_written = IOSTATS(bytes_written);
|
||||
compaction_result_->bytes_read = IOSTATS(bytes_read);
|
||||
RecordCompactionIOStats();
|
||||
|
||||
LogFlush(db_options_.info_log);
|
||||
compact_->status = status;
|
||||
compact_->status.PermitUncheckedError();
|
||||
|
||||
// Build compaction result
|
||||
compaction_result_->output_level = compact_->compaction->output_level();
|
||||
compaction_result_->output_path = output_path_;
|
||||
for (const auto& output_file : sub_compact->outputs) {
|
||||
auto& meta = output_file.meta;
|
||||
compaction_result_->output_files.emplace_back(
|
||||
MakeTableFileName(meta.fd.GetNumber()), meta.fd.smallest_seqno,
|
||||
meta.fd.largest_seqno, meta.smallest.Encode().ToString(),
|
||||
meta.largest.Encode().ToString(), meta.oldest_ancester_time,
|
||||
meta.file_creation_time, output_file.validator.GetHash(),
|
||||
meta.marked_for_compaction);
|
||||
}
|
||||
compaction_result_->num_output_records = sub_compact->num_output_records;
|
||||
compaction_result_->total_bytes = sub_compact->total_bytes;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void CompactionServiceCompactionJob::CleanupCompaction() {
|
||||
CompactionJob::CleanupCompaction();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
+139
-22
@@ -84,7 +84,7 @@ class CompactionJob {
|
||||
std::string full_history_ts_low = "",
|
||||
BlobFileCompletionCallback* blob_callback = nullptr);
|
||||
|
||||
~CompactionJob();
|
||||
virtual ~CompactionJob();
|
||||
|
||||
// no copy/move
|
||||
CompactionJob(CompactionJob&& job) = delete;
|
||||
@@ -107,11 +107,35 @@ class CompactionJob {
|
||||
// Return the IO status
|
||||
IOStatus io_status() const { return io_status_; }
|
||||
|
||||
private:
|
||||
protected:
|
||||
struct SubcompactionState;
|
||||
// CompactionJob state
|
||||
struct CompactionState;
|
||||
|
||||
void AggregateStatistics();
|
||||
void UpdateCompactionStats();
|
||||
void LogCompaction();
|
||||
void RecordCompactionIOStats();
|
||||
void CleanupCompaction();
|
||||
|
||||
// Call compaction filter. Then iterate through input and compact the
|
||||
// kv-pairs
|
||||
void ProcessKeyValueCompaction(SubcompactionState* sub_compact);
|
||||
|
||||
CompactionState* compact_;
|
||||
InternalStats::CompactionStats compaction_stats_;
|
||||
const ImmutableDBOptions& db_options_;
|
||||
LogBuffer* log_buffer_;
|
||||
FSDirectory* output_directory_;
|
||||
Statistics* stats_;
|
||||
// Is this compaction creating a file in the bottom most level?
|
||||
bool bottommost_level_;
|
||||
|
||||
Env::WriteLifeTimeHint write_hint_;
|
||||
|
||||
IOStatus io_status_;
|
||||
|
||||
private:
|
||||
// Generates a histogram representing potential divisions of key ranges from
|
||||
// the input. It adds the starting and/or ending keys of certain input files
|
||||
// to the working set and then finds the approximate size of data in between
|
||||
@@ -122,9 +146,6 @@ class CompactionJob {
|
||||
// update the thread status for starting a compaction.
|
||||
void ReportStartedCompaction(Compaction* compaction);
|
||||
void AllocateCompactionOutputFileNumbers();
|
||||
// Call compaction filter. Then iterate through input and compact the
|
||||
// kv-pairs
|
||||
void ProcessKeyValueCompaction(SubcompactionState* sub_compact);
|
||||
|
||||
Status FinishCompactionOutputFile(
|
||||
const Status& input_status, SubcompactionState* sub_compact,
|
||||
@@ -132,33 +153,23 @@ class CompactionJob {
|
||||
CompactionIterationStats* range_del_out_stats,
|
||||
const Slice* next_table_min_key = nullptr);
|
||||
Status InstallCompactionResults(const MutableCFOptions& mutable_cf_options);
|
||||
void RecordCompactionIOStats();
|
||||
Status OpenCompactionOutputFile(SubcompactionState* sub_compact);
|
||||
void CleanupCompaction();
|
||||
void UpdateCompactionJobStats(
|
||||
const InternalStats::CompactionStats& stats) const;
|
||||
void RecordDroppedKeys(const CompactionIterationStats& c_iter_stats,
|
||||
CompactionJobStats* compaction_job_stats = nullptr);
|
||||
|
||||
void UpdateCompactionStats();
|
||||
void UpdateCompactionInputStatsHelper(
|
||||
int* num_files, uint64_t* bytes_read, int input_level);
|
||||
|
||||
void LogCompaction();
|
||||
|
||||
int job_id_;
|
||||
|
||||
// CompactionJob state
|
||||
struct CompactionState;
|
||||
CompactionState* compact_;
|
||||
CompactionJobStats* compaction_job_stats_;
|
||||
InternalStats::CompactionStats compaction_stats_;
|
||||
|
||||
// DBImpl state
|
||||
const std::string& dbname_;
|
||||
const std::string db_id_;
|
||||
const std::string db_session_id_;
|
||||
const ImmutableDBOptions& db_options_;
|
||||
const FileOptions file_options_;
|
||||
|
||||
Env* env_;
|
||||
@@ -170,11 +181,8 @@ class CompactionJob {
|
||||
const std::atomic<bool>* shutting_down_;
|
||||
const std::atomic<int>* manual_compaction_paused_;
|
||||
const SequenceNumber preserve_deletes_seqnum_;
|
||||
LogBuffer* log_buffer_;
|
||||
FSDirectory* db_directory_;
|
||||
FSDirectory* output_directory_;
|
||||
FSDirectory* blob_output_directory_;
|
||||
Statistics* stats_;
|
||||
InstrumentedMutex* db_mutex_;
|
||||
ErrorHandler* db_error_handler_;
|
||||
// If there were two snapshots with seq numbers s1 and
|
||||
@@ -194,19 +202,128 @@ class CompactionJob {
|
||||
|
||||
EventLogger* event_logger_;
|
||||
|
||||
// Is this compaction creating a file in the bottom most level?
|
||||
bool bottommost_level_;
|
||||
bool paranoid_file_checks_;
|
||||
bool measure_io_stats_;
|
||||
// Stores the Slices that designate the boundaries for each subcompaction
|
||||
std::vector<Slice> boundaries_;
|
||||
// Stores the approx size of keys covered in the range of each subcompaction
|
||||
std::vector<uint64_t> sizes_;
|
||||
Env::WriteLifeTimeHint write_hint_;
|
||||
Env::Priority thread_pri_;
|
||||
IOStatus io_status_;
|
||||
std::string full_history_ts_low_;
|
||||
BlobFileCompletionCallback* blob_callback_;
|
||||
|
||||
// Get table file name in where it's outputting to, which should also be in
|
||||
// `output_directory_`.
|
||||
virtual std::string GetTableFileName(uint64_t file_number);
|
||||
};
|
||||
|
||||
// CompactionServiceInput is used the pass compaction information between two
|
||||
// db instances. It contains the information needed to do a compaction. It
|
||||
// doesn't contain the LSM tree information, which is passed though MANIFEST
|
||||
// file.
|
||||
struct CompactionServiceInput {
|
||||
ColumnFamilyDescriptor column_family;
|
||||
|
||||
DBOptions db_options;
|
||||
|
||||
std::vector<SequenceNumber> snapshots;
|
||||
|
||||
// SST files for compaction, it should already be expended to include all the
|
||||
// files needed for this compaction, for both input level files and output
|
||||
// level files.
|
||||
std::vector<std::string> input_files;
|
||||
int output_level;
|
||||
|
||||
// information for subcompaction
|
||||
Slice* begin = nullptr;
|
||||
Slice* end = nullptr;
|
||||
uint64_t approx_size = 0;
|
||||
};
|
||||
|
||||
// CompactionServiceOutputFile is the metadata for the output SST file
|
||||
struct CompactionServiceOutputFile {
|
||||
std::string file_name;
|
||||
SequenceNumber smallest_seqno;
|
||||
SequenceNumber largest_seqno;
|
||||
std::string smallest_internal_key;
|
||||
std::string largest_internal_key;
|
||||
uint64_t oldest_ancester_time;
|
||||
uint64_t file_creation_time;
|
||||
uint64_t paranoid_hash;
|
||||
bool marked_for_compaction;
|
||||
|
||||
CompactionServiceOutputFile() = default;
|
||||
CompactionServiceOutputFile(
|
||||
const std::string& name, SequenceNumber smallest, SequenceNumber largest,
|
||||
std::string _smallest_internal_key, std::string _largest_internal_key,
|
||||
uint64_t _oldest_ancester_time, uint64_t _file_creation_time,
|
||||
uint64_t _paranoid_hash, bool _marked_for_compaction)
|
||||
: file_name(name),
|
||||
smallest_seqno(smallest),
|
||||
largest_seqno(largest),
|
||||
smallest_internal_key(std::move(_smallest_internal_key)),
|
||||
largest_internal_key(std::move(_largest_internal_key)),
|
||||
oldest_ancester_time(_oldest_ancester_time),
|
||||
file_creation_time(_file_creation_time),
|
||||
paranoid_hash(_paranoid_hash),
|
||||
marked_for_compaction(_marked_for_compaction) {}
|
||||
};
|
||||
|
||||
// CompactionServiceResult contains the compaction result from a different db
|
||||
// instance, with these information, the primary db instance with write
|
||||
// permission is able to install the result to the DB.
|
||||
struct CompactionServiceResult {
|
||||
std::vector<CompactionServiceOutputFile> output_files;
|
||||
int output_level;
|
||||
|
||||
// location of the output files
|
||||
std::string output_path;
|
||||
|
||||
// some statistics about the compaction
|
||||
uint64_t num_output_records;
|
||||
uint64_t total_bytes;
|
||||
uint64_t bytes_read;
|
||||
uint64_t bytes_written;
|
||||
CompactionJobStats stats;
|
||||
};
|
||||
|
||||
// CompactionServiceCompactionJob is an read-only compaction job, it takes
|
||||
// input information from `compaction_service_input` and put result information
|
||||
// in `compaction_service_result`, the SST files are generated to `output_path`.
|
||||
class CompactionServiceCompactionJob : private CompactionJob {
|
||||
public:
|
||||
CompactionServiceCompactionJob(
|
||||
int job_id, Compaction* compaction, const ImmutableDBOptions& db_options,
|
||||
const FileOptions& file_options, VersionSet* versions,
|
||||
const std::atomic<bool>* shutting_down, LogBuffer* log_buffer,
|
||||
FSDirectory* output_directory, Statistics* stats,
|
||||
InstrumentedMutex* db_mutex, ErrorHandler* db_error_handler,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
std::shared_ptr<Cache> table_cache, EventLogger* event_logger,
|
||||
const std::string& dbname, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::string& db_id, const std::string& db_session_id,
|
||||
const std::string& output_path,
|
||||
const CompactionServiceInput& compaction_service_input,
|
||||
CompactionServiceResult* compaction_service_result);
|
||||
|
||||
// Run the compaction in current thread and return the result
|
||||
Status Run();
|
||||
|
||||
void CleanupCompaction();
|
||||
|
||||
IOStatus io_status() const { return CompactionJob::io_status(); }
|
||||
|
||||
private:
|
||||
// Get table file name in output_path
|
||||
std::string GetTableFileName(uint64_t file_number) override;
|
||||
// Specific the compaction output path, otherwise it uses default DB path
|
||||
const std::string output_path_;
|
||||
|
||||
// Compaction job input
|
||||
const CompactionServiceInput& compaction_input_;
|
||||
|
||||
// Compaction job result
|
||||
CompactionServiceResult* compaction_result_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -2820,6 +2820,7 @@ class DBBasicTestMultiGet : public DBTestBase {
|
||||
|
||||
const char* Name() const override { return "MyBlockCache"; }
|
||||
|
||||
using Cache::Insert;
|
||||
Status Insert(const Slice& key, void* value, size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
Handle** handle = nullptr,
|
||||
@@ -2828,6 +2829,7 @@ class DBBasicTestMultiGet : public DBTestBase {
|
||||
return target_->Insert(key, value, charge, deleter, handle, priority);
|
||||
}
|
||||
|
||||
using Cache::Lookup;
|
||||
Handle* Lookup(const Slice& key, Statistics* stats = nullptr) override {
|
||||
num_lookups_++;
|
||||
Handle* handle = target_->Lookup(key, stats);
|
||||
|
||||
@@ -446,6 +446,7 @@ class MockCache : public LRUCache {
|
||||
false /*strict_capacity_limit*/, 0.0 /*high_pri_pool_ratio*/) {
|
||||
}
|
||||
|
||||
using ShardedCache::Insert;
|
||||
Status Insert(const Slice& key, void* value, size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value), Handle** handle,
|
||||
Priority priority) override {
|
||||
@@ -533,6 +534,7 @@ class LookupLiarCache : public CacheWrapper {
|
||||
explicit LookupLiarCache(std::shared_ptr<Cache> target)
|
||||
: CacheWrapper(std::move(target)) {}
|
||||
|
||||
using Cache::Lookup;
|
||||
Handle* Lookup(const Slice& key, Statistics* stats) override {
|
||||
if (nth_lookup_not_found_ == 1) {
|
||||
nth_lookup_not_found_ = 0;
|
||||
|
||||
@@ -810,6 +810,7 @@ class TestingContextCustomFilterPolicy
|
||||
TEST_F(DBBloomFilterTest, ContextCustomFilterPolicy) {
|
||||
for (bool fifo : {true, false}) {
|
||||
Options options = CurrentOptions();
|
||||
options.max_open_files = fifo ? -1 : options.max_open_files;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
options.compaction_style =
|
||||
fifo ? kCompactionStyleFIFO : kCompactionStyleLevel;
|
||||
@@ -820,6 +821,7 @@ TEST_F(DBBloomFilterTest, ContextCustomFilterPolicy) {
|
||||
table_options.format_version = 5;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
TryReopen(options);
|
||||
CreateAndReopenWithCF({fifo ? "abe" : "bob"}, options);
|
||||
|
||||
const int maxKey = 10000;
|
||||
|
||||
+14
-6
@@ -160,14 +160,17 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
mutex_(stats_, immutable_db_options_.clock, DB_MUTEX_WAIT_MICROS,
|
||||
immutable_db_options_.use_adaptive_mutex),
|
||||
default_cf_handle_(nullptr),
|
||||
error_handler_(this, immutable_db_options_, &mutex_),
|
||||
event_logger_(immutable_db_options_.info_log.get()),
|
||||
max_total_in_memory_state_(0),
|
||||
file_options_(BuildDBOptions(immutable_db_options_, mutable_db_options_)),
|
||||
file_options_for_compaction_(fs_->OptimizeForCompactionTableWrite(
|
||||
file_options_, immutable_db_options_)),
|
||||
seq_per_batch_(seq_per_batch),
|
||||
batch_per_txn_(batch_per_txn),
|
||||
db_lock_(nullptr),
|
||||
next_job_id_(1),
|
||||
shutting_down_(false),
|
||||
db_lock_(nullptr),
|
||||
manual_compaction_paused_(false),
|
||||
bg_cv_(&mutex_),
|
||||
logfile_number_(0),
|
||||
@@ -194,7 +197,6 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
pending_purge_obsolete_files_(0),
|
||||
delete_obsolete_files_last_run_(immutable_db_options_.clock->NowMicros()),
|
||||
last_stats_dump_time_microsec_(0),
|
||||
next_job_id_(1),
|
||||
has_unpersisted_data_(false),
|
||||
unable_to_release_oldest_log_(false),
|
||||
num_running_ingest_file_(0),
|
||||
@@ -202,7 +204,6 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
wal_manager_(immutable_db_options_, file_options_, io_tracer_,
|
||||
seq_per_batch),
|
||||
#endif // ROCKSDB_LITE
|
||||
event_logger_(immutable_db_options_.info_log.get()),
|
||||
bg_work_paused_(0),
|
||||
bg_compaction_paused_(0),
|
||||
refitting_level_(false),
|
||||
@@ -231,7 +232,6 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
own_sfm_(options.sst_file_manager == nullptr),
|
||||
preserve_deletes_(options.preserve_deletes),
|
||||
closed_(false),
|
||||
error_handler_(this, immutable_db_options_, &mutex_),
|
||||
atomic_flush_install_cv_(&mutex_),
|
||||
blob_callback_(immutable_db_options_.sst_file_manager.get(), &mutex_,
|
||||
&error_handler_) {
|
||||
@@ -270,6 +270,10 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
// we won't drop any deletion markers until SetPreserveDeletesSequenceNumber()
|
||||
// is called by client and this seqnum is advanced.
|
||||
preserve_deletes_seqnum_.store(0);
|
||||
|
||||
if (write_buffer_manager_) {
|
||||
wbm_stall_.reset(new WBMStallInterface());
|
||||
}
|
||||
}
|
||||
|
||||
Status DBImpl::Resume() {
|
||||
@@ -660,6 +664,10 @@ Status DBImpl::CloseHelper() {
|
||||
}
|
||||
}
|
||||
|
||||
if (write_buffer_manager_ && wbm_stall_) {
|
||||
write_buffer_manager_->RemoveDBFromQueue(wbm_stall_.get());
|
||||
}
|
||||
|
||||
if (ret.IsAborted()) {
|
||||
// Reserve IsAborted() error for those where users didn't release
|
||||
// certain resource and they can release them and come back and
|
||||
@@ -689,8 +697,8 @@ void DBImpl::MaybeIgnoreError(Status* s) const {
|
||||
}
|
||||
|
||||
const Status DBImpl::CreateArchivalDirectory() {
|
||||
if (immutable_db_options_.wal_ttl_seconds > 0 ||
|
||||
immutable_db_options_.wal_size_limit_mb > 0) {
|
||||
if (immutable_db_options_.WAL_ttl_seconds > 0 ||
|
||||
immutable_db_options_.WAL_size_limit_MB > 0) {
|
||||
std::string archivalPath = ArchivalDirectory(immutable_db_options_.wal_dir);
|
||||
return env_->CreateDirIfMissing(archivalPath);
|
||||
}
|
||||
|
||||
+72
-15
@@ -1048,6 +1048,56 @@ class DBImpl : public DB {
|
||||
// flush LOG out of application buffer
|
||||
void FlushInfoLog();
|
||||
|
||||
// Interface to block and signal the DB in case of stalling writes by
|
||||
// WriteBufferManager. Each DBImpl object contains ptr to WBMStallInterface.
|
||||
// When DB needs to be blocked or signalled by WriteBufferManager,
|
||||
// state_ is changed accordingly.
|
||||
class WBMStallInterface : public StallInterface {
|
||||
public:
|
||||
enum State {
|
||||
BLOCKED = 0,
|
||||
RUNNING,
|
||||
};
|
||||
|
||||
WBMStallInterface() : state_cv_(&state_mutex_) {
|
||||
MutexLock lock(&state_mutex_);
|
||||
state_ = State::RUNNING;
|
||||
}
|
||||
|
||||
void SetState(State state) {
|
||||
MutexLock lock(&state_mutex_);
|
||||
state_ = state;
|
||||
}
|
||||
|
||||
// Change the state_ to State::BLOCKED and wait until its state is
|
||||
// changed by WriteBufferManager. When stall is cleared, Signal() is
|
||||
// called to change the state and unblock the DB.
|
||||
void Block() override {
|
||||
MutexLock lock(&state_mutex_);
|
||||
while (state_ == State::BLOCKED) {
|
||||
TEST_SYNC_POINT("WBMStallInterface::BlockDB");
|
||||
state_cv_.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
// Called from WriteBufferManager. This function changes the state_
|
||||
// to State::RUNNING indicating the stall is cleared and DB can proceed.
|
||||
void Signal() override {
|
||||
MutexLock lock(&state_mutex_);
|
||||
state_ = State::RUNNING;
|
||||
state_cv_.Signal();
|
||||
}
|
||||
|
||||
private:
|
||||
// Conditional variable and mutex to block and
|
||||
// signal the DB during stalling process.
|
||||
port::Mutex state_mutex_;
|
||||
port::CondVar state_cv_;
|
||||
// state represting whether DB is running or blocked because of stall by
|
||||
// WriteBufferManager.
|
||||
State state_;
|
||||
};
|
||||
|
||||
protected:
|
||||
const std::string dbname_;
|
||||
std::string db_id_;
|
||||
@@ -1080,6 +1130,14 @@ class DBImpl : public DB {
|
||||
ColumnFamilyHandleImpl* default_cf_handle_;
|
||||
InternalStats* default_cf_internal_stats_;
|
||||
|
||||
// table_cache_ provides its own synchronization
|
||||
std::shared_ptr<Cache> table_cache_;
|
||||
|
||||
ErrorHandler error_handler_;
|
||||
|
||||
// Unified interface for logging events
|
||||
EventLogger event_logger_;
|
||||
|
||||
// only used for dynamically adjusting max_total_wal_size. it is a sum of
|
||||
// [write_buffer_size * max_write_buffer_number] over all column families
|
||||
uint64_t max_total_in_memory_state_;
|
||||
@@ -1110,6 +1168,12 @@ class DBImpl : public DB {
|
||||
// Default: true
|
||||
const bool batch_per_txn_;
|
||||
|
||||
// Each flush or compaction gets its own job id. this counter makes sure
|
||||
// they're unique
|
||||
std::atomic<int> next_job_id_;
|
||||
|
||||
std::atomic<bool> shutting_down_;
|
||||
|
||||
// Except in DB::Open(), WriteOptionsFile can only be called when:
|
||||
// Persist options to options file.
|
||||
// If need_mutex_lock = false, the method will lock DB mutex.
|
||||
@@ -1526,6 +1590,10 @@ class DBImpl : public DB {
|
||||
// `num_bytes` going through.
|
||||
Status DelayWrite(uint64_t num_bytes, const WriteOptions& write_options);
|
||||
|
||||
// Begin stalling of writes when memory usage increases beyond a certain
|
||||
// threshold.
|
||||
void WriteBufferManagerStallWrites();
|
||||
|
||||
Status ThrottleLowPriWritesIfNeeded(const WriteOptions& write_options,
|
||||
WriteBatch* my_batch);
|
||||
|
||||
@@ -1605,7 +1673,7 @@ class DBImpl : public DB {
|
||||
Status SwitchWAL(WriteContext* write_context);
|
||||
|
||||
// REQUIRES: mutex locked and in write thread.
|
||||
Status HandleWriteBufferFull(WriteContext* write_context);
|
||||
Status HandleWriteBufferManagerFlush(WriteContext* write_context);
|
||||
|
||||
// REQUIRES: mutex locked
|
||||
Status PreprocessWrite(const WriteOptions& write_options, bool* need_log_sync,
|
||||
@@ -1886,9 +1954,6 @@ class DBImpl : public DB {
|
||||
|
||||
Status IncreaseFullHistoryTsLow(ColumnFamilyData* cfd, std::string ts_low);
|
||||
|
||||
// table_cache_ provides its own synchronization
|
||||
std::shared_ptr<Cache> table_cache_;
|
||||
|
||||
// Lock over the persistent DB state. Non-nullptr iff successfully acquired.
|
||||
FileLock* db_lock_;
|
||||
|
||||
@@ -1902,8 +1967,6 @@ class DBImpl : public DB {
|
||||
// mutex_, the order should be first mutex_ and then log_write_mutex_.
|
||||
InstrumentedMutex log_write_mutex_;
|
||||
|
||||
std::atomic<bool> shutting_down_;
|
||||
|
||||
// If zero, manual compactions are allowed to proceed. If non-zero, manual
|
||||
// compactions may still be running, but will quickly fail with
|
||||
// `Status::Incomplete`. The value indicates how many threads have paused
|
||||
@@ -2112,10 +2175,6 @@ class DBImpl : public DB {
|
||||
// Number of threads intending to write to memtable
|
||||
std::atomic<size_t> pending_memtable_writes_ = {};
|
||||
|
||||
// Each flush or compaction gets its own job id. this counter makes sure
|
||||
// they're unique
|
||||
std::atomic<int> next_job_id_;
|
||||
|
||||
// A flag indicating whether the current rocksdb database has any
|
||||
// data that is not yet persisted into either WAL or SST file.
|
||||
// Used when disableWAL is true.
|
||||
@@ -2144,9 +2203,6 @@ class DBImpl : public DB {
|
||||
WalManager wal_manager_;
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// Unified interface for logging events
|
||||
EventLogger event_logger_;
|
||||
|
||||
// A value of > 0 temporarily disables scheduling of background work
|
||||
int bg_work_paused_;
|
||||
|
||||
@@ -2214,8 +2270,6 @@ class DBImpl : public DB {
|
||||
// Flag to check whether Close() has been called on this DB
|
||||
bool closed_;
|
||||
|
||||
ErrorHandler error_handler_;
|
||||
|
||||
// Conditional variable to coordinate installation of atomic flush results.
|
||||
// With atomic flush, each bg thread installs the result of flushing multiple
|
||||
// column families, and different threads can flush different column
|
||||
@@ -2230,6 +2284,9 @@ class DBImpl : public DB {
|
||||
bool wal_in_db_path_;
|
||||
|
||||
BlobFileCompletionCallback blob_callback_;
|
||||
|
||||
// Pointer to WriteBufferManager stalling interface.
|
||||
std::unique_ptr<StallInterface> wbm_stall_;
|
||||
};
|
||||
|
||||
extern Options SanitizeOptions(const std::string& db, const Options& src,
|
||||
|
||||
@@ -803,6 +803,10 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice* begin_without_ts,
|
||||
const Slice* end_without_ts) {
|
||||
if (manual_compaction_paused_.load(std::memory_order_acquire) > 0) {
|
||||
return Status::Incomplete(Status::SubCode::kManualCompactionPaused);
|
||||
}
|
||||
|
||||
const Comparator* const ucmp = column_family->GetComparator();
|
||||
assert(ucmp);
|
||||
size_t ts_sz = ucmp->timestamp_size();
|
||||
@@ -2582,6 +2586,8 @@ void DBImpl::BackgroundCallFlush(Env::Priority thread_pri) {
|
||||
|
||||
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL,
|
||||
immutable_db_options_.info_log.get());
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCallFlush:Start:1");
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCallFlush:Start:2");
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
assert(bg_flush_scheduled_);
|
||||
|
||||
@@ -558,8 +558,8 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (type == kWalFile && (immutable_db_options_.wal_ttl_seconds > 0 ||
|
||||
immutable_db_options_.wal_size_limit_mb > 0)) {
|
||||
if (type == kWalFile && (immutable_db_options_.WAL_ttl_seconds > 0 ||
|
||||
immutable_db_options_.WAL_size_limit_MB > 0)) {
|
||||
wal_manager_.ArchiveWALFile(fname, number);
|
||||
continue;
|
||||
}
|
||||
@@ -943,7 +943,7 @@ Status DBImpl::DeleteUnreferencedSstFiles() {
|
||||
return s;
|
||||
}
|
||||
|
||||
if (largest_file_number > next_file_number) {
|
||||
if (largest_file_number >= next_file_number) {
|
||||
versions_->next_file_number_.store(largest_file_number + 1);
|
||||
}
|
||||
|
||||
|
||||
@@ -285,6 +285,9 @@ Status DBImpl::NewDB(std::vector<std::string>* new_filenames) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "Creating manifest 1 \n");
|
||||
const std::string manifest = DescriptorFileName(dbname_, 1);
|
||||
{
|
||||
if (fs_->FileExists(manifest, IOOptions(), nullptr).ok()) {
|
||||
fs_->DeleteFile(manifest, IOOptions(), nullptr).PermitUncheckedError();
|
||||
}
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
FileOptions file_options = fs_->OptimizeForManifestWrite(file_options_);
|
||||
s = NewWritableFile(fs_.get(), manifest, &file, file_options);
|
||||
@@ -314,7 +317,7 @@ Status DBImpl::NewDB(std::vector<std::string>* new_filenames) {
|
||||
manifest.substr(manifest.find_last_of("/\\") + 1));
|
||||
}
|
||||
} else {
|
||||
fs_->DeleteFile(manifest, IOOptions(), nullptr);
|
||||
fs_->DeleteFile(manifest, IOOptions(), nullptr).PermitUncheckedError();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -1134,11 +1137,29 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& wal_numbers,
|
||||
immutable_db_options_.wal_recovery_mode ==
|
||||
WALRecoveryMode::kTolerateCorruptedTailRecords)) {
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (cfd->GetLogNumber() > corrupted_wal_number) {
|
||||
// One special case cause cfd->GetLogNumber() > corrupted_wal_number but
|
||||
// the CF is still consistent: If a new column family is created during
|
||||
// the flush and the WAL sync fails at the same time, the new CF points to
|
||||
// the new WAL but the old WAL is curropted. Since the new CF is empty, it
|
||||
// is still consistent. We add the check of CF sst file size to avoid the
|
||||
// false positive alert.
|
||||
|
||||
// Note that, the check of (cfd->GetLiveSstFilesSize() > 0) may leads to
|
||||
// the ignorance of a very rare inconsistency case caused in data
|
||||
// canclation. One CF is empty due to KV deletion. But those operations
|
||||
// are in the WAL. If the WAL is corrupted, the status of this CF might
|
||||
// not be consistent with others. However, the consistency check will be
|
||||
// bypassed due to empty CF.
|
||||
// TODO: a better and complete implementation is needed to ensure strict
|
||||
// consistency check in WAL recovery including hanlding the tailing
|
||||
// issues.
|
||||
if (cfd->GetLogNumber() > corrupted_wal_number &&
|
||||
cfd->GetLiveSstFilesSize() > 0) {
|
||||
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
|
||||
"Column family inconsistency: SST file contains data"
|
||||
" beyond the point of corruption.");
|
||||
return Status::Corruption("SST file is ahead of WALs");
|
||||
return Status::Corruption("SST file is ahead of WALs in CF " +
|
||||
cfd->GetName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1383,7 +1404,7 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
mutable_cf_options.compression_opts, paranoid_file_checks,
|
||||
cfd->internal_stats(), TableFileCreationReason::kRecovery, &io_s,
|
||||
io_tracer_, &event_logger_, job_id, Env::IO_HIGH,
|
||||
nullptr /* table_properties */, -1 /* level */, current_time,
|
||||
nullptr /* table_properties */, 0 /* level */, current_time,
|
||||
0 /* oldest_key_time */, write_hint, 0 /* file_creation_time */,
|
||||
db_id_, db_session_id_, nullptr /*full_history_ts_low*/,
|
||||
&blob_callback_);
|
||||
|
||||
@@ -17,8 +17,10 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
DBImplSecondary::DBImplSecondary(const DBOptions& db_options,
|
||||
const std::string& dbname)
|
||||
: DBImpl(db_options, dbname) {
|
||||
const std::string& dbname,
|
||||
std::string secondary_path)
|
||||
: DBImpl(db_options, dbname, false, true, true),
|
||||
secondary_path_(std::move(secondary_path)) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Opening the db in secondary mode");
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
@@ -617,7 +619,7 @@ Status DB::OpenAsSecondary(
|
||||
}
|
||||
|
||||
handles->clear();
|
||||
DBImplSecondary* impl = new DBImplSecondary(tmp_opts, dbname);
|
||||
DBImplSecondary* impl = new DBImplSecondary(tmp_opts, dbname, secondary_path);
|
||||
impl->versions_.reset(new ReactiveVersionSet(
|
||||
dbname, &impl->immutable_db_options_, impl->file_options_,
|
||||
impl->table_cache_.get(), impl->write_buffer_manager_,
|
||||
@@ -663,6 +665,86 @@ Status DB::OpenAsSecondary(
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImplSecondary::CompactWithoutInstallation(
|
||||
ColumnFamilyHandle* cfh, const CompactionServiceInput& input,
|
||||
CompactionServiceResult* result) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
auto cfd = static_cast_with_check<ColumnFamilyHandleImpl>(cfh)->cfd();
|
||||
if (!cfd) {
|
||||
return Status::InvalidArgument("Cannot find column family" +
|
||||
cfh->GetName());
|
||||
}
|
||||
|
||||
std::unordered_set<uint64_t> input_set;
|
||||
for (const auto& file_name : input.input_files) {
|
||||
input_set.insert(TableFileNameToNumber(file_name));
|
||||
}
|
||||
|
||||
auto* version = cfd->current();
|
||||
|
||||
ColumnFamilyMetaData cf_meta;
|
||||
version->GetColumnFamilyMetaData(&cf_meta);
|
||||
|
||||
const MutableCFOptions* mutable_cf_options = cfd->GetLatestMutableCFOptions();
|
||||
ColumnFamilyOptions cf_options = cfd->GetLatestCFOptions();
|
||||
VersionStorageInfo* vstorage = version->storage_info();
|
||||
|
||||
// Use comp_options to reuse some CompactFiles functions
|
||||
CompactionOptions comp_options;
|
||||
comp_options.compression = kDisableCompressionOption;
|
||||
comp_options.output_file_size_limit = MaxFileSizeForLevel(
|
||||
*mutable_cf_options, input.output_level, cf_options.compaction_style,
|
||||
vstorage->base_level(), cf_options.level_compaction_dynamic_level_bytes);
|
||||
|
||||
std::vector<CompactionInputFiles> input_files;
|
||||
Status s = cfd->compaction_picker()->GetCompactionInputsFromFileNumbers(
|
||||
&input_files, &input_set, vstorage, comp_options);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
std::unique_ptr<Compaction> c;
|
||||
assert(cfd->compaction_picker());
|
||||
c.reset(cfd->compaction_picker()->CompactFiles(
|
||||
comp_options, input_files, input.output_level, vstorage,
|
||||
*mutable_cf_options, mutable_db_options_, 0));
|
||||
assert(c != nullptr);
|
||||
|
||||
c->SetInputVersion(version);
|
||||
|
||||
// Create output directory if it's not existed yet
|
||||
std::unique_ptr<FSDirectory> output_dir;
|
||||
s = CreateAndNewDirectory(fs_.get(), secondary_path_, &output_dir);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL,
|
||||
immutable_db_options_.info_log.get());
|
||||
|
||||
const int job_id = next_job_id_.fetch_add(1);
|
||||
|
||||
CompactionServiceCompactionJob compaction_job(
|
||||
job_id, c.get(), immutable_db_options_, file_options_for_compaction_,
|
||||
versions_.get(), &shutting_down_, &log_buffer, output_dir.get(), stats_,
|
||||
&mutex_, &error_handler_, input.snapshots, table_cache_, &event_logger_,
|
||||
dbname_, io_tracer_, db_id_, db_session_id_, secondary_path_, input,
|
||||
result);
|
||||
|
||||
mutex_.Unlock();
|
||||
s = compaction_job.Run();
|
||||
mutex_.Lock();
|
||||
|
||||
// clean up
|
||||
compaction_job.io_status().PermitUncheckedError();
|
||||
compaction_job.CleanupCompaction();
|
||||
c->ReleaseCompactionFiles(s);
|
||||
c.reset();
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
#else // !ROCKSDB_LITE
|
||||
|
||||
Status DB::OpenAsSecondary(const Options& /*options*/,
|
||||
|
||||
@@ -71,7 +71,8 @@ class LogReaderContainer {
|
||||
// effort attempts to catch up with the primary.
|
||||
class DBImplSecondary : public DBImpl {
|
||||
public:
|
||||
DBImplSecondary(const DBOptions& options, const std::string& dbname);
|
||||
DBImplSecondary(const DBOptions& options, const std::string& dbname,
|
||||
std::string secondary_path);
|
||||
~DBImplSecondary() override;
|
||||
|
||||
// Recover by replaying MANIFEST and WAL. Also initialize manifest_reader_
|
||||
@@ -222,6 +223,14 @@ class DBImplSecondary : public DBImpl {
|
||||
// not flag the missing file as inconsistency.
|
||||
Status CheckConsistency() override;
|
||||
|
||||
#ifndef NDEBUG
|
||||
Status TEST_CompactWithoutInstallation(ColumnFamilyHandle* cfh,
|
||||
const CompactionServiceInput& input,
|
||||
CompactionServiceResult* result) {
|
||||
return CompactWithoutInstallation(cfh, input, result);
|
||||
}
|
||||
#endif // NDEBUG
|
||||
|
||||
protected:
|
||||
// ColumnFamilyCollector is a write batch handler which does nothing
|
||||
// except recording unique column family IDs
|
||||
@@ -316,6 +325,13 @@ class DBImplSecondary : public DBImpl {
|
||||
std::unordered_set<ColumnFamilyData*>* cfds_changed,
|
||||
JobContext* job_context);
|
||||
|
||||
// Run compaction without installation, the output files will be placed in the
|
||||
// secondary DB path. The LSM tree won't be changed, the secondary DB is still
|
||||
// in read-only mode.
|
||||
Status CompactWithoutInstallation(ColumnFamilyHandle* cfh,
|
||||
const CompactionServiceInput& input,
|
||||
CompactionServiceResult* result);
|
||||
|
||||
std::unique_ptr<log::FragmentBufferedReader> manifest_reader_;
|
||||
std::unique_ptr<log::Reader::Reporter> manifest_reporter_;
|
||||
std::unique_ptr<Status> manifest_reader_status_;
|
||||
@@ -326,6 +342,8 @@ class DBImplSecondary : public DBImpl {
|
||||
|
||||
// Current WAL number replayed for each column family.
|
||||
std::unordered_map<ColumnFamilyData*, uint64_t> cfd_to_current_log_;
|
||||
|
||||
const std::string secondary_path_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -937,7 +937,7 @@ Status DBImpl::PreprocessWrite(const WriteOptions& write_options,
|
||||
// be flushed. We may end up with flushing much more DBs than needed. It's
|
||||
// suboptimal but still correct.
|
||||
WaitForPendingWrites();
|
||||
status = HandleWriteBufferFull(write_context);
|
||||
status = HandleWriteBufferManagerFlush(write_context);
|
||||
}
|
||||
|
||||
if (UNLIKELY(status.ok() && !trim_history_scheduler_.Empty())) {
|
||||
@@ -964,6 +964,20 @@ Status DBImpl::PreprocessWrite(const WriteOptions& write_options,
|
||||
PERF_TIMER_START(write_pre_and_post_process_time);
|
||||
}
|
||||
|
||||
// If memory usage exceeded beyond a certain threshold,
|
||||
// write_buffer_manager_->ShouldStall() returns true to all threads writing to
|
||||
// all DBs and writers will be stalled.
|
||||
// It does soft checking because WriteBufferManager::buffer_limit_ has already
|
||||
// exceeded at this point so no new write (including current one) will go
|
||||
// through until memory usage is decreased.
|
||||
if (UNLIKELY(status.ok() && write_buffer_manager_->ShouldStall())) {
|
||||
if (write_options.no_slowdown) {
|
||||
status = Status::Incomplete("Write stall");
|
||||
} else {
|
||||
WriteBufferManagerStallWrites();
|
||||
}
|
||||
}
|
||||
|
||||
if (status.ok() && *need_log_sync) {
|
||||
// Wait until the parallel syncs are finished. Any sync process has to sync
|
||||
// the front log too so it is enough to check the status of front()
|
||||
@@ -1348,20 +1362,20 @@ Status DBImpl::SwitchWAL(WriteContext* write_context) {
|
||||
if (!immutable_db_options_.atomic_flush) {
|
||||
FlushRequest flush_req;
|
||||
GenerateFlushRequest({cfd}, &flush_req);
|
||||
SchedulePendingFlush(flush_req, FlushReason::kWriteBufferManager);
|
||||
SchedulePendingFlush(flush_req, FlushReason::kWalFull);
|
||||
}
|
||||
}
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
FlushRequest flush_req;
|
||||
GenerateFlushRequest(cfds, &flush_req);
|
||||
SchedulePendingFlush(flush_req, FlushReason::kWriteBufferManager);
|
||||
SchedulePendingFlush(flush_req, FlushReason::kWalFull);
|
||||
}
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Status DBImpl::HandleWriteBufferFull(WriteContext* write_context) {
|
||||
Status DBImpl::HandleWriteBufferManagerFlush(WriteContext* write_context) {
|
||||
mutex_.AssertHeld();
|
||||
assert(write_context != nullptr);
|
||||
Status status;
|
||||
@@ -1373,7 +1387,7 @@ Status DBImpl::HandleWriteBufferFull(WriteContext* write_context) {
|
||||
// suboptimal but still correct.
|
||||
ROCKS_LOG_INFO(
|
||||
immutable_db_options_.info_log,
|
||||
"Flushing column family with oldest memtable entry. Write buffer is "
|
||||
"Flushing column family with oldest memtable entry. Write buffers are "
|
||||
"using %" ROCKSDB_PRIszt " bytes out of a total of %" ROCKSDB_PRIszt ".",
|
||||
write_buffer_manager_->memory_usage(),
|
||||
write_buffer_manager_->buffer_size());
|
||||
@@ -1434,13 +1448,13 @@ Status DBImpl::HandleWriteBufferFull(WriteContext* write_context) {
|
||||
if (!immutable_db_options_.atomic_flush) {
|
||||
FlushRequest flush_req;
|
||||
GenerateFlushRequest({cfd}, &flush_req);
|
||||
SchedulePendingFlush(flush_req, FlushReason::kWriteBufferFull);
|
||||
SchedulePendingFlush(flush_req, FlushReason::kWriteBufferManager);
|
||||
}
|
||||
}
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
FlushRequest flush_req;
|
||||
GenerateFlushRequest(cfds, &flush_req);
|
||||
SchedulePendingFlush(flush_req, FlushReason::kWriteBufferFull);
|
||||
SchedulePendingFlush(flush_req, FlushReason::kWriteBufferManager);
|
||||
}
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
@@ -1536,6 +1550,29 @@ Status DBImpl::DelayWrite(uint64_t num_bytes,
|
||||
return s;
|
||||
}
|
||||
|
||||
// REQUIRES: mutex_ is held
|
||||
// REQUIRES: this thread is currently at the front of the writer queue
|
||||
void DBImpl::WriteBufferManagerStallWrites() {
|
||||
mutex_.AssertHeld();
|
||||
// First block future writer threads who want to add themselves to the queue
|
||||
// of WriteThread.
|
||||
write_thread_.BeginWriteStall();
|
||||
mutex_.Unlock();
|
||||
|
||||
// Change the state to State::Blocked.
|
||||
static_cast<WBMStallInterface*>(wbm_stall_.get())
|
||||
->SetState(WBMStallInterface::State::BLOCKED);
|
||||
// Then WriteBufferManager will add DB instance to its queue
|
||||
// and block this thread by calling WBMStallInterface::Block().
|
||||
write_buffer_manager_->BeginWriteStall(wbm_stall_.get());
|
||||
wbm_stall_->Block();
|
||||
|
||||
mutex_.Lock();
|
||||
// Stall has ended. Signal writer threads so that they can add
|
||||
// themselves to the WriteThread queue for writes.
|
||||
write_thread_.EndWriteStall();
|
||||
}
|
||||
|
||||
Status DBImpl::ThrottleLowPriWritesIfNeeded(const WriteOptions& write_options,
|
||||
WriteBatch* my_batch) {
|
||||
assert(write_options.low_pri);
|
||||
|
||||
+2
-2
@@ -45,10 +45,10 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
|
||||
ColumnFamilyData* cfd, bool expose_blob_index)
|
||||
: prefix_extractor_(mutable_cf_options.prefix_extractor.get()),
|
||||
env_(_env),
|
||||
clock_(_env->GetSystemClock().get()),
|
||||
clock_(cf_options.clock),
|
||||
logger_(cf_options.info_log),
|
||||
user_comparator_(cmp),
|
||||
merge_operator_(cf_options.merge_operator),
|
||||
merge_operator_(cf_options.merge_operator.get()),
|
||||
iter_(iter),
|
||||
version_(version),
|
||||
read_callback_(read_callback),
|
||||
|
||||
@@ -1626,6 +1626,7 @@ TEST_F(DBPropertiesTest, EstimateOldestKeyTime) {
|
||||
|
||||
options.compaction_style = kCompactionStyleFIFO;
|
||||
options.ttl = 300;
|
||||
options.max_open_files = -1;
|
||||
options.compaction_options_fifo.allow_compaction = false;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
|
||||
@@ -147,6 +147,206 @@ TEST_F(DBSecondaryTest, ReopenAsSecondary) {
|
||||
ASSERT_EQ(2, count);
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, SimpleInternalCompaction) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
Reopen(options);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ASSERT_OK(Put("foo", "foo_value" + std::to_string(i)));
|
||||
ASSERT_OK(Put("bar", "bar_value" + std::to_string(i)));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
CompactionServiceInput input;
|
||||
|
||||
ColumnFamilyMetaData meta;
|
||||
db_->GetColumnFamilyMetaData(&meta);
|
||||
for (auto& file : meta.levels[0].files) {
|
||||
ASSERT_EQ(0, meta.levels[0].level);
|
||||
input.input_files.push_back(file.name);
|
||||
}
|
||||
ASSERT_EQ(input.input_files.size(), 3);
|
||||
|
||||
input.output_level = 1;
|
||||
Close();
|
||||
|
||||
options.max_open_files = -1;
|
||||
OpenSecondary(options);
|
||||
auto cfh = db_secondary_->DefaultColumnFamily();
|
||||
|
||||
CompactionServiceResult result;
|
||||
ASSERT_OK(db_secondary_full()->TEST_CompactWithoutInstallation(cfh, input,
|
||||
&result));
|
||||
|
||||
ASSERT_EQ(result.output_files.size(), 1);
|
||||
InternalKey smallest, largest;
|
||||
smallest.DecodeFrom(result.output_files[0].smallest_internal_key);
|
||||
largest.DecodeFrom(result.output_files[0].largest_internal_key);
|
||||
ASSERT_EQ(smallest.user_key().ToString(), "bar");
|
||||
ASSERT_EQ(largest.user_key().ToString(), "foo");
|
||||
ASSERT_EQ(result.output_level, 1);
|
||||
ASSERT_EQ(result.output_path, this->secondary_path_);
|
||||
ASSERT_EQ(result.num_output_records, 2);
|
||||
ASSERT_GT(result.bytes_written, 0);
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, InternalCompactionMultiLevels) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.disable_auto_compactions = true;
|
||||
Reopen(options);
|
||||
const int kRangeL2 = 10;
|
||||
const int kRangeL1 = 30;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_OK(Put(Key(i * kRangeL2), "value" + ToString(i)));
|
||||
ASSERT_OK(Put(Key((i + 1) * kRangeL2 - 1), "value" + ToString(i)));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
MoveFilesToLevel(2);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
ASSERT_OK(Put(Key(i * kRangeL1), "value" + ToString(i)));
|
||||
ASSERT_OK(Put(Key((i + 1) * kRangeL1 - 1), "value" + ToString(i)));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
MoveFilesToLevel(1);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ASSERT_OK(Put(Key(i * 30), "value" + ToString(i)));
|
||||
ASSERT_OK(Put(Key(i * 30 + 50), "value" + ToString(i)));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
ColumnFamilyMetaData meta;
|
||||
db_->GetColumnFamilyMetaData(&meta);
|
||||
|
||||
// pick 2 files on level 0 for compaction, which has 3 overlap files on L1
|
||||
CompactionServiceInput input1;
|
||||
input1.input_files.push_back(meta.levels[0].files[2].name);
|
||||
input1.input_files.push_back(meta.levels[0].files[3].name);
|
||||
input1.input_files.push_back(meta.levels[1].files[0].name);
|
||||
input1.input_files.push_back(meta.levels[1].files[1].name);
|
||||
input1.input_files.push_back(meta.levels[1].files[2].name);
|
||||
|
||||
input1.output_level = 1;
|
||||
|
||||
options.max_open_files = -1;
|
||||
Close();
|
||||
|
||||
OpenSecondary(options);
|
||||
auto cfh = db_secondary_->DefaultColumnFamily();
|
||||
CompactionServiceResult result;
|
||||
ASSERT_OK(db_secondary_full()->TEST_CompactWithoutInstallation(cfh, input1,
|
||||
&result));
|
||||
|
||||
// pick 2 files on level 1 for compaction, which has 6 overlap files on L2
|
||||
CompactionServiceInput input2;
|
||||
input2.input_files.push_back(meta.levels[1].files[1].name);
|
||||
input2.input_files.push_back(meta.levels[1].files[2].name);
|
||||
for (int i = 3; i < 9; i++) {
|
||||
input2.input_files.push_back(meta.levels[2].files[i].name);
|
||||
}
|
||||
|
||||
input2.output_level = 2;
|
||||
ASSERT_OK(db_secondary_full()->TEST_CompactWithoutInstallation(cfh, input2,
|
||||
&result));
|
||||
|
||||
CloseSecondary();
|
||||
|
||||
// delete all l2 files, without update manifest
|
||||
for (auto& file : meta.levels[2].files) {
|
||||
ASSERT_OK(env_->DeleteFile(dbname_ + file.name));
|
||||
}
|
||||
OpenSecondary(options);
|
||||
cfh = db_secondary_->DefaultColumnFamily();
|
||||
Status s = db_secondary_full()->TEST_CompactWithoutInstallation(cfh, input2,
|
||||
&result);
|
||||
ASSERT_TRUE(s.IsInvalidArgument());
|
||||
|
||||
// TODO: L0 -> L1 compaction should success, currently version is not built
|
||||
// if files is missing.
|
||||
// ASSERT_OK(db_secondary_full()->TEST_CompactWithoutInstallation(cfh,
|
||||
// input1, &result));
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, InternalCompactionCompactedFiles) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
Reopen(options);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ASSERT_OK(Put("foo", "foo_value" + std::to_string(i)));
|
||||
ASSERT_OK(Put("bar", "bar_value" + std::to_string(i)));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
CompactionServiceInput input;
|
||||
|
||||
ColumnFamilyMetaData meta;
|
||||
db_->GetColumnFamilyMetaData(&meta);
|
||||
for (auto& file : meta.levels[0].files) {
|
||||
ASSERT_EQ(0, meta.levels[0].level);
|
||||
input.input_files.push_back(file.name);
|
||||
}
|
||||
ASSERT_EQ(input.input_files.size(), 3);
|
||||
|
||||
input.output_level = 1;
|
||||
|
||||
// trigger compaction to delete the files for secondary instance compaction
|
||||
ASSERT_OK(Put("foo", "foo_value" + std::to_string(3)));
|
||||
ASSERT_OK(Put("bar", "bar_value" + std::to_string(3)));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
Close();
|
||||
|
||||
options.max_open_files = -1;
|
||||
OpenSecondary(options);
|
||||
auto cfh = db_secondary_->DefaultColumnFamily();
|
||||
|
||||
CompactionServiceResult result;
|
||||
Status s =
|
||||
db_secondary_full()->TEST_CompactWithoutInstallation(cfh, input, &result);
|
||||
ASSERT_TRUE(s.IsInvalidArgument());
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, InternalCompactionMissingFiles) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
Reopen(options);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ASSERT_OK(Put("foo", "foo_value" + std::to_string(i)));
|
||||
ASSERT_OK(Put("bar", "bar_value" + std::to_string(i)));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
CompactionServiceInput input;
|
||||
|
||||
ColumnFamilyMetaData meta;
|
||||
db_->GetColumnFamilyMetaData(&meta);
|
||||
for (auto& file : meta.levels[0].files) {
|
||||
ASSERT_EQ(0, meta.levels[0].level);
|
||||
input.input_files.push_back(file.name);
|
||||
}
|
||||
ASSERT_EQ(input.input_files.size(), 3);
|
||||
|
||||
input.output_level = 1;
|
||||
|
||||
Close();
|
||||
|
||||
ASSERT_OK(env_->DeleteFile(dbname_ + input.input_files[0]));
|
||||
|
||||
options.max_open_files = -1;
|
||||
OpenSecondary(options);
|
||||
auto cfh = db_secondary_->DefaultColumnFamily();
|
||||
|
||||
CompactionServiceResult result;
|
||||
Status s =
|
||||
db_secondary_full()->TEST_CompactWithoutInstallation(cfh, input, &result);
|
||||
ASSERT_TRUE(s.IsInvalidArgument());
|
||||
|
||||
input.input_files.erase(input.input_files.begin());
|
||||
|
||||
ASSERT_OK(db_secondary_full()->TEST_CompactWithoutInstallation(cfh, input,
|
||||
&result));
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, OpenAsSecondary) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
|
||||
@@ -561,6 +561,7 @@ TEST_F(DBSSTTest, DBWithSstFileManagerForBlobFilesWithGC) {
|
||||
constexpr Slice* end = nullptr;
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), begin, end));
|
||||
sfm->WaitForEmptyTrash();
|
||||
|
||||
ASSERT_EQ(Get(first_key), first_value);
|
||||
ASSERT_EQ(Get(second_key), second_value);
|
||||
@@ -593,6 +594,7 @@ TEST_F(DBSSTTest, DBWithSstFileManagerForBlobFilesWithGC) {
|
||||
|
||||
Close();
|
||||
ASSERT_OK(DestroyDB(dbname_, options));
|
||||
sfm->WaitForEmptyTrash();
|
||||
ASSERT_EQ(files_deleted, 5);
|
||||
ASSERT_EQ(files_scheduled_to_delete, 5);
|
||||
|
||||
|
||||
+19
-13
@@ -908,6 +908,9 @@ TEST_F(DBTest, FlushSchedule) {
|
||||
static_cast<int64_t>(options.write_buffer_size);
|
||||
options.max_write_buffer_number = 2;
|
||||
options.write_buffer_size = 120 * 1024;
|
||||
auto flush_listener = std::make_shared<FlushCounterListener>();
|
||||
flush_listener->expected_flush_reason = FlushReason::kWriteBufferFull;
|
||||
options.listeners.push_back(flush_listener);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
std::vector<port::Thread> threads;
|
||||
|
||||
@@ -3504,17 +3507,21 @@ TEST_F(DBTest, FIFOCompactionStyleWithCompactionAndDelete) {
|
||||
}
|
||||
|
||||
// Check that FIFO-with-TTL is not supported with max_open_files != -1.
|
||||
// Github issue #8014
|
||||
TEST_F(DBTest, FIFOCompactionWithTTLAndMaxOpenFilesTest) {
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleFIFO;
|
||||
options.create_if_missing = true;
|
||||
options.ttl = 600; // seconds
|
||||
|
||||
// TTL is now supported with max_open_files != -1.
|
||||
options.max_open_files = 100;
|
||||
options = CurrentOptions(options);
|
||||
ASSERT_OK(TryReopen(options));
|
||||
// TTL is not supported with max_open_files != -1.
|
||||
options.max_open_files = 0;
|
||||
ASSERT_TRUE(TryReopen(options).IsNotSupported());
|
||||
|
||||
options.max_open_files = 100;
|
||||
ASSERT_TRUE(TryReopen(options).IsNotSupported());
|
||||
|
||||
// TTL is supported with unlimited max_open_files
|
||||
options.max_open_files = -1;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
}
|
||||
@@ -6694,20 +6701,19 @@ TEST_F(DBTest, MemoryUsageWithMaxWriteBufferSizeToMaintain) {
|
||||
Reopen(options);
|
||||
Random rnd(301);
|
||||
bool memory_limit_exceeded = false;
|
||||
uint64_t size_all_mem_table = 0;
|
||||
uint64_t cur_active_mem = 0;
|
||||
|
||||
ColumnFamilyData* cfd =
|
||||
static_cast<ColumnFamilyHandleImpl*>(db_->DefaultColumnFamily())->cfd();
|
||||
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
std::string value = rnd.RandomString(1000);
|
||||
ASSERT_OK(Put("keykey_" + std::to_string(i), value));
|
||||
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
|
||||
ASSERT_TRUE(db_->GetIntProperty(db_->DefaultColumnFamily(),
|
||||
DB::Properties::kSizeAllMemTables,
|
||||
&size_all_mem_table));
|
||||
ASSERT_TRUE(db_->GetIntProperty(db_->DefaultColumnFamily(),
|
||||
DB::Properties::kCurSizeActiveMemTable,
|
||||
&cur_active_mem));
|
||||
const uint64_t cur_active_mem = cfd->mem()->ApproximateMemoryUsage();
|
||||
const uint64_t size_all_mem_table =
|
||||
cur_active_mem + cfd->imm()->ApproximateMemoryUsage();
|
||||
|
||||
// Errors out if memory usage keeps on increasing beyond the limit.
|
||||
// Once memory limit exceeds, memory_limit_exceeded is set and if
|
||||
|
||||
+132
@@ -344,6 +344,10 @@ class DBTestSharedWriteBufferAcrossCFs
|
||||
TEST_P(DBTestSharedWriteBufferAcrossCFs, SharedWriteBufferAcrossCFs) {
|
||||
Options options = CurrentOptions();
|
||||
options.arena_block_size = 4096;
|
||||
auto flush_listener = std::make_shared<FlushCounterListener>();
|
||||
options.listeners.push_back(flush_listener);
|
||||
// Don't trip the listener at shutdown.
|
||||
options.avoid_flush_during_shutdown = true;
|
||||
|
||||
// Avoid undeterministic value by malloc_usable_size();
|
||||
// Force arena block size to 1
|
||||
@@ -387,6 +391,7 @@ TEST_P(DBTestSharedWriteBufferAcrossCFs, SharedWriteBufferAcrossCFs) {
|
||||
|
||||
// Create some data and flush "default" and "nikitich" so that they
|
||||
// are newer CFs created.
|
||||
flush_listener->expected_flush_reason = FlushReason::kManualFlush;
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1), wo));
|
||||
Flush(3);
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1), wo));
|
||||
@@ -397,6 +402,7 @@ TEST_P(DBTestSharedWriteBufferAcrossCFs, SharedWriteBufferAcrossCFs) {
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
||||
static_cast<uint64_t>(1));
|
||||
|
||||
flush_listener->expected_flush_reason = FlushReason::kWriteBufferManager;
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(30000), wo));
|
||||
if (cost_cache_) {
|
||||
ASSERT_GE(cache->GetUsage(), 256 * 1024);
|
||||
@@ -521,6 +527,10 @@ TEST_F(DBTest2, SharedWriteBufferLimitAcrossDB) {
|
||||
std::string dbname2 = test::PerThreadDBPath("db_shared_wb_db2");
|
||||
Options options = CurrentOptions();
|
||||
options.arena_block_size = 4096;
|
||||
auto flush_listener = std::make_shared<FlushCounterListener>();
|
||||
options.listeners.push_back(flush_listener);
|
||||
// Don't trip the listener at shutdown.
|
||||
options.avoid_flush_during_shutdown = true;
|
||||
// Avoid undeterministic value by malloc_usable_size();
|
||||
// Force arena block size to 1
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
@@ -558,6 +568,7 @@ TEST_F(DBTest2, SharedWriteBufferLimitAcrossDB) {
|
||||
};
|
||||
|
||||
// Trigger a flush on cf2
|
||||
flush_listener->expected_flush_reason = FlushReason::kWriteBufferManager;
|
||||
ASSERT_OK(Put(2, Key(1), DummyString(70000), wo));
|
||||
wait_flush();
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(20000), wo));
|
||||
@@ -5428,6 +5439,98 @@ TEST_F(DBTest2, AutoPrefixMode1) {
|
||||
ASSERT_EQ("a1", iterator->key().ToString());
|
||||
}
|
||||
}
|
||||
|
||||
class RenameCurrentTest : public DBTestBase,
|
||||
public testing::WithParamInterface<std::string> {
|
||||
public:
|
||||
RenameCurrentTest()
|
||||
: DBTestBase("rename_current_test", /*env_do_fsync=*/true),
|
||||
sync_point_(GetParam()) {}
|
||||
|
||||
~RenameCurrentTest() override {}
|
||||
|
||||
void SetUp() override {
|
||||
env_->no_file_overwrite_.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
env_->no_file_overwrite_.store(false, std::memory_order_release);
|
||||
}
|
||||
|
||||
void SetupSyncPoints() {
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->SetCallBack(sync_point_, [&](void* arg) {
|
||||
Status* s = reinterpret_cast<Status*>(arg);
|
||||
assert(s);
|
||||
*s = Status::IOError("Injected IO error.");
|
||||
});
|
||||
}
|
||||
|
||||
const std::string sync_point_;
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DistributedFS, RenameCurrentTest,
|
||||
::testing::Values("SetCurrentFile:BeforeRename",
|
||||
"SetCurrentFile:AfterRename"));
|
||||
|
||||
TEST_P(RenameCurrentTest, Open) {
|
||||
Destroy(last_options_);
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
SetupSyncPoints();
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Status s = TryReopen(options);
|
||||
ASSERT_NOK(s);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
Reopen(options);
|
||||
}
|
||||
|
||||
TEST_P(RenameCurrentTest, Flush) {
|
||||
Destroy(last_options_);
|
||||
Options options = GetDefaultOptions();
|
||||
options.max_manifest_file_size = 1;
|
||||
options.create_if_missing = true;
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("key", "value"));
|
||||
SetupSyncPoints();
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
ASSERT_NOK(Flush());
|
||||
|
||||
ASSERT_NOK(Put("foo", "value"));
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
Reopen(options);
|
||||
ASSERT_EQ("value", Get("key"));
|
||||
ASSERT_EQ("NOT_FOUND", Get("foo"));
|
||||
}
|
||||
|
||||
TEST_P(RenameCurrentTest, Compaction) {
|
||||
Destroy(last_options_);
|
||||
Options options = GetDefaultOptions();
|
||||
options.max_manifest_file_size = 1;
|
||||
options.create_if_missing = true;
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("a", "a_value"));
|
||||
ASSERT_OK(Put("c", "c_value"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(Put("b", "b_value"));
|
||||
ASSERT_OK(Put("d", "d_value"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
SetupSyncPoints();
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
ASSERT_NOK(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr));
|
||||
|
||||
ASSERT_NOK(Put("foo", "value"));
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
Reopen(options);
|
||||
ASSERT_EQ("NOT_FOUND", Get("foo"));
|
||||
ASSERT_EQ("d_value", Get("d"));
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// WAL recovery mode is WALRecoveryMode::kPointInTimeRecovery.
|
||||
@@ -5455,6 +5558,35 @@ TEST_F(DBTest2, PointInTimeRecoveryWithIOErrorWhileReadingWal) {
|
||||
Status s = TryReopen(options);
|
||||
ASSERT_TRUE(s.IsIOError());
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, PointInTimeRecoveryWithSyncFailureInCFCreation) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::BackgroundCallFlush:Start:1",
|
||||
"PointInTimeRecoveryWithSyncFailureInCFCreation:1"},
|
||||
{"PointInTimeRecoveryWithSyncFailureInCFCreation:2",
|
||||
"DBImpl::BackgroundCallFlush:Start:2"}});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
CreateColumnFamilies({"test1"}, Options());
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
|
||||
// Creating a CF when a flush is going on, log is synced but the
|
||||
// closed log file is not synced and corrupted.
|
||||
port::Thread flush_thread([&]() { ASSERT_NOK(Flush()); });
|
||||
TEST_SYNC_POINT("PointInTimeRecoveryWithSyncFailureInCFCreation:1");
|
||||
CreateColumnFamilies({"test2"}, Options());
|
||||
env_->corrupt_in_sync_ = true;
|
||||
TEST_SYNC_POINT("PointInTimeRecoveryWithSyncFailureInCFCreation:2");
|
||||
flush_thread.join();
|
||||
env_->corrupt_in_sync_ = false;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
// Reopening the DB should not corrupt anything
|
||||
Options options = CurrentOptions();
|
||||
options.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery;
|
||||
ReopenWithColumnFamilies({"default", "test1", "test2"}, options);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
|
||||
@@ -44,6 +44,7 @@ SpecialEnv::SpecialEnv(Env* base, bool time_elapse_only_sleep)
|
||||
manifest_sync_error_.store(false, std::memory_order_release);
|
||||
manifest_write_error_.store(false, std::memory_order_release);
|
||||
log_write_error_.store(false, std::memory_order_release);
|
||||
no_file_overwrite_.store(false, std::memory_order_release);
|
||||
random_file_open_counter_.store(0, std::memory_order_relaxed);
|
||||
delete_count_.store(0, std::memory_order_relaxed);
|
||||
num_open_wal_file_.store(0);
|
||||
@@ -487,6 +488,7 @@ Options DBTestBase::GetOptions(
|
||||
}
|
||||
case kFIFOCompaction: {
|
||||
options.compaction_style = kCompactionStyleFIFO;
|
||||
options.max_open_files = -1;
|
||||
break;
|
||||
}
|
||||
case kBlockBasedTableWithPrefixHashIndex: {
|
||||
|
||||
+38
-3
@@ -378,14 +378,20 @@ class SpecialEnv : public EnvWrapper {
|
||||
return Append(data);
|
||||
}
|
||||
Status Truncate(uint64_t size) override { return base_->Truncate(size); }
|
||||
void PrepareWrite(size_t offset, size_t len) override {
|
||||
base_->PrepareWrite(offset, len);
|
||||
}
|
||||
void SetPreallocationBlockSize(size_t size) override {
|
||||
base_->SetPreallocationBlockSize(size);
|
||||
}
|
||||
Status Close() override {
|
||||
// SyncPoint is not supported in Released Windows Mode.
|
||||
#if !(defined NDEBUG) || !defined(OS_WIN)
|
||||
// Check preallocation size
|
||||
// preallocation size is never passed to base file.
|
||||
size_t preallocation_size = preallocation_block_size();
|
||||
size_t block_size, last_allocated_block;
|
||||
base_->GetPreallocationStatus(&block_size, &last_allocated_block);
|
||||
TEST_SYNC_POINT_CALLBACK("DBTestWalFile.GetPreallocationStatus",
|
||||
&preallocation_size);
|
||||
&block_size);
|
||||
#endif // !(defined NDEBUG) || !defined(OS_WIN)
|
||||
|
||||
return base_->Close();
|
||||
@@ -393,6 +399,10 @@ class SpecialEnv : public EnvWrapper {
|
||||
Status Flush() override { return base_->Flush(); }
|
||||
Status Sync() override {
|
||||
++env_->sync_counter_;
|
||||
if (env_->corrupt_in_sync_) {
|
||||
Append(std::string(33000, ' '));
|
||||
return Status::IOError("Ingested Sync Failure");
|
||||
}
|
||||
if (env_->skip_fsync_) {
|
||||
return Status::OK();
|
||||
} else {
|
||||
@@ -440,6 +450,11 @@ class SpecialEnv : public EnvWrapper {
|
||||
std::unique_ptr<WritableFile> base_;
|
||||
};
|
||||
|
||||
if (no_file_overwrite_.load(std::memory_order_acquire) &&
|
||||
target()->FileExists(f).ok()) {
|
||||
return Status::NotSupported("SpecialEnv::no_file_overwrite_ is true.");
|
||||
}
|
||||
|
||||
if (non_writeable_rate_.load(std::memory_order_acquire) > 0) {
|
||||
uint32_t random_number;
|
||||
{
|
||||
@@ -687,6 +702,9 @@ class SpecialEnv : public EnvWrapper {
|
||||
// Slow down every log write, in micro-seconds.
|
||||
std::atomic<int> log_write_slowdown_;
|
||||
|
||||
// If true, returns Status::NotSupported for file overwrite.
|
||||
std::atomic<bool> no_file_overwrite_;
|
||||
|
||||
// Number of WAL files that are still open for write.
|
||||
std::atomic<int> num_open_wal_file_;
|
||||
|
||||
@@ -709,6 +727,9 @@ class SpecialEnv : public EnvWrapper {
|
||||
// If true, all fsync to files and directories are skipped.
|
||||
bool skip_fsync_ = false;
|
||||
|
||||
// If true, ingest the corruption to file during sync.
|
||||
bool corrupt_in_sync_ = false;
|
||||
|
||||
std::atomic<uint32_t> non_writeable_rate_;
|
||||
|
||||
std::atomic<uint32_t> new_writable_count_;
|
||||
@@ -761,6 +782,17 @@ class OnFileDeletionListener : public EventListener {
|
||||
size_t matched_count_;
|
||||
std::string expected_file_name_;
|
||||
};
|
||||
|
||||
class FlushCounterListener : public EventListener {
|
||||
public:
|
||||
std::atomic<int> count{0};
|
||||
std::atomic<FlushReason> expected_flush_reason{FlushReason::kOthers};
|
||||
|
||||
void OnFlushBegin(DB* /*db*/, const FlushJobInfo& flush_job_info) override {
|
||||
count++;
|
||||
ASSERT_EQ(expected_flush_reason.load(), flush_job_info.flush_reason);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
// A test merge operator mimics put but also fails if one of merge operands is
|
||||
@@ -794,6 +826,7 @@ class CacheWrapper : public Cache {
|
||||
|
||||
const char* Name() const override { return target_->Name(); }
|
||||
|
||||
using Cache::Insert;
|
||||
Status Insert(const Slice& key, void* value, size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
Handle** handle = nullptr,
|
||||
@@ -801,12 +834,14 @@ class CacheWrapper : public Cache {
|
||||
return target_->Insert(key, value, charge, deleter, handle, priority);
|
||||
}
|
||||
|
||||
using Cache::Lookup;
|
||||
Handle* Lookup(const Slice& key, Statistics* stats = nullptr) override {
|
||||
return target_->Lookup(key, stats);
|
||||
}
|
||||
|
||||
bool Ref(Handle* handle) override { return target_->Ref(handle); }
|
||||
|
||||
using Cache::Release;
|
||||
bool Release(Handle* handle, bool force_erase = false) override {
|
||||
return target_->Release(handle, force_erase);
|
||||
}
|
||||
|
||||
+2
-13
@@ -1801,19 +1801,8 @@ TEST_P(DBWALTestWithParamsVaryingRecoveryMode,
|
||||
// avoid_flush_during_recovery=true.
|
||||
// Flush should trigger if max_total_wal_size is reached.
|
||||
TEST_F(DBWALTest, RestoreTotalLogSizeAfterRecoverWithoutFlush) {
|
||||
class TestFlushListener : public EventListener {
|
||||
public:
|
||||
std::atomic<int> count{0};
|
||||
|
||||
TestFlushListener() = default;
|
||||
|
||||
void OnFlushBegin(DB* /*db*/, const FlushJobInfo& flush_job_info) override {
|
||||
count++;
|
||||
ASSERT_EQ(FlushReason::kWriteBufferManager, flush_job_info.flush_reason);
|
||||
}
|
||||
};
|
||||
std::shared_ptr<TestFlushListener> test_listener =
|
||||
std::make_shared<TestFlushListener>();
|
||||
auto test_listener = std::make_shared<FlushCounterListener>();
|
||||
test_listener->expected_flush_reason = FlushReason::kWalFull;
|
||||
|
||||
constexpr size_t kKB = 1024;
|
||||
constexpr size_t kMB = 1024 * 1024;
|
||||
|
||||
@@ -0,0 +1,801 @@
|
||||
// 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 "db/db_test_util.h"
|
||||
#include "db/write_thread.h"
|
||||
#include "port/stack_trace.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class DBWriteBufferManagerTest : public DBTestBase,
|
||||
public testing::WithParamInterface<bool> {
|
||||
public:
|
||||
DBWriteBufferManagerTest()
|
||||
: DBTestBase("/db_write_buffer_manager_test", /*env_do_fsync=*/false) {}
|
||||
bool cost_cache_;
|
||||
};
|
||||
|
||||
TEST_P(DBWriteBufferManagerTest, SharedBufferAcrossCFs1) {
|
||||
Options options = CurrentOptions();
|
||||
options.arena_block_size = 4096;
|
||||
options.write_buffer_size = 500000; // this is never hit
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(4 * 1024 * 1024, 2);
|
||||
ASSERT_LT(cache->GetUsage(), 256 * 1024);
|
||||
cost_cache_ = GetParam();
|
||||
|
||||
if (cost_cache_) {
|
||||
options.write_buffer_manager.reset(
|
||||
new WriteBufferManager(100000, cache, true));
|
||||
} else {
|
||||
options.write_buffer_manager.reset(
|
||||
new WriteBufferManager(100000, nullptr, true));
|
||||
}
|
||||
|
||||
WriteOptions wo;
|
||||
wo.disableWAL = true;
|
||||
|
||||
CreateAndReopenWithCF({"cf1", "cf2", "cf3"}, options);
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1), wo));
|
||||
Flush(3);
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1), wo));
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(1), wo));
|
||||
Flush(0);
|
||||
|
||||
// Write to "Default", "cf2" and "cf3".
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(30000), wo));
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(40000), wo));
|
||||
ASSERT_OK(Put(2, Key(1), DummyString(1), wo));
|
||||
|
||||
ASSERT_OK(Put(3, Key(2), DummyString(40000), wo));
|
||||
// WriteBufferManager::buffer_size_ has exceeded after the previous write is
|
||||
// completed.
|
||||
|
||||
// This make sures write will go through and if stall was in effect, it will
|
||||
// end.
|
||||
ASSERT_OK(Put(0, Key(2), DummyString(1), wo));
|
||||
}
|
||||
|
||||
// Test Single DB with multiple writer threads get blocked when
|
||||
// WriteBufferManager execeeds buffer_size_ and flush is waiting to be
|
||||
// finished.
|
||||
TEST_P(DBWriteBufferManagerTest, SharedWriteBufferAcrossCFs2) {
|
||||
Options options = CurrentOptions();
|
||||
options.arena_block_size = 4096;
|
||||
options.write_buffer_size = 500000; // this is never hit
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(4 * 1024 * 1024, 2);
|
||||
ASSERT_LT(cache->GetUsage(), 256 * 1024);
|
||||
cost_cache_ = GetParam();
|
||||
|
||||
if (cost_cache_) {
|
||||
options.write_buffer_manager.reset(
|
||||
new WriteBufferManager(100000, cache, true));
|
||||
} else {
|
||||
options.write_buffer_manager.reset(
|
||||
new WriteBufferManager(100000, nullptr, true));
|
||||
}
|
||||
WriteOptions wo;
|
||||
wo.disableWAL = true;
|
||||
|
||||
CreateAndReopenWithCF({"cf1", "cf2", "cf3"}, options);
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1), wo));
|
||||
Flush(3);
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1), wo));
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(1), wo));
|
||||
Flush(0);
|
||||
|
||||
// Write to "Default", "cf2" and "cf3". No flush will be triggered.
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(30000), wo));
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(40000), wo));
|
||||
ASSERT_OK(Put(2, Key(1), DummyString(1), wo));
|
||||
|
||||
ASSERT_OK(Put(3, Key(2), DummyString(40000), wo));
|
||||
// WriteBufferManager::buffer_size_ has exceeded after the previous write is
|
||||
// completed.
|
||||
|
||||
std::unordered_set<WriteThread::Writer*> w_set;
|
||||
std::vector<port::Thread> threads;
|
||||
int wait_count_db = 0;
|
||||
int num_writers = 4;
|
||||
InstrumentedMutex mutex;
|
||||
InstrumentedCondVar cv(&mutex);
|
||||
std::atomic<int> thread_num(0);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0",
|
||||
"DBImpl::BackgroundCallFlush:start"}});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"WBMStallInterface::BlockDB", [&](void*) {
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
wait_count_db++;
|
||||
cv.SignalAll();
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"WriteThread::WriteStall::Wait", [&](void* arg) {
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
WriteThread::Writer* w = reinterpret_cast<WriteThread::Writer*>(arg);
|
||||
w_set.insert(w);
|
||||
// Allow the flush to continue if all writer threads are blocked.
|
||||
if (w_set.size() == (unsigned long)num_writers) {
|
||||
TEST_SYNC_POINT(
|
||||
"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0");
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
bool s = true;
|
||||
|
||||
std::function<void(int)> writer = [&](int cf) {
|
||||
int a = thread_num.fetch_add(1);
|
||||
std::string key = "foo" + std::to_string(a);
|
||||
Status tmp = Put(cf, Slice(key), DummyString(1), wo);
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
s = s && tmp.ok();
|
||||
};
|
||||
|
||||
// Flow:
|
||||
// main_writer thread will write but will be blocked (as Flush will on hold,
|
||||
// buffer_size_ has exceeded, thus will create stall in effect).
|
||||
// |
|
||||
// |
|
||||
// multiple writer threads will be created to write across multiple columns
|
||||
// and they will be blocked.
|
||||
// |
|
||||
// |
|
||||
// Last writer thread will write and when its blocked it will signal Flush to
|
||||
// continue to clear the stall.
|
||||
|
||||
threads.emplace_back(writer, 1);
|
||||
// Wait untill first thread (main_writer) writing to DB is blocked and then
|
||||
// create the multiple writers which will be blocked from getting added to the
|
||||
// queue because stall is in effect.
|
||||
{
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
while (wait_count_db != 1) {
|
||||
cv.Wait();
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < num_writers; i++) {
|
||||
threads.emplace_back(writer, i % 4);
|
||||
}
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
ASSERT_TRUE(s);
|
||||
|
||||
// Number of DBs blocked.
|
||||
ASSERT_EQ(wait_count_db, 1);
|
||||
// Number of Writer threads blocked.
|
||||
ASSERT_EQ(w_set.size(), num_writers);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
// Test multiple DBs get blocked when WriteBufferManager limit exceeds and flush
|
||||
// is waiting to be finished but DBs tries to write meanwhile.
|
||||
TEST_P(DBWriteBufferManagerTest, SharedWriteBufferLimitAcrossDB) {
|
||||
std::vector<std::string> dbnames;
|
||||
std::vector<DB*> dbs;
|
||||
int num_dbs = 3;
|
||||
|
||||
for (int i = 0; i < num_dbs; i++) {
|
||||
dbs.push_back(nullptr);
|
||||
dbnames.push_back(
|
||||
test::PerThreadDBPath("db_shared_wb_db" + std::to_string(i)));
|
||||
}
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.arena_block_size = 4096;
|
||||
options.write_buffer_size = 500000; // this is never hit
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(4 * 1024 * 1024, 2);
|
||||
ASSERT_LT(cache->GetUsage(), 256 * 1024);
|
||||
cost_cache_ = GetParam();
|
||||
|
||||
if (cost_cache_) {
|
||||
options.write_buffer_manager.reset(
|
||||
new WriteBufferManager(100000, cache, true));
|
||||
} else {
|
||||
options.write_buffer_manager.reset(
|
||||
new WriteBufferManager(100000, nullptr, true));
|
||||
}
|
||||
CreateAndReopenWithCF({"cf1", "cf2"}, options);
|
||||
|
||||
for (int i = 0; i < num_dbs; i++) {
|
||||
ASSERT_OK(DestroyDB(dbnames[i], options));
|
||||
ASSERT_OK(DB::Open(options, dbnames[i], &(dbs[i])));
|
||||
}
|
||||
WriteOptions wo;
|
||||
wo.disableWAL = true;
|
||||
|
||||
for (int i = 0; i < num_dbs; i++) {
|
||||
ASSERT_OK(dbs[i]->Put(wo, Key(1), DummyString(20000)));
|
||||
}
|
||||
// Insert to db_.
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(30000), wo));
|
||||
|
||||
// WriteBufferManager Limit exceeded.
|
||||
std::vector<port::Thread> threads;
|
||||
int wait_count_db = 0;
|
||||
InstrumentedMutex mutex;
|
||||
InstrumentedCondVar cv(&mutex);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0",
|
||||
"DBImpl::BackgroundCallFlush:start"}});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"WBMStallInterface::BlockDB", [&](void*) {
|
||||
{
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
wait_count_db++;
|
||||
cv.Signal();
|
||||
// Since this is the last DB, signal Flush to continue.
|
||||
if (wait_count_db == num_dbs + 1) {
|
||||
TEST_SYNC_POINT(
|
||||
"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0");
|
||||
}
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
bool s = true;
|
||||
|
||||
// Write to DB.
|
||||
std::function<void(DB*)> write_db = [&](DB* db) {
|
||||
Status tmp = db->Put(wo, Key(3), DummyString(1));
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
s = s && tmp.ok();
|
||||
};
|
||||
|
||||
// Flow:
|
||||
// db_ will write and will be blocked (as Flush will on hold and will create
|
||||
// stall in effect).
|
||||
// |
|
||||
// multiple dbs writers will be created to write to that db and they will be
|
||||
// blocked.
|
||||
// |
|
||||
// |
|
||||
// Last writer will write and when its blocked it will signal Flush to
|
||||
// continue to clear the stall.
|
||||
|
||||
threads.emplace_back(write_db, db_);
|
||||
// Wait untill first DB is blocked and then create the multiple writers for
|
||||
// different DBs which will be blocked from getting added to the queue because
|
||||
// stall is in effect.
|
||||
{
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
while (wait_count_db != 1) {
|
||||
cv.Wait();
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < num_dbs; i++) {
|
||||
threads.emplace_back(write_db, dbs[i]);
|
||||
}
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
ASSERT_TRUE(s);
|
||||
ASSERT_EQ(num_dbs + 1, wait_count_db);
|
||||
// Clean up DBs.
|
||||
for (int i = 0; i < num_dbs; i++) {
|
||||
ASSERT_OK(dbs[i]->Close());
|
||||
ASSERT_OK(DestroyDB(dbnames[i], options));
|
||||
delete dbs[i];
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
// Test multiple threads writing across multiple DBs and multiple columns get
|
||||
// blocked when stall by WriteBufferManager is in effect.
|
||||
TEST_P(DBWriteBufferManagerTest, SharedWriteBufferLimitAcrossDB1) {
|
||||
std::vector<std::string> dbnames;
|
||||
std::vector<DB*> dbs;
|
||||
int num_dbs = 3;
|
||||
|
||||
for (int i = 0; i < num_dbs; i++) {
|
||||
dbs.push_back(nullptr);
|
||||
dbnames.push_back(
|
||||
test::PerThreadDBPath("db_shared_wb_db" + std::to_string(i)));
|
||||
}
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.arena_block_size = 4096;
|
||||
options.write_buffer_size = 500000; // this is never hit
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(4 * 1024 * 1024, 2);
|
||||
ASSERT_LT(cache->GetUsage(), 256 * 1024);
|
||||
cost_cache_ = GetParam();
|
||||
|
||||
if (cost_cache_) {
|
||||
options.write_buffer_manager.reset(
|
||||
new WriteBufferManager(100000, cache, true));
|
||||
} else {
|
||||
options.write_buffer_manager.reset(
|
||||
new WriteBufferManager(100000, nullptr, true));
|
||||
}
|
||||
CreateAndReopenWithCF({"cf1", "cf2"}, options);
|
||||
|
||||
for (int i = 0; i < num_dbs; i++) {
|
||||
ASSERT_OK(DestroyDB(dbnames[i], options));
|
||||
ASSERT_OK(DB::Open(options, dbnames[i], &(dbs[i])));
|
||||
}
|
||||
WriteOptions wo;
|
||||
wo.disableWAL = true;
|
||||
|
||||
for (int i = 0; i < num_dbs; i++) {
|
||||
ASSERT_OK(dbs[i]->Put(wo, Key(1), DummyString(20000)));
|
||||
}
|
||||
// Insert to db_.
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(30000), wo));
|
||||
|
||||
// WriteBufferManager::buffer_size_ has exceeded after the previous write to
|
||||
// dbs[0] is completed.
|
||||
std::vector<port::Thread> threads;
|
||||
int wait_count_db = 0;
|
||||
InstrumentedMutex mutex;
|
||||
InstrumentedCondVar cv(&mutex);
|
||||
std::unordered_set<WriteThread::Writer*> w_set;
|
||||
std::vector<port::Thread> writer_threads;
|
||||
std::atomic<int> thread_num(0);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0",
|
||||
"DBImpl::BackgroundCallFlush:start"}});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"WBMStallInterface::BlockDB", [&](void*) {
|
||||
{
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
wait_count_db++;
|
||||
thread_num.fetch_add(1);
|
||||
cv.Signal();
|
||||
// Allow the flush to continue if all writer threads are blocked.
|
||||
if (thread_num.load(std::memory_order_relaxed) == 2 * num_dbs + 1) {
|
||||
TEST_SYNC_POINT(
|
||||
"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0");
|
||||
}
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"WriteThread::WriteStall::Wait", [&](void* arg) {
|
||||
WriteThread::Writer* w = reinterpret_cast<WriteThread::Writer*>(arg);
|
||||
{
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
w_set.insert(w);
|
||||
thread_num.fetch_add(1);
|
||||
// Allow the flush continue if all writer threads are blocked.
|
||||
if (thread_num.load(std::memory_order_relaxed) == 2 * num_dbs + 1) {
|
||||
TEST_SYNC_POINT(
|
||||
"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0");
|
||||
}
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
bool s1 = true, s2 = true;
|
||||
// Write to multiple columns of db_.
|
||||
std::function<void(int)> write_cf = [&](int cf) {
|
||||
Status tmp = Put(cf, Key(3), DummyString(1), wo);
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
s1 = s1 && tmp.ok();
|
||||
};
|
||||
// Write to multiple DBs.
|
||||
std::function<void(DB*)> write_db = [&](DB* db) {
|
||||
Status tmp = db->Put(wo, Key(3), DummyString(1));
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
s2 = s2 && tmp.ok();
|
||||
};
|
||||
|
||||
// Flow:
|
||||
// thread will write to db_ will be blocked (as Flush will on hold,
|
||||
// buffer_size_ has exceeded and will create stall in effect).
|
||||
// |
|
||||
// |
|
||||
// multiple writers threads writing to different DBs and to db_ across
|
||||
// multiple columns will be created and they will be blocked due to stall.
|
||||
// |
|
||||
// |
|
||||
// Last writer thread will write and when its blocked it will signal Flush to
|
||||
// continue to clear the stall.
|
||||
threads.emplace_back(write_db, db_);
|
||||
// Wait untill first thread is blocked and then create the multiple writer
|
||||
// threads.
|
||||
{
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
while (wait_count_db != 1) {
|
||||
cv.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < num_dbs; i++) {
|
||||
// Write to multiple columns of db_.
|
||||
writer_threads.emplace_back(write_cf, i % 3);
|
||||
// Write to different dbs.
|
||||
threads.emplace_back(write_db, dbs[i]);
|
||||
}
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
for (auto& t : writer_threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
ASSERT_TRUE(s1);
|
||||
ASSERT_TRUE(s2);
|
||||
|
||||
// Number of DBs blocked.
|
||||
ASSERT_EQ(num_dbs + 1, wait_count_db);
|
||||
// Number of Writer threads blocked.
|
||||
ASSERT_EQ(w_set.size(), num_dbs);
|
||||
// Clean up DBs.
|
||||
for (int i = 0; i < num_dbs; i++) {
|
||||
ASSERT_OK(dbs[i]->Close());
|
||||
ASSERT_OK(DestroyDB(dbnames[i], options));
|
||||
delete dbs[i];
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
// Test multiple threads writing across multiple columns of db_ by passing
|
||||
// different values to WriteOption.no_slown_down.
|
||||
TEST_P(DBWriteBufferManagerTest, MixedSlowDownOptionsSingleDB) {
|
||||
Options options = CurrentOptions();
|
||||
options.arena_block_size = 4096;
|
||||
options.write_buffer_size = 500000; // this is never hit
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(4 * 1024 * 1024, 2);
|
||||
ASSERT_LT(cache->GetUsage(), 256 * 1024);
|
||||
cost_cache_ = GetParam();
|
||||
|
||||
if (cost_cache_) {
|
||||
options.write_buffer_manager.reset(
|
||||
new WriteBufferManager(100000, cache, true));
|
||||
} else {
|
||||
options.write_buffer_manager.reset(
|
||||
new WriteBufferManager(100000, nullptr, true));
|
||||
}
|
||||
WriteOptions wo;
|
||||
wo.disableWAL = true;
|
||||
|
||||
CreateAndReopenWithCF({"cf1", "cf2", "cf3"}, options);
|
||||
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1), wo));
|
||||
Flush(3);
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1), wo));
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(1), wo));
|
||||
Flush(0);
|
||||
|
||||
// Write to "Default", "cf2" and "cf3". No flush will be triggered.
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(30000), wo));
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(40000), wo));
|
||||
ASSERT_OK(Put(2, Key(1), DummyString(1), wo));
|
||||
ASSERT_OK(Put(3, Key(2), DummyString(40000), wo));
|
||||
|
||||
// WriteBufferManager::buffer_size_ has exceeded after the previous write to
|
||||
// db_ is completed.
|
||||
|
||||
std::unordered_set<WriteThread::Writer*> w_slowdown_set;
|
||||
std::vector<port::Thread> threads;
|
||||
int wait_count_db = 0;
|
||||
int num_writers = 4;
|
||||
InstrumentedMutex mutex;
|
||||
InstrumentedCondVar cv(&mutex);
|
||||
std::atomic<int> thread_num(0);
|
||||
std::atomic<int> w_no_slowdown(0);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0",
|
||||
"DBImpl::BackgroundCallFlush:start"}});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"WBMStallInterface::BlockDB", [&](void*) {
|
||||
{
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
wait_count_db++;
|
||||
cv.SignalAll();
|
||||
}
|
||||
});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"WriteThread::WriteStall::Wait", [&](void* arg) {
|
||||
{
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
WriteThread::Writer* w = reinterpret_cast<WriteThread::Writer*>(arg);
|
||||
w_slowdown_set.insert(w);
|
||||
// Allow the flush continue if all writer threads are blocked.
|
||||
if (w_slowdown_set.size() + (unsigned long)w_no_slowdown.load(
|
||||
std::memory_order_relaxed) ==
|
||||
(unsigned long)num_writers) {
|
||||
TEST_SYNC_POINT(
|
||||
"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0");
|
||||
}
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
bool s1 = true, s2 = true;
|
||||
|
||||
std::function<void(int)> write_slow_down = [&](int cf) {
|
||||
int a = thread_num.fetch_add(1);
|
||||
std::string key = "foo" + std::to_string(a);
|
||||
WriteOptions write_op;
|
||||
write_op.no_slowdown = false;
|
||||
Status tmp = Put(cf, Slice(key), DummyString(1), write_op);
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
s1 = s1 && tmp.ok();
|
||||
};
|
||||
|
||||
std::function<void(int)> write_no_slow_down = [&](int cf) {
|
||||
int a = thread_num.fetch_add(1);
|
||||
std::string key = "foo" + std::to_string(a);
|
||||
WriteOptions write_op;
|
||||
write_op.no_slowdown = true;
|
||||
Status tmp = Put(cf, Slice(key), DummyString(1), write_op);
|
||||
{
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
s2 = s2 && !tmp.ok();
|
||||
w_no_slowdown.fetch_add(1);
|
||||
// Allow the flush continue if all writer threads are blocked.
|
||||
if (w_slowdown_set.size() +
|
||||
(unsigned long)w_no_slowdown.load(std::memory_order_relaxed) ==
|
||||
(unsigned long)num_writers) {
|
||||
TEST_SYNC_POINT(
|
||||
"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Flow:
|
||||
// main_writer thread will write but will be blocked (as Flush will on hold,
|
||||
// buffer_size_ has exceeded, thus will create stall in effect).
|
||||
// |
|
||||
// |
|
||||
// multiple writer threads will be created to write across multiple columns
|
||||
// with different values of WriteOptions.no_slowdown. Some of them will
|
||||
// be blocked and some of them will return with Incomplete status.
|
||||
// |
|
||||
// |
|
||||
// Last writer thread will write and when its blocked/return it will signal
|
||||
// Flush to continue to clear the stall.
|
||||
threads.emplace_back(write_slow_down, 1);
|
||||
// Wait untill first thread (main_writer) writing to DB is blocked and then
|
||||
// create the multiple writers which will be blocked from getting added to the
|
||||
// queue because stall is in effect.
|
||||
{
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
while (wait_count_db != 1) {
|
||||
cv.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < num_writers; i += 2) {
|
||||
threads.emplace_back(write_no_slow_down, (i) % 4);
|
||||
threads.emplace_back(write_slow_down, (i + 1) % 4);
|
||||
}
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
ASSERT_TRUE(s1);
|
||||
ASSERT_TRUE(s2);
|
||||
// Number of DBs blocked.
|
||||
ASSERT_EQ(wait_count_db, 1);
|
||||
// Number of Writer threads blocked.
|
||||
ASSERT_EQ(w_slowdown_set.size(), num_writers / 2);
|
||||
// Number of Writer threads with WriteOptions.no_slowdown = true.
|
||||
ASSERT_EQ(w_no_slowdown.load(std::memory_order_relaxed), num_writers / 2);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
// Test multiple threads writing across multiple columns of db_ and different
|
||||
// dbs by passing different values to WriteOption.no_slown_down.
|
||||
TEST_P(DBWriteBufferManagerTest, MixedSlowDownOptionsMultipleDB) {
|
||||
std::vector<std::string> dbnames;
|
||||
std::vector<DB*> dbs;
|
||||
int num_dbs = 4;
|
||||
|
||||
for (int i = 0; i < num_dbs; i++) {
|
||||
dbs.push_back(nullptr);
|
||||
dbnames.push_back(
|
||||
test::PerThreadDBPath("db_shared_wb_db" + std::to_string(i)));
|
||||
}
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.arena_block_size = 4096;
|
||||
options.write_buffer_size = 500000; // this is never hit
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(4 * 1024 * 1024, 2);
|
||||
ASSERT_LT(cache->GetUsage(), 256 * 1024);
|
||||
cost_cache_ = GetParam();
|
||||
|
||||
if (cost_cache_) {
|
||||
options.write_buffer_manager.reset(
|
||||
new WriteBufferManager(100000, cache, true));
|
||||
} else {
|
||||
options.write_buffer_manager.reset(
|
||||
new WriteBufferManager(100000, nullptr, true));
|
||||
}
|
||||
CreateAndReopenWithCF({"cf1", "cf2"}, options);
|
||||
|
||||
for (int i = 0; i < num_dbs; i++) {
|
||||
ASSERT_OK(DestroyDB(dbnames[i], options));
|
||||
ASSERT_OK(DB::Open(options, dbnames[i], &(dbs[i])));
|
||||
}
|
||||
WriteOptions wo;
|
||||
wo.disableWAL = true;
|
||||
|
||||
for (int i = 0; i < num_dbs; i++) {
|
||||
ASSERT_OK(dbs[i]->Put(wo, Key(1), DummyString(20000)));
|
||||
}
|
||||
// Insert to db_.
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(30000), wo));
|
||||
|
||||
// WriteBufferManager::buffer_size_ has exceeded after the previous write to
|
||||
// dbs[0] is completed.
|
||||
std::vector<port::Thread> threads;
|
||||
int wait_count_db = 0;
|
||||
InstrumentedMutex mutex;
|
||||
InstrumentedCondVar cv(&mutex);
|
||||
std::unordered_set<WriteThread::Writer*> w_slowdown_set;
|
||||
std::vector<port::Thread> writer_threads;
|
||||
std::atomic<int> thread_num(0);
|
||||
std::atomic<int> w_no_slowdown(0);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0",
|
||||
"DBImpl::BackgroundCallFlush:start"}});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"WBMStallInterface::BlockDB", [&](void*) {
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
wait_count_db++;
|
||||
cv.Signal();
|
||||
// Allow the flush continue if all writer threads are blocked.
|
||||
if (w_slowdown_set.size() +
|
||||
(unsigned long)(w_no_slowdown.load(std::memory_order_relaxed) +
|
||||
wait_count_db) ==
|
||||
(unsigned long)(2 * num_dbs + 1)) {
|
||||
TEST_SYNC_POINT(
|
||||
"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0");
|
||||
}
|
||||
});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"WriteThread::WriteStall::Wait", [&](void* arg) {
|
||||
WriteThread::Writer* w = reinterpret_cast<WriteThread::Writer*>(arg);
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
w_slowdown_set.insert(w);
|
||||
// Allow the flush continue if all writer threads are blocked.
|
||||
if (w_slowdown_set.size() +
|
||||
(unsigned long)(w_no_slowdown.load(std::memory_order_relaxed) +
|
||||
wait_count_db) ==
|
||||
(unsigned long)(2 * num_dbs + 1)) {
|
||||
TEST_SYNC_POINT(
|
||||
"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0");
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
bool s1 = true, s2 = true;
|
||||
std::function<void(DB*)> write_slow_down = [&](DB* db) {
|
||||
int a = thread_num.fetch_add(1);
|
||||
std::string key = "foo" + std::to_string(a);
|
||||
WriteOptions write_op;
|
||||
write_op.no_slowdown = false;
|
||||
Status tmp = db->Put(write_op, Slice(key), DummyString(1));
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
s1 = s1 && tmp.ok();
|
||||
};
|
||||
|
||||
std::function<void(DB*)> write_no_slow_down = [&](DB* db) {
|
||||
int a = thread_num.fetch_add(1);
|
||||
std::string key = "foo" + std::to_string(a);
|
||||
WriteOptions write_op;
|
||||
write_op.no_slowdown = true;
|
||||
Status tmp = db->Put(write_op, Slice(key), DummyString(1));
|
||||
{
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
s2 = s2 && !tmp.ok();
|
||||
w_no_slowdown.fetch_add(1);
|
||||
if (w_slowdown_set.size() +
|
||||
(unsigned long)(w_no_slowdown.load(std::memory_order_relaxed) +
|
||||
wait_count_db) ==
|
||||
(unsigned long)(2 * num_dbs + 1)) {
|
||||
TEST_SYNC_POINT(
|
||||
"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Flow:
|
||||
// first thread will write but will be blocked (as Flush will on hold,
|
||||
// buffer_size_ has exceeded, thus will create stall in effect).
|
||||
// |
|
||||
// |
|
||||
// multiple writer threads will be created to write across multiple columns
|
||||
// of db_ and different DBs with different values of
|
||||
// WriteOptions.no_slowdown. Some of them will be blocked and some of them
|
||||
// will return with Incomplete status.
|
||||
// |
|
||||
// |
|
||||
// Last writer thread will write and when its blocked/return it will signal
|
||||
// Flush to continue to clear the stall.
|
||||
threads.emplace_back(write_slow_down, db_);
|
||||
// Wait untill first thread writing to DB is blocked and then
|
||||
// create the multiple writers.
|
||||
{
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
while (wait_count_db != 1) {
|
||||
cv.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < num_dbs; i += 2) {
|
||||
// Write to multiple columns of db_.
|
||||
writer_threads.emplace_back(write_slow_down, db_);
|
||||
writer_threads.emplace_back(write_no_slow_down, db_);
|
||||
// Write to different DBs.
|
||||
threads.emplace_back(write_slow_down, dbs[i]);
|
||||
threads.emplace_back(write_no_slow_down, dbs[i + 1]);
|
||||
}
|
||||
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
for (auto& t : writer_threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
ASSERT_TRUE(s1);
|
||||
ASSERT_TRUE(s2);
|
||||
// Number of DBs blocked.
|
||||
ASSERT_EQ((num_dbs / 2) + 1, wait_count_db);
|
||||
// Number of writer threads writing to db_ blocked from getting added to the
|
||||
// queue.
|
||||
ASSERT_EQ(w_slowdown_set.size(), num_dbs / 2);
|
||||
// Number of threads with WriteOptions.no_slowdown = true.
|
||||
ASSERT_EQ(w_no_slowdown.load(std::memory_order_relaxed), num_dbs);
|
||||
|
||||
// Clean up DBs.
|
||||
for (int i = 0; i < num_dbs; i++) {
|
||||
ASSERT_OK(dbs[i]->Close());
|
||||
ASSERT_OK(DestroyDB(dbnames[i], options));
|
||||
delete dbs[i];
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBWriteBufferManagerTest, DBWriteBufferManagerTest,
|
||||
testing::Bool());
|
||||
|
||||
} // 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);
|
||||
RegisterCustomObjects(argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -1542,6 +1542,44 @@ TEST_F(ExternalSSTFileBasicTest, OverlappingFiles) {
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(0));
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileBasicTest, IngestFileAfterDBPut) {
|
||||
// Repro https://github.com/facebook/rocksdb/issues/6245.
|
||||
// Flush three files to L0. Ingest one more file to trigger L0->L1 compaction
|
||||
// via trivial move. The bug happened when L1 files were incorrectly sorted
|
||||
// resulting in an old value for "k" returned by `Get()`.
|
||||
Options options = CurrentOptions();
|
||||
|
||||
ASSERT_OK(Put("k", "a"));
|
||||
Flush();
|
||||
ASSERT_OK(Put("k", "a"));
|
||||
Flush();
|
||||
ASSERT_OK(Put("k", "a"));
|
||||
Flush();
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
|
||||
// Current file size should be 0 after sst_file_writer init and before open a
|
||||
// file.
|
||||
ASSERT_EQ(sst_file_writer.FileSize(), 0);
|
||||
|
||||
std::string file1 = sst_files_dir_ + "file1.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file1));
|
||||
ASSERT_OK(sst_file_writer.Put("k", "b"));
|
||||
|
||||
ExternalSstFileInfo file1_info;
|
||||
Status s = sst_file_writer.Finish(&file1_info);
|
||||
ASSERT_OK(s) << s.ToString();
|
||||
|
||||
// Current file size should be non-zero after success write.
|
||||
ASSERT_GT(sst_file_writer.FileSize(), 0);
|
||||
|
||||
IngestExternalFileOptions ifo;
|
||||
s = db_->IngestExternalFile({file1}, ifo);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
ASSERT_EQ(Get("k"), "b");
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(ExternalSSTFileBasicTest, ExternalSSTFileBasicTest,
|
||||
testing::Values(std::make_tuple(true, true),
|
||||
std::make_tuple(true, false),
|
||||
|
||||
@@ -367,9 +367,32 @@ Status ExternalSstFileIngestionJob::Run() {
|
||||
super_version, force_global_seqno, cfd_->ioptions()->compaction_style,
|
||||
last_seqno, &f, &assigned_seqno);
|
||||
}
|
||||
|
||||
// Modify the smallest/largest internal key to include the sequence number
|
||||
// that we just learned. Only overwrite sequence number zero. There could
|
||||
// be a nonzero sequence number already to indicate a range tombstone's
|
||||
// exclusive endpoint.
|
||||
ParsedInternalKey smallest_parsed, largest_parsed;
|
||||
if (status.ok()) {
|
||||
status = ParseInternalKey(*f.smallest_internal_key.rep(),
|
||||
&smallest_parsed, false /* log_err_key */);
|
||||
}
|
||||
if (status.ok()) {
|
||||
status = ParseInternalKey(*f.largest_internal_key.rep(), &largest_parsed,
|
||||
false /* log_err_key */);
|
||||
}
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
if (smallest_parsed.sequence == 0) {
|
||||
UpdateInternalKey(f.smallest_internal_key.rep(), assigned_seqno,
|
||||
smallest_parsed.type);
|
||||
}
|
||||
if (largest_parsed.sequence == 0) {
|
||||
UpdateInternalKey(f.largest_internal_key.rep(), assigned_seqno,
|
||||
largest_parsed.type);
|
||||
}
|
||||
|
||||
status = AssignGlobalSeqnoForIngestedFile(&f, assigned_seqno);
|
||||
TEST_SYNC_POINT_CALLBACK("ExternalSstFileIngestionJob::Run",
|
||||
&assigned_seqno);
|
||||
|
||||
@@ -75,6 +75,8 @@ const char* GetFlushReasonString (FlushReason flush_reason) {
|
||||
return "Manual Flush";
|
||||
case FlushReason::kErrorRecovery:
|
||||
return "Error Recovery";
|
||||
case FlushReason::kWalFull:
|
||||
return "WAL Full";
|
||||
default:
|
||||
return "Invalid";
|
||||
}
|
||||
|
||||
@@ -751,21 +751,24 @@ bool InternalStats::HandleBackgroundErrors(uint64_t* value, DBImpl* /*db*/,
|
||||
bool InternalStats::HandleCurSizeActiveMemTable(uint64_t* value, DBImpl* /*db*/,
|
||||
Version* /*version*/) {
|
||||
// Current size of the active memtable
|
||||
*value = cfd_->mem()->ApproximateMemoryUsage();
|
||||
// Using ApproximateMemoryUsageFast to avoid the need for synchronization
|
||||
*value = cfd_->mem()->ApproximateMemoryUsageFast();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InternalStats::HandleCurSizeAllMemTables(uint64_t* value, DBImpl* /*db*/,
|
||||
Version* /*version*/) {
|
||||
// Current size of the active memtable + immutable memtables
|
||||
*value = cfd_->mem()->ApproximateMemoryUsage() +
|
||||
// Using ApproximateMemoryUsageFast to avoid the need for synchronization
|
||||
*value = cfd_->mem()->ApproximateMemoryUsageFast() +
|
||||
cfd_->imm()->ApproximateUnflushedMemTablesMemoryUsage();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InternalStats::HandleSizeAllMemTables(uint64_t* value, DBImpl* /*db*/,
|
||||
Version* /*version*/) {
|
||||
*value = cfd_->mem()->ApproximateMemoryUsage() +
|
||||
// Using ApproximateMemoryUsageFast to avoid the need for synchronization
|
||||
*value = cfd_->mem()->ApproximateMemoryUsageFast() +
|
||||
cfd_->imm()->ApproximateMemoryUsage();
|
||||
return true;
|
||||
}
|
||||
@@ -958,7 +961,7 @@ bool InternalStats::HandleEstimateOldestKeyTime(uint64_t* value, DBImpl* /*db*/,
|
||||
|
||||
bool InternalStats::HandleBlockCacheStat(Cache** block_cache) {
|
||||
assert(block_cache != nullptr);
|
||||
auto* table_factory = cfd_->ioptions()->table_factory;
|
||||
auto* table_factory = cfd_->ioptions()->table_factory.get();
|
||||
assert(table_factory != nullptr);
|
||||
*block_cache =
|
||||
table_factory->GetOptions<Cache>(TableFactory::kBlockCacheOpts());
|
||||
|
||||
+2
-2
@@ -59,7 +59,7 @@ ImmutableMemTableOptions::ImmutableMemTableOptions(
|
||||
inplace_callback(ioptions.inplace_callback),
|
||||
max_successive_merges(mutable_cf_options.max_successive_merges),
|
||||
statistics(ioptions.statistics),
|
||||
merge_operator(ioptions.merge_operator),
|
||||
merge_operator(ioptions.merge_operator.get()),
|
||||
info_log(ioptions.info_log),
|
||||
allow_data_in_errors(ioptions.allow_data_in_errors) {}
|
||||
|
||||
@@ -106,7 +106,7 @@ MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
flush_state_(FLUSH_NOT_REQUESTED),
|
||||
clock_(ioptions.clock),
|
||||
insert_with_hint_prefix_extractor_(
|
||||
ioptions.memtable_insert_with_hint_prefix_extractor),
|
||||
ioptions.memtable_insert_with_hint_prefix_extractor.get()),
|
||||
oldest_key_time_(std::numeric_limits<uint64_t>::max()),
|
||||
atomic_flush_seqno_(kMaxSequenceNumber),
|
||||
approximate_memory_usage_(0) {
|
||||
|
||||
@@ -33,11 +33,13 @@ class OutputValidator {
|
||||
return GetHash() == other_validator.GetHash();
|
||||
}
|
||||
|
||||
private:
|
||||
// Not (yet) intended to be persisted, so subject to change
|
||||
// without notice between releases.
|
||||
uint64_t GetHash() const { return paranoid_hash_; }
|
||||
|
||||
void SetHash(uint64_t hash) { paranoid_hash_ = hash; }
|
||||
|
||||
private:
|
||||
const InternalKeyComparator& icmp_;
|
||||
std::string prev_key_;
|
||||
uint64_t paranoid_hash_ = 0;
|
||||
|
||||
+1
-1
@@ -130,7 +130,7 @@ Status TableCache::GetTableReader(
|
||||
new RandomAccessFileReader(
|
||||
std::move(file), fname, ioptions_.clock, io_tracer_,
|
||||
record_read_stats ? ioptions_.statistics : nullptr, SST_READ_MICROS,
|
||||
file_read_hist, ioptions_.rate_limiter, ioptions_.listeners));
|
||||
file_read_hist, ioptions_.rate_limiter.get(), ioptions_.listeners));
|
||||
s = ioptions_.table_factory->NewTableReader(
|
||||
ro,
|
||||
TableReaderOptions(ioptions_, prefix_extractor, file_options,
|
||||
|
||||
+27
-1
@@ -517,6 +517,28 @@ class VersionBuilder::Rep {
|
||||
return meta->oldest_blob_file_number;
|
||||
}
|
||||
|
||||
uint64_t GetMinOldestBlobFileNumber() const {
|
||||
uint64_t min_oldest_blob_file_num = std::numeric_limits<uint64_t>::max();
|
||||
for (int level = 0; level < num_levels_; ++level) {
|
||||
const auto& base_files = base_vstorage_->LevelFiles(level);
|
||||
for (const auto* fmeta : base_files) {
|
||||
assert(fmeta);
|
||||
min_oldest_blob_file_num =
|
||||
std::min(min_oldest_blob_file_num, fmeta->oldest_blob_file_number);
|
||||
}
|
||||
const auto& added_files = levels_[level].added_files;
|
||||
for (const auto& elem : added_files) {
|
||||
assert(elem.second);
|
||||
min_oldest_blob_file_num = std::min(
|
||||
min_oldest_blob_file_num, elem.second->oldest_blob_file_number);
|
||||
}
|
||||
}
|
||||
if (min_oldest_blob_file_num == std::numeric_limits<uint64_t>::max()) {
|
||||
min_oldest_blob_file_num = kInvalidBlobFileNumber;
|
||||
}
|
||||
return min_oldest_blob_file_num;
|
||||
}
|
||||
|
||||
Status ApplyFileDeletion(int level, uint64_t file_number) {
|
||||
assert(level != VersionStorageInfo::FileLocation::Invalid().GetLevel());
|
||||
|
||||
@@ -834,7 +856,7 @@ class VersionBuilder::Rep {
|
||||
}
|
||||
}
|
||||
|
||||
// Save the current state in *v.
|
||||
// Save the current state in *vstorage.
|
||||
Status SaveTo(VersionStorageInfo* vstorage) {
|
||||
Status s = CheckConsistency(base_vstorage_);
|
||||
if (!s.ok()) {
|
||||
@@ -1052,6 +1074,10 @@ Status VersionBuilder::LoadTableHandlers(
|
||||
is_initial_load, prefix_extractor, max_file_size_for_l0_meta_pin);
|
||||
}
|
||||
|
||||
uint64_t VersionBuilder::GetMinOldestBlobFileNumber() const {
|
||||
return rep_->GetMinOldestBlobFileNumber();
|
||||
}
|
||||
|
||||
BaseReferencedVersionBuilder::BaseReferencedVersionBuilder(
|
||||
ColumnFamilyData* cfd)
|
||||
: version_builder_(new VersionBuilder(
|
||||
|
||||
@@ -44,6 +44,7 @@ class VersionBuilder {
|
||||
bool is_initial_load,
|
||||
const SliceTransform* prefix_extractor,
|
||||
size_t max_file_size_for_l0_meta_pin);
|
||||
uint64_t GetMinOldestBlobFileNumber() const;
|
||||
|
||||
private:
|
||||
class Rep;
|
||||
|
||||
+129
-17
@@ -11,6 +11,8 @@
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
#include "db/blob/blob_file_cache.h"
|
||||
#include "db/blob/blob_file_reader.h"
|
||||
#include "monitoring/persistent_stats_history.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -129,14 +131,14 @@ Status FileChecksumRetriever::ApplyVersionEdit(VersionEdit& edit,
|
||||
VersionEditHandler::VersionEditHandler(
|
||||
bool read_only, std::vector<ColumnFamilyDescriptor> column_families,
|
||||
VersionSet* version_set, bool track_missing_files,
|
||||
bool no_error_if_table_files_missing,
|
||||
const std::shared_ptr<IOTracer>& io_tracer, bool skip_load_table_files)
|
||||
bool no_error_if_files_missing, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
bool skip_load_table_files)
|
||||
: VersionEditHandlerBase(),
|
||||
read_only_(read_only),
|
||||
column_families_(std::move(column_families)),
|
||||
version_set_(version_set),
|
||||
track_missing_files_(track_missing_files),
|
||||
no_error_if_table_files_missing_(no_error_if_table_files_missing),
|
||||
no_error_if_files_missing_(no_error_if_files_missing),
|
||||
io_tracer_(io_tracer),
|
||||
skip_load_table_files_(skip_load_table_files),
|
||||
initialized_(false) {
|
||||
@@ -301,6 +303,14 @@ bool VersionEditHandler::HasMissingFiles() const {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!ret) {
|
||||
for (const auto& elem : cf_to_missing_blob_files_high_) {
|
||||
if (elem.second != kInvalidBlobFileNumber) {
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -437,6 +447,8 @@ ColumnFamilyData* VersionEditHandler::CreateCfAndInit(
|
||||
if (track_missing_files_) {
|
||||
cf_to_missing_files_.emplace(edit.column_family_,
|
||||
std::unordered_set<uint64_t>());
|
||||
cf_to_missing_blob_files_high_.emplace(edit.column_family_,
|
||||
kInvalidBlobFileNumber);
|
||||
}
|
||||
return cfd;
|
||||
}
|
||||
@@ -450,6 +462,12 @@ ColumnFamilyData* VersionEditHandler::DestroyCfAndCleanup(
|
||||
auto missing_files_iter = cf_to_missing_files_.find(edit.column_family_);
|
||||
assert(missing_files_iter != cf_to_missing_files_.end());
|
||||
cf_to_missing_files_.erase(missing_files_iter);
|
||||
|
||||
auto missing_blob_files_high_iter =
|
||||
cf_to_missing_blob_files_high_.find(edit.column_family_);
|
||||
assert(missing_blob_files_high_iter !=
|
||||
cf_to_missing_blob_files_high_.end());
|
||||
cf_to_missing_blob_files_high_.erase(missing_blob_files_high_iter);
|
||||
}
|
||||
ColumnFamilyData* ret =
|
||||
version_set_->GetColumnFamilySet()->GetColumnFamily(edit.column_family_);
|
||||
@@ -505,8 +523,7 @@ Status VersionEditHandler::LoadTables(ColumnFamilyData* cfd,
|
||||
prefetch_index_and_filter_in_cache, is_initial_load,
|
||||
cfd->GetLatestMutableCFOptions()->prefix_extractor.get(),
|
||||
MaxFileSizeForL0MetaPin(*cfd->GetLatestMutableCFOptions()));
|
||||
if ((s.IsPathNotFound() || s.IsCorruption()) &&
|
||||
no_error_if_table_files_missing_) {
|
||||
if ((s.IsPathNotFound() || s.IsCorruption()) && no_error_if_files_missing_) {
|
||||
s = Status::OK();
|
||||
}
|
||||
if (!s.ok() && !version_set_->db_options_->paranoid_checks) {
|
||||
@@ -536,9 +553,13 @@ Status VersionEditHandler::ExtractInfoFromVersionEdit(ColumnFamilyData* cfd,
|
||||
}
|
||||
if (edit.has_comparator_ &&
|
||||
edit.comparator_ != cfd->user_comparator()->Name()) {
|
||||
s = Status::InvalidArgument(
|
||||
cfd->user_comparator()->Name(),
|
||||
"does not match existing comparator " + edit.comparator_);
|
||||
if (!cf_to_cmp_names_) {
|
||||
s = Status::InvalidArgument(
|
||||
cfd->user_comparator()->Name(),
|
||||
"does not match existing comparator " + edit.comparator_);
|
||||
} else {
|
||||
cf_to_cmp_names_->emplace(cfd->GetID(), edit.comparator_);
|
||||
}
|
||||
}
|
||||
if (edit.HasFullHistoryTsLow()) {
|
||||
const std::string& new_ts = edit.GetFullHistoryTsLow();
|
||||
@@ -576,7 +597,7 @@ VersionEditHandlerPointInTime::VersionEditHandlerPointInTime(
|
||||
VersionSet* version_set, const std::shared_ptr<IOTracer>& io_tracer)
|
||||
: VersionEditHandler(read_only, column_families, version_set,
|
||||
/*track_missing_files=*/true,
|
||||
/*no_error_if_table_files_missing=*/true, io_tracer) {}
|
||||
/*no_error_if_files_missing=*/true, io_tracer) {}
|
||||
|
||||
VersionEditHandlerPointInTime::~VersionEditHandlerPointInTime() {
|
||||
for (const auto& elem : versions_) {
|
||||
@@ -626,7 +647,29 @@ Status VersionEditHandlerPointInTime::MaybeCreateVersion(
|
||||
auto missing_files_iter = cf_to_missing_files_.find(cfd->GetID());
|
||||
assert(missing_files_iter != cf_to_missing_files_.end());
|
||||
std::unordered_set<uint64_t>& missing_files = missing_files_iter->second;
|
||||
const bool prev_has_missing_files = !missing_files.empty();
|
||||
|
||||
auto missing_blob_files_high_iter =
|
||||
cf_to_missing_blob_files_high_.find(cfd->GetID());
|
||||
assert(missing_blob_files_high_iter != cf_to_missing_blob_files_high_.end());
|
||||
const uint64_t prev_missing_blob_file_high =
|
||||
missing_blob_files_high_iter->second;
|
||||
|
||||
VersionBuilder* builder = nullptr;
|
||||
|
||||
if (prev_missing_blob_file_high != kInvalidBlobFileNumber) {
|
||||
auto builder_iter = builders_.find(cfd->GetID());
|
||||
assert(builder_iter != builders_.end());
|
||||
builder = builder_iter->second->version_builder();
|
||||
assert(builder != nullptr);
|
||||
}
|
||||
|
||||
// At this point, we have not yet applied the new version edits read from the
|
||||
// MANIFEST. We check whether we have any missing table and blob files.
|
||||
const bool prev_has_missing_files =
|
||||
!missing_files.empty() ||
|
||||
(prev_missing_blob_file_high != kInvalidBlobFileNumber &&
|
||||
prev_missing_blob_file_high >= builder->GetMinOldestBlobFileNumber());
|
||||
|
||||
for (const auto& file : edit.GetDeletedFiles()) {
|
||||
uint64_t file_num = file.second;
|
||||
auto fiter = missing_files.find(file_num);
|
||||
@@ -634,6 +677,8 @@ Status VersionEditHandlerPointInTime::MaybeCreateVersion(
|
||||
missing_files.erase(fiter);
|
||||
}
|
||||
}
|
||||
|
||||
assert(!cfd->ioptions()->cf_paths.empty());
|
||||
Status s;
|
||||
for (const auto& elem : edit.GetNewFiles()) {
|
||||
const FileMetaData& meta = elem.second;
|
||||
@@ -649,17 +694,60 @@ Status VersionEditHandlerPointInTime::MaybeCreateVersion(
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t missing_blob_file_num = prev_missing_blob_file_high;
|
||||
for (const auto& elem : edit.GetBlobFileAdditions()) {
|
||||
uint64_t file_num = elem.GetBlobFileNumber();
|
||||
s = VerifyBlobFile(cfd, file_num, elem);
|
||||
if (s.IsPathNotFound() || s.IsNotFound() || s.IsCorruption()) {
|
||||
missing_blob_file_num = std::max(missing_blob_file_num, file_num);
|
||||
s = Status::OK();
|
||||
} else if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool has_missing_blob_files = false;
|
||||
if (missing_blob_file_num != kInvalidBlobFileNumber &&
|
||||
missing_blob_file_num >= prev_missing_blob_file_high) {
|
||||
missing_blob_files_high_iter->second = missing_blob_file_num;
|
||||
has_missing_blob_files = true;
|
||||
} else if (missing_blob_file_num < prev_missing_blob_file_high) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
// We still have not applied the new version edit, but have tried to add new
|
||||
// table and blob files after verifying their presence and consistency.
|
||||
// Therefore, we know whether we will see new missing table and blob files
|
||||
// later after actually applying the version edit. We perform the check here
|
||||
// and record the result.
|
||||
const bool has_missing_files =
|
||||
!missing_files.empty() || has_missing_blob_files;
|
||||
|
||||
bool missing_info = !version_edit_params_.has_log_number_ ||
|
||||
!version_edit_params_.has_next_file_number_ ||
|
||||
!version_edit_params_.has_last_sequence_;
|
||||
|
||||
// Create version before apply edit
|
||||
// Create version before apply edit. The version will represent the state
|
||||
// before applying the version edit.
|
||||
// A new version will created if:
|
||||
// 1) no error has occurred so far, and
|
||||
// 2) log_number_, next_file_number_ and last_sequence_ are known, and
|
||||
// 3) any of the following:
|
||||
// a) no missing file before, but will have missing file(s) after applying
|
||||
// this version edit.
|
||||
// b) no missing file after applying the version edit, and the caller
|
||||
// explicitly request that a new version be created.
|
||||
if (s.ok() && !missing_info &&
|
||||
((!missing_files.empty() && !prev_has_missing_files) ||
|
||||
(missing_files.empty() && force_create_version))) {
|
||||
auto builder_iter = builders_.find(cfd->GetID());
|
||||
assert(builder_iter != builders_.end());
|
||||
auto* builder = builder_iter->second->version_builder();
|
||||
((has_missing_files && !prev_has_missing_files) ||
|
||||
(!has_missing_files && force_create_version))) {
|
||||
if (!builder) {
|
||||
auto builder_iter = builders_.find(cfd->GetID());
|
||||
assert(builder_iter != builders_.end());
|
||||
builder = builder_iter->second->version_builder();
|
||||
assert(builder);
|
||||
}
|
||||
|
||||
auto* version = new Version(cfd, version_set_, version_set_->file_options_,
|
||||
*cfd->GetLatestMutableCFOptions(), io_tracer_,
|
||||
version_set_->current_version_number_++);
|
||||
@@ -687,6 +775,22 @@ Status VersionEditHandlerPointInTime::VerifyFile(const std::string& fpath,
|
||||
return version_set_->VerifyFileMetadata(fpath, fmeta);
|
||||
}
|
||||
|
||||
Status VersionEditHandlerPointInTime::VerifyBlobFile(
|
||||
ColumnFamilyData* cfd, uint64_t blob_file_num,
|
||||
const BlobFileAddition& blob_addition) {
|
||||
BlobFileCache* blob_file_cache = cfd->blob_file_cache();
|
||||
assert(blob_file_cache);
|
||||
CacheHandleGuard<BlobFileReader> blob_file_reader;
|
||||
Status s =
|
||||
blob_file_cache->GetBlobFileReader(blob_file_num, &blob_file_reader);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
// TODO: verify checksum
|
||||
(void)blob_addition;
|
||||
return s;
|
||||
}
|
||||
|
||||
Status ManifestTailer::Initialize() {
|
||||
if (Mode::kRecovery == mode_) {
|
||||
return VersionEditHandler::Initialize();
|
||||
@@ -789,13 +893,21 @@ void DumpManifestHandler::CheckIterationResult(const log::Reader& reader,
|
||||
fprintf(stdout, "%s\n", s->ToString().c_str());
|
||||
return;
|
||||
}
|
||||
assert(cf_to_cmp_names_);
|
||||
for (auto* cfd : *(version_set_->column_family_set_)) {
|
||||
fprintf(stdout,
|
||||
"--------------- Column family \"%s\" (ID %" PRIu32
|
||||
") --------------\n",
|
||||
cfd->GetName().c_str(), cfd->GetID());
|
||||
fprintf(stdout, "log number: %" PRIu64 "\n", cfd->GetLogNumber());
|
||||
fprintf(stdout, "comparator: %s\n", cfd->user_comparator()->Name());
|
||||
auto it = cf_to_cmp_names_->find(cfd->GetID());
|
||||
if (it != cf_to_cmp_names_->end()) {
|
||||
fprintf(stdout,
|
||||
"comparator: <%s>, but the comparator object is not available.\n",
|
||||
it->second.c_str());
|
||||
} else {
|
||||
fprintf(stdout, "comparator: %s\n", cfd->user_comparator()->Name());
|
||||
}
|
||||
assert(cfd->current());
|
||||
fprintf(stdout, "%s \n", cfd->current()->DebugString(hex_).c_str());
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ using VersionBuilderUPtr = std::unique_ptr<BaseReferencedVersionBuilder>;
|
||||
// 1. Create an object of VersionEditHandler or its subclasses.
|
||||
// VersionEditHandler handler(read_only, column_families, version_set,
|
||||
// track_missing_files,
|
||||
// no_error_if_table_files_missing);
|
||||
// no_error_if_files_missing);
|
||||
// 2. Status s = handler.Iterate(reader, &db_id);
|
||||
// 3. Check s and handle possible errors.
|
||||
//
|
||||
@@ -109,10 +109,10 @@ class VersionEditHandler : public VersionEditHandlerBase {
|
||||
bool read_only,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
VersionSet* version_set, bool track_missing_files,
|
||||
bool no_error_if_table_files_missing,
|
||||
bool no_error_if_files_missing,
|
||||
const std::shared_ptr<IOTracer>& io_tracer)
|
||||
: VersionEditHandler(read_only, column_families, version_set,
|
||||
track_missing_files, no_error_if_table_files_missing,
|
||||
track_missing_files, no_error_if_files_missing,
|
||||
io_tracer, /*skip_load_table_files=*/false) {}
|
||||
|
||||
~VersionEditHandler() override {}
|
||||
@@ -133,7 +133,7 @@ class VersionEditHandler : public VersionEditHandlerBase {
|
||||
explicit VersionEditHandler(
|
||||
bool read_only, std::vector<ColumnFamilyDescriptor> column_families,
|
||||
VersionSet* version_set, bool track_missing_files,
|
||||
bool no_error_if_table_files_missing,
|
||||
bool no_error_if_files_missing,
|
||||
const std::shared_ptr<IOTracer>& io_tracer, bool skip_load_table_files);
|
||||
|
||||
Status ApplyVersionEdit(VersionEdit& edit, ColumnFamilyData** cfd) override;
|
||||
@@ -183,10 +183,12 @@ class VersionEditHandler : public VersionEditHandlerBase {
|
||||
const bool track_missing_files_;
|
||||
std::unordered_map<uint32_t, std::unordered_set<uint64_t>>
|
||||
cf_to_missing_files_;
|
||||
bool no_error_if_table_files_missing_;
|
||||
std::unordered_map<uint32_t, uint64_t> cf_to_missing_blob_files_high_;
|
||||
bool no_error_if_files_missing_;
|
||||
std::shared_ptr<IOTracer> io_tracer_;
|
||||
bool skip_load_table_files_;
|
||||
bool initialized_;
|
||||
std::unique_ptr<std::unordered_map<uint32_t, std::string>> cf_to_cmp_names_;
|
||||
|
||||
private:
|
||||
Status ExtractInfoFromVersionEdit(ColumnFamilyData* cfd,
|
||||
@@ -213,6 +215,8 @@ class VersionEditHandlerPointInTime : public VersionEditHandler {
|
||||
bool force_create_version) override;
|
||||
virtual Status VerifyFile(const std::string& fpath,
|
||||
const FileMetaData& fmeta);
|
||||
virtual Status VerifyBlobFile(ColumnFamilyData* cfd, uint64_t blob_file_num,
|
||||
const BlobFileAddition& blob_addition);
|
||||
|
||||
std::unordered_map<uint32_t, Version*> versions_;
|
||||
};
|
||||
@@ -267,12 +271,14 @@ class DumpManifestHandler : public VersionEditHandler {
|
||||
: VersionEditHandler(
|
||||
/*read_only=*/true, column_families, version_set,
|
||||
/*track_missing_files=*/false,
|
||||
/*no_error_if_table_files_missing=*/false, io_tracer,
|
||||
/*no_error_if_files_missing=*/false, io_tracer,
|
||||
/*skip_load_table_files=*/true),
|
||||
verbose_(verbose),
|
||||
hex_(hex),
|
||||
json_(json),
|
||||
count_(0) {}
|
||||
count_(0) {
|
||||
cf_to_cmp_names_.reset(new std::unordered_map<uint32_t, std::string>());
|
||||
}
|
||||
|
||||
~DumpManifestHandler() override {}
|
||||
|
||||
|
||||
+70
-67
@@ -1768,8 +1768,8 @@ Version::Version(ColumnFamilyData* column_family_data, VersionSet* vset,
|
||||
: cfd_->ioptions()->statistics),
|
||||
table_cache_((cfd_ == nullptr) ? nullptr : cfd_->table_cache()),
|
||||
blob_file_cache_(cfd_ ? cfd_->blob_file_cache() : nullptr),
|
||||
merge_operator_((cfd_ == nullptr) ? nullptr
|
||||
: cfd_->ioptions()->merge_operator),
|
||||
merge_operator_(
|
||||
(cfd_ == nullptr) ? nullptr : cfd_->ioptions()->merge_operator.get()),
|
||||
storage_info_(
|
||||
(cfd_ == nullptr) ? nullptr : &cfd_->internal_comparator(),
|
||||
(cfd_ == nullptr) ? nullptr : cfd_->user_comparator(),
|
||||
@@ -4083,6 +4083,7 @@ Status VersionSet::ProcessManifestWrites(
|
||||
uint64_t new_manifest_file_size = 0;
|
||||
Status s;
|
||||
IOStatus io_s;
|
||||
IOStatus manifest_io_status;
|
||||
{
|
||||
FileOptions opt_file_opts = fs_->OptimizeForManifestWrite(file_options_);
|
||||
mu->Unlock();
|
||||
@@ -4134,6 +4135,7 @@ Status VersionSet::ProcessManifestWrites(
|
||||
s = WriteCurrentStateToManifest(curr_state, wal_additions,
|
||||
descriptor_log_.get(), io_s);
|
||||
} else {
|
||||
manifest_io_status = io_s;
|
||||
s = io_s;
|
||||
}
|
||||
}
|
||||
@@ -4171,11 +4173,13 @@ Status VersionSet::ProcessManifestWrites(
|
||||
io_s = descriptor_log_->AddRecord(record);
|
||||
if (!io_s.ok()) {
|
||||
s = io_s;
|
||||
manifest_io_status = io_s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
io_s = SyncManifest(db_options_, descriptor_log_->file());
|
||||
manifest_io_status = io_s;
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"VersionSet::ProcessManifestWrites:AfterSyncManifest", &io_s);
|
||||
}
|
||||
@@ -4188,6 +4192,9 @@ Status VersionSet::ProcessManifestWrites(
|
||||
|
||||
// If we just created a new descriptor file, install it by writing a
|
||||
// new CURRENT file that points to it.
|
||||
if (s.ok()) {
|
||||
assert(manifest_io_status.ok());
|
||||
}
|
||||
if (s.ok() && new_descriptor_log) {
|
||||
io_s = SetCurrentFile(fs_.get(), dbname_, pending_manifest_file_number_,
|
||||
db_directory);
|
||||
@@ -4303,11 +4310,41 @@ Status VersionSet::ProcessManifestWrites(
|
||||
for (auto v : versions) {
|
||||
delete v;
|
||||
}
|
||||
if (manifest_io_status.ok()) {
|
||||
manifest_file_number_ = pending_manifest_file_number_;
|
||||
manifest_file_size_ = new_manifest_file_size;
|
||||
}
|
||||
// If manifest append failed for whatever reason, the file could be
|
||||
// corrupted. So we need to force the next version update to start a
|
||||
// new manifest file.
|
||||
descriptor_log_.reset();
|
||||
if (new_descriptor_log) {
|
||||
// If manifest operations failed, then we know the CURRENT file still
|
||||
// points to the original MANIFEST. Therefore, we can safely delete the
|
||||
// new MANIFEST.
|
||||
// If manifest operations succeeded, and we are here, then it is possible
|
||||
// that renaming tmp file to CURRENT failed.
|
||||
//
|
||||
// On local POSIX-compliant FS, the CURRENT must point to the original
|
||||
// MANIFEST. We can delete the new MANIFEST for simplicity, but we can also
|
||||
// keep it. Future recovery will ignore this MANIFEST. It's also ok for the
|
||||
// process not to crash and continue using the db. Any future LogAndApply()
|
||||
// call will switch to a new MANIFEST and update CURRENT, still ignoring
|
||||
// this one.
|
||||
//
|
||||
// On non-local FS, it is
|
||||
// possible that the rename operation succeeded on the server (remote)
|
||||
// side, but the client somehow returns a non-ok status to RocksDB. Note
|
||||
// that this does not violate atomicity. Should we delete the new MANIFEST
|
||||
// successfully, a subsequent recovery attempt will likely see the CURRENT
|
||||
// pointing to the new MANIFEST, thus fail. We will not be able to open the
|
||||
// DB again. Therefore, if manifest operations succeed, we should keep the
|
||||
// the new MANIFEST. If the process proceeds, any future LogAndApply() call
|
||||
// will switch to a new MANIFEST and update CURRENT. If user tries to
|
||||
// re-open the DB,
|
||||
// a) CURRENT points to the new MANIFEST, and the new MANIFEST is present.
|
||||
// b) CURRENT points to the original MANIFEST, and the original MANIFEST
|
||||
// also exists.
|
||||
if (new_descriptor_log && !manifest_io_status.ok()) {
|
||||
ROCKS_LOG_INFO(db_options_->info_log,
|
||||
"Deleting manifest %" PRIu64 " current manifest %" PRIu64
|
||||
"\n",
|
||||
@@ -4490,60 +4527,6 @@ Status VersionSet::LogAndApplyHelper(ColumnFamilyData* cfd,
|
||||
return builder ? builder->Apply(edit) : Status::OK();
|
||||
}
|
||||
|
||||
Status VersionSet::ExtractInfoFromVersionEdit(
|
||||
ColumnFamilyData* cfd, const VersionEdit& from_edit,
|
||||
VersionEditParams* version_edit_params) {
|
||||
if (cfd != nullptr) {
|
||||
if (from_edit.has_db_id_) {
|
||||
version_edit_params->SetDBId(from_edit.db_id_);
|
||||
}
|
||||
if (from_edit.has_log_number_) {
|
||||
if (cfd->GetLogNumber() > from_edit.log_number_) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_->info_log,
|
||||
"MANIFEST corruption detected, but ignored - Log numbers in "
|
||||
"records NOT monotonically increasing");
|
||||
} else {
|
||||
cfd->SetLogNumber(from_edit.log_number_);
|
||||
version_edit_params->SetLogNumber(from_edit.log_number_);
|
||||
}
|
||||
}
|
||||
if (from_edit.has_comparator_ &&
|
||||
from_edit.comparator_ != cfd->user_comparator()->Name()) {
|
||||
return Status::InvalidArgument(
|
||||
cfd->user_comparator()->Name(),
|
||||
"does not match existing comparator " + from_edit.comparator_);
|
||||
}
|
||||
if (from_edit.HasFullHistoryTsLow()) {
|
||||
const std::string& new_ts = from_edit.GetFullHistoryTsLow();
|
||||
cfd->SetFullHistoryTsLow(new_ts);
|
||||
}
|
||||
}
|
||||
|
||||
if (from_edit.has_prev_log_number_) {
|
||||
version_edit_params->SetPrevLogNumber(from_edit.prev_log_number_);
|
||||
}
|
||||
|
||||
if (from_edit.has_next_file_number_) {
|
||||
version_edit_params->SetNextFile(from_edit.next_file_number_);
|
||||
}
|
||||
|
||||
if (from_edit.has_max_column_family_) {
|
||||
version_edit_params->SetMaxColumnFamily(from_edit.max_column_family_);
|
||||
}
|
||||
|
||||
if (from_edit.has_min_log_number_to_keep_) {
|
||||
version_edit_params->min_log_number_to_keep_ =
|
||||
std::max(version_edit_params->min_log_number_to_keep_,
|
||||
from_edit.min_log_number_to_keep_);
|
||||
}
|
||||
|
||||
if (from_edit.has_last_sequence_) {
|
||||
version_edit_params->SetLastSequence(from_edit.last_sequence_);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status VersionSet::GetCurrentManifestPath(const std::string& dbname,
|
||||
FileSystem* fs,
|
||||
std::string* manifest_path,
|
||||
@@ -4610,10 +4593,10 @@ Status VersionSet::Recover(
|
||||
reporter.status = &log_read_status;
|
||||
log::Reader reader(nullptr, std::move(manifest_file_reader), &reporter,
|
||||
true /* checksum */, 0 /* log_number */);
|
||||
VersionEditHandler handler(
|
||||
read_only, column_families, const_cast<VersionSet*>(this),
|
||||
/*track_missing_files=*/false,
|
||||
/*no_error_if_table_files_missing=*/false, io_tracer_);
|
||||
VersionEditHandler handler(read_only, column_families,
|
||||
const_cast<VersionSet*>(this),
|
||||
/*track_missing_files=*/false,
|
||||
/*no_error_if_files_missing=*/false, io_tracer_);
|
||||
handler.Iterate(reader, &log_read_status);
|
||||
s = handler.status();
|
||||
if (s.ok()) {
|
||||
@@ -4937,7 +4920,7 @@ Status VersionSet::ReduceNumberOfLevels(const std::string& dbname,
|
||||
}
|
||||
|
||||
// Get the checksum information including the checksum and checksum function
|
||||
// name of all SST files in VersionSet. Store the information in
|
||||
// name of all SST and blob files in VersionSet. Store the information in
|
||||
// FileChecksumList which contains a map from file number to its checksum info.
|
||||
// If DB is not running, make sure call VersionSet::Recover() to load the file
|
||||
// metadata from Manifest to VersionSet before calling this function.
|
||||
@@ -4954,6 +4937,7 @@ Status VersionSet::GetLiveFilesChecksumInfo(FileChecksumList* checksum_list) {
|
||||
if (cfd->IsDropped() || !cfd->initialized()) {
|
||||
continue;
|
||||
}
|
||||
/* SST files */
|
||||
for (int level = 0; level < cfd->NumberLevels(); level++) {
|
||||
for (const auto& file :
|
||||
cfd->current()->storage_info()->LevelFiles(level)) {
|
||||
@@ -4961,17 +4945,36 @@ Status VersionSet::GetLiveFilesChecksumInfo(FileChecksumList* checksum_list) {
|
||||
file->file_checksum,
|
||||
file->file_checksum_func_name);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Blob files */
|
||||
const auto& blob_files = cfd->current()->storage_info()->GetBlobFiles();
|
||||
for (const auto& pair : blob_files) {
|
||||
const uint64_t blob_file_number = pair.first;
|
||||
const auto& meta = pair.second;
|
||||
|
||||
assert(meta);
|
||||
assert(blob_file_number == meta->GetBlobFileNumber());
|
||||
|
||||
std::string checksum_value = meta->GetChecksumValue();
|
||||
std::string checksum_method = meta->GetChecksumMethod();
|
||||
assert(checksum_value.empty() == checksum_method.empty());
|
||||
if (meta->GetChecksumMethod().empty()) {
|
||||
checksum_value = kUnknownFileChecksum;
|
||||
checksum_method = kUnknownFileChecksumFuncName;
|
||||
}
|
||||
|
||||
s = checksum_list->InsertOneFileChecksum(blob_file_number, checksum_value,
|
||||
checksum_method);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
return s;
|
||||
}
|
||||
}
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
@@ -1331,10 +1331,6 @@ class VersionSet {
|
||||
ColumnFamilyData* CreateColumnFamily(const ColumnFamilyOptions& cf_options,
|
||||
const VersionEdit* edit);
|
||||
|
||||
Status ExtractInfoFromVersionEdit(ColumnFamilyData* cfd,
|
||||
const VersionEdit& from_edit,
|
||||
VersionEditParams* version_edit_params);
|
||||
|
||||
Status VerifyFileMetadata(const std::string& fpath,
|
||||
const FileMetaData& meta) const;
|
||||
|
||||
|
||||
+6
-6
@@ -134,8 +134,8 @@ Status WalManager::GetUpdatesSince(
|
||||
// b. get sorted non-empty archived logs
|
||||
// c. delete what should be deleted
|
||||
void WalManager::PurgeObsoleteWALFiles() {
|
||||
bool const ttl_enabled = db_options_.wal_ttl_seconds > 0;
|
||||
bool const size_limit_enabled = db_options_.wal_size_limit_mb > 0;
|
||||
bool const ttl_enabled = db_options_.WAL_ttl_seconds > 0;
|
||||
bool const size_limit_enabled = db_options_.WAL_size_limit_MB > 0;
|
||||
if (!ttl_enabled && !size_limit_enabled) {
|
||||
return;
|
||||
}
|
||||
@@ -150,7 +150,7 @@ void WalManager::PurgeObsoleteWALFiles() {
|
||||
}
|
||||
uint64_t const now_seconds = static_cast<uint64_t>(current_time);
|
||||
uint64_t const time_to_check = (ttl_enabled && !size_limit_enabled)
|
||||
? db_options_.wal_ttl_seconds / 2
|
||||
? db_options_.WAL_ttl_seconds / 2
|
||||
: kDefaultIntervalToDeleteObsoleteWAL;
|
||||
|
||||
if (purge_wal_files_last_run_ + time_to_check > now_seconds) {
|
||||
@@ -185,7 +185,7 @@ void WalManager::PurgeObsoleteWALFiles() {
|
||||
s.ToString().c_str());
|
||||
continue;
|
||||
}
|
||||
if (now_seconds - file_m_time > db_options_.wal_ttl_seconds) {
|
||||
if (now_seconds - file_m_time > db_options_.WAL_ttl_seconds) {
|
||||
s = DeleteDBFile(&db_options_, file_path, archival_dir, false,
|
||||
/*force_fg=*/!wal_in_db_path_);
|
||||
if (!s.ok()) {
|
||||
@@ -234,8 +234,8 @@ void WalManager::PurgeObsoleteWALFiles() {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t const files_keep_num =
|
||||
static_cast<size_t>(db_options_.wal_size_limit_mb * 1024 * 1024 / log_file_size);
|
||||
size_t const files_keep_num = static_cast<size_t>(
|
||||
db_options_.WAL_size_limit_MB * 1024 * 1024 / log_file_size);
|
||||
if (log_files_num <= files_keep_num) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -217,8 +217,8 @@ int CountRecords(TransactionLogIterator* iter) {
|
||||
} // namespace
|
||||
|
||||
TEST_F(WalManagerTest, WALArchivalSizeLimit) {
|
||||
db_options_.wal_ttl_seconds = 0;
|
||||
db_options_.wal_size_limit_mb = 1000;
|
||||
db_options_.WAL_ttl_seconds = 0;
|
||||
db_options_.WAL_size_limit_MB = 1000;
|
||||
Init();
|
||||
|
||||
// TEST : Create WalManager with huge size limit and no ttl.
|
||||
@@ -226,7 +226,7 @@ TEST_F(WalManagerTest, WALArchivalSizeLimit) {
|
||||
// Count the archived log files that survived.
|
||||
// Assert that all of them did.
|
||||
// Change size limit. Re-open WalManager.
|
||||
// Assert that archive is not greater than wal_size_limit_mb after
|
||||
// Assert that archive is not greater than WAL_size_limit_MB after
|
||||
// PurgeObsoleteWALFiles()
|
||||
// Set ttl and time_to_check_ to small values. Re-open db.
|
||||
// Assert that there are no archived logs left.
|
||||
@@ -238,14 +238,14 @@ TEST_F(WalManagerTest, WALArchivalSizeLimit) {
|
||||
ListSpecificFiles(env_.get(), archive_dir, kWalFile);
|
||||
ASSERT_EQ(log_files.size(), 20U);
|
||||
|
||||
db_options_.wal_size_limit_mb = 8;
|
||||
db_options_.WAL_size_limit_MB = 8;
|
||||
Reopen();
|
||||
wal_manager_->PurgeObsoleteWALFiles();
|
||||
|
||||
uint64_t archive_size = GetLogDirSize(archive_dir, env_.get());
|
||||
ASSERT_TRUE(archive_size <= db_options_.wal_size_limit_mb * 1024 * 1024);
|
||||
ASSERT_TRUE(archive_size <= db_options_.WAL_size_limit_MB * 1024 * 1024);
|
||||
|
||||
db_options_.wal_ttl_seconds = 1;
|
||||
db_options_.WAL_ttl_seconds = 1;
|
||||
env_->FakeSleepForMicroseconds(2 * 1000 * 1000);
|
||||
Reopen();
|
||||
wal_manager_->PurgeObsoleteWALFiles();
|
||||
@@ -255,7 +255,7 @@ TEST_F(WalManagerTest, WALArchivalSizeLimit) {
|
||||
}
|
||||
|
||||
TEST_F(WalManagerTest, WALArchivalTtl) {
|
||||
db_options_.wal_ttl_seconds = 1000;
|
||||
db_options_.WAL_ttl_seconds = 1000;
|
||||
Init();
|
||||
|
||||
// TEST : Create WalManager with a ttl and no size limit.
|
||||
@@ -271,7 +271,7 @@ TEST_F(WalManagerTest, WALArchivalTtl) {
|
||||
ListSpecificFiles(env_.get(), archive_dir, kWalFile);
|
||||
ASSERT_GT(log_files.size(), 0U);
|
||||
|
||||
db_options_.wal_ttl_seconds = 1;
|
||||
db_options_.WAL_ttl_seconds = 1;
|
||||
env_->FakeSleepForMicroseconds(3 * 1000 * 1000);
|
||||
Reopen();
|
||||
wal_manager_->PurgeObsoleteWALFiles();
|
||||
|
||||
@@ -241,6 +241,7 @@ bool WriteThread::LinkOne(Writer* w, std::atomic<Writer*>* newest_writer) {
|
||||
MutexLock lock(&stall_mu_);
|
||||
writers = newest_writer->load(std::memory_order_relaxed);
|
||||
if (writers == &write_stall_dummy_) {
|
||||
TEST_SYNC_POINT_CALLBACK("WriteThread::WriteStall::Wait", w);
|
||||
stall_cv_.Wait();
|
||||
// Load newest_writers_ again since it may have changed
|
||||
writers = newest_writer->load(std::memory_order_relaxed);
|
||||
|
||||
@@ -2328,11 +2328,10 @@ void StressTest::Open() {
|
||||
|
||||
if ((options_.enable_blob_files || options_.enable_blob_garbage_collection ||
|
||||
FLAGS_allow_setting_blob_options_dynamically) &&
|
||||
(FLAGS_use_merge || FLAGS_backup_one_in > 0 ||
|
||||
FLAGS_best_efforts_recovery)) {
|
||||
(FLAGS_use_merge || FLAGS_best_efforts_recovery)) {
|
||||
fprintf(stderr,
|
||||
"Integrated BlobDB is currently incompatible with Merge, "
|
||||
"backup/restore, and best-effort recovery\n");
|
||||
"and best-effort recovery\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
title: (Call For Contribution) Make Universal Compaction More Incremental
|
||||
layout: post
|
||||
author: sdong
|
||||
category: blog
|
||||
---
|
||||
|
||||
### Motivation
|
||||
|
||||
Universal Compaction is an important compaction style, but few changes were made after we made the structure multi-leveled. Yet the major restriction of always compacting full sorted run is not relaxed. Compared to Leveled Compaction, where we usually only compile several SST files together, in universal compaction, we frequently compact GBs of data. Two issues with this gap: 1. it makes it harder to unify universal and leveled compaction; 2. periodically data is fully compacted, and in the mean time space is doubled. To ease the problem, we can break the restriction and do similar as leveled compaction, and bring it closer to unified compaction.
|
||||
|
||||
We call for help for making following improvements.
|
||||
|
||||
|
||||
### How Universal Compaction Works
|
||||
|
||||
In universal, whole levels are compacted together to satisfy two conditions (See [wiki page](https://github.com/facebook/rocksdb/wiki/Universal-Compaction) for more details):
|
||||
|
||||
1. total size / bottommost level size > a threshold, or
|
||||
2. total number of sorted runs (non-0 levels + L0 files) is within a threshold
|
||||
|
||||
1 is to limit extra space overhead used for dead data and 2 is for read performance.
|
||||
|
||||
If 1 is triggered, likely a full compaction will be triggered. If 2 is triggered, RocksDB compact some sorted runs to bring the number down. It does it by using a simple heuristic so that less writes needed for that purpose over time: it starts from compacting smaller files, but if total size to compact is similar to or larger than size of the next level, it will take that level together, as soon on (whether it is the best heuristic is another question and we’ve never seriously looked at it).
|
||||
|
||||
### How We Can Improve?
|
||||
|
||||
Let’s start from condition 1. Here we do full compaction but is not necessary. A simple optimization would be to compact so that just enough files are merged into the bottommost level (Lmax) to satisfy condition 1. It would work if we only need to pick some files from Lmax-1, or if it is cheaper over time, we can pick some files from other levels too.
|
||||
|
||||
Then condition 2. If we finish condition 1, there might be holes in some ranges in older levels. These holes might make it possible that only by compacting some sub ranges, we can fix the LSM-tree for condition 2. RocksDB can take single files into consideration and apply more sophisticated heuristic.
|
||||
|
||||
This new approach makes universal compaction closer to leveled compaction. The operation for 1 is closer to how Leveled compaction triggeres Lmax-1 to Lmax compaction. And 2 can potentially be implemented as something similar to level picking in Leveled Compaction. In fact, all those file picking can co-existing in one single compaction style and there isn’t fundamental conflicts to that.
|
||||
|
||||
### Limitation
|
||||
|
||||
There are two limitations:
|
||||
|
||||
* Periodic automatic full compaction is unpleasant but at the same time is pleasant in another way. Some users might uses it to reason that everything is periodically collapsed so dead data is gone and old data is rewritten. We need to make sure periodic compaction works to continue with that.
|
||||
* L0 to the first non-L0 level compaction is the first time data is partitioned in LSM-tree so that incremental compaction by range is possible. We might need to do more of these compactions in order to make incremental possible, which will increase compaction slightly.
|
||||
* Compacting subset of a level would introduce some extra overhead for unaligned files, just as in leveled compaction. More SST boundary cutting heuristic can reduce this overhead but it will be there.
|
||||
|
||||
But I believe the benefits would outweight the limitations. Reducing temporary space doubling and moving towards to unified compaction would be important achievements.
|
||||
|
||||
### Interested in Help?
|
||||
|
||||
Compaction is the core of LSM-tree, but its improvements are far overdue. If you are a user of universal compaction and would be able to benefit from those improvements, we will be happy to work with you on speeding up the project and bring them to RocksDB sooner. Feel free to communicate with us in [this issue](https://github.com/facebook/rocksdb/issues/8181).
|
||||
Vendored
+11
@@ -362,6 +362,17 @@ void EncryptedWritableFile::PrepareWrite(size_t offset, size_t len,
|
||||
file_->PrepareWrite(offset + prefixLength_, len, options, dbg);
|
||||
}
|
||||
|
||||
void EncryptedWritableFile::SetPreallocationBlockSize(size_t size) {
|
||||
// the size here doesn't need to include prefixLength_, as it's a
|
||||
// configuration will be use for `PrepareWrite()`.
|
||||
file_->SetPreallocationBlockSize(size);
|
||||
}
|
||||
|
||||
void EncryptedWritableFile::GetPreallocationStatus(
|
||||
size_t* block_size, size_t* last_allocated_block) {
|
||||
file_->GetPreallocationStatus(block_size, last_allocated_block);
|
||||
}
|
||||
|
||||
// Pre-allocates space for a file.
|
||||
IOStatus EncryptedWritableFile::Allocate(uint64_t offset, uint64_t len,
|
||||
const IOOptions& options,
|
||||
|
||||
@@ -383,10 +383,12 @@ IOStatus SetCurrentFile(FileSystem* fs, const std::string& dbname,
|
||||
contents.remove_prefix(dbname.size() + 1);
|
||||
std::string tmp = TempFileName(dbname, descriptor_number);
|
||||
IOStatus s = WriteStringToFile(fs, contents.ToString() + "\n", tmp, true);
|
||||
TEST_SYNC_POINT_CALLBACK("SetCurrentFile:BeforeRename", &s);
|
||||
if (s.ok()) {
|
||||
TEST_KILL_RANDOM("SetCurrentFile:0", rocksdb_kill_odds * REDUCE_ODDS2);
|
||||
s = fs->RenameFile(tmp, CurrentFileName(dbname), IOOptions(), nullptr);
|
||||
TEST_KILL_RANDOM("SetCurrentFile:1", rocksdb_kill_odds * REDUCE_ODDS2);
|
||||
TEST_SYNC_POINT_CALLBACK("SetCurrentFile:AfterRename", &s);
|
||||
}
|
||||
if (s.ok()) {
|
||||
if (directory_to_fsync != nullptr) {
|
||||
|
||||
+54
-1
@@ -73,7 +73,9 @@ typedef struct rocksdb_backup_engine_t rocksdb_backup_engine_t;
|
||||
typedef struct rocksdb_backup_engine_info_t rocksdb_backup_engine_info_t;
|
||||
typedef struct rocksdb_backupable_db_options_t rocksdb_backupable_db_options_t;
|
||||
typedef struct rocksdb_restore_options_t rocksdb_restore_options_t;
|
||||
typedef struct rocksdb_cache_t rocksdb_cache_t;
|
||||
typedef struct rocksdb_memory_allocator_t rocksdb_memory_allocator_t;
|
||||
typedef struct rocksdb_lru_cache_options_t rocksdb_lru_cache_options_t;
|
||||
typedef struct rocksdb_cache_t rocksdb_cache_t;
|
||||
typedef struct rocksdb_compactionfilter_t rocksdb_compactionfilter_t;
|
||||
typedef struct rocksdb_compactionfiltercontext_t
|
||||
rocksdb_compactionfiltercontext_t;
|
||||
@@ -1068,6 +1070,37 @@ extern ROCKSDB_LIBRARY_API unsigned char
|
||||
rocksdb_options_get_skip_checking_sst_file_sizes_on_db_open(
|
||||
rocksdb_options_t* opt);
|
||||
|
||||
/* Blob Options Settings */
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_enable_blob_files(
|
||||
rocksdb_options_t* opt, unsigned char val);
|
||||
extern ROCKSDB_LIBRARY_API unsigned char rocksdb_options_get_enable_blob_files(
|
||||
rocksdb_options_t* opt);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_min_blob_size(
|
||||
rocksdb_options_t* opt, uint64_t val);
|
||||
extern ROCKSDB_LIBRARY_API uint64_t
|
||||
rocksdb_options_get_min_blob_size(rocksdb_options_t* opt);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_blob_file_size(
|
||||
rocksdb_options_t* opt, uint64_t val);
|
||||
extern ROCKSDB_LIBRARY_API uint64_t
|
||||
rocksdb_options_get_blob_file_size(rocksdb_options_t* opt);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_blob_compression_type(
|
||||
rocksdb_options_t* opt, int val);
|
||||
extern ROCKSDB_LIBRARY_API int rocksdb_options_get_blob_compression_type(
|
||||
rocksdb_options_t* opt);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_enable_blob_gc(
|
||||
rocksdb_options_t* opt, unsigned char val);
|
||||
extern ROCKSDB_LIBRARY_API unsigned char rocksdb_options_get_enable_blob_gc(
|
||||
rocksdb_options_t* opt);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_blob_gc_age_cutoff(
|
||||
rocksdb_options_t* opt, double val);
|
||||
extern ROCKSDB_LIBRARY_API double rocksdb_options_get_blob_gc_age_cutoff(
|
||||
rocksdb_options_t* opt);
|
||||
|
||||
/* returns a pointer to a malloc()-ed, null terminated string */
|
||||
extern ROCKSDB_LIBRARY_API char* rocksdb_options_statistics_get_string(
|
||||
rocksdb_options_t* opt);
|
||||
@@ -1707,11 +1740,31 @@ extern ROCKSDB_LIBRARY_API void rocksdb_flushoptions_set_wait(
|
||||
extern ROCKSDB_LIBRARY_API unsigned char rocksdb_flushoptions_get_wait(
|
||||
rocksdb_flushoptions_t*);
|
||||
|
||||
/* Memory allocator */
|
||||
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_memory_allocator_t*
|
||||
rocksdb_jemalloc_nodump_allocator_create(char** errptr);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_memory_allocator_destroy(
|
||||
rocksdb_memory_allocator_t*);
|
||||
|
||||
/* Cache */
|
||||
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_lru_cache_options_t*
|
||||
rocksdb_lru_cache_options_create(void);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_lru_cache_options_destroy(
|
||||
rocksdb_lru_cache_options_t*);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_lru_cache_options_set_capacity(
|
||||
rocksdb_lru_cache_options_t*, size_t);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_lru_cache_options_set_memory_allocator(
|
||||
rocksdb_lru_cache_options_t*, rocksdb_memory_allocator_t*);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_cache_t* rocksdb_cache_create_lru(
|
||||
size_t capacity);
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_cache_t* rocksdb_cache_create_lru_opts(
|
||||
rocksdb_lru_cache_options_t*);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_cache_destroy(rocksdb_cache_t* cache);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_cache_disown_data(
|
||||
rocksdb_cache_t* cache);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_cache_set_capacity(
|
||||
rocksdb_cache_t* cache, size_t capacity);
|
||||
extern ROCKSDB_LIBRARY_API size_t
|
||||
|
||||
+133
-2
@@ -23,8 +23,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "rocksdb/memory_allocator.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
@@ -34,6 +37,7 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class Cache;
|
||||
struct ConfigOptions;
|
||||
class TieredCache;
|
||||
|
||||
extern const bool kDefaultToAdaptiveMutex;
|
||||
|
||||
@@ -87,6 +91,9 @@ struct LRUCacheOptions {
|
||||
CacheMetadataChargePolicy metadata_charge_policy =
|
||||
kDefaultCacheMetadataChargePolicy;
|
||||
|
||||
// A TieredCache instance to use a the non-volatile tier
|
||||
std::shared_ptr<TieredCache> tiered_cache;
|
||||
|
||||
LRUCacheOptions() {}
|
||||
LRUCacheOptions(size_t _capacity, int _num_shard_bits,
|
||||
bool _strict_capacity_limit, double _high_pri_pool_ratio,
|
||||
@@ -137,6 +144,57 @@ class Cache {
|
||||
// likely to get evicted than low priority entries.
|
||||
enum class Priority { HIGH, LOW };
|
||||
|
||||
// A set of callbacks to allow objects in the volatile block cache to be
|
||||
// be persisted in a NVM cache tier. Since the volatile cache holds C++
|
||||
// objects and the NVM cache may only hold flat data that doesn't need
|
||||
// relocation, these callbacks need to be provided by the user of the block
|
||||
// cache to do the conversion.
|
||||
// The CacheItemHelperCallback is passed to Insert(). When invoked, it
|
||||
// returns the callback functions for size, saving and deletion of the
|
||||
// object. We do it this way so that the cache implementation only needs to
|
||||
// save one function pointer in its metadata per object, the
|
||||
// CacheItemHelperCallback pointer which is a C-style function pointer.
|
||||
// Saving multiple std::function objects will take up 32 bytes per
|
||||
// function, even if its not bound to an object and does no capture. The
|
||||
// other alternative is to take a pointer to another object that implements
|
||||
// this interface, but that would create issues with managing the object
|
||||
// lifecycle.
|
||||
//
|
||||
// All the callbacks are C-style function pointers in order to simplify
|
||||
// lifecycle management. Objects in the cache can outlive the parent DB,
|
||||
// so anything required for these operations should be contained in the
|
||||
// object itself.
|
||||
//
|
||||
// The SizeCallback takes a void* pointer to the object and returns the size
|
||||
// of the persistable data. It can be used by the NVM cache to allocate
|
||||
// memory if needed.
|
||||
typedef size_t (*SizeCallback)(void* obj);
|
||||
|
||||
// The SaveToCallback takes a void* object pointer and saves the persistable
|
||||
// data into a buffer. The NVM cache may decide to not store it in a
|
||||
// contiguous buffer, in which case this callback will be called multiple
|
||||
// times with increasing offset
|
||||
typedef ROCKSDB_NAMESPACE::Status (*SaveToCallback)(void* obj, size_t offset,
|
||||
size_t size, void* out);
|
||||
|
||||
// DeletionCallback is a function pointer that deletes the cached
|
||||
// object. The signature matches the old deleter function.
|
||||
typedef void (*DeletionCallback)(const Slice&, void*);
|
||||
|
||||
// A callback function that returns the size, save to, and deletion
|
||||
// callbacks. Fill any of size_cb, saveto_cb, del_cb that is non-null
|
||||
typedef void (*CacheItemHelperCallback)(SizeCallback* size_cb,
|
||||
SaveToCallback* saveto_cb,
|
||||
DeletionCallback* del_cb);
|
||||
|
||||
// The CreateCallback is passed by the block cache user to Lookup(). It
|
||||
// takes in a buffer from the NVM cache and constructs an object using
|
||||
// it. The callback doesn't have ownership of the buffer and should
|
||||
// copy the contents into its own buffer.
|
||||
typedef std::function<ROCKSDB_NAMESPACE::Status(
|
||||
void* buf, size_t size, void** out_obj, size_t* charge)>
|
||||
CreateCallback;
|
||||
|
||||
Cache(std::shared_ptr<MemoryAllocator> allocator = nullptr)
|
||||
: memory_allocator_(std::move(allocator)) {}
|
||||
// No copying allowed
|
||||
@@ -170,8 +228,8 @@ class Cache {
|
||||
// The type of the Cache
|
||||
virtual const char* Name() const = 0;
|
||||
|
||||
// Insert a mapping from key->value into the cache and assign it
|
||||
// the specified charge against the total cache capacity.
|
||||
// Insert a mapping from key->value into the volatile cache only
|
||||
// and assign it // the specified charge against the total cache capacity.
|
||||
// If strict_capacity_limit is true and cache reaches its full capacity,
|
||||
// return Status::Incomplete.
|
||||
//
|
||||
@@ -287,6 +345,79 @@ class Cache {
|
||||
|
||||
MemoryAllocator* memory_allocator() const { return memory_allocator_.get(); }
|
||||
|
||||
// Insert a mapping from key->value into the volatile cache and assign it
|
||||
// the specified charge against the total cache capacity.
|
||||
// If strict_capacity_limit is true and cache reaches its full capacity,
|
||||
// return Status::Incomplete.
|
||||
//
|
||||
// If handle is not nullptr, returns a handle that corresponds to the
|
||||
// mapping. The caller must call this->Release(handle) when the returned
|
||||
// mapping is no longer needed. In case of error caller is responsible to
|
||||
// cleanup the value (i.e. calling "deleter").
|
||||
//
|
||||
// If handle is nullptr, it is as if Release is called immediately after
|
||||
// insert. In case of error value will be cleanup.
|
||||
//
|
||||
// Regardless of whether the item was inserted into the volatile cache,
|
||||
// it will attempt to insert it into the NVM cache if one is configured.
|
||||
// The block cache implementation must support the NVM tier, otherwise
|
||||
// the item is only inserted into the volatile tier. It may
|
||||
// defer the insertion to NVM as it sees fit. The NVM
|
||||
// cache may or may not write it to NVM depending on its admission
|
||||
// policy.
|
||||
//
|
||||
// When the inserted entry is no longer needed, the key and
|
||||
// value will be passed to "deleter".
|
||||
virtual Status Insert(const Slice& key, void* value,
|
||||
CacheItemHelperCallback helper_cb, size_t charge,
|
||||
Handle** handle = nullptr,
|
||||
Priority priority = Priority::LOW) {
|
||||
DeletionCallback delete_cb = nullptr;
|
||||
assert(helper_cb != nullptr);
|
||||
(*helper_cb)(nullptr, nullptr, &delete_cb);
|
||||
return Insert(key, value, charge, delete_cb, handle, priority);
|
||||
}
|
||||
|
||||
// Lookup the key in the volatile and NVM tiers (if one is configured).
|
||||
// The create_cb callback function object will be used to contruct the
|
||||
// cached object.
|
||||
// If none of the tiers have the mapping for the key, rturns nullptr.
|
||||
// Else, returns a handle that corresponds to the mapping.
|
||||
//
|
||||
// The handle returned may not be ready. The caller should call isReady()
|
||||
// to check if the item value is ready, and call Wait() or WaitAll() if
|
||||
// its not ready. The caller should then call Value() to check if the
|
||||
// item was successfully retrieved. If unsuccessful (perhaps due to an
|
||||
// IO error), Value() will return nullptr.
|
||||
virtual Handle* Lookup(const Slice& key,
|
||||
CacheItemHelperCallback /*helper_cb*/,
|
||||
const CreateCallback& /*create_cb*/,
|
||||
Priority /*priority*/, bool /*wait*/,
|
||||
Statistics* stats = nullptr) {
|
||||
return Lookup(key, stats);
|
||||
}
|
||||
|
||||
// Release a mapping returned by a previous Lookup(). The "useful"
|
||||
// parameter specifies whether the data was actually used or not,
|
||||
// which may be used by the cache implementation to decide whether
|
||||
// to consider it as a hit for retention purposes.
|
||||
virtual bool Release(Handle* handle, bool /*useful*/, bool force_erase) {
|
||||
return Release(handle, force_erase);
|
||||
}
|
||||
|
||||
// Determines if the handle returned by Lookup() has a valid value yet.
|
||||
virtual bool isReady(Handle* /*handle*/) { return true; }
|
||||
|
||||
// If the handle returned by Lookup() is not ready yet, wait till it
|
||||
// becomes ready.
|
||||
// Note: A ready handle doesn't necessarily mean it has a valid value. The
|
||||
// user should call Value() and check for nullptr.
|
||||
virtual void Wait(Handle* /*handle*/) {}
|
||||
|
||||
// Wait for a vector of handles to become ready. As with Wait(), the user
|
||||
// should check the Value() of each handle for nullptr
|
||||
virtual void WaitAll(std::vector<Handle*>& /*handles*/) {}
|
||||
|
||||
private:
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator_;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2013-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 "rocksdb/rocksdb_namespace.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/types.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
int cache_bench_tool(int argc, char** argv);
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -350,6 +350,35 @@ class Configurable {
|
||||
// Given a name (e.g. rocksdb.my.type.opt), returns the short name (opt)
|
||||
virtual std::string GetOptionName(const std::string& long_name) const;
|
||||
|
||||
// Registers the input name with the options and associated map.
|
||||
// When classes register their options in this manner, most of the
|
||||
// functionality (excluding unknown options and validate/prepare) is
|
||||
// implemented by the base class.
|
||||
//
|
||||
// This method should be called in the class constructor to register the
|
||||
// option set for this object. For example, to register the options
|
||||
// associated with the BlockBasedTableFactory, the constructor calls this
|
||||
// method passing in:
|
||||
// - the name of the options ("BlockBasedTableOptions");
|
||||
// - the options object (the BlockBasedTableOptions object for this object;
|
||||
// - the options type map for the BlockBasedTableOptions.
|
||||
// This registration allows the Configurable class to process the option
|
||||
// values associated with the BlockBasedTableOptions without further code in
|
||||
// the derived class.
|
||||
//
|
||||
// @param name The name of this set of options (@see GetOptionsPtr)
|
||||
// @param opt_ptr Pointer to the options to associate with this name
|
||||
// @param opt_map Options map that controls how this option is configured.
|
||||
template <typename T>
|
||||
void RegisterOptions(
|
||||
T* opt_ptr,
|
||||
const std::unordered_map<std::string, OptionTypeInfo>* opt_map) {
|
||||
RegisterOptions(T::kName(), opt_ptr, opt_map);
|
||||
}
|
||||
void RegisterOptions(
|
||||
const std::string& name, void* opt_ptr,
|
||||
const std::unordered_map<std::string, OptionTypeInfo>* opt_map);
|
||||
|
||||
private:
|
||||
// Contains the collection of options (name, opt_ptr, opt_map) associated with
|
||||
// this object. This collection is typically set in the constructor of the
|
||||
|
||||
@@ -1180,7 +1180,13 @@ class DB {
|
||||
virtual Status EnableAutoCompaction(
|
||||
const std::vector<ColumnFamilyHandle*>& column_family_handles) = 0;
|
||||
|
||||
// After this function call, CompactRange() or CompactFiles() will not
|
||||
// run compactions and fail. The function will wait for all outstanding
|
||||
// manual compactions to finish before returning
|
||||
virtual void DisableManualCompaction() = 0;
|
||||
// Re-enable CompactRange() and ComapctFiles() that are disabled by
|
||||
// DisableManualCompaction(). In debug mode, it might hit assertion if
|
||||
// no DisableManualCompaction() was previously called.
|
||||
virtual void EnableManualCompaction() = 0;
|
||||
|
||||
// Number of levels used for this DB.
|
||||
@@ -1368,7 +1374,7 @@ class DB {
|
||||
virtual void GetLiveFilesMetaData(
|
||||
std::vector<LiveFileMetaData>* /*metadata*/) {}
|
||||
|
||||
// Return a list of all table file checksum info.
|
||||
// Return a list of all table and blob files checksum info.
|
||||
// Note: This function might be of limited use because it cannot be
|
||||
// synchronized with GetLiveFiles.
|
||||
virtual Status GetLiveFilesChecksumInfo(FileChecksumList* checksum_list) = 0;
|
||||
|
||||
@@ -366,6 +366,11 @@ class EncryptedWritableFile : public FSWritableFile {
|
||||
void PrepareWrite(size_t offset, size_t len, const IOOptions& options,
|
||||
IODebugContext* dbg) override;
|
||||
|
||||
void SetPreallocationBlockSize(size_t size) override;
|
||||
|
||||
void GetPreallocationStatus(size_t* block_size,
|
||||
size_t* last_allocated_block) override;
|
||||
|
||||
// Pre-allocates space for a file.
|
||||
IOStatus Allocate(uint64_t offset, uint64_t len, const IOOptions& options,
|
||||
IODebugContext* dbg) override;
|
||||
|
||||
@@ -216,16 +216,18 @@ class FilterPolicy {
|
||||
extern const FilterPolicy* NewBloomFilterPolicy(
|
||||
double bits_per_key, bool use_block_based_builder = false);
|
||||
|
||||
// An EXPERIMENTAL new Bloom alternative that saves about 30% space
|
||||
// compared to Bloom filters, with about 3-4x construction time and
|
||||
// similar query times. For example, if you pass in 10 for
|
||||
// An new Bloom alternative that saves about 30% space compared to
|
||||
// Bloom filters, with about 3-4x construction CPU time and similar
|
||||
// query times. For example, if you pass in 10 for
|
||||
// bloom_equivalent_bits_per_key, you'll get the same 0.95% FP rate
|
||||
// as Bloom filter but only using about 7 bits per key. (This
|
||||
// way of configuring the new filter is considered experimental
|
||||
// and/or transitional, so is expected to go away.)
|
||||
// and/or transitional, so is expected to be replaced with a new API.
|
||||
// The constructed filters will be given long-term support.)
|
||||
//
|
||||
// Ribbon filters are ignored by previous versions of RocksDB, as if
|
||||
// no filter was used.
|
||||
// Ribbon filters are compatible with RocksDB >= 6.15.0. Earlier
|
||||
// versions reading the data will behave as if no filter was used
|
||||
// (degraded performance until compaction rebuilds filters).
|
||||
//
|
||||
// Note: this policy can generate Bloom filters in some cases.
|
||||
// For very small filters (well under 1KB), Bloom fallback is by
|
||||
|
||||
@@ -50,7 +50,15 @@ struct IOStatsContext {
|
||||
uint64_t cpu_read_nanos;
|
||||
};
|
||||
|
||||
// Get Thread-local IOStatsContext object pointer
|
||||
// If RocksDB is compiled with -DNIOSTATS_CONTEXT, then a pointer to a global,
|
||||
// non-thread-local IOStatsContext object will be returned. Attempts to update
|
||||
// this object will be ignored, and reading from it will also be no-op.
|
||||
// Otherwise,
|
||||
// a) if thread-local is supported on the platform, then a pointer to
|
||||
// a thread-local IOStatsContext object will be returned.
|
||||
// b) if thread-local is NOT supported, then compilation will fail.
|
||||
//
|
||||
// This function never returns nullptr.
|
||||
IOStatsContext* get_iostats_context();
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -118,6 +118,7 @@ enum class FlushReason : int {
|
||||
// When set the flush reason to kErrorRecoveryRetryFlush, SwitchMemtable
|
||||
// will not be called to avoid many small immutable memtables.
|
||||
kErrorRecoveryRetryFlush = 0xc,
|
||||
kWalFull = 0xd,
|
||||
};
|
||||
|
||||
// TODO: In the future, BackgroundErrorReason will only be used to indicate
|
||||
|
||||
@@ -230,8 +230,15 @@ struct PerfContext {
|
||||
bool per_level_perf_context_enabled = false;
|
||||
};
|
||||
|
||||
// Get Thread-local PerfContext object pointer
|
||||
// if defined(NPERF_CONTEXT), then the pointer is not thread-local
|
||||
// If RocksDB is compiled with -DNPERF_CONTEXT, then a pointer to a global,
|
||||
// non-thread-local PerfContext object will be returned. Attempts to update
|
||||
// this object will be ignored, and reading from it will also be no-op.
|
||||
// Otherwise,
|
||||
// a) if thread-local is supported on the platform, then a pointer to
|
||||
// a thread-local PerfContext object will be returned.
|
||||
// b) if thread-local is NOT supported, then compilation will fail.
|
||||
//
|
||||
// This function never returns nullptr.
|
||||
PerfContext* get_perf_context();
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright (c) 2021, 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 <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// A handle for lookup result. The handle may not be immediately ready or
|
||||
// have a valid value. The caller must call isReady() to determine if its
|
||||
// ready, and call Wait() in order to block until it becomes ready.
|
||||
// The caller must call value() after it becomes ready to determine if the
|
||||
// handle successfullly read the item.
|
||||
class TieredCacheHandle {
|
||||
public:
|
||||
virtual ~TieredCacheHandle() {}
|
||||
|
||||
// Returns whether the handle is ready or not
|
||||
virtual bool isReady() = 0;
|
||||
|
||||
// Block until handle becomes ready
|
||||
virtual void Wait() = 0;
|
||||
|
||||
// Return the value. If nullptr, it means the lookup was unsuccessful
|
||||
virtual void* Value() = 0;
|
||||
|
||||
// Return the size of value
|
||||
virtual size_t Size() = 0;
|
||||
};
|
||||
|
||||
// TieredCache
|
||||
//
|
||||
// Cache interface for caching blocks on a stackable tiers (which can include
|
||||
// non-volatile mediums)
|
||||
class TieredCache {
|
||||
public:
|
||||
virtual ~TieredCache() {}
|
||||
|
||||
virtual std::string Name() = 0;
|
||||
|
||||
static const char* Type() { return "TieredCache"; }
|
||||
|
||||
// Insert the given value into this tier. The value is not written
|
||||
// directly. Rather, the SaveToCallback provided by helper_cb will be
|
||||
// used to extract the persistable data in value, which will be written
|
||||
// to this tier. The implementation may or may not write it to cache
|
||||
// depending on the admission control policy, even if the return status is
|
||||
// success.
|
||||
virtual Status Insert(const Slice& key, void* value,
|
||||
Cache::CacheItemHelperCallback helper_cb) = 0;
|
||||
|
||||
// Lookup the data for the given key in this tier. The create_cb
|
||||
// will be used to create the object. The handle returned may not be
|
||||
// ready yet, unless wait=true, in which case Lookup() will block until
|
||||
// the handle is ready
|
||||
virtual std::unique_ptr<TieredCacheHandle> Lookup(
|
||||
const Slice& key, const Cache::CreateCallback& create_cb, bool wait) = 0;
|
||||
|
||||
// At the discretion of the implementation, erase the data associated
|
||||
// with key
|
||||
virtual void Erase(const Slice& key) = 0;
|
||||
|
||||
// Wait for a collection of handles to become ready
|
||||
virtual void WaitAll(std::vector<TieredCacheHandle*> handles) = 0;
|
||||
|
||||
virtual std::string GetPrintableOptions() const = 0;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -371,6 +371,21 @@ class BackupEngineReadOnlyBase {
|
||||
public:
|
||||
virtual ~BackupEngineReadOnlyBase() {}
|
||||
|
||||
// Returns info about the latest good backup in backup_info, or NotFound
|
||||
// no good backup exists.
|
||||
// Setting include_file_details=true provides information about each
|
||||
// backed-up file in BackupInfo::file_details and more.
|
||||
virtual Status GetLatestBackupInfo(
|
||||
BackupInfo* backup_info, bool include_file_details = false) const = 0;
|
||||
|
||||
// Returns info about a specific backup in backup_info, or NotFound
|
||||
// or Corruption status if the requested backup id does not exist or is
|
||||
// known corrupt.
|
||||
// Setting include_file_details=true provides information about each
|
||||
// backed-up file in BackupInfo::file_details and more.
|
||||
virtual Status GetBackupInfo(BackupID backup_id, BackupInfo* backup_info,
|
||||
bool include_file_details = false) const = 0;
|
||||
|
||||
// Returns info about backups in backup_info
|
||||
// Setting include_file_details=true provides information about each
|
||||
// backed-up file in BackupInfo::file_details and more.
|
||||
@@ -439,7 +454,7 @@ class BackupEngineAppendOnlyBase {
|
||||
// same as CreateNewBackup, but stores extra application metadata.
|
||||
virtual Status CreateNewBackupWithMetadata(
|
||||
const CreateBackupOptions& options, DB* db,
|
||||
const std::string& app_metadata) = 0;
|
||||
const std::string& app_metadata, BackupID* new_backup_id = nullptr) = 0;
|
||||
|
||||
// keep here for backward compatibility.
|
||||
virtual Status CreateNewBackupWithMetadata(
|
||||
@@ -451,9 +466,12 @@ class BackupEngineAppendOnlyBase {
|
||||
return CreateNewBackupWithMetadata(options, db, app_metadata);
|
||||
}
|
||||
|
||||
// Captures the state of the database by creating a new (latest) backup
|
||||
virtual Status CreateNewBackup(const CreateBackupOptions& options, DB* db) {
|
||||
return CreateNewBackupWithMetadata(options, db, "");
|
||||
// Captures the state of the database by creating a new (latest) backup.
|
||||
// On success (OK status), the BackupID of the new backup is saved to
|
||||
// *new_backup_id when not nullptr.
|
||||
virtual Status CreateNewBackup(const CreateBackupOptions& options, DB* db,
|
||||
BackupID* new_backup_id = nullptr) {
|
||||
return CreateNewBackupWithMetadata(options, db, "", new_backup_id);
|
||||
}
|
||||
|
||||
// keep here for backward compatibility.
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
|
||||
#define ROCKSDB_MAJOR 6
|
||||
#define ROCKSDB_MINOR 19
|
||||
#define ROCKSDB_MINOR 20
|
||||
#define ROCKSDB_PATCH 0
|
||||
|
||||
// Do not use these. We made the mistake of declaring macros starting with
|
||||
|
||||
@@ -13,43 +13,86 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
|
||||
#include "rocksdb/cache.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// Interface to block and signal DB instances.
|
||||
// Each DB instance contains ptr to StallInterface.
|
||||
class StallInterface {
|
||||
public:
|
||||
virtual ~StallInterface() {}
|
||||
|
||||
virtual void Block() = 0;
|
||||
|
||||
virtual void Signal() = 0;
|
||||
};
|
||||
|
||||
class WriteBufferManager {
|
||||
public:
|
||||
// _buffer_size = 0 indicates no limit. Memory won't be capped.
|
||||
// Parameters:
|
||||
// _buffer_size: _buffer_size = 0 indicates no limit. Memory won't be capped.
|
||||
// memory_usage() won't be valid and ShouldFlush() will always return true.
|
||||
// if `cache` is provided, we'll put dummy entries in the cache and cost
|
||||
// the memory allocated to the cache. It can be used even if _buffer_size = 0.
|
||||
//
|
||||
// cache_: if `cache` is provided, we'll put dummy entries in the cache and
|
||||
// cost the memory allocated to the cache. It can be used even if _buffer_size
|
||||
// = 0.
|
||||
//
|
||||
// allow_stall: if set true, it will enable stalling of writes when
|
||||
// memory_usage() exceeds buffer_size. It will wait for flush to complete and
|
||||
// memory usage to drop down.
|
||||
explicit WriteBufferManager(size_t _buffer_size,
|
||||
std::shared_ptr<Cache> cache = {});
|
||||
std::shared_ptr<Cache> cache = {},
|
||||
bool allow_stall = false);
|
||||
// No copying allowed
|
||||
WriteBufferManager(const WriteBufferManager&) = delete;
|
||||
WriteBufferManager& operator=(const WriteBufferManager&) = delete;
|
||||
|
||||
~WriteBufferManager();
|
||||
|
||||
// Returns true if buffer_limit is passed to limit the total memory usage and
|
||||
// is greater than 0.
|
||||
bool enabled() const { return buffer_size() > 0; }
|
||||
|
||||
// Returns true if pointer to cache is passed.
|
||||
bool cost_to_cache() const { return cache_rep_ != nullptr; }
|
||||
|
||||
// Returns the total memory used by memtables.
|
||||
// Only valid if enabled()
|
||||
size_t memory_usage() const {
|
||||
return memory_used_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Returns the total memory used by active memtables.
|
||||
size_t mutable_memtable_memory_usage() const {
|
||||
return memory_active_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
size_t dummy_entries_in_cache_usage() const {
|
||||
return dummy_size_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Returns the buffer_size.
|
||||
size_t buffer_size() const {
|
||||
return buffer_size_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void SetBufferSize(size_t new_size) {
|
||||
buffer_size_.store(new_size, std::memory_order_relaxed);
|
||||
mutable_limit_.store(new_size * 7 / 8, std::memory_order_relaxed);
|
||||
// Check if stall is active and can be ended.
|
||||
if (allow_stall_) {
|
||||
EndWriteStall();
|
||||
}
|
||||
}
|
||||
|
||||
// Below functions should be called by RocksDB internally.
|
||||
|
||||
// Should only be called from write thread
|
||||
bool ShouldFlush() const {
|
||||
if (enabled()) {
|
||||
@@ -69,36 +112,51 @@ class WriteBufferManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
void ReserveMem(size_t mem) {
|
||||
if (cache_rep_ != nullptr) {
|
||||
ReserveMemWithCache(mem);
|
||||
} else if (enabled()) {
|
||||
memory_used_.fetch_add(mem, std::memory_order_relaxed);
|
||||
}
|
||||
if (enabled()) {
|
||||
memory_active_.fetch_add(mem, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
// We are in the process of freeing `mem` bytes, so it is not considered
|
||||
// when checking the soft limit.
|
||||
void ScheduleFreeMem(size_t mem) {
|
||||
if (enabled()) {
|
||||
memory_active_.fetch_sub(mem, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
void FreeMem(size_t mem) {
|
||||
if (cache_rep_ != nullptr) {
|
||||
FreeMemWithCache(mem);
|
||||
} else if (enabled()) {
|
||||
memory_used_.fetch_sub(mem, std::memory_order_relaxed);
|
||||
// Returns true if total memory usage exceeded buffer_size.
|
||||
// We stall the writes untill memory_usage drops below buffer_size. When the
|
||||
// function returns true, all writer threads (including one checking this
|
||||
// condition) across all DBs will be stalled. Stall is allowed only if user
|
||||
// pass allow_stall = true during WriteBufferManager instance creation.
|
||||
//
|
||||
// Should only be called by RocksDB internally .
|
||||
bool ShouldStall() {
|
||||
if (allow_stall_ && enabled()) {
|
||||
if (IsStallActive()) {
|
||||
return true;
|
||||
}
|
||||
if (IsStallThresholdExceeded()) {
|
||||
stall_active_.store(true, std::memory_order_relaxed);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SetBufferSize(size_t new_size) {
|
||||
buffer_size_.store(new_size, std::memory_order_relaxed);
|
||||
mutable_limit_.store(new_size * 7 / 8, std::memory_order_relaxed);
|
||||
// Returns true if stall is active.
|
||||
bool IsStallActive() const {
|
||||
return stall_active_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Returns true if stalling condition is met.
|
||||
bool IsStallThresholdExceeded() { return memory_usage() >= buffer_size_; }
|
||||
|
||||
void ReserveMem(size_t mem);
|
||||
|
||||
// We are in the process of freeing `mem` bytes, so it is not considered
|
||||
// when checking the soft limit.
|
||||
void ScheduleFreeMem(size_t mem);
|
||||
|
||||
void FreeMem(size_t mem);
|
||||
|
||||
// Add the DB instance to the queue and block the DB.
|
||||
// Should only be called by RocksDB internally.
|
||||
void BeginWriteStall(StallInterface* wbm_stall);
|
||||
|
||||
// Remove DB instances from queue and signal them to continue.
|
||||
void EndWriteStall();
|
||||
|
||||
void RemoveDBFromQueue(StallInterface* wbm_stall);
|
||||
|
||||
private:
|
||||
std::atomic<size_t> buffer_size_;
|
||||
std::atomic<size_t> mutable_limit_;
|
||||
@@ -108,6 +166,11 @@ class WriteBufferManager {
|
||||
std::atomic<size_t> dummy_size_;
|
||||
struct CacheRep;
|
||||
std::unique_ptr<CacheRep> cache_rep_;
|
||||
std::list<StallInterface*> queue_;
|
||||
// Protects the queue_
|
||||
std::mutex mu_;
|
||||
bool allow_stall_;
|
||||
std::atomic<bool> stall_active_;
|
||||
|
||||
void ReserveMemWithCache(size_t mem);
|
||||
void FreeMemWithCache(size_t mem);
|
||||
|
||||
@@ -2546,7 +2546,9 @@ public class RocksDB extends RocksObject {
|
||||
|
||||
/**
|
||||
* If the key definitely does not exist in the database, then this method
|
||||
* returns null, else it returns an instance of KeyMayExistResult
|
||||
* returns false, otherwise it returns true if the key might exist.
|
||||
* That is to say that this method is probabilistic and may return false
|
||||
* positives, but never a true negative.
|
||||
*
|
||||
* If the caller wants to obtain value when the key
|
||||
* is found in memory, then {@code valueHolder} must be set.
|
||||
@@ -2570,7 +2572,9 @@ public class RocksDB extends RocksObject {
|
||||
|
||||
/**
|
||||
* If the key definitely does not exist in the database, then this method
|
||||
* returns null, else it returns an instance of KeyMayExistResult
|
||||
* returns false, otherwise it returns true if the key might exist.
|
||||
* That is to say that this method is probabilistic and may return false
|
||||
* positives, but never a true negative.
|
||||
*
|
||||
* If the caller wants to obtain value when the key
|
||||
* is found in memory, then {@code valueHolder} must be set.
|
||||
@@ -2599,7 +2603,9 @@ public class RocksDB extends RocksObject {
|
||||
|
||||
/**
|
||||
* If the key definitely does not exist in the database, then this method
|
||||
* returns null, else it returns an instance of KeyMayExistResult
|
||||
* returns false, otherwise it returns true if the key might exist.
|
||||
* That is to say that this method is probabilistic and may return false
|
||||
* positives, but never a true negative.
|
||||
*
|
||||
* If the caller wants to obtain value when the key
|
||||
* is found in memory, then {@code valueHolder} must be set.
|
||||
@@ -2626,7 +2632,9 @@ public class RocksDB extends RocksObject {
|
||||
|
||||
/**
|
||||
* If the key definitely does not exist in the database, then this method
|
||||
* returns null, else it returns an instance of KeyMayExistResult
|
||||
* returns false, otherwise it returns true if the key might exist.
|
||||
* That is to say that this method is probabilistic and may return false
|
||||
* positives, but never a true negative.
|
||||
*
|
||||
* If the caller wants to obtain value when the key
|
||||
* is found in memory, then {@code valueHolder} must be set.
|
||||
@@ -2658,7 +2666,9 @@ public class RocksDB extends RocksObject {
|
||||
|
||||
/**
|
||||
* If the key definitely does not exist in the database, then this method
|
||||
* returns null, else it returns an instance of KeyMayExistResult
|
||||
* returns false, otherwise it returns true if the key might exist.
|
||||
* That is to say that this method is probabilistic and may return false
|
||||
* positives, but never a true negative.
|
||||
*
|
||||
* If the caller wants to obtain value when the key
|
||||
* is found in memory, then {@code valueHolder} must be set.
|
||||
@@ -2685,7 +2695,9 @@ public class RocksDB extends RocksObject {
|
||||
|
||||
/**
|
||||
* If the key definitely does not exist in the database, then this method
|
||||
* returns null, else it returns an instance of KeyMayExistResult
|
||||
* returns false, otherwise it returns true if the key might exist.
|
||||
* That is to say that this method is probabilistic and may return false
|
||||
* positives, but never a true negative.
|
||||
*
|
||||
* If the caller wants to obtain value when the key
|
||||
* is found in memory, then {@code valueHolder} must be set.
|
||||
@@ -2717,7 +2729,9 @@ public class RocksDB extends RocksObject {
|
||||
|
||||
/**
|
||||
* If the key definitely does not exist in the database, then this method
|
||||
* returns null, else it returns an instance of KeyMayExistResult
|
||||
* returns false, otherwise it returns true if the key might exist.
|
||||
* That is to say that this method is probabilistic and may return false
|
||||
* positives, but never a true negative.
|
||||
*
|
||||
* If the caller wants to obtain value when the key
|
||||
* is found in memory, then {@code valueHolder} must be set.
|
||||
@@ -2746,7 +2760,9 @@ public class RocksDB extends RocksObject {
|
||||
|
||||
/**
|
||||
* If the key definitely does not exist in the database, then this method
|
||||
* returns null, else it returns an instance of KeyMayExistResult
|
||||
* returns false, otherwise it returns true if the key might exist.
|
||||
* That is to say that this method is probabilistic and may return false
|
||||
* positives, but never a true negative.
|
||||
*
|
||||
* If the caller wants to obtain value when the key
|
||||
* is found in memory, then {@code valueHolder} must be set.
|
||||
|
||||
@@ -1479,8 +1479,8 @@ public class RocksDBTest {
|
||||
assertThat(livefiles.manifestFileSize).isEqualTo(57);
|
||||
assertThat(livefiles.files.size()).isEqualTo(3);
|
||||
assertThat(livefiles.files.get(0)).isEqualTo("/CURRENT");
|
||||
assertThat(livefiles.files.get(1)).isEqualTo("/MANIFEST-000003");
|
||||
assertThat(livefiles.files.get(2)).isEqualTo("/OPTIONS-000006");
|
||||
assertThat(livefiles.files.get(1)).isEqualTo("/MANIFEST-000004");
|
||||
assertThat(livefiles.files.get(2)).isEqualTo("/OPTIONS-000007");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
#include <mutex>
|
||||
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "util/coding.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -49,13 +50,16 @@ struct WriteBufferManager::CacheRep {};
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
WriteBufferManager::WriteBufferManager(size_t _buffer_size,
|
||||
std::shared_ptr<Cache> cache)
|
||||
std::shared_ptr<Cache> cache,
|
||||
bool allow_stall)
|
||||
: buffer_size_(_buffer_size),
|
||||
mutable_limit_(buffer_size_ * 7 / 8),
|
||||
memory_used_(0),
|
||||
memory_active_(0),
|
||||
dummy_size_(0),
|
||||
cache_rep_(nullptr) {
|
||||
cache_rep_(nullptr),
|
||||
allow_stall_(allow_stall),
|
||||
stall_active_(false) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (cache) {
|
||||
// Construct the cache key using the pointer to this.
|
||||
@@ -78,6 +82,17 @@ WriteBufferManager::~WriteBufferManager() {
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
|
||||
void WriteBufferManager::ReserveMem(size_t mem) {
|
||||
if (cache_rep_ != nullptr) {
|
||||
ReserveMemWithCache(mem);
|
||||
} else if (enabled()) {
|
||||
memory_used_.fetch_add(mem, std::memory_order_relaxed);
|
||||
}
|
||||
if (enabled()) {
|
||||
memory_active_.fetch_add(mem, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
// Should only be called from write thread
|
||||
void WriteBufferManager::ReserveMemWithCache(size_t mem) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
@@ -112,6 +127,24 @@ void WriteBufferManager::ReserveMemWithCache(size_t mem) {
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
|
||||
void WriteBufferManager::ScheduleFreeMem(size_t mem) {
|
||||
if (enabled()) {
|
||||
memory_active_.fetch_sub(mem, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
void WriteBufferManager::FreeMem(size_t mem) {
|
||||
if (cache_rep_ != nullptr) {
|
||||
FreeMemWithCache(mem);
|
||||
} else if (enabled()) {
|
||||
memory_used_.fetch_sub(mem, std::memory_order_relaxed);
|
||||
}
|
||||
// Check if stall is active and can be ended.
|
||||
if (allow_stall_) {
|
||||
EndWriteStall();
|
||||
}
|
||||
}
|
||||
|
||||
void WriteBufferManager::FreeMemWithCache(size_t mem) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
assert(cache_rep_ != nullptr);
|
||||
@@ -145,4 +178,50 @@ void WriteBufferManager::FreeMemWithCache(size_t mem) {
|
||||
(void)mem;
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
|
||||
void WriteBufferManager::BeginWriteStall(StallInterface* wbm_stall) {
|
||||
assert(wbm_stall != nullptr);
|
||||
if (wbm_stall) {
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
queue_.push_back(wbm_stall);
|
||||
}
|
||||
// In case thread enqueue itself and memory got freed in parallel, end the
|
||||
// stall.
|
||||
if (!ShouldStall()) {
|
||||
EndWriteStall();
|
||||
}
|
||||
}
|
||||
|
||||
// Called when memory is freed in FreeMem.
|
||||
void WriteBufferManager::EndWriteStall() {
|
||||
if (enabled() && !IsStallThresholdExceeded()) {
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
stall_active_.store(false, std::memory_order_relaxed);
|
||||
if (queue_.empty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the instances from the list and call WBMStallInterface::Signal to
|
||||
// change the state to running and unblock the DB instances.
|
||||
// Check ShouldStall() incase stall got active by other DBs.
|
||||
while (!ShouldStall() && !queue_.empty()) {
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
StallInterface* wbm_stall = queue_.front();
|
||||
queue_.pop_front();
|
||||
wbm_stall->Signal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WriteBufferManager::RemoveDBFromQueue(StallInterface* wbm_stall) {
|
||||
assert(wbm_stall != nullptr);
|
||||
if (enabled() && allow_stall_) {
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
queue_.remove(wbm_stall);
|
||||
wbm_stall->Signal();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -9,19 +9,23 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
#ifdef ROCKSDB_SUPPORT_THREAD_LOCAL
|
||||
#ifdef NIOSTATS_CONTEXT
|
||||
// Should not be used because the counters are not thread-safe.
|
||||
// Put here just to make get_iostats_context() simple without ifdef.
|
||||
static IOStatsContext iostats_context;
|
||||
#elif defined(ROCKSDB_SUPPORT_THREAD_LOCAL)
|
||||
__thread IOStatsContext iostats_context;
|
||||
#else
|
||||
#error \
|
||||
"No thread-local support. Disable iostats context with -DNIOSTATS_CONTEXT."
|
||||
#endif
|
||||
|
||||
IOStatsContext* get_iostats_context() {
|
||||
#ifdef ROCKSDB_SUPPORT_THREAD_LOCAL
|
||||
return &iostats_context;
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
void IOStatsContext::Reset() {
|
||||
#ifndef NIOSTATS_CONTEXT
|
||||
thread_pool_id = Env::Priority::TOTAL;
|
||||
bytes_read = 0;
|
||||
bytes_written = 0;
|
||||
@@ -35,6 +39,7 @@ void IOStatsContext::Reset() {
|
||||
logger_nanos = 0;
|
||||
cpu_write_nanos = 0;
|
||||
cpu_read_nanos = 0;
|
||||
#endif //! NIOSTATS_CONTEXT
|
||||
}
|
||||
|
||||
#define IOSTATS_CONTEXT_OUTPUT(counter) \
|
||||
@@ -43,6 +48,10 @@ void IOStatsContext::Reset() {
|
||||
}
|
||||
|
||||
std::string IOStatsContext::ToString(bool exclude_zero_counters) const {
|
||||
#ifdef NIOSTATS_CONTEXT
|
||||
(void)exclude_zero_counters;
|
||||
return "";
|
||||
#else
|
||||
std::ostringstream ss;
|
||||
IOSTATS_CONTEXT_OUTPUT(thread_pool_id);
|
||||
IOSTATS_CONTEXT_OUTPUT(bytes_read);
|
||||
@@ -61,6 +70,7 @@ std::string IOStatsContext::ToString(bool exclude_zero_counters) const {
|
||||
std::string str = ss.str();
|
||||
str.erase(str.find_last_not_of(", ") + 1);
|
||||
return str;
|
||||
#endif //! NIOSTATS_CONTEXT
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "monitoring/perf_step_timer.h"
|
||||
#include "rocksdb/iostats_context.h"
|
||||
|
||||
#ifdef ROCKSDB_SUPPORT_THREAD_LOCAL
|
||||
#if defined(ROCKSDB_SUPPORT_THREAD_LOCAL) && !defined(NIOSTATS_CONTEXT)
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
extern __thread IOStatsContext iostats_context;
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -44,7 +44,7 @@ extern __thread IOStatsContext iostats_context;
|
||||
PerfLevel::kEnableTimeAndCPUTimeExceptForMutex); \
|
||||
iostats_step_timer_##metric.Start();
|
||||
|
||||
#else // ROCKSDB_SUPPORT_THREAD_LOCAL
|
||||
#else // ROCKSDB_SUPPORT_THREAD_LOCAL && !NIOSTATS_CONTEXT
|
||||
|
||||
#define IOSTATS_ADD(metric, value)
|
||||
#define IOSTATS_ADD_IF_POSITIVE(metric, value)
|
||||
@@ -57,4 +57,4 @@ extern __thread IOStatsContext iostats_context;
|
||||
#define IOSTATS_TIMER_GUARD(metric)
|
||||
#define IOSTATS_CPU_TIMER_GUARD(metric, clock) static_cast<void>(clock)
|
||||
|
||||
#endif // ROCKSDB_SUPPORT_THREAD_LOCAL
|
||||
#endif // ROCKSDB_SUPPORT_THREAD_LOCAL && !NIOSTATS_CONTEXT
|
||||
|
||||
@@ -9,26 +9,22 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
#if defined(NPERF_CONTEXT) || !defined(ROCKSDB_SUPPORT_THREAD_LOCAL)
|
||||
#if defined(NPERF_CONTEXT)
|
||||
// Should not be used because the counters are not thread-safe.
|
||||
// Put here just to make get_perf_context() simple without ifdef.
|
||||
PerfContext perf_context;
|
||||
#else
|
||||
#elif defined(ROCKSDB_SUPPORT_THREAD_LOCAL)
|
||||
#if defined(OS_SOLARIS)
|
||||
__thread PerfContext perf_context_;
|
||||
#else
|
||||
__thread PerfContext perf_context;
|
||||
#else // OS_SOLARIS
|
||||
thread_local PerfContext perf_context;
|
||||
#endif
|
||||
#endif // OS_SOLARIS
|
||||
#else
|
||||
#error "No thread-local support. Disable perf context with -DNPERF_CONTEXT."
|
||||
#endif
|
||||
|
||||
PerfContext* get_perf_context() {
|
||||
#if defined(NPERF_CONTEXT) || !defined(ROCKSDB_SUPPORT_THREAD_LOCAL)
|
||||
return &perf_context;
|
||||
#else
|
||||
#if defined(OS_SOLARIS)
|
||||
return &perf_context_;
|
||||
#else
|
||||
return &perf_context;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
PerfContext::~PerfContext() {
|
||||
|
||||
+46
-48
@@ -38,13 +38,9 @@ namespace ROCKSDB_NAMESPACE {
|
||||
// http://en.cppreference.com/w/cpp/concept/StandardLayoutType
|
||||
// https://gist.github.com/graphitemaster/494f21190bb2c63c5516
|
||||
#ifndef ROCKSDB_LITE
|
||||
static ColumnFamilyOptions dummy_cf_options;
|
||||
static ImmutableCFOptions dummy_cf_options;
|
||||
template <typename T1>
|
||||
int offset_of(T1 ColumnFamilyOptions::*member) {
|
||||
return int(size_t(&(dummy_cf_options.*member)) - size_t(&dummy_cf_options));
|
||||
}
|
||||
template <typename T1>
|
||||
int offset_of(T1 AdvancedColumnFamilyOptions::*member) {
|
||||
int offset_of(T1 ImmutableCFOptions::*member) {
|
||||
return int(size_t(&(dummy_cf_options.*member)) - size_t(&dummy_cf_options));
|
||||
}
|
||||
|
||||
@@ -492,57 +488,58 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
{0, OptionType::kBoolean, OptionVerificationType::kDeprecated,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"inplace_update_support",
|
||||
{offset_of(&ColumnFamilyOptions::inplace_update_support),
|
||||
{offset_of(&ImmutableCFOptions::inplace_update_support),
|
||||
OptionType::kBoolean, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"level_compaction_dynamic_level_bytes",
|
||||
{offset_of(&ColumnFamilyOptions::level_compaction_dynamic_level_bytes),
|
||||
{offset_of(&ImmutableCFOptions::level_compaction_dynamic_level_bytes),
|
||||
OptionType::kBoolean, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"optimize_filters_for_hits",
|
||||
{offset_of(&ColumnFamilyOptions::optimize_filters_for_hits),
|
||||
{offset_of(&ImmutableCFOptions::optimize_filters_for_hits),
|
||||
OptionType::kBoolean, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"force_consistency_checks",
|
||||
{offset_of(&ColumnFamilyOptions::force_consistency_checks),
|
||||
{offset_of(&ImmutableCFOptions::force_consistency_checks),
|
||||
OptionType::kBoolean, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"purge_redundant_kvs_while_flush",
|
||||
{offset_of(&ColumnFamilyOptions::purge_redundant_kvs_while_flush),
|
||||
{offset_of(&ImmutableCFOptions::purge_redundant_kvs_while_flush),
|
||||
OptionType::kBoolean, OptionVerificationType::kDeprecated,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"max_mem_compaction_level",
|
||||
{0, OptionType::kInt, OptionVerificationType::kDeprecated,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"max_write_buffer_number_to_maintain",
|
||||
{offset_of(&ColumnFamilyOptions::max_write_buffer_number_to_maintain),
|
||||
{offset_of(&ImmutableCFOptions::max_write_buffer_number_to_maintain),
|
||||
OptionType::kInt, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone, 0}},
|
||||
{"max_write_buffer_size_to_maintain",
|
||||
{offset_of(&ColumnFamilyOptions::max_write_buffer_size_to_maintain),
|
||||
{offset_of(&ImmutableCFOptions::max_write_buffer_size_to_maintain),
|
||||
OptionType::kInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"min_write_buffer_number_to_merge",
|
||||
{offset_of(&ColumnFamilyOptions::min_write_buffer_number_to_merge),
|
||||
{offset_of(&ImmutableCFOptions::min_write_buffer_number_to_merge),
|
||||
OptionType::kInt, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone, 0}},
|
||||
{"num_levels",
|
||||
{offset_of(&ColumnFamilyOptions::num_levels), OptionType::kInt,
|
||||
{offset_of(&ImmutableCFOptions::num_levels), OptionType::kInt,
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone}},
|
||||
{"bloom_locality",
|
||||
{offset_of(&ColumnFamilyOptions::bloom_locality), OptionType::kUInt32T,
|
||||
{offset_of(&ImmutableCFOptions::bloom_locality), OptionType::kUInt32T,
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone}},
|
||||
{"rate_limit_delay_max_milliseconds",
|
||||
{0, OptionType::kUInt, OptionVerificationType::kDeprecated,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"compression_per_level",
|
||||
OptionTypeInfo::Vector<CompressionType>(
|
||||
offset_of(&ColumnFamilyOptions::compression_per_level),
|
||||
offset_of(&ImmutableCFOptions::compression_per_level),
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone,
|
||||
{0, OptionType::kCompressionType})},
|
||||
{"comparator",
|
||||
{offset_of(&ColumnFamilyOptions::comparator), OptionType::kComparator,
|
||||
OptionVerificationType::kByName, OptionTypeFlags::kCompareLoose,
|
||||
{offset_of(&ImmutableCFOptions::user_comparator),
|
||||
OptionType::kComparator, OptionVerificationType::kByName,
|
||||
OptionTypeFlags::kCompareLoose,
|
||||
// Parses the string and sets the corresponding comparator
|
||||
[](const ConfigOptions& /*opts*/, const std::string& /*name*/,
|
||||
const std::string& value, char* addr) {
|
||||
@@ -558,15 +555,15 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
}}},
|
||||
{"memtable_insert_with_hint_prefix_extractor",
|
||||
{offset_of(
|
||||
&ColumnFamilyOptions::memtable_insert_with_hint_prefix_extractor),
|
||||
&ImmutableCFOptions::memtable_insert_with_hint_prefix_extractor),
|
||||
OptionType::kSliceTransform, OptionVerificationType::kByNameAllowNull,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"memtable_factory",
|
||||
{offset_of(&ColumnFamilyOptions::memtable_factory),
|
||||
{offset_of(&ImmutableCFOptions::memtable_factory),
|
||||
OptionType::kMemTableRepFactory, OptionVerificationType::kByName,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"memtable",
|
||||
{offset_of(&ColumnFamilyOptions::memtable_factory),
|
||||
{offset_of(&ImmutableCFOptions::memtable_factory),
|
||||
OptionType::kMemTableRepFactory, OptionVerificationType::kAlias,
|
||||
OptionTypeFlags::kNone,
|
||||
// Parses the value string and updates the memtable_factory
|
||||
@@ -582,13 +579,13 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
return s;
|
||||
}}},
|
||||
{"table_factory", OptionTypeInfo::AsCustomSharedPtr<TableFactory>(
|
||||
offset_of(&ColumnFamilyOptions::table_factory),
|
||||
offset_of(&ImmutableCFOptions::table_factory),
|
||||
OptionVerificationType::kByName,
|
||||
(OptionTypeFlags::kCompareLoose |
|
||||
OptionTypeFlags::kStringNameOnly |
|
||||
OptionTypeFlags::kDontPrepare))},
|
||||
{"block_based_table_factory",
|
||||
{offset_of(&ColumnFamilyOptions::table_factory),
|
||||
{offset_of(&ImmutableCFOptions::table_factory),
|
||||
OptionType::kCustomizable, OptionVerificationType::kAlias,
|
||||
OptionTypeFlags::kShared | OptionTypeFlags::kCompareLoose,
|
||||
// Parses the input value and creates a BlockBasedTableFactory
|
||||
@@ -620,7 +617,7 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
}
|
||||
}}},
|
||||
{"plain_table_factory",
|
||||
{offset_of(&ColumnFamilyOptions::table_factory),
|
||||
{offset_of(&ImmutableCFOptions::table_factory),
|
||||
OptionType::kCustomizable, OptionVerificationType::kAlias,
|
||||
OptionTypeFlags::kShared | OptionTypeFlags::kCompareLoose,
|
||||
// Parses the input value and creates a PlainTableFactory
|
||||
@@ -651,15 +648,15 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
}
|
||||
}}},
|
||||
{"compaction_filter",
|
||||
{offset_of(&ColumnFamilyOptions::compaction_filter),
|
||||
{offset_of(&ImmutableCFOptions::compaction_filter),
|
||||
OptionType::kCompactionFilter, OptionVerificationType::kByName,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"compaction_filter_factory",
|
||||
{offset_of(&ColumnFamilyOptions::compaction_filter_factory),
|
||||
{offset_of(&ImmutableCFOptions::compaction_filter_factory),
|
||||
OptionType::kCompactionFilterFactory, OptionVerificationType::kByName,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"merge_operator",
|
||||
{offset_of(&ColumnFamilyOptions::merge_operator),
|
||||
{offset_of(&ImmutableCFOptions::merge_operator),
|
||||
OptionType::kMergeOperator,
|
||||
OptionVerificationType::kByNameAllowFromNull,
|
||||
OptionTypeFlags::kCompareLoose,
|
||||
@@ -677,11 +674,11 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
return Status::OK();
|
||||
}}},
|
||||
{"compaction_style",
|
||||
{offset_of(&ColumnFamilyOptions::compaction_style),
|
||||
{offset_of(&ImmutableCFOptions::compaction_style),
|
||||
OptionType::kCompactionStyle, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"compaction_pri",
|
||||
{offset_of(&ColumnFamilyOptions::compaction_pri),
|
||||
{offset_of(&ImmutableCFOptions::compaction_pri),
|
||||
OptionType::kCompactionPri, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
};
|
||||
@@ -690,10 +687,9 @@ const std::string OptionsHelper::kCFOptionsName = "ColumnFamilyOptions";
|
||||
|
||||
class ConfigurableMutableCFOptions : public Configurable {
|
||||
public:
|
||||
ConfigurableMutableCFOptions(const MutableCFOptions& mcf) {
|
||||
explicit ConfigurableMutableCFOptions(const MutableCFOptions& mcf) {
|
||||
mutable_ = mcf;
|
||||
ConfigurableHelper::RegisterOptions(*this, &mutable_,
|
||||
&cf_mutable_options_type_info);
|
||||
RegisterOptions(&mutable_, &cf_mutable_options_type_info);
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -705,12 +701,10 @@ class ConfigurableCFOptions : public ConfigurableMutableCFOptions {
|
||||
ConfigurableCFOptions(const ColumnFamilyOptions& opts,
|
||||
const std::unordered_map<std::string, std::string>* map)
|
||||
: ConfigurableMutableCFOptions(MutableCFOptions(opts)),
|
||||
immutable_(opts),
|
||||
immutable_(ImmutableDBOptions(), opts),
|
||||
cf_options_(opts),
|
||||
opt_map_(map) {
|
||||
ConfigurableHelper::RegisterOptions(*this, OptionsHelper::kCFOptionsName,
|
||||
&immutable_,
|
||||
&cf_immutable_options_type_info);
|
||||
RegisterOptions(&immutable_, &cf_immutable_options_type_info);
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -774,7 +768,7 @@ class ConfigurableCFOptions : public ConfigurableMutableCFOptions {
|
||||
}
|
||||
|
||||
private:
|
||||
ColumnFamilyOptions immutable_;
|
||||
ImmutableCFOptions immutable_;
|
||||
ColumnFamilyOptions cf_options_;
|
||||
const std::unordered_map<std::string, std::string>* opt_map_;
|
||||
};
|
||||
@@ -792,18 +786,22 @@ std::unique_ptr<Configurable> CFOptionsAsConfigurable(
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
ImmutableCFOptions::ImmutableCFOptions() : ImmutableCFOptions(Options()) {}
|
||||
|
||||
ImmutableCFOptions::ImmutableCFOptions(const Options& options)
|
||||
: ImmutableCFOptions(ImmutableDBOptions(options), options) {}
|
||||
|
||||
ImmutableCFOptions::ImmutableCFOptions(const ImmutableDBOptions& db_options,
|
||||
const ColumnFamilyOptions& cf_options)
|
||||
: compaction_style(cf_options.compaction_style),
|
||||
: logger(db_options.info_log),
|
||||
stats(db_options.statistics),
|
||||
compaction_style(cf_options.compaction_style),
|
||||
compaction_pri(cf_options.compaction_pri),
|
||||
user_comparator(cf_options.comparator),
|
||||
internal_comparator(InternalKeyComparator(cf_options.comparator)),
|
||||
merge_operator(cf_options.merge_operator.get()),
|
||||
merge_operator(cf_options.merge_operator),
|
||||
compaction_filter(cf_options.compaction_filter),
|
||||
compaction_filter_factory(cf_options.compaction_filter_factory.get()),
|
||||
compaction_filter_factory(cf_options.compaction_filter_factory),
|
||||
min_write_buffer_number_to_merge(
|
||||
cf_options.min_write_buffer_number_to_merge),
|
||||
max_write_buffer_number_to_maintain(
|
||||
@@ -812,9 +810,9 @@ ImmutableCFOptions::ImmutableCFOptions(const ImmutableDBOptions& db_options,
|
||||
cf_options.max_write_buffer_size_to_maintain),
|
||||
inplace_update_support(cf_options.inplace_update_support),
|
||||
inplace_callback(cf_options.inplace_callback),
|
||||
info_log(db_options.info_log.get()),
|
||||
statistics(db_options.statistics.get()),
|
||||
rate_limiter(db_options.rate_limiter.get()),
|
||||
info_log(logger.get()),
|
||||
statistics(stats.get()),
|
||||
rate_limiter(db_options.rate_limiter),
|
||||
info_log_level(db_options.info_log_level),
|
||||
env(db_options.env),
|
||||
fs(db_options.fs.get()),
|
||||
@@ -822,8 +820,8 @@ ImmutableCFOptions::ImmutableCFOptions(const ImmutableDBOptions& db_options,
|
||||
allow_mmap_reads(db_options.allow_mmap_reads),
|
||||
allow_mmap_writes(db_options.allow_mmap_writes),
|
||||
db_paths(db_options.db_paths),
|
||||
memtable_factory(cf_options.memtable_factory.get()),
|
||||
table_factory(cf_options.table_factory.get()),
|
||||
memtable_factory(cf_options.memtable_factory),
|
||||
table_factory(cf_options.table_factory),
|
||||
table_properties_collector_factories(
|
||||
cf_options.table_properties_collector_factories),
|
||||
advise_random_on_open(db_options.advise_random_on_open),
|
||||
@@ -846,10 +844,10 @@ ImmutableCFOptions::ImmutableCFOptions(const ImmutableDBOptions& db_options,
|
||||
listeners(db_options.listeners),
|
||||
row_cache(db_options.row_cache),
|
||||
memtable_insert_with_hint_prefix_extractor(
|
||||
cf_options.memtable_insert_with_hint_prefix_extractor.get()),
|
||||
cf_options.memtable_insert_with_hint_prefix_extractor),
|
||||
cf_paths(cf_options.cf_paths),
|
||||
compaction_thread_limiter(cf_options.compaction_thread_limiter),
|
||||
file_checksum_gen_factory(db_options.file_checksum_gen_factory.get()),
|
||||
file_checksum_gen_factory(db_options.file_checksum_gen_factory),
|
||||
sst_partitioner_factory(cf_options.sst_partitioner_factory),
|
||||
allow_data_in_errors(db_options.allow_data_in_errors),
|
||||
db_host_id(db_options.db_host_id),
|
||||
|
||||
+35
-28
@@ -20,7 +20,12 @@ namespace ROCKSDB_NAMESPACE {
|
||||
// of DB. Raw pointers defined in this struct do not have ownership to the data
|
||||
// they point to. Options contains std::shared_ptr to these data.
|
||||
struct ImmutableCFOptions {
|
||||
private:
|
||||
std::shared_ptr<Logger> logger; // ImmutableDBOptions
|
||||
std::shared_ptr<Statistics> stats; // ImmutableDBOptions
|
||||
public:
|
||||
static const char* kName() { return "ImmutableCFOptions"; }
|
||||
explicit ImmutableCFOptions();
|
||||
explicit ImmutableCFOptions(const Options& options);
|
||||
|
||||
ImmutableCFOptions(const ImmutableDBOptions& db_options,
|
||||
@@ -31,13 +36,13 @@ struct ImmutableCFOptions {
|
||||
CompactionPri compaction_pri;
|
||||
|
||||
const Comparator* user_comparator;
|
||||
InternalKeyComparator internal_comparator;
|
||||
InternalKeyComparator internal_comparator; // Only in Immutable
|
||||
|
||||
MergeOperator* merge_operator;
|
||||
std::shared_ptr<MergeOperator> merge_operator;
|
||||
|
||||
const CompactionFilter* compaction_filter;
|
||||
|
||||
CompactionFilterFactory* compaction_filter_factory;
|
||||
std::shared_ptr<CompactionFilterFactory> compaction_filter_factory;
|
||||
|
||||
int min_write_buffer_number_to_merge;
|
||||
|
||||
@@ -52,36 +57,36 @@ struct ImmutableCFOptions {
|
||||
Slice delta_value,
|
||||
std::string* merged_value);
|
||||
|
||||
Logger* info_log;
|
||||
Logger* info_log; // ImmutableDBOptions
|
||||
|
||||
Statistics* statistics;
|
||||
Statistics* statistics; // ImmutableDBOptions
|
||||
|
||||
RateLimiter* rate_limiter;
|
||||
std::shared_ptr<RateLimiter> rate_limiter; // ImmutableDBOptions
|
||||
|
||||
InfoLogLevel info_log_level;
|
||||
InfoLogLevel info_log_level; // ImmutableDBOptions
|
||||
|
||||
Env* env;
|
||||
Env* env; // ImmutableDBOptions
|
||||
|
||||
FileSystem* fs;
|
||||
FileSystem* fs; // ImmutableDBOptions
|
||||
|
||||
SystemClock* clock;
|
||||
SystemClock* clock; // ImmutableDBOptions
|
||||
|
||||
// Allow the OS to mmap file for reading sst tables. Default: false
|
||||
bool allow_mmap_reads;
|
||||
bool allow_mmap_reads; // ImmutableDBOptions
|
||||
|
||||
// Allow the OS to mmap file for writing. Default: false
|
||||
bool allow_mmap_writes;
|
||||
bool allow_mmap_writes; // ImmutableDBOptions
|
||||
|
||||
std::vector<DbPath> db_paths;
|
||||
std::vector<DbPath> db_paths; // ImmutableDBOptions
|
||||
|
||||
MemTableRepFactory* memtable_factory;
|
||||
std::shared_ptr<MemTableRepFactory> memtable_factory;
|
||||
|
||||
TableFactory* table_factory;
|
||||
std::shared_ptr<TableFactory> table_factory;
|
||||
|
||||
Options::TablePropertiesCollectorFactories
|
||||
table_properties_collector_factories;
|
||||
|
||||
bool advise_random_on_open;
|
||||
bool advise_random_on_open; // ImmutableDBOptions
|
||||
|
||||
// This options is required by PlainTableReader. May need to move it
|
||||
// to PlainTableOptions just like bloom_bits_per_key
|
||||
@@ -89,15 +94,15 @@ struct ImmutableCFOptions {
|
||||
|
||||
bool purge_redundant_kvs_while_flush;
|
||||
|
||||
bool use_fsync;
|
||||
bool use_fsync; // ImmutableDBOptions
|
||||
|
||||
std::vector<CompressionType> compression_per_level;
|
||||
|
||||
bool level_compaction_dynamic_level_bytes;
|
||||
|
||||
Options::AccessHint access_hint_on_compaction_start;
|
||||
Options::AccessHint access_hint_on_compaction_start; // ImmutableDBOptions
|
||||
|
||||
bool new_table_reader_for_compaction_inputs;
|
||||
bool new_table_reader_for_compaction_inputs; // ImmutableDBOptions
|
||||
|
||||
int num_levels;
|
||||
|
||||
@@ -105,31 +110,33 @@ struct ImmutableCFOptions {
|
||||
|
||||
bool force_consistency_checks;
|
||||
|
||||
bool allow_ingest_behind;
|
||||
bool allow_ingest_behind; // ImmutableDBOptions
|
||||
|
||||
bool preserve_deletes;
|
||||
bool preserve_deletes; // ImmutableDBOptions
|
||||
|
||||
// A vector of EventListeners which callback functions will be called
|
||||
// when specific RocksDB event happens.
|
||||
std::vector<std::shared_ptr<EventListener>> listeners;
|
||||
std::vector<std::shared_ptr<EventListener>> listeners; // ImmutableDBOptions
|
||||
|
||||
std::shared_ptr<Cache> row_cache;
|
||||
std::shared_ptr<Cache> row_cache; // ImmutableDBOptions
|
||||
|
||||
const SliceTransform* memtable_insert_with_hint_prefix_extractor;
|
||||
std::shared_ptr<const SliceTransform>
|
||||
memtable_insert_with_hint_prefix_extractor;
|
||||
|
||||
std::vector<DbPath> cf_paths;
|
||||
|
||||
std::shared_ptr<ConcurrentTaskLimiter> compaction_thread_limiter;
|
||||
|
||||
FileChecksumGenFactory* file_checksum_gen_factory;
|
||||
std::shared_ptr<FileChecksumGenFactory>
|
||||
file_checksum_gen_factory; // ImmutableDBOptions
|
||||
|
||||
std::shared_ptr<SstPartitionerFactory> sst_partitioner_factory;
|
||||
|
||||
bool allow_data_in_errors;
|
||||
bool allow_data_in_errors; // ImmutableDBOptions
|
||||
|
||||
std::string db_host_id;
|
||||
std::string db_host_id; // ImmutableDBOptions
|
||||
|
||||
FileTypeSet checksum_handoff_file_types;
|
||||
FileTypeSet checksum_handoff_file_types; // ImmutableDBOptions
|
||||
};
|
||||
|
||||
struct MutableCFOptions {
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
void ConfigurableHelper::RegisterOptions(
|
||||
Configurable& configurable, const std::string& name, void* opt_ptr,
|
||||
void Configurable::RegisterOptions(
|
||||
const std::string& name, void* opt_ptr,
|
||||
const std::unordered_map<std::string, OptionTypeInfo>* type_map) {
|
||||
Configurable::RegisteredOptions opts;
|
||||
RegisteredOptions opts;
|
||||
opts.name = name;
|
||||
#ifndef ROCKSDB_LITE
|
||||
opts.type_map = type_map;
|
||||
@@ -28,7 +28,7 @@ void ConfigurableHelper::RegisterOptions(
|
||||
(void)type_map;
|
||||
#endif // ROCKSDB_LITE
|
||||
opts.opt_ptr = opt_ptr;
|
||||
configurable.options_.emplace_back(opts);
|
||||
options_.emplace_back(opts);
|
||||
}
|
||||
|
||||
//*************************************************************************
|
||||
|
||||
@@ -22,35 +22,6 @@ class ConfigurableHelper {
|
||||
public:
|
||||
constexpr static const char* kIdPropName = "id";
|
||||
constexpr static const char* kIdPropSuffix = ".id";
|
||||
// Registers the input name with the options and associated map.
|
||||
// When classes register their options in this manner, most of the
|
||||
// functionality (excluding unknown options and validate/prepare) is
|
||||
// implemented by the base class.
|
||||
//
|
||||
// This method should be called in the class constructor to register the
|
||||
// option set for this object. For example, to register the options
|
||||
// associated with the BlockBasedTableFactory, the constructor calls this
|
||||
// method passing in:
|
||||
// - the name of the options ("BlockBasedTableOptions");
|
||||
// - the options object (the BlockBasedTableOptions object for this object;
|
||||
// - the options type map for the BlockBasedTableOptions.
|
||||
// This registration allows the Configurable class to process the option
|
||||
// values associated with the BlockBasedTableOptions without further code in
|
||||
// the derived class.
|
||||
//
|
||||
// @param name The name of this set of options (@see GetOptionsPtr)
|
||||
// @param opt_ptr Pointer to the options to associate with this name
|
||||
// @param opt_map Options map that controls how this option is configured.
|
||||
template <typename T>
|
||||
static void RegisterOptions(
|
||||
Configurable& configurable, T* opt_ptr,
|
||||
const std::unordered_map<std::string, OptionTypeInfo>* opt_map) {
|
||||
RegisterOptions(configurable, T::kName(), opt_ptr, opt_map);
|
||||
}
|
||||
static void RegisterOptions(
|
||||
Configurable& configurable, const std::string& name, void* opt_ptr,
|
||||
const std::unordered_map<std::string, OptionTypeInfo>* opt_map);
|
||||
|
||||
// Configures the input Configurable object based on the parameters.
|
||||
// On successful completion, the Configurable is updated with the settings
|
||||
// from the opt_map.
|
||||
|
||||
@@ -78,18 +78,15 @@ class SimpleConfigurable : public TestConfigurable<Configurable> {
|
||||
: TestConfigurable(name, mode, map) {
|
||||
if ((mode & TestConfigMode::kUniqueMode) != 0) {
|
||||
unique_.reset(SimpleConfigurable::Create("Unique" + name_));
|
||||
ConfigurableHelper::RegisterOptions(*this, name_ + "Unique", &unique_,
|
||||
&unique_option_info);
|
||||
RegisterOptions(name_ + "Unique", &unique_, &unique_option_info);
|
||||
}
|
||||
if ((mode & TestConfigMode::kSharedMode) != 0) {
|
||||
shared_.reset(SimpleConfigurable::Create("Shared" + name_));
|
||||
ConfigurableHelper::RegisterOptions(*this, name_ + "Shared", &shared_,
|
||||
&shared_option_info);
|
||||
RegisterOptions(name_ + "Shared", &shared_, &shared_option_info);
|
||||
}
|
||||
if ((mode & TestConfigMode::kRawPtrMode) != 0) {
|
||||
pointer_ = SimpleConfigurable::Create("Pointer" + name_);
|
||||
ConfigurableHelper::RegisterOptions(*this, name_ + "Pointer", &pointer_,
|
||||
&pointer_option_info);
|
||||
RegisterOptions(name_ + "Pointer", &pointer_, &pointer_option_info);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,19 +247,15 @@ class ValidatedConfigurable : public SimpleConfigurable {
|
||||
: SimpleConfigurable(name, TestConfigMode::kDefaultMode),
|
||||
validated(false),
|
||||
prepared(0) {
|
||||
ConfigurableHelper::RegisterOptions(*this, "Validated", &validated,
|
||||
&validated_option_info);
|
||||
ConfigurableHelper::RegisterOptions(*this, "Prepared", &prepared,
|
||||
&prepared_option_info);
|
||||
RegisterOptions("Validated", &validated, &validated_option_info);
|
||||
RegisterOptions("Prepared", &prepared, &prepared_option_info);
|
||||
if ((mode & TestConfigMode::kUniqueMode) != 0) {
|
||||
unique_.reset(new ValidatedConfigurable(
|
||||
"Unique" + name_, TestConfigMode::kDefaultMode, false));
|
||||
if (dont_prepare) {
|
||||
ConfigurableHelper::RegisterOptions(*this, name_ + "Unique", &unique_,
|
||||
&dont_prepare_option_info);
|
||||
RegisterOptions(name_ + "Unique", &unique_, &dont_prepare_option_info);
|
||||
} else {
|
||||
ConfigurableHelper::RegisterOptions(*this, name_ + "Unique", &unique_,
|
||||
&unique_option_info);
|
||||
RegisterOptions(name_ + "Unique", &unique_, &unique_option_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -353,10 +346,8 @@ TEST_F(ConfigurableTest, MutableOptionsTest) {
|
||||
: SimpleConfigurable("mutable", TestConfigMode::kDefaultMode |
|
||||
TestConfigMode::kUniqueMode |
|
||||
TestConfigMode::kSharedMode) {
|
||||
ConfigurableHelper::RegisterOptions(*this, "struct", &options_,
|
||||
&struct_option_info);
|
||||
ConfigurableHelper::RegisterOptions(*this, "imm", &options_,
|
||||
&imm_option_info);
|
||||
RegisterOptions("struct", &options_, &struct_option_info);
|
||||
RegisterOptions("imm", &options_, &imm_option_info);
|
||||
}
|
||||
};
|
||||
MutableConfigurable mc;
|
||||
|
||||
@@ -112,11 +112,10 @@ class TestConfigurable : public Configurable {
|
||||
: name_(name), pointer_(nullptr) {
|
||||
prefix_ = "test." + name + ".";
|
||||
if ((mode & TestConfigMode::kSimpleMode) != 0) {
|
||||
ConfigurableHelper::RegisterOptions(*this, name_, &options_, map);
|
||||
RegisterOptions(name_, &options_, map);
|
||||
}
|
||||
if ((mode & TestConfigMode::kEnumMode) != 0) {
|
||||
ConfigurableHelper::RegisterOptions(*this, name_ + "Enum", &options_,
|
||||
&enum_option_info);
|
||||
RegisterOptions(name_ + "Enum", &options_, &enum_option_info);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include <unordered_map>
|
||||
|
||||
#include "options/configurable_helper.h"
|
||||
#include "options/options_helper.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/customizable.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
@@ -98,8 +98,9 @@ static std::unordered_map<std::string, OptionTypeInfo> a_option_info = {
|
||||
};
|
||||
class ACustomizable : public TestCustomizable {
|
||||
public:
|
||||
ACustomizable(const std::string& id) : TestCustomizable("A"), id_(id) {
|
||||
ConfigurableHelper::RegisterOptions(*this, "A", &opts_, &a_option_info);
|
||||
explicit ACustomizable(const std::string& id)
|
||||
: TestCustomizable("A"), id_(id) {
|
||||
RegisterOptions("A", &opts_, &a_option_info);
|
||||
}
|
||||
std::string GetId() const override { return id_; }
|
||||
static const char* kClassName() { return "A"; }
|
||||
@@ -141,8 +142,8 @@ static std::unordered_map<std::string, OptionTypeInfo> b_option_info = {
|
||||
class BCustomizable : public TestCustomizable {
|
||||
private:
|
||||
public:
|
||||
BCustomizable(const std::string& name) : TestCustomizable(name) {
|
||||
ConfigurableHelper::RegisterOptions(*this, name, &opts_, &b_option_info);
|
||||
explicit BCustomizable(const std::string& name) : TestCustomizable(name) {
|
||||
RegisterOptions(name, &opts_, &b_option_info);
|
||||
}
|
||||
static const char* kClassName() { return "B"; }
|
||||
|
||||
@@ -246,13 +247,12 @@ class SimpleConfigurable : public Configurable {
|
||||
|
||||
public:
|
||||
SimpleConfigurable() {
|
||||
ConfigurableHelper::RegisterOptions(*this, "simple", &simple_,
|
||||
&simple_option_info);
|
||||
RegisterOptions("simple", &simple_, &simple_option_info);
|
||||
}
|
||||
|
||||
SimpleConfigurable(
|
||||
explicit SimpleConfigurable(
|
||||
const std::unordered_map<std::string, OptionTypeInfo>* map) {
|
||||
ConfigurableHelper::RegisterOptions(*this, "simple", &simple_, map);
|
||||
RegisterOptions("simple", &simple_, map);
|
||||
}
|
||||
|
||||
bool IsPrepared() const override {
|
||||
@@ -509,8 +509,7 @@ class ShallowCustomizable : public Customizable {
|
||||
public:
|
||||
ShallowCustomizable() {
|
||||
inner_ = std::make_shared<ACustomizable>("a");
|
||||
ConfigurableHelper::RegisterOptions(*this, "inner", &inner_,
|
||||
&inner_option_info);
|
||||
RegisterOptions("inner", &inner_, &inner_option_info);
|
||||
};
|
||||
static const char* kClassName() { return "shallow"; }
|
||||
const char* Name() const override { return kClassName(); }
|
||||
@@ -641,10 +640,8 @@ TEST_F(CustomizableTest, MutableOptionsTest) {
|
||||
|
||||
public:
|
||||
MutableCustomizable() {
|
||||
ConfigurableHelper::RegisterOptions(*this, "mutable", &mutable_,
|
||||
&mutable_option_info);
|
||||
ConfigurableHelper::RegisterOptions(*this, "immutable", &immutable_,
|
||||
&immutable_option_info);
|
||||
RegisterOptions("mutable", &mutable_, &mutable_option_info);
|
||||
RegisterOptions("immutable", &immutable_, &immutable_option_info);
|
||||
}
|
||||
const char* Name() const override { return "MutableCustomizable"; }
|
||||
};
|
||||
|
||||
+11
-13
@@ -272,11 +272,11 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
{offsetof(struct ImmutableDBOptions, wal_dir), OptionType::kString,
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone}},
|
||||
{"WAL_size_limit_MB",
|
||||
{offsetof(struct ImmutableDBOptions, wal_size_limit_mb),
|
||||
{offsetof(struct ImmutableDBOptions, WAL_size_limit_MB),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"WAL_ttl_seconds",
|
||||
{offsetof(struct ImmutableDBOptions, wal_ttl_seconds),
|
||||
{offsetof(struct ImmutableDBOptions, WAL_ttl_seconds),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"max_manifest_file_size",
|
||||
@@ -435,10 +435,9 @@ const std::string OptionsHelper::kDBOptionsName = "DBOptions";
|
||||
|
||||
class MutableDBConfigurable : public Configurable {
|
||||
public:
|
||||
MutableDBConfigurable(const MutableDBOptions& mdb) {
|
||||
explicit MutableDBConfigurable(const MutableDBOptions& mdb) {
|
||||
mutable_ = mdb;
|
||||
ConfigurableHelper::RegisterOptions(*this, &mutable_,
|
||||
&db_mutable_options_type_info);
|
||||
RegisterOptions(&mutable_, &db_mutable_options_type_info);
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -447,7 +446,7 @@ class MutableDBConfigurable : public Configurable {
|
||||
|
||||
class DBOptionsConfigurable : public MutableDBConfigurable {
|
||||
public:
|
||||
DBOptionsConfigurable(const DBOptions& opts)
|
||||
explicit DBOptionsConfigurable(const DBOptions& opts)
|
||||
: MutableDBConfigurable(MutableDBOptions(opts)), db_options_(opts) {
|
||||
// The ImmutableDBOptions currently requires the env to be non-null. Make
|
||||
// sure it is
|
||||
@@ -458,8 +457,7 @@ class DBOptionsConfigurable : public MutableDBConfigurable {
|
||||
copy.env = Env::Default();
|
||||
immutable_ = ImmutableDBOptions(copy);
|
||||
}
|
||||
ConfigurableHelper::RegisterOptions(*this, &immutable_,
|
||||
&db_immutable_options_type_info);
|
||||
RegisterOptions(&immutable_, &db_immutable_options_type_info);
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -509,7 +507,6 @@ ImmutableDBOptions::ImmutableDBOptions(const DBOptions& options)
|
||||
track_and_verify_wals_in_manifest(
|
||||
options.track_and_verify_wals_in_manifest),
|
||||
env(options.env),
|
||||
fs(options.env->GetFileSystem()),
|
||||
rate_limiter(options.rate_limiter),
|
||||
sst_file_manager(options.sst_file_manager),
|
||||
info_log(options.info_log),
|
||||
@@ -526,8 +523,8 @@ ImmutableDBOptions::ImmutableDBOptions(const DBOptions& options)
|
||||
recycle_log_file_num(options.recycle_log_file_num),
|
||||
max_manifest_file_size(options.max_manifest_file_size),
|
||||
table_cache_numshardbits(options.table_cache_numshardbits),
|
||||
wal_ttl_seconds(options.WAL_ttl_seconds),
|
||||
wal_size_limit_mb(options.WAL_size_limit_MB),
|
||||
WAL_ttl_seconds(options.WAL_ttl_seconds),
|
||||
WAL_size_limit_MB(options.WAL_size_limit_MB),
|
||||
max_write_batch_group_size_bytes(
|
||||
options.max_write_batch_group_size_bytes),
|
||||
manifest_preallocation_size(options.manifest_preallocation_size),
|
||||
@@ -583,6 +580,7 @@ ImmutableDBOptions::ImmutableDBOptions(const DBOptions& options)
|
||||
allow_data_in_errors(options.allow_data_in_errors),
|
||||
db_host_id(options.db_host_id),
|
||||
checksum_handoff_file_types(options.checksum_handoff_file_types) {
|
||||
fs = env->GetFileSystem();
|
||||
if (env != nullptr) {
|
||||
clock = env->GetSystemClock().get();
|
||||
} else {
|
||||
@@ -650,10 +648,10 @@ void ImmutableDBOptions::Dump(Logger* log) const {
|
||||
table_cache_numshardbits);
|
||||
ROCKS_LOG_HEADER(log,
|
||||
" Options.WAL_ttl_seconds: %" PRIu64,
|
||||
wal_ttl_seconds);
|
||||
WAL_ttl_seconds);
|
||||
ROCKS_LOG_HEADER(log,
|
||||
" Options.WAL_size_limit_MB: %" PRIu64,
|
||||
wal_size_limit_mb);
|
||||
WAL_size_limit_MB);
|
||||
ROCKS_LOG_HEADER(log,
|
||||
" "
|
||||
"Options.max_write_batch_group_size_bytes: %" PRIu64,
|
||||
|
||||
@@ -26,8 +26,6 @@ struct ImmutableDBOptions {
|
||||
bool paranoid_checks;
|
||||
bool track_and_verify_wals_in_manifest;
|
||||
Env* env;
|
||||
std::shared_ptr<FileSystem> fs;
|
||||
SystemClock* clock;
|
||||
std::shared_ptr<RateLimiter> rate_limiter;
|
||||
std::shared_ptr<SstFileManager> sst_file_manager;
|
||||
std::shared_ptr<Logger> info_log;
|
||||
@@ -44,8 +42,8 @@ struct ImmutableDBOptions {
|
||||
size_t recycle_log_file_num;
|
||||
uint64_t max_manifest_file_size;
|
||||
int table_cache_numshardbits;
|
||||
uint64_t wal_ttl_seconds;
|
||||
uint64_t wal_size_limit_mb;
|
||||
uint64_t WAL_ttl_seconds;
|
||||
uint64_t WAL_size_limit_MB;
|
||||
uint64_t max_write_batch_group_size_bytes;
|
||||
size_t manifest_preallocation_size;
|
||||
bool allow_mmap_reads;
|
||||
@@ -96,6 +94,9 @@ struct ImmutableDBOptions {
|
||||
bool allow_data_in_errors;
|
||||
std::string db_host_id;
|
||||
FileTypeSet checksum_handoff_file_types;
|
||||
// Convenience/Helper objects that are not part of the base DBOptions
|
||||
std::shared_ptr<FileSystem> fs;
|
||||
SystemClock* clock;
|
||||
};
|
||||
|
||||
struct MutableDBOptions {
|
||||
|
||||
+107
-2
@@ -86,8 +86,8 @@ DBOptions BuildDBOptions(const ImmutableDBOptions& immutable_db_options,
|
||||
options.max_manifest_file_size = immutable_db_options.max_manifest_file_size;
|
||||
options.table_cache_numshardbits =
|
||||
immutable_db_options.table_cache_numshardbits;
|
||||
options.WAL_ttl_seconds = immutable_db_options.wal_ttl_seconds;
|
||||
options.WAL_size_limit_MB = immutable_db_options.wal_size_limit_mb;
|
||||
options.WAL_ttl_seconds = immutable_db_options.WAL_ttl_seconds;
|
||||
options.WAL_size_limit_MB = immutable_db_options.WAL_size_limit_MB;
|
||||
options.manifest_preallocation_size =
|
||||
immutable_db_options.manifest_preallocation_size;
|
||||
options.allow_mmap_reads = immutable_db_options.allow_mmap_reads;
|
||||
@@ -257,6 +257,111 @@ ColumnFamilyOptions BuildColumnFamilyOptions(
|
||||
|
||||
return cf_opts;
|
||||
}
|
||||
ColumnFamilyOptions BuildColumnFamilyOptions(const ImmutableCFOptions& ioptions,
|
||||
const MutableCFOptions& moptions) {
|
||||
ColumnFamilyOptions cf_opts;
|
||||
|
||||
// Memtable related options
|
||||
cf_opts.write_buffer_size = moptions.write_buffer_size;
|
||||
cf_opts.max_write_buffer_number = moptions.max_write_buffer_number;
|
||||
cf_opts.arena_block_size = moptions.arena_block_size;
|
||||
cf_opts.memtable_prefix_bloom_size_ratio =
|
||||
moptions.memtable_prefix_bloom_size_ratio;
|
||||
cf_opts.memtable_whole_key_filtering = moptions.memtable_whole_key_filtering;
|
||||
cf_opts.memtable_huge_page_size = moptions.memtable_huge_page_size;
|
||||
cf_opts.max_successive_merges = moptions.max_successive_merges;
|
||||
cf_opts.inplace_update_num_locks = moptions.inplace_update_num_locks;
|
||||
cf_opts.prefix_extractor = moptions.prefix_extractor;
|
||||
|
||||
// Compaction related options
|
||||
cf_opts.disable_auto_compactions = moptions.disable_auto_compactions;
|
||||
cf_opts.soft_pending_compaction_bytes_limit =
|
||||
moptions.soft_pending_compaction_bytes_limit;
|
||||
cf_opts.hard_pending_compaction_bytes_limit =
|
||||
moptions.hard_pending_compaction_bytes_limit;
|
||||
cf_opts.level0_file_num_compaction_trigger =
|
||||
moptions.level0_file_num_compaction_trigger;
|
||||
cf_opts.level0_slowdown_writes_trigger =
|
||||
moptions.level0_slowdown_writes_trigger;
|
||||
cf_opts.level0_stop_writes_trigger = moptions.level0_stop_writes_trigger;
|
||||
cf_opts.max_compaction_bytes = moptions.max_compaction_bytes;
|
||||
cf_opts.target_file_size_base = moptions.target_file_size_base;
|
||||
cf_opts.target_file_size_multiplier = moptions.target_file_size_multiplier;
|
||||
cf_opts.max_bytes_for_level_base = moptions.max_bytes_for_level_base;
|
||||
cf_opts.max_bytes_for_level_multiplier =
|
||||
moptions.max_bytes_for_level_multiplier;
|
||||
cf_opts.ttl = moptions.ttl;
|
||||
cf_opts.periodic_compaction_seconds = moptions.periodic_compaction_seconds;
|
||||
|
||||
cf_opts.max_bytes_for_level_multiplier_additional.clear();
|
||||
for (auto value : moptions.max_bytes_for_level_multiplier_additional) {
|
||||
cf_opts.max_bytes_for_level_multiplier_additional.emplace_back(value);
|
||||
}
|
||||
|
||||
cf_opts.compaction_options_fifo = moptions.compaction_options_fifo;
|
||||
cf_opts.compaction_options_universal = moptions.compaction_options_universal;
|
||||
|
||||
// Blob file related options
|
||||
cf_opts.enable_blob_files = moptions.enable_blob_files;
|
||||
cf_opts.min_blob_size = moptions.min_blob_size;
|
||||
cf_opts.blob_file_size = moptions.blob_file_size;
|
||||
cf_opts.blob_compression_type = moptions.blob_compression_type;
|
||||
cf_opts.enable_blob_garbage_collection =
|
||||
moptions.enable_blob_garbage_collection;
|
||||
cf_opts.blob_garbage_collection_age_cutoff =
|
||||
moptions.blob_garbage_collection_age_cutoff;
|
||||
|
||||
// Misc options
|
||||
cf_opts.max_sequential_skip_in_iterations =
|
||||
moptions.max_sequential_skip_in_iterations;
|
||||
cf_opts.check_flush_compaction_key_order =
|
||||
moptions.check_flush_compaction_key_order;
|
||||
cf_opts.paranoid_file_checks = moptions.paranoid_file_checks;
|
||||
cf_opts.report_bg_io_stats = moptions.report_bg_io_stats;
|
||||
cf_opts.compression = moptions.compression;
|
||||
cf_opts.compression_opts = moptions.compression_opts;
|
||||
cf_opts.bottommost_compression = moptions.bottommost_compression;
|
||||
cf_opts.bottommost_compression_opts = moptions.bottommost_compression_opts;
|
||||
cf_opts.sample_for_compression = moptions.sample_for_compression;
|
||||
|
||||
cf_opts.compaction_style = ioptions.compaction_style;
|
||||
cf_opts.compaction_pri = ioptions.compaction_pri;
|
||||
cf_opts.comparator = ioptions.user_comparator;
|
||||
cf_opts.merge_operator = ioptions.merge_operator;
|
||||
cf_opts.compaction_filter = ioptions.compaction_filter;
|
||||
cf_opts.compaction_filter_factory = ioptions.compaction_filter_factory;
|
||||
cf_opts.min_write_buffer_number_to_merge =
|
||||
ioptions.min_write_buffer_number_to_merge;
|
||||
cf_opts.max_write_buffer_number_to_maintain =
|
||||
ioptions.max_write_buffer_number_to_maintain;
|
||||
cf_opts.max_write_buffer_size_to_maintain =
|
||||
ioptions.max_write_buffer_size_to_maintain;
|
||||
cf_opts.inplace_update_support = ioptions.inplace_update_support;
|
||||
cf_opts.inplace_callback = ioptions.inplace_callback;
|
||||
cf_opts.memtable_factory = ioptions.memtable_factory;
|
||||
cf_opts.table_factory = ioptions.table_factory;
|
||||
cf_opts.table_properties_collector_factories =
|
||||
ioptions.table_properties_collector_factories;
|
||||
cf_opts.bloom_locality = ioptions.bloom_locality;
|
||||
cf_opts.purge_redundant_kvs_while_flush =
|
||||
ioptions.purge_redundant_kvs_while_flush;
|
||||
cf_opts.compression_per_level = ioptions.compression_per_level;
|
||||
cf_opts.level_compaction_dynamic_level_bytes =
|
||||
ioptions.level_compaction_dynamic_level_bytes;
|
||||
cf_opts.num_levels = ioptions.num_levels;
|
||||
cf_opts.optimize_filters_for_hits = ioptions.optimize_filters_for_hits;
|
||||
cf_opts.force_consistency_checks = ioptions.force_consistency_checks;
|
||||
cf_opts.memtable_insert_with_hint_prefix_extractor =
|
||||
ioptions.memtable_insert_with_hint_prefix_extractor;
|
||||
cf_opts.cf_paths = ioptions.cf_paths;
|
||||
cf_opts.compaction_thread_limiter = ioptions.compaction_thread_limiter;
|
||||
cf_opts.sst_partitioner_factory = ioptions.sst_partitioner_factory;
|
||||
|
||||
// TODO(yhchiang): find some way to handle the following derived options
|
||||
// * max_file_size
|
||||
|
||||
return cf_opts;
|
||||
}
|
||||
|
||||
std::map<CompactionStyle, std::string>
|
||||
OptionsHelper::compaction_style_to_string = {
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace ROCKSDB_NAMESPACE {
|
||||
struct ColumnFamilyOptions;
|
||||
struct ConfigOptions;
|
||||
struct DBOptions;
|
||||
struct ImmutableCFOptions;
|
||||
struct ImmutableDBOptions;
|
||||
struct MutableDBOptions;
|
||||
struct MutableCFOptions;
|
||||
@@ -37,6 +38,8 @@ DBOptions BuildDBOptions(const ImmutableDBOptions& immutable_db_options,
|
||||
ColumnFamilyOptions BuildColumnFamilyOptions(
|
||||
const ColumnFamilyOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options);
|
||||
ColumnFamilyOptions BuildColumnFamilyOptions(const ImmutableCFOptions& ioptions,
|
||||
const MutableCFOptions& moptions);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
std::unique_ptr<Configurable> DBOptionsAsConfigurable(
|
||||
|
||||
@@ -1298,6 +1298,36 @@ TEST_F(OptionsTest, OptionsComposeDecompose) {
|
||||
delete new_cf_opts.compaction_filter;
|
||||
}
|
||||
|
||||
TEST_F(OptionsTest, DBOptionsComposeImmutable) {
|
||||
// Build a DBOptions from an Immutable/Mutable one and verify that
|
||||
// we get same constituent options.
|
||||
ConfigOptions config_options;
|
||||
Random rnd(301);
|
||||
DBOptions base_opts, new_opts;
|
||||
test::RandomInitDBOptions(&base_opts, &rnd);
|
||||
MutableDBOptions m_opts(base_opts);
|
||||
ImmutableDBOptions i_opts(base_opts);
|
||||
new_opts = BuildDBOptions(i_opts, m_opts);
|
||||
ASSERT_OK(RocksDBOptionsParser::VerifyDBOptions(config_options, base_opts,
|
||||
new_opts));
|
||||
}
|
||||
|
||||
TEST_F(OptionsTest, CFOptionsComposeImmutable) {
|
||||
// Build a DBOptions from an Immutable/Mutable one and verify that
|
||||
// we get same constituent options.
|
||||
ConfigOptions config_options;
|
||||
Random rnd(301);
|
||||
ColumnFamilyOptions base_opts, new_opts;
|
||||
DBOptions dummy; // Needed to create ImmutableCFOptions
|
||||
test::RandomInitCFOptions(&base_opts, dummy, &rnd);
|
||||
MutableCFOptions m_opts(base_opts);
|
||||
ImmutableCFOptions i_opts(ImmutableDBOptions(dummy), base_opts);
|
||||
new_opts = BuildColumnFamilyOptions(i_opts, m_opts);
|
||||
ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(config_options, base_opts,
|
||||
new_opts));
|
||||
delete new_opts.compaction_filter;
|
||||
}
|
||||
|
||||
TEST_F(OptionsTest, ColumnFamilyOptionsSerialization) {
|
||||
Options options;
|
||||
ColumnFamilyOptions base_opt, new_opt;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user