mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 11:02:36 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df4d3cf6fd | ||
|
|
70a68ddc06 | ||
|
|
c79005a6b6 | ||
|
|
0500c49f62 | ||
|
|
0cf2c6aa3b | ||
|
|
b0e248e2a4 | ||
|
|
fdb3125547 | ||
|
|
128e36ca53 | ||
|
|
7bfad07194 | ||
|
|
0d9b256813 | ||
|
|
6b5e28a43c | ||
|
|
423538a816 | ||
|
|
9828b6d5fd | ||
|
|
0050a73a4f | ||
|
|
9918e1ee5a | ||
|
|
5383f1eec4 | ||
|
|
c1ec0b28eb | ||
|
|
7ac3a5d406 | ||
|
|
96d0773a11 | ||
|
|
c9818b3325 | ||
|
|
806d8916da | ||
|
|
e05c2bb549 | ||
|
|
e92a0ed040 | ||
|
|
08721293ea | ||
|
|
8a97c541e4 | ||
|
|
eca85cdb66 | ||
|
|
5455cacd18 | ||
|
|
297d913275 | ||
|
|
cd85439632 | ||
|
|
bd513fd075 | ||
|
|
c39a808cb6 | ||
|
|
653c392e47 | ||
|
|
f57745814f | ||
|
|
aec95b8c09 | ||
|
|
5486717ee2 | ||
|
|
79f4a04ee3 | ||
|
|
c879910102 | ||
|
|
80ac7412b5 | ||
|
|
ecf2bec613 | ||
|
|
9e4d56f2c9 | ||
|
|
94d99400dc | ||
|
|
d1f053b0ae | ||
|
|
66b31c5098 | ||
|
|
ce42ae6ffd | ||
|
|
a6a6aad74e | ||
|
|
88875df821 | ||
|
|
bf2f504188 | ||
|
|
930f2e92e6 | ||
|
|
77c7085594 | ||
|
|
9daf07305c | ||
|
|
44ac714808 | ||
|
|
7cd5835a28 | ||
|
|
04b2c16f9b | ||
|
|
924616526a | ||
|
|
29954b8b57 | ||
|
|
552256cb1a | ||
|
|
074562226f | ||
|
|
c712b68f5b | ||
|
|
e8b5d05e93 | ||
|
|
9c932816cf | ||
|
|
7aa31ba4a9 | ||
|
|
42fef0224f | ||
|
|
fc3a6eb74a | ||
|
|
735fe61e8f | ||
|
|
2a67d475f1 | ||
|
|
5384f0af6e | ||
|
|
ea3aa60dcc | ||
|
|
dc0ee3e5ca | ||
|
|
12e98add68 |
No files matched your search
+17
-5
@@ -24,6 +24,13 @@ commands:
|
||||
command: |
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install cmake
|
||||
|
||||
install-jdk8-on-macos:
|
||||
steps:
|
||||
- run:
|
||||
name: Install JDK 8 on macos
|
||||
command: |
|
||||
brew install --cask adoptopenjdk/openjdk/adoptopenjdk8
|
||||
|
||||
increase-max-open-files-on-macos:
|
||||
steps:
|
||||
- run:
|
||||
@@ -55,7 +62,7 @@ commands:
|
||||
pre-steps-macos:
|
||||
steps:
|
||||
- pre-steps:
|
||||
python-version: "3.6.0"
|
||||
python-version: "3.7.8"
|
||||
|
||||
post-steps:
|
||||
steps:
|
||||
@@ -144,8 +151,10 @@ executors:
|
||||
jobs:
|
||||
build-macos:
|
||||
macos:
|
||||
xcode: 11.3.0
|
||||
xcode: 12.5.1
|
||||
resource_class: large
|
||||
environment:
|
||||
DISABLE_JEMALLOC: 1 # jemalloc cause env_test hang, disable it for now
|
||||
steps:
|
||||
- increase-max-open-files-on-macos
|
||||
- install-pyenv-on-macos
|
||||
@@ -156,7 +165,7 @@ jobs:
|
||||
|
||||
build-macos-cmake:
|
||||
macos:
|
||||
xcode: 11.3.0
|
||||
xcode: 12.5.1
|
||||
resource_class: large
|
||||
steps:
|
||||
- increase-max-open-files-on-macos
|
||||
@@ -522,14 +531,16 @@ jobs:
|
||||
|
||||
build-macos-java:
|
||||
macos:
|
||||
xcode: 11.3.0
|
||||
xcode: 12.5.1
|
||||
resource_class: medium
|
||||
environment:
|
||||
JAVA_HOME: /Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home
|
||||
DISABLE_JEMALLOC: 1 # jemalloc causes java 8 crash, maybe a known issue
|
||||
steps:
|
||||
- increase-max-open-files-on-macos
|
||||
- install-pyenv-on-macos
|
||||
- install-gflags-on-macos
|
||||
- install-jdk8-on-macos
|
||||
- pre-steps-macos
|
||||
- run:
|
||||
name: "Set Java Environment"
|
||||
@@ -548,7 +559,7 @@ jobs:
|
||||
|
||||
build-macos-java-static:
|
||||
macos:
|
||||
xcode: 11.3.0
|
||||
xcode: 12.5.1
|
||||
resource_class: medium
|
||||
environment:
|
||||
JAVA_HOME: /Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home
|
||||
@@ -556,6 +567,7 @@ jobs:
|
||||
- increase-max-open-files-on-macos
|
||||
- install-pyenv-on-macos
|
||||
- install-gflags-on-macos
|
||||
- install-jdk8-on-macos
|
||||
- install-cmake-on-macos
|
||||
- pre-steps-macos
|
||||
- run:
|
||||
|
||||
+28
-1
@@ -636,6 +636,7 @@ find_package(Threads REQUIRED)
|
||||
set(SOURCES
|
||||
cache/cache.cc
|
||||
cache/cache_entry_roles.cc
|
||||
cache/cache_key.cc
|
||||
cache/cache_reservation_manager.cc
|
||||
cache/clock_cache.cc
|
||||
cache/lru_cache.cc
|
||||
@@ -745,6 +746,7 @@ set(SOURCES
|
||||
memory/concurrent_arena.cc
|
||||
memory/jemalloc_nodump_allocator.cc
|
||||
memory/memkind_kmem_allocator.cc
|
||||
memory/memory_allocator.cc
|
||||
memtable/alloc_tracker.cc
|
||||
memtable/hash_linklist_rep.cc
|
||||
memtable/hash_skiplist_rep.cc
|
||||
@@ -938,6 +940,31 @@ list(APPEND SOURCES
|
||||
utilities/transactions/lock/range/range_tree/lib/util/dbt.cc
|
||||
utilities/transactions/lock/range/range_tree/lib/util/memarena.cc)
|
||||
|
||||
message(STATUS "ROCKSDB_PLUGINS: ${ROCKSDB_PLUGINS}")
|
||||
if ( ROCKSDB_PLUGINS )
|
||||
string(REPLACE " " ";" PLUGINS ${ROCKSDB_PLUGINS})
|
||||
foreach (plugin ${PLUGINS})
|
||||
add_subdirectory("plugin/${plugin}")
|
||||
foreach (src ${${plugin}_SOURCES})
|
||||
list(APPEND SOURCES plugin/${plugin}/${src})
|
||||
set_source_files_properties(
|
||||
plugin/${plugin}/${src}
|
||||
PROPERTIES COMPILE_FLAGS "${${plugin}_COMPILE_FLAGS}")
|
||||
endforeach()
|
||||
foreach (path ${${plugin}_INCLUDE_PATHS})
|
||||
include_directories(${path})
|
||||
endforeach()
|
||||
foreach (lib ${${plugin}_LIBS})
|
||||
list(APPEND THIRDPARTY_LIBS ${lib})
|
||||
endforeach()
|
||||
foreach (link_path ${${plugin}_LINK_PATHS})
|
||||
link_directories(AFTER ${link_path})
|
||||
endforeach()
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${${plugin}_CMAKE_SHARED_LINKER_FLAGS}")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${${plugin}_CMAKE_EXE_LINKER_FLAGS}")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
if(HAVE_SSE42 AND NOT MSVC)
|
||||
set_source_files_properties(
|
||||
util/crc32c.cc
|
||||
@@ -1244,7 +1271,7 @@ if(WITH_TESTS)
|
||||
logging/env_logger_test.cc
|
||||
logging/event_logger_test.cc
|
||||
memory/arena_test.cc
|
||||
memory/memkind_kmem_allocator_test.cc
|
||||
memory/memory_allocator_test.cc
|
||||
memtable/inlineskiplist_test.cc
|
||||
memtable/skiplist_test.cc
|
||||
memtable/write_buffer_manager_test.cc
|
||||
|
||||
+39
-1
@@ -1,4 +1,38 @@
|
||||
# Rocksdb Change Log
|
||||
## 6.28.2 (2022-01-31)
|
||||
### Bug Fixes
|
||||
* Fixed a major bug in which batched MultiGet could return old values for keys deleted by DeleteRange when memtable Bloom filter is enabled (memtable_prefix_bloom_size_ratio > 0). (The fix includes a substantial MultiGet performance improvement in the unusual case of both memtable_whole_key_filtering and prefix_extractor.)
|
||||
|
||||
## 6.28.1 (2022-01-10)
|
||||
### Bug Fixes
|
||||
* Fixed compilation errors on newer compiler, e.g. clang-12
|
||||
|
||||
## 6.28.0 (2021-12-17)
|
||||
### New Features
|
||||
* Introduced 'CommitWithTimestamp' as a new tag. Currently, there is no API for user to trigger a write with this tag to the WAL. This is part of the efforts to support write-commited transactions with user-defined timestamps.
|
||||
|
||||
### Bug Fixes
|
||||
* Fixed a bug in rocksdb automatic implicit prefetching which got broken because of new feature adaptive_readahead and internal prefetching got disabled when iterator moves from one file to next.
|
||||
* Fixed a bug in TableOptions.prepopulate_block_cache which causes segmentation fault when used with TableOptions.partition_filters = true and TableOptions.cache_index_and_filter_blocks = true.
|
||||
* Fixed a bug affecting custom memtable factories which are not registered with the `ObjectRegistry`. The bug could result in failure to save the OPTIONS file.
|
||||
* Fixed a bug causing two duplicate entries to be appended to a file opened in non-direct mode and tracked by `FaultInjectionTestFS`.
|
||||
* Fixed a bug in TableOptions.prepopulate_block_cache to support block-based filters also.
|
||||
* Block cache keys no longer use `FSRandomAccessFile::GetUniqueId()` (previously used when available), so a filesystem recycling unique ids can no longer lead to incorrect result or crash (#7405). For files generated by RocksDB >= 6.24, the cache keys are stable across DB::Open and DB directory move / copy / import / export / migration, etc. Although collisions are still theoretically possible, they are (a) impossible in many common cases, (b) not dependent on environmental factors, and (c) much less likely than a CPU miscalculation while executing RocksDB.
|
||||
|
||||
### Behavior Changes
|
||||
* MemTableList::TrimHistory now use allocated bytes when max_write_buffer_size_to_maintain > 0(default in TrasactionDB, introduced in PR#5022) Fix #8371.
|
||||
|
||||
### Public API change
|
||||
* Extend WriteBatch::AssignTimestamp and AssignTimestamps API so that both functions can accept an optional `checker` argument that performs additional checking on timestamp sizes.
|
||||
* Introduce a new EventListener callback that will be called upon the end of automatic error recovery.
|
||||
|
||||
### Performance Improvements
|
||||
* Replaced map property `TableProperties::properties_offsets` with uint64_t property `external_sst_file_global_seqno_offset` to save table properties's memory.
|
||||
* Block cache accesses are faster by RocksDB using cache keys of fixed size (16 bytes).
|
||||
|
||||
### Java API Changes
|
||||
* Removed Java API `TableProperties.getPropertiesOffsets()` as it exposed internal details to external users.
|
||||
|
||||
## 6.27.0 (2021-11-19)
|
||||
### New Features
|
||||
* Added new ChecksumType kXXH3 which is faster than kCRC32c on almost all x86\_64 hardware.
|
||||
@@ -24,6 +58,7 @@
|
||||
* Users who configured a dedicated thread pool for bottommost compactions by explicitly adding threads to the `Env::Priority::BOTTOM` pool will no longer see RocksDB schedule automatic compactions exceeding the DB's compaction concurrency limit. For details on per-DB compaction concurrency limit, see API docs of `max_background_compactions` and `max_background_jobs`.
|
||||
* Fixed a bug of background flush thread picking more memtables to flush and prematurely advancing column family's log_number.
|
||||
* Fixed an assertion failure in ManifestTailer.
|
||||
* Fixed a bug that could, with WAL enabled, cause backups, checkpoints, and `GetSortedWalFiles()` to fail randomly with an error like `IO error: 001234.log: No such file or directory`
|
||||
|
||||
### Behavior Changes
|
||||
* `NUM_FILES_IN_SINGLE_COMPACTION` was only counting the first input level files, now it's including all input files.
|
||||
@@ -32,7 +67,7 @@
|
||||
|
||||
### Public API change
|
||||
* When options.ttl is used with leveled compaction with compactinon priority kMinOverlappingRatio, files exceeding half of TTL value will be prioritized more, so that by the time TTL is reached, fewer extra compactions will be scheduled to clear them up. At the same time, when compacting files with data older than half of TTL, output files may be cut off based on those files' boundaries, in order for the early TTL compaction to work properly.
|
||||
* Made FileSystem extend the Customizable class and added a CreateFromString method. Implementations need to be registered with the ObjectRegistry and to implement a Name() method in order to be created via this method.
|
||||
* Made FileSystem and RateLimiter extend the Customizable class and added a CreateFromString method. Implementations need to be registered with the ObjectRegistry and to implement a Name() method in order to be created via this method.
|
||||
* Clarified in API comments that RocksDB is not exception safe for callbacks and custom extensions. An exception propagating into RocksDB can lead to undefined behavior, including data loss, unreported corruption, deadlocks, and more.
|
||||
* Marked `WriteBufferManager` as `final` because it is not intended for extension.
|
||||
* Removed unimportant implementation details from table_properties.h
|
||||
@@ -45,6 +80,9 @@
|
||||
### Performance Improvements
|
||||
* Released some memory related to filter construction earlier in `BlockBasedTableBuilder` for `FullFilter` and `PartitionedFilter` case (#9070)
|
||||
|
||||
### Behavior Changes
|
||||
* `NUM_FILES_IN_SINGLE_COMPACTION` was only counting the first input level files, now it's including all input files.
|
||||
|
||||
## 6.26.0 (2021-10-20)
|
||||
### Bug Fixes
|
||||
* Fixes a bug in directed IO mode when calling MultiGet() for blobs in the same blob file. The bug is caused by not sorting the blob read requests by file offsets.
|
||||
|
||||
@@ -249,7 +249,20 @@ ROCKSDB_PLUGIN_MKS = $(foreach plugin, $(ROCKSDB_PLUGINS), plugin/$(plugin)/*.mk
|
||||
include $(ROCKSDB_PLUGIN_MKS)
|
||||
ROCKSDB_PLUGIN_SOURCES = $(foreach plugin, $(ROCKSDB_PLUGINS), $(foreach source, $($(plugin)_SOURCES), plugin/$(plugin)/$(source)))
|
||||
ROCKSDB_PLUGIN_HEADERS = $(foreach plugin, $(ROCKSDB_PLUGINS), $(foreach header, $($(plugin)_HEADERS), plugin/$(plugin)/$(header)))
|
||||
ROCKSDB_PLUGIN_PKGCONFIG_REQUIRES = $(foreach plugin, $(ROCKSDB_PLUGINS), $($(plugin)_PKGCONFIG_REQUIRES))
|
||||
PLATFORM_LDFLAGS += $(foreach plugin, $(ROCKSDB_PLUGINS), $($(plugin)_LDFLAGS))
|
||||
CXXFLAGS += $(foreach plugin, $(ROCKSDB_PLUGINS), $($(plugin)_CXXFLAGS))
|
||||
|
||||
ifneq ($(ROCKSDB_PLUGIN_PKGCONFIG_REQUIRES),)
|
||||
LDFLAGS := $(LDFLAGS) $(shell pkg-config --libs $(ROCKSDB_PLUGIN_PKGCONFIG_REQUIRES))
|
||||
ifneq ($(.SHELLSTATUS),0)
|
||||
$(error pkg-config failed)
|
||||
endif
|
||||
CXXFLAGS := $(CXXFLAGS) $(shell pkg-config --cflags $(ROCKSDB_PLUGIN_PKGCONFIG_REQUIRES))
|
||||
ifneq ($(.SHELLSTATUS),0)
|
||||
$(error pkg-config failed)
|
||||
endif
|
||||
endif
|
||||
|
||||
export JAVAC_ARGS
|
||||
CLEAN_FILES += make_config.mk rocksdb.pc
|
||||
@@ -556,7 +569,6 @@ ifdef ASSERT_STATUS_CHECKED
|
||||
# TODO: finish fixing all tests to pass this check
|
||||
TESTS_FAILING_ASC = \
|
||||
c_test \
|
||||
db_test \
|
||||
env_test \
|
||||
range_locking_test \
|
||||
testutil_test \
|
||||
@@ -1349,7 +1361,7 @@ db_repl_stress: $(OBJ_DIR)/tools/db_repl_stress.o $(LIBRARY)
|
||||
arena_test: $(OBJ_DIR)/memory/arena_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
memkind_kmem_allocator_test: memory/memkind_kmem_allocator_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
memory_allocator_test: memory/memory_allocator_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
autovector_test: $(OBJ_DIR)/util/autovector_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
@@ -2011,6 +2023,7 @@ gen-pc:
|
||||
-echo 'Libs: -L$${libdir} $(EXEC_LDFLAGS) -lrocksdb' >> rocksdb.pc
|
||||
-echo 'Libs.private: $(PLATFORM_LDFLAGS)' >> rocksdb.pc
|
||||
-echo 'Cflags: -I$${includedir} $(PLATFORM_CXXFLAGS)' >> rocksdb.pc
|
||||
-echo 'Requires: $(subst ",,$(ROCKSDB_PLUGIN_PKGCONFIG_REQUIRES))' >> rocksdb.pc
|
||||
|
||||
#-------------------------------------------------
|
||||
|
||||
|
||||
@@ -143,6 +143,7 @@ cpp_library(
|
||||
srcs = [
|
||||
"cache/cache.cc",
|
||||
"cache/cache_entry_roles.cc",
|
||||
"cache/cache_key.cc",
|
||||
"cache/cache_reservation_manager.cc",
|
||||
"cache/clock_cache.cc",
|
||||
"cache/lru_cache.cc",
|
||||
@@ -255,6 +256,7 @@ cpp_library(
|
||||
"memory/concurrent_arena.cc",
|
||||
"memory/jemalloc_nodump_allocator.cc",
|
||||
"memory/memkind_kmem_allocator.cc",
|
||||
"memory/memory_allocator.cc",
|
||||
"memtable/alloc_tracker.cc",
|
||||
"memtable/hash_linklist_rep.cc",
|
||||
"memtable/hash_skiplist_rep.cc",
|
||||
@@ -462,9 +464,8 @@ cpp_library(
|
||||
os_deps = ROCKSDB_OS_DEPS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
unexported_deps_by_default = False,
|
||||
deps = [],
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
exported_deps = [],
|
||||
exported_external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
)
|
||||
|
||||
cpp_library(
|
||||
@@ -472,6 +473,7 @@ cpp_library(
|
||||
srcs = [
|
||||
"cache/cache.cc",
|
||||
"cache/cache_entry_roles.cc",
|
||||
"cache/cache_key.cc",
|
||||
"cache/cache_reservation_manager.cc",
|
||||
"cache/clock_cache.cc",
|
||||
"cache/lru_cache.cc",
|
||||
@@ -584,6 +586,7 @@ cpp_library(
|
||||
"memory/concurrent_arena.cc",
|
||||
"memory/jemalloc_nodump_allocator.cc",
|
||||
"memory/memkind_kmem_allocator.cc",
|
||||
"memory/memory_allocator.cc",
|
||||
"memtable/alloc_tracker.cc",
|
||||
"memtable/hash_linklist_rep.cc",
|
||||
"memtable/hash_skiplist_rep.cc",
|
||||
@@ -791,9 +794,8 @@ cpp_library(
|
||||
os_deps = ROCKSDB_OS_DEPS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
unexported_deps_by_default = False,
|
||||
deps = [],
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
exported_deps = [],
|
||||
exported_external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
)
|
||||
|
||||
cpp_library(
|
||||
@@ -816,9 +818,8 @@ cpp_library(
|
||||
os_deps = ROCKSDB_OS_DEPS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
unexported_deps_by_default = False,
|
||||
deps = [":rocksdb_lib"],
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS + [
|
||||
exported_deps = [":rocksdb_lib"],
|
||||
exported_external_deps = ROCKSDB_EXTERNAL_DEPS + [
|
||||
("googletest", None, "gtest"),
|
||||
],
|
||||
)
|
||||
@@ -840,9 +841,8 @@ cpp_library(
|
||||
os_deps = ROCKSDB_OS_DEPS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
unexported_deps_by_default = False,
|
||||
deps = [":rocksdb_lib"],
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
exported_deps = [":rocksdb_lib"],
|
||||
exported_external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
)
|
||||
|
||||
cpp_library(
|
||||
@@ -856,9 +856,8 @@ cpp_library(
|
||||
os_deps = ROCKSDB_OS_DEPS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
unexported_deps_by_default = False,
|
||||
deps = [":rocksdb_lib"],
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
exported_deps = [":rocksdb_lib"],
|
||||
exported_external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
)
|
||||
|
||||
cpp_library(
|
||||
@@ -871,9 +870,11 @@ cpp_library(
|
||||
"db_stress_tool/db_stress_gflags.cc",
|
||||
"db_stress_tool/db_stress_listener.cc",
|
||||
"db_stress_tool/db_stress_shared_state.cc",
|
||||
"db_stress_tool/db_stress_stat.cc",
|
||||
"db_stress_tool/db_stress_test_base.cc",
|
||||
"db_stress_tool/db_stress_tool.cc",
|
||||
"db_stress_tool/expected_state.cc",
|
||||
"db_stress_tool/multi_ops_txns_stress.cc",
|
||||
"db_stress_tool/no_batched_ops_stress.cc",
|
||||
"test_util/testutil.cc",
|
||||
"tools/block_cache_analyzer/block_cache_trace_analyzer.cc",
|
||||
@@ -886,9 +887,8 @@ cpp_library(
|
||||
os_deps = ROCKSDB_OS_DEPS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
unexported_deps_by_default = False,
|
||||
deps = ROCKSDB_LIB_DEPS,
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
exported_deps = ROCKSDB_LIB_DEPS,
|
||||
exported_external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
)
|
||||
|
||||
cpp_binary(
|
||||
@@ -922,9 +922,8 @@ cpp_library(
|
||||
os_deps = ROCKSDB_OS_DEPS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
unexported_deps_by_default = False,
|
||||
deps = [":rocksdb_test_lib"],
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
exported_deps = [":rocksdb_test_lib"],
|
||||
exported_external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
)
|
||||
|
||||
# [test_name, test_src, test_type, extra_deps, extra_compiler_flags]
|
||||
@@ -1777,8 +1776,8 @@ ROCKS_TESTS = [
|
||||
[],
|
||||
],
|
||||
[
|
||||
"memkind_kmem_allocator_test",
|
||||
"memory/memkind_kmem_allocator_test.cc",
|
||||
"memory_allocator_test",
|
||||
"memory/memory_allocator_test.cc",
|
||||
"parallel",
|
||||
[],
|
||||
[],
|
||||
|
||||
@@ -159,9 +159,8 @@ cpp_library(
|
||||
os_deps = ROCKSDB_OS_DEPS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
unexported_deps_by_default = False,
|
||||
deps = [{deps}],
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS{extra_external_deps},
|
||||
exported_deps = [{deps}],
|
||||
exported_external_deps = ROCKSDB_EXTERNAL_DEPS{extra_external_deps},
|
||||
)
|
||||
"""
|
||||
|
||||
@@ -176,9 +175,8 @@ cpp_library(
|
||||
os_deps = ROCKSDB_OS_DEPS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
unexported_deps_by_default = False,
|
||||
deps = ROCKSDB_LIB_DEPS,
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
exported_deps = ROCKSDB_LIB_DEPS,
|
||||
exported_external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
)
|
||||
"""
|
||||
|
||||
|
||||
+34
-10
@@ -1842,7 +1842,6 @@ sub start_another_job {
|
||||
}
|
||||
|
||||
$opt::min_progress_interval = 0;
|
||||
$opt::progress_sep = "\r";
|
||||
|
||||
sub init_progress {
|
||||
# Uses:
|
||||
@@ -1852,7 +1851,6 @@ sub init_progress {
|
||||
$|=1;
|
||||
if (not $Global::is_terminal) {
|
||||
$opt::min_progress_interval = 30;
|
||||
$opt::progress_sep = "\n";
|
||||
}
|
||||
if($opt::bar) {
|
||||
return("","");
|
||||
@@ -1878,7 +1876,9 @@ sub drain_job_queue {
|
||||
}
|
||||
my $last_header="";
|
||||
my $sleep = 0.2;
|
||||
my $last_left = 1000000000;
|
||||
my $last_progress_time = 0;
|
||||
my $ps_reported = 0;
|
||||
do {
|
||||
while($Global::total_running > 0) {
|
||||
debug($Global::total_running, "==", scalar
|
||||
@@ -1889,15 +1889,38 @@ sub drain_job_queue {
|
||||
close $job->fh(0,"w");
|
||||
}
|
||||
}
|
||||
if($opt::progress and (time() - $last_progress_time) >= $opt::min_progress_interval) {
|
||||
$last_progress_time = time();
|
||||
# When not connected to terminal, assume CI (e.g. CircleCI). In
|
||||
# that case we want occasional progress output to prevent abort
|
||||
# due to timeout with no output, but we also need to stop sending
|
||||
# progress output if there has been no actual progress, so that
|
||||
# the job can time out appropriately (CirecleCI: 10m) in case of
|
||||
# a hung test. But without special output, it is extremely
|
||||
# annoying to diagnose which test is hung, so we add that using
|
||||
# `ps` below.
|
||||
if($opt::progress and
|
||||
($Global::is_terminal or (time() - $last_progress_time) >= 30)) {
|
||||
my %progress = progress();
|
||||
if($last_header ne $progress{'header'}) {
|
||||
print $Global::original_stderr "\n", $progress{'header'}, "\n";
|
||||
$last_header = $progress{'header'};
|
||||
}
|
||||
print $Global::original_stderr $opt::progress_sep,$progress{'status'};
|
||||
flush $Global::original_stderr;
|
||||
if ($Global::is_terminal) {
|
||||
print $Global::original_stderr "\r",$progress{'status'};
|
||||
}
|
||||
if ($last_left > $Global::left) {
|
||||
if (not $Global::is_terminal) {
|
||||
print $Global::original_stderr $progress{'status'},"\n";
|
||||
}
|
||||
$last_progress_time = time();
|
||||
$ps_reported = 0;
|
||||
} elsif (not $ps_reported and (time() - $last_progress_time) >= 60) {
|
||||
# No progress in at least 60 seconds: run ps
|
||||
print $Global::original_stderr "\n";
|
||||
system("ps", "-wf");
|
||||
$ps_reported = 1;
|
||||
}
|
||||
$last_left = $Global::left;
|
||||
flush $Global::original_stderr;
|
||||
}
|
||||
if($Global::total_running < $Global::max_jobs_running
|
||||
and not $Global::JobQueue->empty()) {
|
||||
@@ -1964,10 +1987,11 @@ sub progress {
|
||||
my $eta = "";
|
||||
my ($status,$header)=("","");
|
||||
if($opt::eta) {
|
||||
my($total, $completed, $left, $pctcomplete, $avgtime, $this_eta) =
|
||||
compute_eta();
|
||||
$eta = sprintf("ETA: %ds Left: %d AVG: %.2fs ",
|
||||
$this_eta, $left, $avgtime);
|
||||
my($total, $completed, $left, $pctcomplete, $avgtime, $this_eta) =
|
||||
compute_eta();
|
||||
$eta = sprintf("ETA: %ds Left: %d AVG: %.2fs ",
|
||||
$this_eta, $left, $avgtime);
|
||||
$Global::left = $left;
|
||||
}
|
||||
my $termcols = terminal_columns();
|
||||
my @workers = sort keys %Global::host;
|
||||
|
||||
Vendored
+224
-2
@@ -5,11 +5,14 @@
|
||||
|
||||
#ifdef GFLAGS
|
||||
#include <cinttypes>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "monitoring/histogram.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/cache.h"
|
||||
@@ -18,6 +21,8 @@
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
#include "rocksdb/system_clock.h"
|
||||
#include "rocksdb/table_properties.h"
|
||||
#include "table/block_based/block_based_table_reader.h"
|
||||
#include "table/block_based/cachable_entry.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/gflags_compat.h"
|
||||
@@ -73,6 +78,36 @@ static class std::shared_ptr<ROCKSDB_NAMESPACE::SecondaryCache> secondary_cache;
|
||||
|
||||
DEFINE_bool(use_clock_cache, false, "");
|
||||
|
||||
// ## BEGIN stress_cache_key sub-tool options ##
|
||||
DEFINE_bool(stress_cache_key, false,
|
||||
"If true, run cache key stress test instead");
|
||||
DEFINE_uint32(sck_files_per_day, 2500000,
|
||||
"(-stress_cache_key) Simulated files generated per day");
|
||||
DEFINE_uint32(sck_duration, 90,
|
||||
"(-stress_cache_key) Number of days to simulate in each run");
|
||||
DEFINE_uint32(
|
||||
sck_min_collision, 15,
|
||||
"(-stress_cache_key) Keep running until this many collisions seen");
|
||||
DEFINE_uint32(
|
||||
sck_file_size_mb, 32,
|
||||
"(-stress_cache_key) Simulated file size in MiB, for accounting purposes");
|
||||
DEFINE_uint32(sck_reopen_nfiles, 100,
|
||||
"(-stress_cache_key) Re-opens DB average every n files");
|
||||
DEFINE_uint32(
|
||||
sck_restarts_per_day, 24,
|
||||
"(-stress_cache_key) Simulated process restarts per day (across DBs)");
|
||||
DEFINE_uint32(sck_db_count, 100,
|
||||
"(-stress_cache_key) Parallel DBs in operation");
|
||||
DEFINE_uint32(sck_table_bits, 20,
|
||||
"(-stress_cache_key) Log2 number of tracked files");
|
||||
DEFINE_uint32(sck_keep_bits, 50,
|
||||
"(-stress_cache_key) Number of cache key bits to keep");
|
||||
DEFINE_bool(sck_randomize, false,
|
||||
"(-stress_cache_key) Randomize (hash) cache key");
|
||||
DEFINE_bool(sck_footer_unique_id, false,
|
||||
"(-stress_cache_key) Simulate using proposed footer unique id");
|
||||
// ## END stress_cache_key sub-tool options ##
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class CacheBench;
|
||||
@@ -220,7 +255,7 @@ class CacheBench {
|
||||
if (skewed_) {
|
||||
uint64_t max_key = max_key_;
|
||||
while (max_key >>= 1) max_log_++;
|
||||
if (max_key > (1u << max_log_)) max_log_++;
|
||||
if (max_key > (static_cast<uint64_t>(1) << max_log_)) max_log_++;
|
||||
}
|
||||
|
||||
if (FLAGS_use_clock_cache) {
|
||||
@@ -380,7 +415,8 @@ class CacheBench {
|
||||
<< "Average key size: "
|
||||
<< (1.0 * total_key_size / total_entry_count) << "\n"
|
||||
<< "Average charge: "
|
||||
<< BytesToHumanString(1.0 * total_charge / total_entry_count)
|
||||
<< BytesToHumanString(static_cast<uint64_t>(
|
||||
1.0 * total_charge / total_entry_count))
|
||||
<< "\n"
|
||||
<< "Unique deleters: " << deleters.size() << "\n";
|
||||
*stats_report = ostr.str();
|
||||
@@ -547,9 +583,195 @@ class CacheBench {
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: better description (see PR #9126 for some info)
|
||||
class StressCacheKey {
|
||||
public:
|
||||
void Run() {
|
||||
if (FLAGS_sck_footer_unique_id) {
|
||||
FLAGS_sck_db_count = 1;
|
||||
}
|
||||
|
||||
uint64_t mb_per_day =
|
||||
uint64_t{FLAGS_sck_files_per_day} * FLAGS_sck_file_size_mb;
|
||||
printf("Total cache or DBs size: %gTiB Writing %g MiB/s or %gTiB/day\n",
|
||||
FLAGS_sck_file_size_mb / 1024.0 / 1024.0 *
|
||||
std::pow(2.0, FLAGS_sck_table_bits),
|
||||
mb_per_day / 86400.0, mb_per_day / 1024.0 / 1024.0);
|
||||
multiplier_ = std::pow(2.0, 128 - FLAGS_sck_keep_bits) /
|
||||
(FLAGS_sck_file_size_mb * 1024.0 * 1024.0);
|
||||
printf(
|
||||
"Multiply by %g to correct for simulation losses (but still assume "
|
||||
"whole file cached)\n",
|
||||
multiplier_);
|
||||
restart_nfiles_ = FLAGS_sck_files_per_day / FLAGS_sck_restarts_per_day;
|
||||
double without_ejection =
|
||||
std::pow(1.414214, FLAGS_sck_keep_bits) / FLAGS_sck_files_per_day;
|
||||
printf(
|
||||
"Without ejection, expect random collision after %g days (%g "
|
||||
"corrected)\n",
|
||||
without_ejection, without_ejection * multiplier_);
|
||||
double with_full_table =
|
||||
std::pow(2.0, FLAGS_sck_keep_bits - FLAGS_sck_table_bits) /
|
||||
FLAGS_sck_files_per_day;
|
||||
printf(
|
||||
"With ejection and full table, expect random collision after %g "
|
||||
"days (%g corrected)\n",
|
||||
with_full_table, with_full_table * multiplier_);
|
||||
collisions_ = 0;
|
||||
|
||||
for (int i = 1; collisions_ < FLAGS_sck_min_collision; i++) {
|
||||
RunOnce();
|
||||
if (collisions_ == 0) {
|
||||
printf(
|
||||
"No collisions after %d x %u days "
|
||||
" \n",
|
||||
i, FLAGS_sck_duration);
|
||||
} else {
|
||||
double est = 1.0 * i * FLAGS_sck_duration / collisions_;
|
||||
printf("%" PRIu64
|
||||
" collisions after %d x %u days, est %g days between (%g "
|
||||
"corrected) \n",
|
||||
collisions_, i, FLAGS_sck_duration, est, est * multiplier_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RunOnce() {
|
||||
const size_t db_count = FLAGS_sck_db_count;
|
||||
dbs_.reset(new TableProperties[db_count]{});
|
||||
const size_t table_mask = (size_t{1} << FLAGS_sck_table_bits) - 1;
|
||||
table_.reset(new uint64_t[table_mask + 1]{});
|
||||
if (FLAGS_sck_keep_bits > 64) {
|
||||
FLAGS_sck_keep_bits = 64;
|
||||
}
|
||||
uint32_t shift_away = 64 - FLAGS_sck_keep_bits;
|
||||
uint32_t shift_away_b = shift_away / 3;
|
||||
uint32_t shift_away_a = shift_away - shift_away_b;
|
||||
|
||||
process_count_ = 0;
|
||||
session_count_ = 0;
|
||||
ResetProcess();
|
||||
|
||||
Random64 r{std::random_device{}()};
|
||||
|
||||
uint64_t max_file_count =
|
||||
uint64_t{FLAGS_sck_files_per_day} * FLAGS_sck_duration;
|
||||
uint64_t file_count = 0;
|
||||
uint32_t report_count = 0;
|
||||
uint32_t collisions_this_run = 0;
|
||||
// Round robin through DBs
|
||||
for (size_t db_i = 0;; ++db_i) {
|
||||
if (db_i >= db_count) {
|
||||
db_i = 0;
|
||||
}
|
||||
if (file_count >= max_file_count) {
|
||||
break;
|
||||
}
|
||||
if (!FLAGS_sck_footer_unique_id && r.OneIn(FLAGS_sck_reopen_nfiles)) {
|
||||
ResetSession(db_i);
|
||||
} else if (r.OneIn(restart_nfiles_)) {
|
||||
ResetProcess();
|
||||
}
|
||||
OffsetableCacheKey ock;
|
||||
dbs_[db_i].orig_file_number += 1;
|
||||
// skip some file numbers, unless 1 DB so that that can simulate
|
||||
// better (DB-independent) unique IDs
|
||||
if (db_count > 1) {
|
||||
dbs_[db_i].orig_file_number += (r.Next() & 3);
|
||||
}
|
||||
BlockBasedTable::SetupBaseCacheKey(&dbs_[db_i], "", 42, 42, &ock);
|
||||
CacheKey ck = ock.WithOffset(0);
|
||||
uint64_t stripped;
|
||||
if (FLAGS_sck_randomize) {
|
||||
stripped = GetSliceHash64(ck.AsSlice()) >> shift_away;
|
||||
} else if (FLAGS_sck_footer_unique_id) {
|
||||
uint32_t a = DecodeFixed32(ck.AsSlice().data() + 4) >> shift_away_a;
|
||||
uint32_t b = DecodeFixed32(ck.AsSlice().data() + 12) >> shift_away_b;
|
||||
stripped = (uint64_t{a} << 32) + b;
|
||||
} else {
|
||||
uint32_t a = DecodeFixed32(ck.AsSlice().data()) << shift_away_a;
|
||||
uint32_t b = DecodeFixed32(ck.AsSlice().data() + 12) >> shift_away_b;
|
||||
stripped = (uint64_t{a} << 32) + b;
|
||||
}
|
||||
if (stripped == 0) {
|
||||
// Unlikely, but we need to exclude tracking this value
|
||||
printf("Hit Zero! \n");
|
||||
continue;
|
||||
}
|
||||
file_count++;
|
||||
uint64_t h = NPHash64(reinterpret_cast<char*>(&stripped), 8);
|
||||
// Skew lifetimes
|
||||
size_t pos =
|
||||
std::min(Lower32of64(h) & table_mask, Upper32of64(h) & table_mask);
|
||||
if (table_[pos] == stripped) {
|
||||
collisions_this_run++;
|
||||
// To predict probability of no collisions, we have to get rid of
|
||||
// correlated collisions, which this takes care of:
|
||||
ResetProcess();
|
||||
} else {
|
||||
// Replace
|
||||
table_[pos] = stripped;
|
||||
}
|
||||
|
||||
if (++report_count == FLAGS_sck_files_per_day) {
|
||||
report_count = 0;
|
||||
// Estimate fill %
|
||||
size_t incr = table_mask / 1000;
|
||||
size_t sampled_count = 0;
|
||||
for (size_t i = 0; i <= table_mask; i += incr) {
|
||||
if (table_[i] != 0) {
|
||||
sampled_count++;
|
||||
}
|
||||
}
|
||||
// Report
|
||||
printf(
|
||||
"%" PRIu64 " days, %" PRIu64 " proc, %" PRIu64
|
||||
" sess, %u coll, occ %g%%, ejected %g%% \r",
|
||||
file_count / FLAGS_sck_files_per_day, process_count_,
|
||||
session_count_, collisions_this_run, 100.0 * sampled_count / 1000.0,
|
||||
100.0 * (1.0 - sampled_count / 1000.0 * table_mask / file_count));
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
collisions_ += collisions_this_run;
|
||||
}
|
||||
|
||||
void ResetSession(size_t i) {
|
||||
dbs_[i].db_session_id = DBImpl::GenerateDbSessionId(nullptr);
|
||||
session_count_++;
|
||||
}
|
||||
|
||||
void ResetProcess() {
|
||||
process_count_++;
|
||||
DBImpl::TEST_ResetDbSessionIdGen();
|
||||
for (size_t i = 0; i < FLAGS_sck_db_count; ++i) {
|
||||
ResetSession(i);
|
||||
}
|
||||
if (FLAGS_sck_footer_unique_id) {
|
||||
dbs_[0].orig_file_number = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// Use db_session_id and orig_file_number from TableProperties
|
||||
std::unique_ptr<TableProperties[]> dbs_;
|
||||
std::unique_ptr<uint64_t[]> table_;
|
||||
uint64_t process_count_ = 0;
|
||||
uint64_t session_count_ = 0;
|
||||
uint64_t collisions_ = 0;
|
||||
uint32_t restart_nfiles_ = 0;
|
||||
double multiplier_ = 0.0;
|
||||
};
|
||||
|
||||
int cache_bench_tool(int argc, char** argv) {
|
||||
ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
if (FLAGS_stress_cache_key) {
|
||||
// Alternate tool
|
||||
StressCacheKey().Run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (FLAGS_threads <= 0) {
|
||||
fprintf(stderr, "threads number <= 0\n");
|
||||
exit(1);
|
||||
|
||||
Vendored
+9
-7
@@ -11,6 +11,7 @@
|
||||
#include <mutex>
|
||||
|
||||
#include "cache/cache_helpers.h"
|
||||
#include "cache/cache_key.h"
|
||||
#include "port/lang.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/status.h"
|
||||
@@ -112,13 +113,7 @@ class CacheEntryStatsCollector {
|
||||
// entry in cache until all refs are destroyed.
|
||||
static Status GetShared(Cache *cache, SystemClock *clock,
|
||||
std::shared_ptr<CacheEntryStatsCollector> *ptr) {
|
||||
std::array<uint64_t, 3> cache_key_data{
|
||||
{// First 16 bytes == md5 of class name
|
||||
0x7eba5a8fb5437c90U, 0x8ca68c9b11655855U,
|
||||
// Last 8 bytes based on a function pointer to make unique for each
|
||||
// template instantiation
|
||||
reinterpret_cast<uint64_t>(&CacheEntryStatsCollector::GetShared)}};
|
||||
Slice cache_key = GetSlice(&cache_key_data);
|
||||
const Slice &cache_key = GetCacheKey();
|
||||
|
||||
Cache::Handle *h = cache->Lookup(cache_key);
|
||||
if (h == nullptr) {
|
||||
@@ -166,6 +161,13 @@ class CacheEntryStatsCollector {
|
||||
delete static_cast<CacheEntryStatsCollector *>(value);
|
||||
}
|
||||
|
||||
static const Slice &GetCacheKey() {
|
||||
// For each template instantiation
|
||||
static CacheKey ckey = CacheKey::CreateUniqueForProcessLifetime();
|
||||
static Slice ckey_slice = ckey.AsSlice();
|
||||
return ckey_slice;
|
||||
}
|
||||
|
||||
std::mutex saved_mutex_;
|
||||
Stats saved_stats_;
|
||||
|
||||
|
||||
Vendored
+271
@@ -0,0 +1,271 @@
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "cache/cache_key.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
|
||||
#include "rocksdb/cache.h"
|
||||
#include "table/unique_id_impl.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/math.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// Value space plan for CacheKey:
|
||||
//
|
||||
// session_etc64_ | offset_etc64_ | Only generated by
|
||||
// ---------------+---------------+------------------------------------------
|
||||
// 0 | 0 | Reserved for "empty" CacheKey()
|
||||
// 0 | > 0, < 1<<63 | CreateUniqueForCacheLifetime
|
||||
// 0 | >= 1<<63 | CreateUniqueForProcessLifetime
|
||||
// > 0 | any | OffsetableCacheKey.WithOffset
|
||||
|
||||
CacheKey CacheKey::CreateUniqueForCacheLifetime(Cache *cache) {
|
||||
// +1 so that we can reserve all zeros for "unset" cache key
|
||||
uint64_t id = cache->NewId() + 1;
|
||||
// Ensure we don't collide with CreateUniqueForProcessLifetime
|
||||
assert((id >> 63) == 0U);
|
||||
return CacheKey(0, id);
|
||||
}
|
||||
|
||||
CacheKey CacheKey::CreateUniqueForProcessLifetime() {
|
||||
// To avoid colliding with CreateUniqueForCacheLifetime, assuming
|
||||
// Cache::NewId counts up from zero, here we count down from UINT64_MAX.
|
||||
// If this ever becomes a point of contention, we could use CoreLocalArray.
|
||||
static std::atomic<uint64_t> counter{UINT64_MAX};
|
||||
uint64_t id = counter.fetch_sub(1, std::memory_order_relaxed);
|
||||
// Ensure we don't collide with CreateUniqueForCacheLifetime
|
||||
assert((id >> 63) == 1U);
|
||||
return CacheKey(0, id);
|
||||
}
|
||||
|
||||
// Value plan for CacheKeys from OffsetableCacheKey, assuming that
|
||||
// db_session_ids are generated from a base_session_id and
|
||||
// session_id_counter (by SemiStructuredUniqueIdGen+EncodeSessionId
|
||||
// in DBImpl::GenerateDbSessionId):
|
||||
//
|
||||
// Conceptual inputs:
|
||||
// db_id (unstructured, from GenerateRawUniqueId or equiv)
|
||||
// * could be shared between cloned DBs but rare
|
||||
// * could be constant, if session id suffices
|
||||
// base_session_id (unstructured, from GenerateRawUniqueId)
|
||||
// session_id_counter (structured)
|
||||
// * usually much smaller than 2**24
|
||||
// file_number (structured)
|
||||
// * usually smaller than 2**24
|
||||
// offset_in_file (structured, might skip lots of values)
|
||||
// * usually smaller than 2**32
|
||||
// max_offset determines placement of file_number to prevent
|
||||
// overlapping with offset
|
||||
//
|
||||
// Outputs come from bitwise-xor of the constituent pieces, low bits on left:
|
||||
//
|
||||
// |------------------------- session_etc64 -------------------------|
|
||||
// | +++++++++++++++ base_session_id (lower 64 bits) +++++++++++++++ |
|
||||
// |-----------------------------------------------------------------|
|
||||
// | session_id_counter ...| |
|
||||
// |-----------------------------------------------------------------|
|
||||
// | | ... file_number |
|
||||
// | | overflow & meta |
|
||||
// |-----------------------------------------------------------------|
|
||||
//
|
||||
//
|
||||
// |------------------------- offset_etc64 --------------------------|
|
||||
// | hash of: ++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
|
||||
// | * base_session_id (upper ~39 bits) |
|
||||
// | * db_id (~122 bits entropy) |
|
||||
// |-----------------------------------------------------------------|
|
||||
// | offset_in_file ............... | |
|
||||
// |-----------------------------------------------------------------|
|
||||
// | | file_number, 0-3 |
|
||||
// | | lower bytes |
|
||||
// |-----------------------------------------------------------------|
|
||||
//
|
||||
// Based on max_offset, a maximal number of bytes 0..3 is chosen for
|
||||
// including from lower bits of file_number in offset_etc64. The choice
|
||||
// is encoded in two bits of metadata going into session_etc64, though
|
||||
// the common case of 3 bytes is encoded as 0 so that session_etc64
|
||||
// is unmodified by file_number concerns in the common case.
|
||||
//
|
||||
// There is nothing preventing "file number overflow & meta" from meeting
|
||||
// and overlapping with session_id_counter, but reaching such a case requires
|
||||
// an intractable combination of large file offsets (thus at least some large
|
||||
// files), large file numbers (thus large number of files generated), and
|
||||
// large number of session IDs generated in a single process. A trillion each
|
||||
// (2**40) of session ids, offsets, and file numbers comes to 120 bits.
|
||||
// With two bits of metadata and byte granularity, this is on the verge of
|
||||
// overlap, but even in the overlap case, it doesn't seem likely that
|
||||
// a file from billions of files or session ids ago will still be live
|
||||
// or cached.
|
||||
//
|
||||
// In fact, if our SST files are all < 4TB (see
|
||||
// BlockBasedTable::kMaxFileSizeStandardEncoding), then SST files generated
|
||||
// in a single process are guaranteed to have unique cache keys, unless/until
|
||||
// number session ids * max file number = 2**86, e.g. 1 trillion DB::Open in
|
||||
// a single process and 64 trillion files generated. Even at that point, to
|
||||
// see a collision we would need a miraculous re-synchronization of session
|
||||
// id and file number, along with a live file or stale cache entry from
|
||||
// trillions of files ago.
|
||||
//
|
||||
// How https://github.com/pdillinger/unique_id applies here:
|
||||
// Every bit of output always includes "unstructured" uniqueness bits and
|
||||
// often combines with "structured" uniqueness bits. The "unstructured" bits
|
||||
// change infrequently: only when we cannot guarantee our state tracking for
|
||||
// "structured" uniqueness hasn't been cloned. Using a static
|
||||
// SemiStructuredUniqueIdGen for db_session_ids, this means we only get an
|
||||
// "all new" session id when a new process uses RocksDB. (Between processes,
|
||||
// we don't know if a DB or other persistent storage has been cloned.) Within
|
||||
// a process, only the session_lower of the db_session_id changes
|
||||
// incrementally ("structured" uniqueness).
|
||||
//
|
||||
// This basically means that our offsets, counters and file numbers allow us
|
||||
// to do somewhat "better than random" (birthday paradox) while in the
|
||||
// degenerate case of completely new session for each tiny file, we still
|
||||
// have strong uniqueness properties from the birthday paradox, with ~103
|
||||
// bit session IDs or up to 128 bits entropy with different DB IDs sharing a
|
||||
// cache.
|
||||
//
|
||||
// More collision probability analysis:
|
||||
// Suppose a RocksDB host generates (generously) 2 GB/s (10TB data, 17 DWPD)
|
||||
// with average process/session lifetime of (pessimistically) 4 minutes.
|
||||
// In 180 days (generous allowable data lifespan), we generate 31 million GB
|
||||
// of data, or 2^55 bytes, and 2^16 "all new" session IDs.
|
||||
//
|
||||
// First, suppose this is in a single DB (lifetime 180 days):
|
||||
// 128 bits cache key size
|
||||
// - 55 <- ideal size for byte offsets + file numbers
|
||||
// - 2 <- bits for offsets and file numbers not exactly powers of two
|
||||
// - 2 <- bits for file number encoding metadata
|
||||
// + 2 <- bits saved not using byte offsets in BlockBasedTable::GetCacheKey
|
||||
// ----
|
||||
// 71 <- bits remaining for distinguishing session IDs
|
||||
// The probability of a collision in 71 bits of session ID data is less than
|
||||
// 1 in 2**(71 - (2 * 16)), or roughly 1 in a trillion. And this assumes all
|
||||
// data from the last 180 days is in cache for potential collision, and that
|
||||
// cache keys under each session id exhaustively cover the remaining 57 bits
|
||||
// while in reality they'll only cover a small fraction of it.
|
||||
//
|
||||
// Although data could be transferred between hosts, each host has its own
|
||||
// cache and we are already assuming a high rate of "all new" session ids.
|
||||
// So this doesn't really change the collision calculation. Across a fleet
|
||||
// of 1 million, each with <1 in a trillion collision possibility,
|
||||
// fleetwide collision probability is <1 in a million.
|
||||
//
|
||||
// Now suppose we have many DBs per host, say 2**10, with same host-wide write
|
||||
// rate and process/session lifetime. File numbers will be ~10 bits smaller
|
||||
// and we will have 2**10 times as many session IDs because of simultaneous
|
||||
// lifetimes. So now collision chance is less than 1 in 2**(81 - (2 * 26)),
|
||||
// or roughly 1 in a billion.
|
||||
//
|
||||
// Suppose instead we generated random or hashed cache keys for each
|
||||
// (compressed) block. For 1KB compressed block size, that is 2^45 cache keys
|
||||
// in 180 days. Collision probability is more easily estimated at roughly
|
||||
// 1 in 2**(128 - (2 * 45)) or roughly 1 in a trillion (assuming all
|
||||
// data from the last 180 days is in cache, but NOT the other assumption
|
||||
// for the 1 in a trillion estimate above).
|
||||
//
|
||||
// Conclusion: Burning through session IDs, particularly "all new" IDs that
|
||||
// only arise when a new process is started, is the only way to have a
|
||||
// plausible chance of cache key collision. When processes live for hours
|
||||
// or days, the chance of a cache key collision seems more plausibly due
|
||||
// to bad hardware than to bad luck in random session ID data.
|
||||
//
|
||||
OffsetableCacheKey::OffsetableCacheKey(const std::string &db_id,
|
||||
const std::string &db_session_id,
|
||||
uint64_t file_number,
|
||||
uint64_t max_offset) {
|
||||
#ifndef NDEBUG
|
||||
max_offset_ = max_offset;
|
||||
#endif
|
||||
// Closely related to GetSstInternalUniqueId, but only need 128 bits and
|
||||
// need to include an offset within the file.
|
||||
// See also https://github.com/pdillinger/unique_id for background.
|
||||
uint64_t session_upper = 0; // Assignment to appease clang-analyze
|
||||
uint64_t session_lower = 0; // Assignment to appease clang-analyze
|
||||
{
|
||||
Status s = DecodeSessionId(db_session_id, &session_upper, &session_lower);
|
||||
if (!s.ok()) {
|
||||
// A reasonable fallback in case malformed
|
||||
Hash2x64(db_session_id.data(), db_session_id.size(), &session_upper,
|
||||
&session_lower);
|
||||
}
|
||||
}
|
||||
|
||||
// Hash the session upper (~39 bits entropy) and DB id (120+ bits entropy)
|
||||
// for more global uniqueness entropy.
|
||||
// (It is possible that many DBs descended from one common DB id are copied
|
||||
// around and proliferate, in which case session id is critical, but it is
|
||||
// more common for different DBs to have different DB ids.)
|
||||
uint64_t db_hash = Hash64(db_id.data(), db_id.size(), session_upper);
|
||||
|
||||
// This establishes the db+session id part of the cache key.
|
||||
//
|
||||
// Exactly preserve (in common cases; see modifiers below) session lower to
|
||||
// ensure that session ids generated during the same process lifetime are
|
||||
// guaranteed unique.
|
||||
//
|
||||
// We put this first for CommonPrefixSlice(), so that a small-ish set of
|
||||
// cache key prefixes to cover entries relevant to any DB.
|
||||
session_etc64_ = session_lower;
|
||||
// This provides extra entopy in case of different DB id or process
|
||||
// generating a session id, but is also partly/variably obscured by
|
||||
// file_number and offset (see below).
|
||||
offset_etc64_ = db_hash;
|
||||
|
||||
// Into offset_etc64_ we are (eventually) going to pack & xor in an offset and
|
||||
// a file_number, but we might need the file_number to overflow into
|
||||
// session_etc64_. (There must only be one session_etc64_ value per
|
||||
// file, and preferably shared among many files.)
|
||||
//
|
||||
// Figure out how many bytes of file_number we are going to be able to
|
||||
// pack in with max_offset, though our encoding will only support packing
|
||||
// in up to 3 bytes of file_number. (16M file numbers is enough for a new
|
||||
// file number every second for half a year.)
|
||||
int file_number_bytes_in_offset_etc =
|
||||
(63 - FloorLog2(max_offset | 0x100000000U)) / 8;
|
||||
int file_number_bits_in_offset_etc = file_number_bytes_in_offset_etc * 8;
|
||||
|
||||
// Assert two bits of metadata
|
||||
assert(file_number_bytes_in_offset_etc >= 0 &&
|
||||
file_number_bytes_in_offset_etc <= 3);
|
||||
// Assert we couldn't have used a larger allowed number of bytes (shift
|
||||
// would chop off bytes).
|
||||
assert(file_number_bytes_in_offset_etc == 3 ||
|
||||
(max_offset << (file_number_bits_in_offset_etc + 8) >>
|
||||
(file_number_bits_in_offset_etc + 8)) != max_offset);
|
||||
|
||||
uint64_t mask = (uint64_t{1} << (file_number_bits_in_offset_etc)) - 1;
|
||||
// Pack into high bits of etc so that offset can go in low bits of etc
|
||||
// TODO: could be EndianSwapValue?
|
||||
uint64_t offset_etc_modifier = ReverseBits(file_number & mask);
|
||||
assert(offset_etc_modifier << file_number_bits_in_offset_etc == 0U);
|
||||
|
||||
// Overflow and 3 - byte count (likely both zero) go into session_id part
|
||||
uint64_t session_etc_modifier =
|
||||
(file_number >> file_number_bits_in_offset_etc << 2) |
|
||||
static_cast<uint64_t>(3 - file_number_bytes_in_offset_etc);
|
||||
// Packed into high bits to minimize interference with session id counter.
|
||||
session_etc_modifier = ReverseBits(session_etc_modifier);
|
||||
|
||||
// Assert session_id part is only modified in extreme cases
|
||||
assert(session_etc_modifier == 0 || file_number > /*3 bytes*/ 0xffffffU ||
|
||||
max_offset > /*5 bytes*/ 0xffffffffffU);
|
||||
|
||||
// Xor in the modifiers
|
||||
session_etc64_ ^= session_etc_modifier;
|
||||
offset_etc64_ ^= offset_etc_modifier;
|
||||
|
||||
// Although DBImpl guarantees (in recent versions) that session_lower is not
|
||||
// zero, that's not entirely sufficient to guarantee that session_etc64_ is
|
||||
// not zero (so that the 0 case can be used by CacheKey::CreateUnique*)
|
||||
if (session_etc64_ == 0U) {
|
||||
session_etc64_ = session_upper | 1U;
|
||||
}
|
||||
assert(session_etc64_ != 0);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+132
@@ -0,0 +1,132 @@
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
#include "rocksdb/slice.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class Cache;
|
||||
|
||||
// A standard holder for fixed-size block cache keys (and for related caches).
|
||||
// They are created through one of these, each using its own range of values:
|
||||
// * CacheKey::CreateUniqueForCacheLifetime
|
||||
// * CacheKey::CreateUniqueForProcessLifetime
|
||||
// * Default ctor ("empty" cache key)
|
||||
// * OffsetableCacheKey->WithOffset
|
||||
//
|
||||
// The first two use atomic counters to guarantee uniqueness over the given
|
||||
// lifetime and the last uses a form of universally unique identifier for
|
||||
// uniqueness with very high probabilty (and guaranteed for files generated
|
||||
// during a single process lifetime).
|
||||
//
|
||||
// CacheKeys are currently used by calling AsSlice() to pass as a key to
|
||||
// Cache. For performance, the keys are endianness-dependent (though otherwise
|
||||
// portable). (Persistable cache entries are not intended to cross platforms.)
|
||||
class CacheKey {
|
||||
public:
|
||||
// For convenience, constructs an "empty" cache key that is never returned
|
||||
// by other means.
|
||||
inline CacheKey() : session_etc64_(), offset_etc64_() {}
|
||||
|
||||
inline bool IsEmpty() const {
|
||||
return (session_etc64_ == 0) & (offset_etc64_ == 0);
|
||||
}
|
||||
|
||||
// Use this cache key as a Slice (byte order is endianness-dependent)
|
||||
inline Slice AsSlice() const {
|
||||
static_assert(sizeof(*this) == 16, "Standardized on 16-byte cache key");
|
||||
assert(!IsEmpty());
|
||||
return Slice(reinterpret_cast<const char *>(this), sizeof(*this));
|
||||
}
|
||||
|
||||
// Create a CacheKey that is unique among others associated with this Cache
|
||||
// instance. Depends on Cache::NewId. This is useful for block cache
|
||||
// "reservations".
|
||||
static CacheKey CreateUniqueForCacheLifetime(Cache *cache);
|
||||
|
||||
// Create a CacheKey that is unique among others for the lifetime of this
|
||||
// process. This is useful for saving in a static data member so that
|
||||
// different DB instances can agree on a cache key for shared entities,
|
||||
// such as for CacheEntryStatsCollector.
|
||||
static CacheKey CreateUniqueForProcessLifetime();
|
||||
|
||||
protected:
|
||||
friend class OffsetableCacheKey;
|
||||
CacheKey(uint64_t session_etc64, uint64_t offset_etc64)
|
||||
: session_etc64_(session_etc64), offset_etc64_(offset_etc64) {}
|
||||
uint64_t session_etc64_;
|
||||
uint64_t offset_etc64_;
|
||||
};
|
||||
|
||||
// A file-specific generator of cache keys, sometimes referred to as the
|
||||
// "base" cache key for a file because all the cache keys for various offsets
|
||||
// within the file are computed using simple arithmetic. The basis for the
|
||||
// general approach is dicussed here: https://github.com/pdillinger/unique_id
|
||||
// Heavily related to GetUniqueIdFromTableProperties.
|
||||
//
|
||||
// If the db_id, db_session_id, and file_number come from the file's table
|
||||
// properties, then the keys will be stable across DB::Open/Close, backup/
|
||||
// restore, import/export, etc.
|
||||
//
|
||||
// This class "is a" CacheKey only privately so that it is not misused as
|
||||
// a ready-to-use CacheKey.
|
||||
class OffsetableCacheKey : private CacheKey {
|
||||
public:
|
||||
// For convenience, constructs an "empty" cache key that should not be used.
|
||||
inline OffsetableCacheKey() : CacheKey() {}
|
||||
|
||||
// Constructs an OffsetableCacheKey with the given information about a file.
|
||||
// max_offset is based on file size (see WithOffset) and is required here to
|
||||
// choose an appropriate (sub-)encoding. This constructor never generates an
|
||||
// "empty" base key.
|
||||
OffsetableCacheKey(const std::string &db_id, const std::string &db_session_id,
|
||||
uint64_t file_number, uint64_t max_offset);
|
||||
|
||||
inline bool IsEmpty() const {
|
||||
bool result = session_etc64_ == 0;
|
||||
assert(!(offset_etc64_ > 0 && result));
|
||||
return result;
|
||||
}
|
||||
|
||||
// Construct a CacheKey for an offset within a file, which must be
|
||||
// <= max_offset provided in constructor. An offset is not necessarily a
|
||||
// byte offset if a smaller unique identifier of keyable offsets is used.
|
||||
//
|
||||
// This class was designed to make this hot code extremely fast.
|
||||
inline CacheKey WithOffset(uint64_t offset) const {
|
||||
assert(!IsEmpty());
|
||||
assert(offset <= max_offset_);
|
||||
return CacheKey(session_etc64_, offset_etc64_ ^ offset);
|
||||
}
|
||||
|
||||
// The "common prefix" is a shared prefix for all the returned CacheKeys,
|
||||
// that also happens to usually be the same among many files in the same DB,
|
||||
// so is efficient and highly accurate (not perfectly) for DB-specific cache
|
||||
// dump selection (but not file-specific).
|
||||
static constexpr size_t kCommonPrefixSize = 8;
|
||||
inline Slice CommonPrefixSlice() const {
|
||||
static_assert(sizeof(session_etc64_) == kCommonPrefixSize,
|
||||
"8 byte common prefix expected");
|
||||
assert(!IsEmpty());
|
||||
assert(&this->session_etc64_ == static_cast<const void *>(this));
|
||||
|
||||
return Slice(reinterpret_cast<const char *>(this), kCommonPrefixSize);
|
||||
}
|
||||
|
||||
// For any max_offset <= this value, the same encoding scheme is guaranteed.
|
||||
static constexpr uint64_t kMaxOffsetStandardEncoding = 0xffffffffffU;
|
||||
|
||||
private:
|
||||
#ifndef NDEBUG
|
||||
uint64_t max_offset_ = 0;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+2
-6
@@ -28,8 +28,6 @@ CacheReservationManager::CacheReservationManager(std::shared_ptr<Cache> cache,
|
||||
memory_used_(0) {
|
||||
assert(cache != nullptr);
|
||||
cache_ = cache;
|
||||
std::memset(cache_key_, 0, kCacheKeyPrefixSize + kMaxVarint64Length);
|
||||
EncodeVarint64(cache_key_, cache_->NewId());
|
||||
}
|
||||
|
||||
CacheReservationManager::~CacheReservationManager() {
|
||||
@@ -152,10 +150,8 @@ Slice CacheReservationManager::GetNextCacheKey() {
|
||||
// underlying cache_key_ that is shared among other keys generated from this
|
||||
// fucntion. Therefore please make sure the previous keys are saved/copied
|
||||
// before calling this function.
|
||||
std::memset(cache_key_ + kCacheKeyPrefixSize, 0, kMaxVarint64Length);
|
||||
char* end =
|
||||
EncodeVarint64(cache_key_ + kCacheKeyPrefixSize, next_cache_key_id_++);
|
||||
return Slice(cache_key_, static_cast<std::size_t>(end - cache_key_));
|
||||
cache_key_ = CacheKey::CreateUniqueForCacheLifetime(cache_.get());
|
||||
return cache_key_.AsSlice();
|
||||
}
|
||||
|
||||
template <CacheEntryRole R>
|
||||
|
||||
Vendored
+1
-7
@@ -158,10 +158,6 @@ class CacheReservationManager
|
||||
|
||||
private:
|
||||
static constexpr std::size_t kSizeDummyEntry = 256 * 1024;
|
||||
// The key will be longer than keys for blocks in SST files so they won't
|
||||
// conflict.
|
||||
static const std::size_t kCacheKeyPrefixSize =
|
||||
BlockBasedTable::kMaxCacheKeyPrefixSize + kMaxVarint64Length;
|
||||
|
||||
Slice GetNextCacheKey();
|
||||
template <CacheEntryRole R>
|
||||
@@ -173,9 +169,7 @@ class CacheReservationManager
|
||||
std::atomic<std::size_t> cache_allocated_size_;
|
||||
std::size_t memory_used_;
|
||||
std::vector<Cache::Handle *> dummy_handles_;
|
||||
std::uint64_t next_cache_key_id_ = 0;
|
||||
// The non-prefix part will be updated according to the ID to use.
|
||||
char cache_key_[kCacheKeyPrefixSize + kMaxVarint64Length];
|
||||
CacheKey cache_key_;
|
||||
};
|
||||
|
||||
// CacheReservationHandle is for managing the lifetime of a cache reservation
|
||||
|
||||
+11
-19
@@ -26,8 +26,6 @@ class CacheReservationManagerTest : public ::testing::Test {
|
||||
CacheReservationManager::GetDummyEntrySize();
|
||||
static constexpr std::size_t kCacheCapacity = 4096 * kSizeDummyEntry;
|
||||
static constexpr int kNumShardBits = 0; // 2^0 shard
|
||||
static const std::size_t kCacheKeyPrefixSize =
|
||||
BlockBasedTable::kMaxCacheKeyPrefixSize + kMaxVarint64Length;
|
||||
static constexpr std::size_t kMetaDataChargeOverhead = 10000;
|
||||
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(kCacheCapacity, kNumShardBits);
|
||||
@@ -39,22 +37,6 @@ class CacheReservationManagerTest : public ::testing::Test {
|
||||
};
|
||||
|
||||
TEST_F(CacheReservationManagerTest, GenerateCacheKey) {
|
||||
// The first cache reservation manager owning the cache will have
|
||||
// cache->NewId() = 1
|
||||
constexpr std::size_t kCacheNewId = 1;
|
||||
// The first key generated inside of cache reservation manager will have
|
||||
// next_cache_key_id = 0
|
||||
constexpr std::size_t kCacheKeyId = 0;
|
||||
|
||||
char expected_cache_key[kCacheKeyPrefixSize + kMaxVarint64Length];
|
||||
std::memset(expected_cache_key, 0, kCacheKeyPrefixSize + kMaxVarint64Length);
|
||||
|
||||
EncodeVarint64(expected_cache_key, kCacheNewId);
|
||||
char* end =
|
||||
EncodeVarint64(expected_cache_key + kCacheKeyPrefixSize, kCacheKeyId);
|
||||
Slice expected_cache_key_slice(
|
||||
expected_cache_key, static_cast<std::size_t>(end - expected_cache_key));
|
||||
|
||||
std::size_t new_mem_used = 1 * kSizeDummyEntry;
|
||||
Status s =
|
||||
test_cache_rev_mng
|
||||
@@ -65,7 +47,17 @@ TEST_F(CacheReservationManagerTest, GenerateCacheKey) {
|
||||
ASSERT_LT(cache->GetPinnedUsage(),
|
||||
1 * kSizeDummyEntry + kMetaDataChargeOverhead);
|
||||
|
||||
Cache::Handle* handle = cache->Lookup(expected_cache_key_slice);
|
||||
// Next unique Cache key
|
||||
CacheKey ckey = CacheKey::CreateUniqueForCacheLifetime(cache.get());
|
||||
// Back it up to the one used by CRM (using CacheKey implementation details)
|
||||
using PairU64 = std::pair<uint64_t, uint64_t>;
|
||||
auto& ckey_pair = *reinterpret_cast<PairU64*>(&ckey);
|
||||
ckey_pair.second--;
|
||||
|
||||
// Specific key (subject to implementation details)
|
||||
EXPECT_EQ(ckey_pair, PairU64(0, 2));
|
||||
|
||||
Cache::Handle* handle = cache->Lookup(ckey.AsSlice());
|
||||
EXPECT_NE(handle, nullptr)
|
||||
<< "Failed to generate the cache key for the dummy entry correctly";
|
||||
// Clean up the returned handle from Lookup() to prevent memory leak
|
||||
|
||||
Vendored
+3
@@ -609,6 +609,9 @@ TEST_P(CacheTest, SetCapacity) {
|
||||
for (size_t i = 5; i < 10; i++) {
|
||||
cache->Release(handles[i]);
|
||||
}
|
||||
|
||||
// Make sure this doesn't crash or upset ASAN/valgrind
|
||||
cache->DisownData();
|
||||
}
|
||||
|
||||
TEST_P(LRUCacheTest, SetStrictCapacityLimit) {
|
||||
|
||||
Vendored
+18
-16
@@ -33,11 +33,11 @@ std::shared_ptr<Cache> NewClockCache(
|
||||
#ifndef ROCKSDB_USE_RTTI
|
||||
#define TBB_USE_EXCEPTIONS 0
|
||||
#endif
|
||||
#include "tbb/concurrent_hash_map.h"
|
||||
|
||||
#include "cache/sharded_cache.h"
|
||||
#include "port/lang.h"
|
||||
#include "port/malloc.h"
|
||||
#include "port/port.h"
|
||||
#include "tbb/concurrent_hash_map.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/mutexlock.h"
|
||||
|
||||
@@ -228,22 +228,22 @@ struct CacheHandle {
|
||||
};
|
||||
|
||||
// Key of hash map. We store hash value with the key for convenience.
|
||||
struct CacheKey {
|
||||
struct ClockCacheKey {
|
||||
Slice key;
|
||||
uint32_t hash_value;
|
||||
|
||||
CacheKey() = default;
|
||||
ClockCacheKey() = default;
|
||||
|
||||
CacheKey(const Slice& k, uint32_t h) {
|
||||
ClockCacheKey(const Slice& k, uint32_t h) {
|
||||
key = k;
|
||||
hash_value = h;
|
||||
}
|
||||
|
||||
static bool equal(const CacheKey& a, const CacheKey& b) {
|
||||
static bool equal(const ClockCacheKey& a, const ClockCacheKey& b) {
|
||||
return a.hash_value == b.hash_value && a.key == b.key;
|
||||
}
|
||||
|
||||
static size_t hash(const CacheKey& a) {
|
||||
static size_t hash(const ClockCacheKey& a) {
|
||||
return static_cast<size_t>(a.hash_value);
|
||||
}
|
||||
};
|
||||
@@ -260,7 +260,8 @@ struct CleanupContext {
|
||||
class ClockCacheShard final : public CacheShard {
|
||||
public:
|
||||
// Hash map type.
|
||||
using HashTable = tbb::concurrent_hash_map<CacheKey, CacheHandle*, CacheKey>;
|
||||
using HashTable =
|
||||
tbb::concurrent_hash_map<ClockCacheKey, CacheHandle*, ClockCacheKey>;
|
||||
|
||||
ClockCacheShard();
|
||||
~ClockCacheShard() override;
|
||||
@@ -559,7 +560,7 @@ bool ClockCacheShard::TryEvict(CacheHandle* handle, CleanupContext* context) {
|
||||
if (handle->flags.compare_exchange_strong(flags, 0, std::memory_order_acquire,
|
||||
std::memory_order_relaxed)) {
|
||||
bool erased __attribute__((__unused__)) =
|
||||
table_.erase(CacheKey(handle->key, handle->hash));
|
||||
table_.erase(ClockCacheKey(handle->key, handle->hash));
|
||||
assert(erased);
|
||||
RecycleHandle(handle, context);
|
||||
return true;
|
||||
@@ -656,13 +657,13 @@ CacheHandle* ClockCacheShard::Insert(
|
||||
// relative updates (fetch_add, etc).
|
||||
handle->flags.store(flags, std::memory_order_relaxed);
|
||||
HashTable::accessor accessor;
|
||||
if (table_.find(accessor, CacheKey(key, hash))) {
|
||||
if (table_.find(accessor, ClockCacheKey(key, hash))) {
|
||||
*overwritten = true;
|
||||
CacheHandle* existing_handle = accessor->second;
|
||||
table_.erase(accessor);
|
||||
UnsetInCache(existing_handle, context);
|
||||
}
|
||||
table_.insert(HashTable::value_type(CacheKey(key, hash), handle));
|
||||
table_.insert(HashTable::value_type(ClockCacheKey(key, hash), handle));
|
||||
if (hold_reference) {
|
||||
pinned_usage_.fetch_add(total_charge, std::memory_order_relaxed);
|
||||
}
|
||||
@@ -701,7 +702,7 @@ Status ClockCacheShard::Insert(const Slice& key, uint32_t hash, void* value,
|
||||
|
||||
Cache::Handle* ClockCacheShard::Lookup(const Slice& key, uint32_t hash) {
|
||||
HashTable::const_accessor accessor;
|
||||
if (!table_.find(accessor, CacheKey(key, hash))) {
|
||||
if (!table_.find(accessor, ClockCacheKey(key, hash))) {
|
||||
return nullptr;
|
||||
}
|
||||
CacheHandle* handle = accessor->second;
|
||||
@@ -746,7 +747,7 @@ bool ClockCacheShard::EraseAndConfirm(const Slice& key, uint32_t hash,
|
||||
MutexLock l(&mutex_);
|
||||
HashTable::accessor accessor;
|
||||
bool erased = false;
|
||||
if (table_.find(accessor, CacheKey(key, hash))) {
|
||||
if (table_.find(accessor, ClockCacheKey(key, hash))) {
|
||||
CacheHandle* handle = accessor->second;
|
||||
table_.erase(accessor);
|
||||
erased = UnsetInCache(handle, context);
|
||||
@@ -809,9 +810,10 @@ class ClockCache final : public ShardedCache {
|
||||
}
|
||||
|
||||
void DisownData() override {
|
||||
#ifndef MUST_FREE_HEAP_ALLOCATIONS
|
||||
shards_ = nullptr;
|
||||
#endif
|
||||
// Leak data only if that won't generate an ASAN/valgrind warning
|
||||
if (!kMustFreeHeapAllocations) {
|
||||
shards_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void WaitAll(std::vector<Handle*>& /*handles*/) override {}
|
||||
|
||||
Vendored
+6
-5
@@ -15,6 +15,7 @@
|
||||
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "port/lang.h"
|
||||
#include "util/mutexlock.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -704,11 +705,11 @@ uint32_t LRUCache::GetHash(Handle* handle) const {
|
||||
}
|
||||
|
||||
void LRUCache::DisownData() {
|
||||
// Do not drop data if compile with ASAN to suppress leak warning.
|
||||
#ifndef MUST_FREE_HEAP_ALLOCATIONS
|
||||
shards_ = nullptr;
|
||||
num_shards_ = 0;
|
||||
#endif
|
||||
// Leak data only if that won't generate an ASAN/valgrind warning
|
||||
if (!kMustFreeHeapAllocations) {
|
||||
shards_ = nullptr;
|
||||
num_shards_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
size_t LRUCache::TEST_GetLRUSize() {
|
||||
|
||||
Vendored
+9
-15
@@ -8,6 +8,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cache/cache_key.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "file/sst_file_manager_impl.h"
|
||||
#include "port/port.h"
|
||||
@@ -233,7 +234,10 @@ class TestSecondaryCache : public SecondaryCache {
|
||||
void ResetInjectFailure() { inject_failure_ = false; }
|
||||
|
||||
void SetDbSessionId(const std::string& db_session_id) {
|
||||
db_session_id_ = db_session_id;
|
||||
// NOTE: we assume the file is smaller than kMaxFileSizeStandardEncoding
|
||||
// for this to work, but that's safe in a test.
|
||||
auto base = OffsetableCacheKey("unknown", db_session_id, 1, 1);
|
||||
ckey_prefix_ = base.CommonPrefixSlice().ToString();
|
||||
}
|
||||
|
||||
Status Insert(const Slice& key, void* value,
|
||||
@@ -241,7 +245,7 @@ class TestSecondaryCache : public SecondaryCache {
|
||||
if (inject_failure_) {
|
||||
return Status::Corruption("Insertion Data Corrupted");
|
||||
}
|
||||
assert(IsDbSessionIdAsKeyPrefix(key) == true);
|
||||
EXPECT_TRUE(IsDbSessionLowerAsKeyPrefix(key));
|
||||
size_t size;
|
||||
char* buf;
|
||||
Status s;
|
||||
@@ -317,18 +321,8 @@ class TestSecondaryCache : public SecondaryCache {
|
||||
|
||||
uint32_t num_lookups() { return num_lookups_; }
|
||||
|
||||
bool IsDbSessionIdAsKeyPrefix(const Slice& key) {
|
||||
if (db_session_id_.size() == 0) {
|
||||
return true;
|
||||
}
|
||||
if (key.size() < 20) {
|
||||
return false;
|
||||
}
|
||||
std::string s_key = key.ToString();
|
||||
if (s_key.substr(0, 20) != db_session_id_) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
bool IsDbSessionLowerAsKeyPrefix(const Slice& key) {
|
||||
return key.starts_with(ckey_prefix_);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -373,7 +367,7 @@ class TestSecondaryCache : public SecondaryCache {
|
||||
uint32_t num_inserts_;
|
||||
uint32_t num_lookups_;
|
||||
bool inject_failure_;
|
||||
std::string db_session_id_;
|
||||
std::string ckey_prefix_;
|
||||
ResultMap result_map_;
|
||||
};
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ class DBBlobCompactionTest : public DBTestBase {
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
const std::vector<InternalStats::CompactionStats>& GetCompactionStats() {
|
||||
VersionSet* const versions = dbfull()->TEST_GetVersionSet();
|
||||
VersionSet* const versions = dbfull()->GetVersionSet();
|
||||
assert(versions);
|
||||
assert(versions->GetColumnFamilySet());
|
||||
|
||||
@@ -495,7 +495,7 @@ TEST_F(DBBlobCompactionTest, TrackGarbage) {
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), begin, end));
|
||||
|
||||
VersionSet* const versions = dbfull()->TEST_GetVersionSet();
|
||||
VersionSet* const versions = dbfull()->GetVersionSet();
|
||||
assert(versions);
|
||||
assert(versions->GetColumnFamilySet());
|
||||
|
||||
|
||||
@@ -4737,6 +4737,11 @@ int rocksdb_livefiles_count(
|
||||
return static_cast<int>(lf->rep.size());
|
||||
}
|
||||
|
||||
const char* rocksdb_livefiles_column_family_name(const rocksdb_livefiles_t* lf,
|
||||
int index) {
|
||||
return lf->rep[index].column_family_name.c_str();
|
||||
}
|
||||
|
||||
const char* rocksdb_livefiles_name(
|
||||
const rocksdb_livefiles_t* lf,
|
||||
int index) {
|
||||
|
||||
@@ -75,11 +75,6 @@ ColumnFamilyHandleImpl::~ColumnFamilyHandleImpl() {
|
||||
bool defer_purge =
|
||||
db_->immutable_db_options().avoid_unnecessary_blocking_io;
|
||||
db_->PurgeObsoleteFiles(job_context, defer_purge);
|
||||
if (defer_purge) {
|
||||
mutex_->Lock();
|
||||
db_->SchedulePurge();
|
||||
mutex_->Unlock();
|
||||
}
|
||||
}
|
||||
job_context.Clean();
|
||||
}
|
||||
|
||||
@@ -554,7 +554,7 @@ class ColumnFamilyTest
|
||||
INSTANTIATE_TEST_CASE_P(FormatDef, ColumnFamilyTest,
|
||||
testing::Values(test::kDefaultFormatVersion));
|
||||
INSTANTIATE_TEST_CASE_P(FormatLatest, ColumnFamilyTest,
|
||||
testing::Values(test::kLatestFormatVersion));
|
||||
testing::Values(kLatestFormatVersion));
|
||||
|
||||
TEST_P(ColumnFamilyTest, DontReuseColumnFamilyID) {
|
||||
for (int iter = 0; iter < 3; ++iter) {
|
||||
@@ -746,8 +746,8 @@ INSTANTIATE_TEST_CASE_P(
|
||||
std::make_tuple(test::kDefaultFormatVersion, false)));
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
FormatLatest, FlushEmptyCFTestWithParam,
|
||||
testing::Values(std::make_tuple(test::kLatestFormatVersion, true),
|
||||
std::make_tuple(test::kLatestFormatVersion, false)));
|
||||
testing::Values(std::make_tuple(kLatestFormatVersion, true),
|
||||
std::make_tuple(kLatestFormatVersion, false)));
|
||||
|
||||
TEST_P(ColumnFamilyTest, AddDrop) {
|
||||
Open();
|
||||
|
||||
@@ -203,10 +203,11 @@ class CompactionJobTestBase : public testing::Test {
|
||||
|
||||
VersionEdit edit;
|
||||
edit.AddFile(level, file_number, 0, 10, smallest_key, largest_key,
|
||||
smallest_seqno, largest_seqno, false, oldest_blob_file_number,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
smallest_seqno, largest_seqno, false, Temperature::kUnknown,
|
||||
oldest_blob_file_number, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
|
||||
mutex_.Lock();
|
||||
EXPECT_OK(
|
||||
|
||||
@@ -112,13 +112,12 @@ class CompactionPickerTest : public testing::Test {
|
||||
file_number, path_id, file_size,
|
||||
InternalKey(smallest, smallest_seq, kTypeValue),
|
||||
InternalKey(largest, largest_seq, kTypeValue), smallest_seq,
|
||||
largest_seq, marked_for_compact, kInvalidBlobFileNumber,
|
||||
largest_seq, marked_for_compact, temperature, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
f->compensated_file_size =
|
||||
(compensated_file_size != 0) ? compensated_file_size : file_size;
|
||||
f->temperature = temperature;
|
||||
f->oldest_ancester_time = oldest_ancestor_time;
|
||||
vstorage->AddFile(level, f);
|
||||
files_.emplace_back(f);
|
||||
|
||||
@@ -498,8 +498,11 @@ Compaction* UniversalCompactionBuilder::PickCompaction() {
|
||||
}
|
||||
#endif
|
||||
// update statistics
|
||||
RecordInHistogram(ioptions_.stats, NUM_FILES_IN_SINGLE_COMPACTION,
|
||||
c->inputs(0)->size());
|
||||
size_t num_files = 0;
|
||||
for (auto& each_level : *c->inputs()) {
|
||||
num_files += each_level.files.size();
|
||||
}
|
||||
RecordInHistogram(ioptions_.stats, NUM_FILES_IN_SINGLE_COMPACTION, num_files);
|
||||
|
||||
picker_->RegisterCompaction(c);
|
||||
vstorage_->ComputeCompactionScore(ioptions_, mutable_cf_options_);
|
||||
|
||||
@@ -317,7 +317,7 @@ class ComparatorDBTest
|
||||
INSTANTIATE_TEST_CASE_P(FormatDef, ComparatorDBTest,
|
||||
testing::Values(test::kDefaultFormatVersion));
|
||||
INSTANTIATE_TEST_CASE_P(FormatLatest, ComparatorDBTest,
|
||||
testing::Values(test::kLatestFormatVersion));
|
||||
testing::Values(kLatestFormatVersion));
|
||||
|
||||
TEST_P(ComparatorDBTest, Bytewise) {
|
||||
for (int rand_seed = 301; rand_seed < 306; rand_seed++) {
|
||||
|
||||
@@ -546,7 +546,7 @@ TEST_F(CorruptionTest, RangeDeletionCorrupted) {
|
||||
BlockHandle range_del_handle;
|
||||
ASSERT_OK(FindMetaBlockInFile(
|
||||
file_reader.get(), file_size, kBlockBasedTableMagicNumber,
|
||||
ImmutableOptions(options_), kRangeDelBlock, &range_del_handle));
|
||||
ImmutableOptions(options_), kRangeDelBlockName, &range_del_handle));
|
||||
|
||||
ASSERT_OK(TryReopen());
|
||||
ASSERT_OK(test::CorruptFile(env_, filename,
|
||||
|
||||
+12
-4
@@ -15,6 +15,7 @@
|
||||
#include "rocksdb/flush_block_policy.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/utilities/debug.h"
|
||||
#include "table/block_based/block_based_table_reader.h"
|
||||
#include "table/block_based/block_builder.h"
|
||||
@@ -972,8 +973,15 @@ TEST_F(DBBasicTest, MultiGetEmpty) {
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, ChecksumTest) {
|
||||
class DBBlockChecksumTest : public DBBasicTest,
|
||||
public testing::WithParamInterface<uint32_t> {};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(FormatVersions, DBBlockChecksumTest,
|
||||
testing::ValuesIn(test::kFooterFormatVersionsToTest));
|
||||
|
||||
TEST_P(DBBlockChecksumTest, BlockChecksumTest) {
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.format_version = GetParam();
|
||||
Options options = CurrentOptions();
|
||||
const int kNumPerFile = 2;
|
||||
|
||||
@@ -2557,7 +2565,7 @@ TEST_F(DBBasicTest, DisableTrackWal) {
|
||||
ASSERT_OK(dbfull()->TEST_SwitchMemtable());
|
||||
ASSERT_OK(db_->SyncWAL());
|
||||
// Some WALs are tracked.
|
||||
ASSERT_FALSE(dbfull()->TEST_GetVersionSet()->GetWalSet().GetWals().empty());
|
||||
ASSERT_FALSE(dbfull()->GetVersionSet()->GetWalSet().GetWals().empty());
|
||||
Close();
|
||||
|
||||
// Disable WAL tracking.
|
||||
@@ -2565,14 +2573,14 @@ TEST_F(DBBasicTest, DisableTrackWal) {
|
||||
options.create_if_missing = false;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
// Previously tracked WALs are cleared.
|
||||
ASSERT_TRUE(dbfull()->TEST_GetVersionSet()->GetWalSet().GetWals().empty());
|
||||
ASSERT_TRUE(dbfull()->GetVersionSet()->GetWalSet().GetWals().empty());
|
||||
Close();
|
||||
|
||||
// Re-enable WAL tracking again.
|
||||
options.track_and_verify_wals_in_manifest = true;
|
||||
options.create_if_missing = false;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
ASSERT_TRUE(dbfull()->TEST_GetVersionSet()->GetWalSet().GetWals().empty());
|
||||
ASSERT_TRUE(dbfull()->GetVersionSet()->GetWalSet().GetWals().empty());
|
||||
Close();
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
+63
-26
@@ -15,6 +15,7 @@
|
||||
#include "db/column_family.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/persistent_cache.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "util/compression.h"
|
||||
@@ -650,17 +651,46 @@ TEST_F(DBBlockCacheTest, WarmCacheWithDataBlocksDuringFlush) {
|
||||
}
|
||||
|
||||
// This test cache data, index and filter blocks during flush.
|
||||
TEST_F(DBBlockCacheTest, WarmCacheWithBlocksDuringFlush) {
|
||||
class DBBlockCacheTest1 : public DBTestBase,
|
||||
public ::testing::WithParamInterface<uint32_t> {
|
||||
public:
|
||||
const size_t kNumBlocks = 10;
|
||||
const size_t kValueSize = 100;
|
||||
DBBlockCacheTest1() : DBTestBase("db_block_cache_test1", true) {}
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBBlockCacheTest1, DBBlockCacheTest1,
|
||||
::testing::Values(1, 2, 3));
|
||||
|
||||
TEST_P(DBBlockCacheTest1, WarmCacheWithBlocksDuringFlush) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_cache = NewLRUCache(1 << 25, 0, false);
|
||||
|
||||
uint32_t filter_type = GetParam();
|
||||
switch (filter_type) {
|
||||
case 1: // partition_filter
|
||||
table_options.partition_filters = true;
|
||||
table_options.index_type =
|
||||
BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
break;
|
||||
case 2: // block-based filter
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10, true));
|
||||
break;
|
||||
case 3: // full filter
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
table_options.prepopulate_block_cache =
|
||||
BlockBasedTableOptions::PrepopulateBlockCache::kFlushOnly;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
@@ -669,9 +699,15 @@ TEST_F(DBBlockCacheTest, WarmCacheWithBlocksDuringFlush) {
|
||||
ASSERT_OK(Put(ToString(i), value));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(i, options.statistics->getTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
ASSERT_EQ(i, options.statistics->getTickerCount(BLOCK_CACHE_INDEX_ADD));
|
||||
ASSERT_EQ(i, options.statistics->getTickerCount(BLOCK_CACHE_FILTER_ADD));
|
||||
|
||||
if (filter_type == 1) {
|
||||
ASSERT_EQ(2 * i,
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_INDEX_ADD));
|
||||
ASSERT_EQ(2 * i,
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_FILTER_ADD));
|
||||
} else {
|
||||
ASSERT_EQ(i, options.statistics->getTickerCount(BLOCK_CACHE_INDEX_ADD));
|
||||
ASSERT_EQ(i, options.statistics->getTickerCount(BLOCK_CACHE_FILTER_ADD));
|
||||
}
|
||||
ASSERT_EQ(value, Get(ToString(i)));
|
||||
|
||||
ASSERT_EQ(0, options.statistics->getTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
@@ -679,11 +715,16 @@ TEST_F(DBBlockCacheTest, WarmCacheWithBlocksDuringFlush) {
|
||||
|
||||
ASSERT_EQ(0, options.statistics->getTickerCount(BLOCK_CACHE_INDEX_MISS));
|
||||
ASSERT_EQ(i * 3, options.statistics->getTickerCount(BLOCK_CACHE_INDEX_HIT));
|
||||
|
||||
if (filter_type == 1) {
|
||||
ASSERT_EQ(i * 3,
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_FILTER_HIT));
|
||||
} else {
|
||||
ASSERT_EQ(i * 2,
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_FILTER_HIT));
|
||||
}
|
||||
ASSERT_EQ(0, options.statistics->getTickerCount(BLOCK_CACHE_FILTER_MISS));
|
||||
ASSERT_EQ(i * 2,
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_FILTER_HIT));
|
||||
}
|
||||
|
||||
// Verify compaction not counted
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr));
|
||||
@@ -692,10 +733,17 @@ TEST_F(DBBlockCacheTest, WarmCacheWithBlocksDuringFlush) {
|
||||
// Index and filter blocks are automatically warmed when the new table file
|
||||
// is automatically opened at the end of compaction. This is not easily
|
||||
// disabled so results in the new index and filter blocks being warmed.
|
||||
EXPECT_EQ(1 + kNumBlocks,
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_INDEX_ADD));
|
||||
EXPECT_EQ(1 + kNumBlocks,
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_FILTER_ADD));
|
||||
if (filter_type == 1) {
|
||||
EXPECT_EQ(2 * (1 + kNumBlocks),
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_INDEX_ADD));
|
||||
EXPECT_EQ(2 * (1 + kNumBlocks),
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_FILTER_ADD));
|
||||
} else {
|
||||
EXPECT_EQ(1 + kNumBlocks,
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_INDEX_ADD));
|
||||
EXPECT_EQ(1 + kNumBlocks,
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_FILTER_ADD));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBlockCacheTest, DynamicallyWarmCacheDuringFlush) {
|
||||
@@ -1541,7 +1589,8 @@ TEST_P(DBBlockCacheKeyTest, StableCacheKeys) {
|
||||
// This is a "control" side of the test that also ensures safely degraded
|
||||
// behavior on old files.
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"PropertyBlockBuilder::AddTableProperty:Start", [&](void* arg) {
|
||||
"BlockBasedTableBuilder::BlockBasedTableBuilder:PreSetupBaseCacheKey",
|
||||
[&](void* arg) {
|
||||
TableProperties* props = reinterpret_cast<TableProperties*>(arg);
|
||||
props->orig_file_number = 0;
|
||||
});
|
||||
@@ -1601,11 +1650,7 @@ TEST_P(DBBlockCacheKeyTest, StableCacheKeys) {
|
||||
}
|
||||
|
||||
if (exclude_file_numbers_) {
|
||||
// FIXME(peterd): figure out where these extra two ADDs are coming from
|
||||
options.statistics->recordTick(BLOCK_CACHE_INDEX_ADD,
|
||||
uint64_t{0} - uint64_t{2});
|
||||
options.statistics->recordTick(BLOCK_CACHE_FILTER_ADD,
|
||||
uint64_t{0} - uint64_t{2});
|
||||
// FIXME(peterd): figure out where these extra ADDs are coming from
|
||||
options.statistics->recordTick(BLOCK_CACHE_COMPRESSED_ADD,
|
||||
uint64_t{0} - uint64_t{2});
|
||||
}
|
||||
@@ -1660,14 +1705,6 @@ TEST_P(DBBlockCacheKeyTest, StableCacheKeys) {
|
||||
IngestExternalFileOptions ingest_opts;
|
||||
ASSERT_OK(db_->IngestExternalFile(handles_[1], {external}, ingest_opts));
|
||||
|
||||
if (exclude_file_numbers_) {
|
||||
// FIXME(peterd): figure out where these extra two ADDs are coming from
|
||||
options.statistics->recordTick(BLOCK_CACHE_INDEX_ADD,
|
||||
uint64_t{0} - uint64_t{2});
|
||||
options.statistics->recordTick(BLOCK_CACHE_FILTER_ADD,
|
||||
uint64_t{0} - uint64_t{2});
|
||||
}
|
||||
|
||||
perform_gets();
|
||||
verify_stats();
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
@@ -551,10 +551,9 @@ INSTANTIATE_TEST_CASE_P(
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
FormatLatest, DBBloomFilterTestWithParam,
|
||||
::testing::Values(
|
||||
std::make_tuple(BFP::kDeprecatedBlock, false,
|
||||
test::kLatestFormatVersion),
|
||||
std::make_tuple(BFP::kAutoBloom, true, test::kLatestFormatVersion),
|
||||
std::make_tuple(BFP::kAutoBloom, false, test::kLatestFormatVersion)));
|
||||
std::make_tuple(BFP::kDeprecatedBlock, false, kLatestFormatVersion),
|
||||
std::make_tuple(BFP::kAutoBloom, true, kLatestFormatVersion),
|
||||
std::make_tuple(BFP::kAutoBloom, false, kLatestFormatVersion)));
|
||||
#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
|
||||
TEST_F(DBBloomFilterTest, BloomFilterRate) {
|
||||
@@ -1503,6 +1502,63 @@ TEST_F(DBBloomFilterTest, MemtableWholeKeyBloomFilter) {
|
||||
ASSERT_EQ(1, get_perf_context()->bloom_memtable_hit_count);
|
||||
}
|
||||
|
||||
TEST_F(DBBloomFilterTest, MemtableWholeKeyBloomFilterMultiGet) {
|
||||
Options options = CurrentOptions();
|
||||
options.memtable_prefix_bloom_size_ratio = 0.015;
|
||||
options.memtable_whole_key_filtering = true;
|
||||
Reopen(options);
|
||||
std::string key1("AA");
|
||||
std::string key2("BB");
|
||||
std::string key3("CC");
|
||||
std::string key4("DD");
|
||||
std::string key_not("EE");
|
||||
std::string value1("Value1");
|
||||
std::string value2("Value2");
|
||||
std::string value3("Value3");
|
||||
std::string value4("Value4");
|
||||
|
||||
ASSERT_OK(Put(key1, value1, WriteOptions()));
|
||||
ASSERT_OK(Put(key2, value2, WriteOptions()));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put(key3, value3, WriteOptions()));
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
ASSERT_OK(Put(key4, value4, WriteOptions()));
|
||||
|
||||
// Delete key2 and key3
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "BA", "CZ"));
|
||||
|
||||
// Read without snapshot
|
||||
auto results = MultiGet({key_not, key1, key2, key3, key4});
|
||||
ASSERT_EQ(results[0], "NOT_FOUND");
|
||||
ASSERT_EQ(results[1], value1);
|
||||
ASSERT_EQ(results[2], "NOT_FOUND");
|
||||
ASSERT_EQ(results[3], "NOT_FOUND");
|
||||
ASSERT_EQ(results[4], value4);
|
||||
|
||||
// Also check Get
|
||||
ASSERT_EQ(Get(key1), value1);
|
||||
ASSERT_EQ(Get(key2), "NOT_FOUND");
|
||||
ASSERT_EQ(Get(key3), "NOT_FOUND");
|
||||
ASSERT_EQ(Get(key4), value4);
|
||||
|
||||
// Read with snapshot
|
||||
results = MultiGet({key_not, key1, key2, key3, key4}, snapshot);
|
||||
ASSERT_EQ(results[0], "NOT_FOUND");
|
||||
ASSERT_EQ(results[1], value1);
|
||||
ASSERT_EQ(results[2], value2);
|
||||
ASSERT_EQ(results[3], value3);
|
||||
ASSERT_EQ(results[4], "NOT_FOUND");
|
||||
|
||||
// Also check Get
|
||||
ASSERT_EQ(Get(key1, snapshot), value1);
|
||||
ASSERT_EQ(Get(key2, snapshot), value2);
|
||||
ASSERT_EQ(Get(key3, snapshot), value3);
|
||||
ASSERT_EQ(Get(key4, snapshot), "NOT_FOUND");
|
||||
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
TEST_F(DBBloomFilterTest, MemtablePrefixBloomOutOfDomain) {
|
||||
constexpr size_t kPrefixSize = 8;
|
||||
const std::string kKey = "key";
|
||||
|
||||
@@ -6127,7 +6127,7 @@ TEST_F(DBCompactionTest, CompactionWithBlob) {
|
||||
ASSERT_EQ(Get(first_key), third_value);
|
||||
ASSERT_EQ(Get(second_key), third_value);
|
||||
|
||||
VersionSet* const versions = dbfull()->TEST_GetVersionSet();
|
||||
VersionSet* const versions = dbfull()->GetVersionSet();
|
||||
assert(versions);
|
||||
|
||||
ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault();
|
||||
@@ -6230,7 +6230,7 @@ TEST_P(DBCompactionTestBlobError, CompactionError) {
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
VersionSet* const versions = dbfull()->TEST_GetVersionSet();
|
||||
VersionSet* const versions = dbfull()->GetVersionSet();
|
||||
assert(versions);
|
||||
|
||||
ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault();
|
||||
@@ -6352,7 +6352,7 @@ TEST_P(DBCompactionTestBlobGC, CompactionWithBlobGC) {
|
||||
ASSERT_EQ(new_blob_files[i - cutoff_index], original_blob_files[i]);
|
||||
}
|
||||
|
||||
VersionSet* const versions = dbfull()->TEST_GetVersionSet();
|
||||
VersionSet* const versions = dbfull()->GetVersionSet();
|
||||
assert(versions);
|
||||
assert(versions->GetColumnFamilySet());
|
||||
|
||||
|
||||
+18
-19
@@ -127,31 +127,30 @@ Status DBImpl::GetLiveFiles(std::vector<std::string>& ret,
|
||||
}
|
||||
|
||||
Status DBImpl::GetSortedWalFiles(VectorLogPtr& files) {
|
||||
// If caller disabled deletions, this function should return files that are
|
||||
// guaranteed not to be deleted until deletions are re-enabled. We need to
|
||||
// wait for pending purges to finish since WalManager doesn't know which
|
||||
// files are going to be purged. Additional purges won't be scheduled as
|
||||
// long as deletions are disabled (so the below loop must terminate).
|
||||
// Also note that we disable deletions anyway to avoid the case where a
|
||||
// file is deleted in the middle of the scan, causing IO error.
|
||||
Status deletions_disabled = DisableFileDeletions();
|
||||
{
|
||||
// If caller disabled deletions, this function should return files that are
|
||||
// guaranteed not to be deleted until deletions are re-enabled. We need to
|
||||
// wait for pending purges to finish since WalManager doesn't know which
|
||||
// files are going to be purged. Additional purges won't be scheduled as
|
||||
// long as deletions are disabled (so the below loop must terminate).
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
while (disable_delete_obsolete_files_ > 0 &&
|
||||
(pending_purge_obsolete_files_ > 0 || bg_purge_scheduled_ > 0)) {
|
||||
while (pending_purge_obsolete_files_ > 0 || bg_purge_scheduled_ > 0) {
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
// Disable deletion in order to avoid the case where a file is deleted in
|
||||
// the middle of the process so IO error is returned.
|
||||
Status s = DisableFileDeletions();
|
||||
bool file_deletion_supported = !s.IsNotSupported();
|
||||
if (s.ok() || !file_deletion_supported) {
|
||||
s = wal_manager_.GetSortedWalFiles(files);
|
||||
if (file_deletion_supported) {
|
||||
Status s2 = EnableFileDeletions(false);
|
||||
if (!s2.ok() && s.ok()) {
|
||||
s = s2;
|
||||
}
|
||||
}
|
||||
Status s = wal_manager_.GetSortedWalFiles(files);
|
||||
|
||||
// DisableFileDeletions / EnableFileDeletions not supported in read-only DB
|
||||
if (deletions_disabled.ok()) {
|
||||
Status s2 = EnableFileDeletions(/*force*/ false);
|
||||
assert(s2.ok());
|
||||
s2.PermitUncheckedError();
|
||||
} else {
|
||||
assert(deletions_disabled.IsNotSupported());
|
||||
}
|
||||
|
||||
return s;
|
||||
|
||||
+4
-4
@@ -1608,7 +1608,7 @@ TEST_F(DBFlushTest, FlushWithBlob) {
|
||||
ASSERT_EQ(Get("key1"), short_value);
|
||||
ASSERT_EQ(Get("key2"), long_value);
|
||||
|
||||
VersionSet* const versions = dbfull()->TEST_GetVersionSet();
|
||||
VersionSet* const versions = dbfull()->GetVersionSet();
|
||||
assert(versions);
|
||||
|
||||
ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault();
|
||||
@@ -1938,7 +1938,7 @@ TEST_P(DBFlushTestBlobError, FlushError) {
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
VersionSet* const versions = dbfull()->TEST_GetVersionSet();
|
||||
VersionSet* const versions = dbfull()->GetVersionSet();
|
||||
assert(versions);
|
||||
|
||||
ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault();
|
||||
@@ -2149,7 +2149,7 @@ TEST_P(DBAtomicFlushTest, PrecomputeMinLogNumberToKeepNon2PC) {
|
||||
flush_edits.push_back({});
|
||||
auto unflushed_cfh = static_cast<ColumnFamilyHandleImpl*>(handles_[1]);
|
||||
|
||||
ASSERT_EQ(PrecomputeMinLogNumberToKeepNon2PC(dbfull()->TEST_GetVersionSet(),
|
||||
ASSERT_EQ(PrecomputeMinLogNumberToKeepNon2PC(dbfull()->GetVersionSet(),
|
||||
flushed_cfds, flush_edits),
|
||||
unflushed_cfh->cfd()->GetLogNumber());
|
||||
}
|
||||
@@ -2174,7 +2174,7 @@ TEST_P(DBAtomicFlushTest, PrecomputeMinLogNumberToKeepNon2PC) {
|
||||
std::min(min_log_number_to_keep, cfh->cfd()->GetLogNumber());
|
||||
}
|
||||
ASSERT_EQ(min_log_number_to_keep, log_num_after_flush);
|
||||
ASSERT_EQ(PrecomputeMinLogNumberToKeepNon2PC(dbfull()->TEST_GetVersionSet(),
|
||||
ASSERT_EQ(PrecomputeMinLogNumberToKeepNon2PC(dbfull()->GetVersionSet(),
|
||||
flushed_cfds, flush_edits),
|
||||
min_log_number_to_keep);
|
||||
}
|
||||
|
||||
+15
-14
@@ -1546,6 +1546,8 @@ void DBImpl::BackgroundCallPurge() {
|
||||
mutex_.Lock();
|
||||
}
|
||||
|
||||
assert(bg_purge_scheduled_ > 0);
|
||||
|
||||
// Can't use iterator to go over purge_files_ because inside the loop we're
|
||||
// unlocking the mutex that protects purge_files_.
|
||||
while (!purge_files_.empty()) {
|
||||
@@ -1613,17 +1615,7 @@ static void CleanupIteratorState(void* arg1, void* /*arg2*/) {
|
||||
delete state->super_version;
|
||||
}
|
||||
if (job_context.HaveSomethingToDelete()) {
|
||||
if (state->background_purge) {
|
||||
// PurgeObsoleteFiles here does not delete files. Instead, it adds the
|
||||
// files to be deleted to a job queue, and deletes it in a separate
|
||||
// background thread.
|
||||
state->db->PurgeObsoleteFiles(job_context, true /* schedule only */);
|
||||
state->mu->Lock();
|
||||
state->db->SchedulePurge();
|
||||
state->mu->Unlock();
|
||||
} else {
|
||||
state->db->PurgeObsoleteFiles(job_context);
|
||||
}
|
||||
state->db->PurgeObsoleteFiles(job_context, state->background_purge);
|
||||
}
|
||||
job_context.Clean();
|
||||
}
|
||||
@@ -3956,16 +3948,25 @@ Status DBImpl::GetDbSessionId(std::string& session_id) const {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
namespace {
|
||||
SemiStructuredUniqueIdGen* DbSessionIdGen() {
|
||||
static SemiStructuredUniqueIdGen gen;
|
||||
return &gen;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void DBImpl::TEST_ResetDbSessionIdGen() { DbSessionIdGen()->Reset(); }
|
||||
|
||||
std::string DBImpl::GenerateDbSessionId(Env*) {
|
||||
// See SemiStructuredUniqueIdGen for its desirable properties.
|
||||
static SemiStructuredUniqueIdGen gen;
|
||||
auto gen = DbSessionIdGen();
|
||||
|
||||
uint64_t lo, hi;
|
||||
gen.GenerateNext(&hi, &lo);
|
||||
gen->GenerateNext(&hi, &lo);
|
||||
if (lo == 0) {
|
||||
// Avoid emitting session ID with lo==0, so that SST unique
|
||||
// IDs can be more easily ensured non-zero
|
||||
gen.GenerateNext(&hi, &lo);
|
||||
gen->GenerateNext(&hi, &lo);
|
||||
assert(lo != 0);
|
||||
}
|
||||
return EncodeSessionId(hi, lo);
|
||||
|
||||
@@ -942,6 +942,8 @@ class DBImpl : public DB {
|
||||
int max_entries_to_print,
|
||||
std::string* out_str);
|
||||
|
||||
VersionSet* GetVersionSet() const { return versions_.get(); }
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Compact any files in the named level that overlap [*begin, *end]
|
||||
Status TEST_CompactRange(int level, const Slice* begin, const Slice* end,
|
||||
@@ -1049,8 +1051,6 @@ class DBImpl : public DB {
|
||||
void TEST_WaitForStatsDumpRun(std::function<void()> callback) const;
|
||||
size_t TEST_EstimateInMemoryStatsHistorySize() const;
|
||||
|
||||
VersionSet* TEST_GetVersionSet() const { return versions_.get(); }
|
||||
|
||||
uint64_t TEST_GetCurrentLogNumber() const {
|
||||
InstrumentedMutexLock l(mutex());
|
||||
assert(!logs_.empty());
|
||||
@@ -1128,10 +1128,12 @@ class DBImpl : public DB {
|
||||
State state_;
|
||||
};
|
||||
|
||||
static void TEST_ResetDbSessionIdGen();
|
||||
static std::string GenerateDbSessionId(Env* env);
|
||||
|
||||
protected:
|
||||
const std::string dbname_;
|
||||
// TODO(peterd): unify with VersionSet::db_id_
|
||||
std::string db_id_;
|
||||
// db_session_id_ is an identifier that gets reset
|
||||
// every time the DB is opened
|
||||
|
||||
@@ -1611,7 +1611,7 @@ Status DBImpl::ReFitLevel(ColumnFamilyData* cfd, int level, int target_level) {
|
||||
edit.AddFile(
|
||||
to_level, f->fd.GetNumber(), f->fd.GetPathId(), f->fd.GetFileSize(),
|
||||
f->smallest, f->largest, f->fd.smallest_seqno, f->fd.largest_seqno,
|
||||
f->marked_for_compaction, f->oldest_blob_file_number,
|
||||
f->marked_for_compaction, f->temperature, f->oldest_blob_file_number,
|
||||
f->oldest_ancester_time, f->file_creation_time, f->file_checksum,
|
||||
f->file_checksum_func_name, f->min_timestamp, f->max_timestamp);
|
||||
}
|
||||
@@ -3196,7 +3196,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
c->edit()->AddFile(
|
||||
c->output_level(), f->fd.GetNumber(), f->fd.GetPathId(),
|
||||
f->fd.GetFileSize(), f->smallest, f->largest, f->fd.smallest_seqno,
|
||||
f->fd.largest_seqno, f->marked_for_compaction,
|
||||
f->fd.largest_seqno, f->marked_for_compaction, f->temperature,
|
||||
f->oldest_blob_file_number, f->oldest_ancester_time,
|
||||
f->file_creation_time, f->file_checksum, f->file_checksum_func_name,
|
||||
f->min_timestamp, f->max_timestamp);
|
||||
|
||||
@@ -76,7 +76,8 @@ Status DBImpl::PromoteL0(ColumnFamilyHandle* column_family, int target_level) {
|
||||
"PromoteL0 FAILED. Target level %d does not exist\n",
|
||||
target_level);
|
||||
job_context.Clean();
|
||||
return Status::InvalidArgument("Target level does not exist");
|
||||
status = Status::InvalidArgument("Target level does not exist");
|
||||
return status;
|
||||
}
|
||||
|
||||
// Sort L0 files by range.
|
||||
@@ -96,7 +97,9 @@ Status DBImpl::PromoteL0(ColumnFamilyHandle* column_family, int target_level) {
|
||||
"PromoteL0 FAILED. File %" PRIu64 " being compacted\n",
|
||||
f->fd.GetNumber());
|
||||
job_context.Clean();
|
||||
return Status::InvalidArgument("PromoteL0 called during L0 compaction");
|
||||
status =
|
||||
Status::InvalidArgument("PromoteL0 called during L0 compaction");
|
||||
return status;
|
||||
}
|
||||
|
||||
if (i == 0) continue;
|
||||
@@ -107,7 +110,8 @@ Status DBImpl::PromoteL0(ColumnFamilyHandle* column_family, int target_level) {
|
||||
" have overlapping ranges\n",
|
||||
prev_f->fd.GetNumber(), f->fd.GetNumber());
|
||||
job_context.Clean();
|
||||
return Status::InvalidArgument("L0 has overlapping files");
|
||||
status = Status::InvalidArgument("L0 has overlapping files");
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,22 +121,23 @@ Status DBImpl::PromoteL0(ColumnFamilyHandle* column_family, int target_level) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"PromoteL0 FAILED. Level %d not empty\n", level);
|
||||
job_context.Clean();
|
||||
return Status::InvalidArgument(
|
||||
status = Status::InvalidArgument(
|
||||
"All levels up to target_level "
|
||||
"must be empty");
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
edit.SetColumnFamily(cfd->GetID());
|
||||
for (const auto& f : l0_files) {
|
||||
edit.DeleteFile(0, f->fd.GetNumber());
|
||||
edit.AddFile(target_level, f->fd.GetNumber(), f->fd.GetPathId(),
|
||||
f->fd.GetFileSize(), f->smallest, f->largest,
|
||||
f->fd.smallest_seqno, f->fd.largest_seqno,
|
||||
f->marked_for_compaction, f->oldest_blob_file_number,
|
||||
f->oldest_ancester_time, f->file_creation_time,
|
||||
f->file_checksum, f->file_checksum_func_name,
|
||||
f->min_timestamp, f->max_timestamp);
|
||||
edit.AddFile(
|
||||
target_level, f->fd.GetNumber(), f->fd.GetPathId(),
|
||||
f->fd.GetFileSize(), f->smallest, f->largest, f->fd.smallest_seqno,
|
||||
f->fd.largest_seqno, f->marked_for_compaction, f->temperature,
|
||||
f->oldest_blob_file_number, f->oldest_ancester_time,
|
||||
f->file_creation_time, f->file_checksum, f->file_checksum_func_name,
|
||||
f->min_timestamp, f->max_timestamp);
|
||||
}
|
||||
|
||||
status = versions_->LogAndApply(cfd, *cfd->GetLatestMutableCFOptions(),
|
||||
|
||||
@@ -56,6 +56,8 @@ Status DBImpl::DisableFileDeletions() {
|
||||
return s;
|
||||
}
|
||||
|
||||
// FIXME: can be inconsistent with DisableFileDeletions in cases like
|
||||
// DBImplReadOnly
|
||||
Status DBImpl::DisableFileDeletionsWithLock() {
|
||||
mutex_.AssertHeld();
|
||||
++disable_delete_obsolete_files_;
|
||||
@@ -642,6 +644,11 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
--pending_purge_obsolete_files_;
|
||||
assert(pending_purge_obsolete_files_ >= 0);
|
||||
if (schedule_only) {
|
||||
// Must change from pending_purge_obsolete_files_ to bg_purge_scheduled_
|
||||
// while holding mutex (for GetSortedWalFiles() etc.)
|
||||
SchedulePurge();
|
||||
}
|
||||
if (pending_purge_obsolete_files_ == 0) {
|
||||
bg_cv_.SignalAll();
|
||||
}
|
||||
@@ -657,11 +664,6 @@ void DBImpl::DeleteObsoleteFiles() {
|
||||
if (job_context.HaveSomethingToDelete()) {
|
||||
bool defer_purge = immutable_db_options_.avoid_unnecessary_blocking_io;
|
||||
PurgeObsoleteFiles(job_context, defer_purge);
|
||||
if (defer_purge) {
|
||||
mutex_.Lock();
|
||||
SchedulePurge();
|
||||
mutex_.Unlock();
|
||||
}
|
||||
}
|
||||
job_context.Clean();
|
||||
mutex_.Lock();
|
||||
|
||||
@@ -1465,13 +1465,13 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
constexpr int level = 0;
|
||||
|
||||
if (s.ok() && has_output) {
|
||||
edit->AddFile(level, meta.fd.GetNumber(), meta.fd.GetPathId(),
|
||||
meta.fd.GetFileSize(), meta.smallest, meta.largest,
|
||||
meta.fd.smallest_seqno, meta.fd.largest_seqno,
|
||||
meta.marked_for_compaction, meta.oldest_blob_file_number,
|
||||
meta.oldest_ancester_time, meta.file_creation_time,
|
||||
meta.file_checksum, meta.file_checksum_func_name,
|
||||
meta.min_timestamp, meta.max_timestamp);
|
||||
edit->AddFile(
|
||||
level, meta.fd.GetNumber(), meta.fd.GetPathId(), meta.fd.GetFileSize(),
|
||||
meta.smallest, meta.largest, meta.fd.smallest_seqno,
|
||||
meta.fd.largest_seqno, meta.marked_for_compaction, meta.temperature,
|
||||
meta.oldest_blob_file_number, meta.oldest_ancester_time,
|
||||
meta.file_creation_time, meta.file_checksum,
|
||||
meta.file_checksum_func_name, meta.min_timestamp, meta.max_timestamp);
|
||||
|
||||
for (const auto& blob : blob_file_additions) {
|
||||
edit->AddBlobFile(blob);
|
||||
|
||||
@@ -290,6 +290,10 @@ class DBImplSecondary : public DBImpl {
|
||||
|
||||
Status MarkCommit(const Slice&) override { return Status::OK(); }
|
||||
|
||||
Status MarkCommitWithTimestamp(const Slice&, const Slice&) override {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status MarkNoop(bool) override { return Status::OK(); }
|
||||
|
||||
const std::unordered_set<uint32_t>& column_families() const {
|
||||
|
||||
@@ -1660,8 +1660,8 @@ Status DBImpl::TrimMemtableHistory(WriteContext* context) {
|
||||
}
|
||||
for (auto& cfd : cfds) {
|
||||
autovector<MemTable*> to_delete;
|
||||
bool trimmed = cfd->imm()->TrimHistory(
|
||||
&context->memtables_to_free_, cfd->mem()->ApproximateMemoryUsage());
|
||||
bool trimmed = cfd->imm()->TrimHistory(&context->memtables_to_free_,
|
||||
cfd->mem()->MemoryAllocatedBytes());
|
||||
if (trimmed) {
|
||||
context->superversion_context.NewSuperVersion();
|
||||
assert(context->superversion_context.new_superversion.get() != nullptr);
|
||||
|
||||
@@ -266,6 +266,50 @@ TEST_F(DBOptionsTest, SetMutableTableOptions) {
|
||||
ASSERT_EQ(c_bbto->block_restart_interval, 13);
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, SetWithCustomMemTableFactory) {
|
||||
class DummySkipListFactory : public SkipListFactory {
|
||||
public:
|
||||
static const char* kClassName() { return "DummySkipListFactory"; }
|
||||
const char* Name() const override { return kClassName(); }
|
||||
explicit DummySkipListFactory() : SkipListFactory(2) {}
|
||||
};
|
||||
{
|
||||
// Verify the DummySkipList cannot be created
|
||||
ConfigOptions config_options;
|
||||
config_options.ignore_unsupported_options = false;
|
||||
std::unique_ptr<MemTableRepFactory> factory;
|
||||
ASSERT_NOK(MemTableRepFactory::CreateFromString(
|
||||
config_options, DummySkipListFactory::kClassName(), &factory));
|
||||
}
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
// Try with fail_if_options_file_error=false/true to update the options
|
||||
for (bool on_error : {false, true}) {
|
||||
options.fail_if_options_file_error = on_error;
|
||||
options.env = env_;
|
||||
options.disable_auto_compactions = false;
|
||||
|
||||
options.memtable_factory.reset(new DummySkipListFactory());
|
||||
Reopen(options);
|
||||
|
||||
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
|
||||
ASSERT_OK(
|
||||
dbfull()->SetOptions(cfh, {{"disable_auto_compactions", "true"}}));
|
||||
ColumnFamilyDescriptor cfd;
|
||||
ASSERT_OK(cfh->GetDescriptor(&cfd));
|
||||
ASSERT_STREQ(cfd.options.memtable_factory->Name(),
|
||||
DummySkipListFactory::kClassName());
|
||||
ColumnFamilyHandle* test = nullptr;
|
||||
ASSERT_OK(dbfull()->CreateColumnFamily(options, "test", &test));
|
||||
ASSERT_OK(test->GetDescriptor(&cfd));
|
||||
ASSERT_STREQ(cfd.options.memtable_factory->Name(),
|
||||
DummySkipListFactory::kClassName());
|
||||
|
||||
ASSERT_OK(dbfull()->DropColumnFamily(test));
|
||||
delete test;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, SetBytesPerSync) {
|
||||
const size_t kValueSize = 1024 * 1024; // 1MB
|
||||
Options options;
|
||||
|
||||
+17
-5
@@ -310,6 +310,7 @@ TEST_F(DBSSTTest, DBWithSstFileManager) {
|
||||
ASSERT_EQ(sfm->GetTrackedFiles(), files_in_db);
|
||||
}
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
std::unordered_map<std::string, uint64_t> files_in_db;
|
||||
ASSERT_OK(GetAllDataFiles(kTableFile, &files_in_db));
|
||||
@@ -416,6 +417,7 @@ TEST_F(DBSSTTest, DBWithSstFileManagerForBlobFiles) {
|
||||
ASSERT_EQ(files_moved, 0);
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
std::unordered_map<std::string, uint64_t> files_in_db;
|
||||
ASSERT_OK(GetAllDataFiles(kTableFile, &files_in_db));
|
||||
@@ -561,6 +563,7 @@ TEST_F(DBSSTTest, DBWithSstFileManagerForBlobFilesWithGC) {
|
||||
constexpr Slice* end = nullptr;
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), begin, end));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
sfm->WaitForEmptyTrash();
|
||||
|
||||
ASSERT_EQ(Get(first_key), first_value);
|
||||
@@ -1030,7 +1033,9 @@ TEST_F(DBSSTTest, DestroyDBWithRateLimitedDelete) {
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(options.sst_file_manager.get());
|
||||
|
||||
sfm->SetDeleteRateBytesPerSecond(1024 * 1024);
|
||||
sfm->delete_scheduler()->SetMaxTrashDBRatio(1.1);
|
||||
// Set an extra high trash ratio to prevent immediate/non-rate limited
|
||||
// deletions
|
||||
sfm->delete_scheduler()->SetMaxTrashDBRatio(1000.0);
|
||||
ASSERT_OK(DestroyDB(dbname_, options));
|
||||
sfm->WaitForEmptyTrash();
|
||||
ASSERT_EQ(bg_delete_file, num_sst_files + num_wal_files);
|
||||
@@ -1097,7 +1102,7 @@ TEST_F(DBSSTTest, DBWithMaxSpaceAllowedWithBlobFiles) {
|
||||
ASSERT_EQ(sfm->GetTotalSize(), total_files_size);
|
||||
|
||||
// Set the maximum allowed space usage to the current total size.
|
||||
sfm->SetMaxAllowedSpaceUsage(files_size + 1);
|
||||
sfm->SetMaxAllowedSpaceUsage(total_files_size + 1);
|
||||
|
||||
bool max_allowed_space_reached = false;
|
||||
bool delete_blob_file = false;
|
||||
@@ -1662,17 +1667,24 @@ TEST_F(DBSSTTest, DBWithSFMForBlobFilesAtomicFlush) {
|
||||
constexpr Slice* end = nullptr;
|
||||
// Compaction job will create a new file and delete the older files.
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), begin, end));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
ASSERT_EQ(files_added, 1);
|
||||
ASSERT_EQ(files_deleted, 1);
|
||||
ASSERT_EQ(files_scheduled_to_delete, 1);
|
||||
|
||||
sfm->WaitForEmptyTrash();
|
||||
|
||||
ASSERT_EQ(files_deleted, 1);
|
||||
|
||||
Close();
|
||||
ASSERT_OK(DestroyDB(dbname_, options));
|
||||
sfm->WaitForEmptyTrash();
|
||||
ASSERT_EQ(files_deleted, 4);
|
||||
|
||||
ASSERT_EQ(files_scheduled_to_delete, 4);
|
||||
|
||||
sfm->WaitForEmptyTrash();
|
||||
|
||||
ASSERT_EQ(files_deleted, 4);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
+267
-230
File diff suppressed because it is too large.
Load diff
@@ -6601,6 +6601,12 @@ TEST_F(DBTest2, BottommostTemperature) {
|
||||
DB::Properties::kLiveSstFilesSizeAtTemperature + std::to_string(22),
|
||||
&prop));
|
||||
ASSERT_EQ(std::atoi(prop.c_str()), 0);
|
||||
|
||||
Reopen(options);
|
||||
db_->GetColumnFamilyMetaData(&metadata);
|
||||
ASSERT_EQ(2, metadata.file_count);
|
||||
ASSERT_EQ(Temperature::kUnknown, metadata.levels[0].files[0].temperature);
|
||||
ASSERT_EQ(Temperature::kWarm, metadata.levels[1].files[0].temperature);
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, BottommostTemperatureUniversal) {
|
||||
|
||||
+7
-1
@@ -15,6 +15,7 @@
|
||||
#include "rocksdb/env_encryption.h"
|
||||
#include "rocksdb/unique_id.h"
|
||||
#include "rocksdb/utilities/object_registry.h"
|
||||
#include "table/format.h"
|
||||
#include "util/random.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -516,6 +517,11 @@ Options DBTestBase::GetOptions(
|
||||
table_options.index_block_restart_interval = 8;
|
||||
break;
|
||||
}
|
||||
case kBlockBasedTableWithLatestFormat: {
|
||||
// In case different from default
|
||||
table_options.format_version = kLatestFormatVersion;
|
||||
break;
|
||||
}
|
||||
case kOptimizeFiltersForHits: {
|
||||
options.optimize_filters_for_hits = true;
|
||||
set_block_based_table_factory = true;
|
||||
@@ -1124,7 +1130,7 @@ std::string DBTestBase::FilesPerLevel(int cf) {
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
std::vector<uint64_t> DBTestBase::GetBlobFileNumbers() {
|
||||
VersionSet* const versions = dbfull()->TEST_GetVersionSet();
|
||||
VersionSet* const versions = dbfull()->GetVersionSet();
|
||||
assert(versions);
|
||||
|
||||
ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault();
|
||||
|
||||
@@ -867,6 +867,7 @@ class DBTestBase : public testing::Test {
|
||||
kBlockBasedTableWithIndexRestartInterval,
|
||||
kBlockBasedTableWithPartitionedIndex,
|
||||
kBlockBasedTableWithPartitionedIndexFormat4,
|
||||
kBlockBasedTableWithLatestFormat,
|
||||
kPartitionedFilterWithNewTableReaderForCompactions,
|
||||
kUniversalSubcompactions,
|
||||
kUnorderedWrite,
|
||||
|
||||
+4
-4
@@ -382,7 +382,7 @@ TEST_F(DBWALTest, RecoverWithBlob) {
|
||||
|
||||
// There should be no files just yet since we haven't flushed.
|
||||
{
|
||||
VersionSet* const versions = dbfull()->TEST_GetVersionSet();
|
||||
VersionSet* const versions = dbfull()->GetVersionSet();
|
||||
ASSERT_NE(versions, nullptr);
|
||||
|
||||
ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault();
|
||||
@@ -412,7 +412,7 @@ TEST_F(DBWALTest, RecoverWithBlob) {
|
||||
ASSERT_EQ(Get("key1"), short_value);
|
||||
ASSERT_EQ(Get("key2"), long_value);
|
||||
|
||||
VersionSet* const versions = dbfull()->TEST_GetVersionSet();
|
||||
VersionSet* const versions = dbfull()->GetVersionSet();
|
||||
ASSERT_NE(versions, nullptr);
|
||||
|
||||
ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault();
|
||||
@@ -477,7 +477,7 @@ TEST_F(DBWALTest, RecoverWithBlobMultiSST) {
|
||||
|
||||
// There should be no files just yet since we haven't flushed.
|
||||
{
|
||||
VersionSet* const versions = dbfull()->TEST_GetVersionSet();
|
||||
VersionSet* const versions = dbfull()->GetVersionSet();
|
||||
ASSERT_NE(versions, nullptr);
|
||||
|
||||
ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault();
|
||||
@@ -509,7 +509,7 @@ TEST_F(DBWALTest, RecoverWithBlobMultiSST) {
|
||||
ASSERT_EQ(Get(Key(i)), large_value);
|
||||
}
|
||||
|
||||
VersionSet* const versions = dbfull()->TEST_GetVersionSet();
|
||||
VersionSet* const versions = dbfull()->GetVersionSet();
|
||||
ASSERT_NE(versions, nullptr);
|
||||
|
||||
ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault();
|
||||
|
||||
+3
-2
@@ -65,7 +65,8 @@ enum ValueType : unsigned char {
|
||||
// another.
|
||||
kTypeBeginUnprepareXID = 0x13, // WAL only.
|
||||
kTypeDeletionWithTimestamp = 0x14,
|
||||
kMaxValue = 0x7F // Not used for storing records.
|
||||
kTypeCommitXIDAndTimestamp = 0x15, // WAL only
|
||||
kMaxValue = 0x7F // Not used for storing records.
|
||||
};
|
||||
|
||||
// Defined in dbformat.cc
|
||||
@@ -654,7 +655,7 @@ extern bool ReadKeyFromWriteBatchEntry(Slice* input, Slice* key,
|
||||
|
||||
// Read record from a write batch piece from input.
|
||||
// tag, column_family, key, value and blob are return values. Callers own the
|
||||
// Slice they point to.
|
||||
// slice they point to.
|
||||
// Tag is defined as ValueType.
|
||||
// input will be advanced to after the record.
|
||||
extern Status ReadRecordFromWriteBatch(Slice* input, char* tag,
|
||||
|
||||
+23
-4
@@ -540,6 +540,8 @@ Status ErrorHandler::ClearBGError() {
|
||||
// Signal that recovery succeeded
|
||||
if (recovery_error_.ok()) {
|
||||
Status old_bg_error = bg_error_;
|
||||
// old_bg_error is only for notifying listeners, so may not be checked
|
||||
old_bg_error.PermitUncheckedError();
|
||||
// Clear and check the recovery IO and BG error
|
||||
bg_error_ = Status::OK();
|
||||
recovery_io_error_ = IOStatus::OK();
|
||||
@@ -547,8 +549,8 @@ Status ErrorHandler::ClearBGError() {
|
||||
recovery_io_error_.PermitUncheckedError();
|
||||
recovery_in_prog_ = false;
|
||||
soft_error_no_bg_work_ = false;
|
||||
EventHelpers::NotifyOnErrorRecoveryCompleted(db_options_.listeners,
|
||||
old_bg_error, db_mutex_);
|
||||
EventHelpers::NotifyOnErrorRecoveryEnd(db_options_.listeners, old_bg_error,
|
||||
bg_error_, db_mutex_);
|
||||
}
|
||||
return recovery_error_;
|
||||
#else
|
||||
@@ -665,6 +667,9 @@ void ErrorHandler::RecoverFromRetryableBGIOError() {
|
||||
TEST_SYNC_POINT("RecoverFromRetryableBGIOError:BeforeStart");
|
||||
InstrumentedMutexLock l(db_mutex_);
|
||||
if (end_recovery_) {
|
||||
EventHelpers::NotifyOnErrorRecoveryEnd(db_options_.listeners, bg_error_,
|
||||
Status::ShutdownInProgress(),
|
||||
db_mutex_);
|
||||
return;
|
||||
}
|
||||
DBRecoverContext context = recover_context_;
|
||||
@@ -674,6 +679,9 @@ void ErrorHandler::RecoverFromRetryableBGIOError() {
|
||||
// Recover from the retryable error. Create a separate thread to do it.
|
||||
while (resume_count > 0) {
|
||||
if (end_recovery_) {
|
||||
EventHelpers::NotifyOnErrorRecoveryEnd(db_options_.listeners, bg_error_,
|
||||
Status::ShutdownInProgress(),
|
||||
db_mutex_);
|
||||
return;
|
||||
}
|
||||
TEST_SYNC_POINT("RecoverFromRetryableBGIOError:BeforeResume0");
|
||||
@@ -695,6 +703,8 @@ void ErrorHandler::RecoverFromRetryableBGIOError() {
|
||||
RecordInHistogram(bg_error_stats_.get(),
|
||||
ERROR_HANDLER_AUTORESUME_RETRY_COUNT, retry_count);
|
||||
}
|
||||
EventHelpers::NotifyOnErrorRecoveryEnd(db_options_.listeners, bg_error_,
|
||||
bg_error_, db_mutex_);
|
||||
return;
|
||||
}
|
||||
if (!recovery_io_error_.ok() &&
|
||||
@@ -718,8 +728,8 @@ void ErrorHandler::RecoverFromRetryableBGIOError() {
|
||||
Status old_bg_error = bg_error_;
|
||||
bg_error_ = Status::OK();
|
||||
bg_error_.PermitUncheckedError();
|
||||
EventHelpers::NotifyOnErrorRecoveryCompleted(db_options_.listeners,
|
||||
old_bg_error, db_mutex_);
|
||||
EventHelpers::NotifyOnErrorRecoveryEnd(
|
||||
db_options_.listeners, old_bg_error, bg_error_, db_mutex_);
|
||||
if (bg_error_stats_ != nullptr) {
|
||||
RecordTick(bg_error_stats_.get(),
|
||||
ERROR_HANDLER_AUTORESUME_SUCCESS_COUNT);
|
||||
@@ -739,12 +749,21 @@ void ErrorHandler::RecoverFromRetryableBGIOError() {
|
||||
RecordInHistogram(bg_error_stats_.get(),
|
||||
ERROR_HANDLER_AUTORESUME_RETRY_COUNT, retry_count);
|
||||
}
|
||||
EventHelpers::NotifyOnErrorRecoveryEnd(
|
||||
db_options_.listeners, bg_error_,
|
||||
!recovery_io_error_.ok()
|
||||
? recovery_io_error_
|
||||
: (!recovery_error_.ok() ? recovery_error_ : s),
|
||||
db_mutex_);
|
||||
return;
|
||||
}
|
||||
}
|
||||
resume_count--;
|
||||
}
|
||||
recovery_in_prog_ = false;
|
||||
EventHelpers::NotifyOnErrorRecoveryEnd(
|
||||
db_options_.listeners, bg_error_,
|
||||
Status::Aborted("Exceeded resume retry count"), db_mutex_);
|
||||
TEST_SYNC_POINT("RecoverFromRetryableBGIOError:LoopOut");
|
||||
if (bg_error_stats_ != nullptr) {
|
||||
RecordInHistogram(bg_error_stats_.get(),
|
||||
|
||||
@@ -66,6 +66,7 @@ class ErrorHandlerFSListener : public EventListener {
|
||||
~ErrorHandlerFSListener() {
|
||||
file_creation_error_.PermitUncheckedError();
|
||||
bg_error_.PermitUncheckedError();
|
||||
new_bg_error_.PermitUncheckedError();
|
||||
}
|
||||
|
||||
void OnTableFileCreationStarted(
|
||||
@@ -96,6 +97,13 @@ class ErrorHandlerFSListener : public EventListener {
|
||||
old_bg_error.PermitUncheckedError();
|
||||
}
|
||||
|
||||
void OnErrorRecoveryEnd(const BackgroundErrorRecoveryInfo& info) override {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
recovery_complete_ = true;
|
||||
cv_.SignalAll();
|
||||
new_bg_error_ = info.new_bg_error;
|
||||
}
|
||||
|
||||
bool WaitForRecovery(uint64_t /*abs_time_us*/) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
while (!recovery_complete_) {
|
||||
@@ -138,6 +146,8 @@ class ErrorHandlerFSListener : public EventListener {
|
||||
file_creation_error_ = io_s;
|
||||
}
|
||||
|
||||
Status new_bg_error() { return new_bg_error_; }
|
||||
|
||||
private:
|
||||
InstrumentedMutex mutex_;
|
||||
InstrumentedCondVar cv_;
|
||||
@@ -148,6 +158,7 @@ class ErrorHandlerFSListener : public EventListener {
|
||||
int file_count_;
|
||||
IOStatus file_creation_error_;
|
||||
Status bg_error_;
|
||||
Status new_bg_error_;
|
||||
FaultInjectionTestFS* fault_fs_;
|
||||
};
|
||||
|
||||
@@ -2428,6 +2439,42 @@ TEST_F(DBErrorHandlingFSTest, WALWriteRetryableErrorAutoRecover2) {
|
||||
Close();
|
||||
}
|
||||
|
||||
// Fail auto resume from a flush retryable error and verify that
|
||||
// OnErrorRecoveryEnd listener callback is called
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWritRetryableErrorAbortRecovery) {
|
||||
// Activate the FS before the first resume
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
options.listeners.emplace_back(listener);
|
||||
options.max_bgerror_resume_count = 2;
|
||||
options.bgerror_resume_retry_interval = 100000; // 0.1 second
|
||||
Status s;
|
||||
|
||||
listener->EnableAutoRecovery(false);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
IOStatus error_msg = IOStatus::IOError("Retryable IO Error");
|
||||
error_msg.SetRetryable(true);
|
||||
|
||||
ASSERT_OK(Put(Key(1), "val1"));
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BuildTable:BeforeFinishBuildTable",
|
||||
[&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); });
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError);
|
||||
ASSERT_EQ(listener->WaitForRecovery(5000000), true);
|
||||
ASSERT_EQ(listener->new_bg_error(), Status::Aborted());
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
class DBErrorHandlingFencingTest : public DBErrorHandlingFSTest,
|
||||
public testing::WithParamInterface<bool> {};
|
||||
|
||||
|
||||
+10
-3
@@ -232,23 +232,30 @@ void EventHelpers::LogAndNotifyTableFileDeletion(
|
||||
#endif // !ROCKSDB_LITE
|
||||
}
|
||||
|
||||
void EventHelpers::NotifyOnErrorRecoveryCompleted(
|
||||
void EventHelpers::NotifyOnErrorRecoveryEnd(
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners,
|
||||
Status old_bg_error, InstrumentedMutex* db_mutex) {
|
||||
const Status& old_bg_error, const Status& new_bg_error,
|
||||
InstrumentedMutex* db_mutex) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (!listeners.empty()) {
|
||||
db_mutex->AssertHeld();
|
||||
// release lock while notifying events
|
||||
db_mutex->Unlock();
|
||||
for (auto& listener : listeners) {
|
||||
BackgroundErrorRecoveryInfo info;
|
||||
info.old_bg_error = old_bg_error;
|
||||
info.new_bg_error = new_bg_error;
|
||||
listener->OnErrorRecoveryCompleted(old_bg_error);
|
||||
listener->OnErrorRecoveryEnd(info);
|
||||
info.old_bg_error.PermitUncheckedError();
|
||||
info.new_bg_error.PermitUncheckedError();
|
||||
}
|
||||
db_mutex->Lock();
|
||||
}
|
||||
old_bg_error.PermitUncheckedError();
|
||||
#else
|
||||
(void)listeners;
|
||||
(void)old_bg_error;
|
||||
(void)new_bg_error;
|
||||
(void)db_mutex;
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
|
||||
+3
-2
@@ -43,9 +43,10 @@ class EventHelpers {
|
||||
uint64_t file_number, const std::string& file_path,
|
||||
const Status& status, const std::string& db_name,
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners);
|
||||
static void NotifyOnErrorRecoveryCompleted(
|
||||
static void NotifyOnErrorRecoveryEnd(
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners,
|
||||
Status bg_error, InstrumentedMutex* db_mutex);
|
||||
const Status& old_bg_error, const Status& new_bg_error,
|
||||
InstrumentedMutex* db_mutex);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
static void NotifyBlobFileCreationStarted(
|
||||
|
||||
@@ -38,7 +38,8 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
// Read the information of files we are ingesting
|
||||
for (const std::string& file_path : external_files_paths) {
|
||||
IngestedFileInfo file_to_ingest;
|
||||
status = GetIngestedFileInfo(file_path, &file_to_ingest, sv);
|
||||
status =
|
||||
GetIngestedFileInfo(file_path, next_file_number++, &file_to_ingest, sv);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
@@ -102,7 +103,6 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
// Copy/Move external files into DB
|
||||
std::unordered_set<size_t> ingestion_path_ids;
|
||||
for (IngestedFileInfo& f : files_to_ingest_) {
|
||||
f.fd = FileDescriptor(next_file_number++, 0, f.file_size);
|
||||
f.copy_file = false;
|
||||
const std::string path_outside_db = f.external_file_path;
|
||||
const std::string path_inside_db =
|
||||
@@ -440,9 +440,10 @@ Status ExternalSstFileIngestionJob::Run() {
|
||||
FileMetaData f_metadata(
|
||||
f.fd.GetNumber(), f.fd.GetPathId(), f.fd.GetFileSize(),
|
||||
f.smallest_internal_key, f.largest_internal_key, f.assigned_seqno,
|
||||
f.assigned_seqno, false, kInvalidBlobFileNumber, oldest_ancester_time,
|
||||
current_time, f.file_checksum, f.file_checksum_func_name,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
f.assigned_seqno, false, f.file_temperature, kInvalidBlobFileNumber,
|
||||
oldest_ancester_time, current_time, f.file_checksum,
|
||||
f.file_checksum_func_name, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
f_metadata.temperature = f.file_temperature;
|
||||
edit_.AddFile(f.picked_level, f_metadata);
|
||||
}
|
||||
@@ -542,8 +543,8 @@ void ExternalSstFileIngestionJob::Cleanup(const Status& status) {
|
||||
}
|
||||
|
||||
Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
const std::string& external_file, IngestedFileInfo* file_to_ingest,
|
||||
SuperVersion* sv) {
|
||||
const std::string& external_file, uint64_t new_file_number,
|
||||
IngestedFileInfo* file_to_ingest, SuperVersion* sv) {
|
||||
file_to_ingest->external_file_path = external_file;
|
||||
|
||||
// Get external file size
|
||||
@@ -553,6 +554,10 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
return status;
|
||||
}
|
||||
|
||||
// Assign FD with number
|
||||
file_to_ingest->fd =
|
||||
FileDescriptor(new_file_number, 0, file_to_ingest->file_size);
|
||||
|
||||
// Create TableReader for external file
|
||||
std::unique_ptr<TableReader> table_reader;
|
||||
std::unique_ptr<FSRandomAccessFile> sst_file;
|
||||
@@ -567,9 +572,14 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
std::move(sst_file), external_file, nullptr /*Env*/, io_tracer_));
|
||||
|
||||
status = cfd_->ioptions()->table_factory->NewTableReader(
|
||||
TableReaderOptions(*cfd_->ioptions(),
|
||||
sv->mutable_cf_options.prefix_extractor.get(),
|
||||
env_options_, cfd_->internal_comparator()),
|
||||
TableReaderOptions(
|
||||
*cfd_->ioptions(), sv->mutable_cf_options.prefix_extractor.get(),
|
||||
env_options_, cfd_->internal_comparator(),
|
||||
/*skip_filters*/ false, /*immortal*/ false,
|
||||
/*force_direct_prefetch*/ false, /*level*/ -1,
|
||||
/*block_cache_tracer*/ nullptr,
|
||||
/*max_file_size_for_l0_meta_pin*/ 0, versions_->DbSessionId(),
|
||||
/*cur_file_num*/ new_file_number),
|
||||
std::move(sst_file_reader), file_to_ingest->file_size, &table_reader);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
@@ -609,14 +619,12 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
|
||||
// Set the global sequence number
|
||||
file_to_ingest->original_seqno = DecodeFixed64(seqno_iter->second.c_str());
|
||||
auto offsets_iter = props->properties_offsets.find(
|
||||
ExternalSstFilePropertyNames::kGlobalSeqno);
|
||||
if (offsets_iter == props->properties_offsets.end() ||
|
||||
offsets_iter->second == 0) {
|
||||
if (props->external_sst_file_global_seqno_offset == 0) {
|
||||
file_to_ingest->global_seqno_offset = 0;
|
||||
return Status::Corruption("Was not able to find file global seqno field");
|
||||
}
|
||||
file_to_ingest->global_seqno_offset = static_cast<size_t>(offsets_iter->second);
|
||||
file_to_ingest->global_seqno_offset =
|
||||
static_cast<size_t>(props->external_sst_file_global_seqno_offset);
|
||||
} else if (file_to_ingest->version == 1) {
|
||||
// SST file V1 should not have global seqno field
|
||||
assert(seqno_iter == uprops.end());
|
||||
|
||||
@@ -138,6 +138,7 @@ class ExternalSstFileIngestionJob {
|
||||
// Open the external file and populate `file_to_ingest` with all the
|
||||
// external information we need to ingest this file.
|
||||
Status GetIngestedFileInfo(const std::string& external_file,
|
||||
uint64_t new_file_number,
|
||||
IngestedFileInfo* file_to_ingest,
|
||||
SuperVersion* sv);
|
||||
|
||||
|
||||
@@ -41,16 +41,33 @@ class ExternalSSTTestEnv : public EnvWrapper {
|
||||
bool fail_link_;
|
||||
};
|
||||
|
||||
class ExternalSSTFileTestBase : public DBTestBase {
|
||||
public:
|
||||
ExternalSSTFileTestBase()
|
||||
: DBTestBase("external_sst_file_test", /*env_do_fsync=*/true) {
|
||||
sst_files_dir_ = dbname_ + "/sst_files/";
|
||||
DestroyAndRecreateExternalSSTFilesDir();
|
||||
}
|
||||
|
||||
void DestroyAndRecreateExternalSSTFilesDir() {
|
||||
ASSERT_OK(DestroyDir(env_, sst_files_dir_));
|
||||
ASSERT_OK(env_->CreateDir(sst_files_dir_));
|
||||
}
|
||||
|
||||
~ExternalSSTFileTestBase() override {
|
||||
DestroyDir(env_, sst_files_dir_).PermitUncheckedError();
|
||||
}
|
||||
|
||||
protected:
|
||||
std::string sst_files_dir_;
|
||||
};
|
||||
|
||||
class ExternSSTFileLinkFailFallbackTest
|
||||
: public DBTestBase,
|
||||
: public ExternalSSTFileTestBase,
|
||||
public ::testing::WithParamInterface<std::tuple<bool, bool>> {
|
||||
public:
|
||||
ExternSSTFileLinkFailFallbackTest()
|
||||
: DBTestBase("external_sst_file_test", /*env_do_fsync=*/true),
|
||||
test_env_(new ExternalSSTTestEnv(env_, true)) {
|
||||
sst_files_dir_ = dbname_ + "/sst_files/";
|
||||
EXPECT_EQ(DestroyDir(env_, sst_files_dir_), Status::OK());
|
||||
EXPECT_EQ(env_->CreateDir(sst_files_dir_), Status::OK());
|
||||
: test_env_(new ExternalSSTTestEnv(env_, true)) {
|
||||
options_ = CurrentOptions();
|
||||
options_.disable_auto_compactions = true;
|
||||
options_.env = test_env_;
|
||||
@@ -65,25 +82,15 @@ class ExternSSTFileLinkFailFallbackTest
|
||||
}
|
||||
|
||||
protected:
|
||||
std::string sst_files_dir_;
|
||||
Options options_;
|
||||
ExternalSSTTestEnv* test_env_;
|
||||
};
|
||||
|
||||
class ExternalSSTFileTest
|
||||
: public DBTestBase,
|
||||
: public ExternalSSTFileTestBase,
|
||||
public ::testing::WithParamInterface<std::tuple<bool, bool>> {
|
||||
public:
|
||||
ExternalSSTFileTest()
|
||||
: DBTestBase("external_sst_file_test", /*env_do_fsync=*/true) {
|
||||
sst_files_dir_ = dbname_ + "/sst_files/";
|
||||
DestroyAndRecreateExternalSSTFilesDir();
|
||||
}
|
||||
|
||||
void DestroyAndRecreateExternalSSTFilesDir() {
|
||||
ASSERT_OK(DestroyDir(env_, sst_files_dir_));
|
||||
ASSERT_OK(env_->CreateDir(sst_files_dir_));
|
||||
}
|
||||
ExternalSSTFileTest() {}
|
||||
|
||||
Status GenerateOneExternalFile(
|
||||
const Options& options, ColumnFamilyHandle* cfh,
|
||||
@@ -282,13 +289,8 @@ class ExternalSSTFileTest
|
||||
return db_->IngestExternalFile(files, opts);
|
||||
}
|
||||
|
||||
~ExternalSSTFileTest() override {
|
||||
DestroyDir(env_, sst_files_dir_).PermitUncheckedError();
|
||||
}
|
||||
|
||||
protected:
|
||||
int last_file_id_ = 0;
|
||||
std::string sst_files_dir_;
|
||||
};
|
||||
|
||||
TEST_F(ExternalSSTFileTest, Basic) {
|
||||
@@ -2382,10 +2384,18 @@ TEST_F(ExternalSSTFileTest, IngestFileWrittenWithCompressionDictionary) {
|
||||
ASSERT_EQ(1, num_compression_dicts);
|
||||
}
|
||||
|
||||
class ExternalSSTBlockChecksumTest
|
||||
: public ExternalSSTFileTestBase,
|
||||
public testing::WithParamInterface<uint32_t> {};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(FormatVersions, ExternalSSTBlockChecksumTest,
|
||||
testing::ValuesIn(test::kFooterFormatVersionsToTest));
|
||||
|
||||
// Very slow, not worth the cost to run regularly
|
||||
TEST_F(ExternalSSTFileTest, DISABLED_HugeBlockChecksum) {
|
||||
TEST_P(ExternalSSTBlockChecksumTest, DISABLED_HugeBlockChecksum) {
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.format_version = GetParam();
|
||||
for (auto t : GetSupportedChecksums()) {
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.checksum = t;
|
||||
Options options = CurrentOptions();
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/random.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
#ifndef NDEBUG
|
||||
#include "utilities/fault_injection_fs.h"
|
||||
#endif
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -58,7 +61,6 @@ class FaultInjectionTest
|
||||
|
||||
bool sequential_order_;
|
||||
|
||||
protected:
|
||||
public:
|
||||
enum ExpectedVerifResult { kValExpectFound, kValExpectNoError };
|
||||
enum ResetMethod {
|
||||
@@ -544,6 +546,76 @@ TEST_P(FaultInjectionTest, WriteBatchWalTerminationTest) {
|
||||
ASSERT_EQ(db_->Get(ro, "boys", &val), Status::NotFound());
|
||||
}
|
||||
|
||||
TEST_P(FaultInjectionTest, NoDuplicateTrailingEntries) {
|
||||
auto fault_fs = std::make_shared<FaultInjectionTestFS>(FileSystem::Default());
|
||||
fault_fs->EnableWriteErrorInjection();
|
||||
fault_fs->SetFilesystemDirectWritable(false);
|
||||
const std::string file_name = NormalizePath(dbname_ + "/test_file");
|
||||
std::unique_ptr<log::Writer> log_writer = nullptr;
|
||||
constexpr uint64_t log_number = 0;
|
||||
{
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
const Status s =
|
||||
fault_fs->NewWritableFile(file_name, FileOptions(), &file, nullptr);
|
||||
ASSERT_OK(s);
|
||||
std::unique_ptr<WritableFileWriter> fwriter(
|
||||
new WritableFileWriter(std::move(file), file_name, FileOptions()));
|
||||
log_writer.reset(new log::Writer(std::move(fwriter), log_number,
|
||||
/*recycle_log_files=*/false));
|
||||
}
|
||||
|
||||
fault_fs->SetRandomWriteError(
|
||||
0xdeadbeef, /*one_in=*/1, IOStatus::IOError("Injected IOError"),
|
||||
/*inject_for_all_file_types=*/true, /*types=*/{});
|
||||
|
||||
{
|
||||
VersionEdit edit;
|
||||
edit.SetColumnFamily(0);
|
||||
std::string buf;
|
||||
assert(edit.EncodeTo(&buf));
|
||||
const Status s = log_writer->AddRecord(buf);
|
||||
ASSERT_NOK(s);
|
||||
}
|
||||
|
||||
fault_fs->DisableWriteErrorInjection();
|
||||
|
||||
// Closing the log writer will cause WritableFileWriter::Close() and flush
|
||||
// remaining data from its buffer to underlying file.
|
||||
log_writer.reset();
|
||||
|
||||
{
|
||||
std::unique_ptr<FSSequentialFile> file;
|
||||
Status s =
|
||||
fault_fs->NewSequentialFile(file_name, FileOptions(), &file, nullptr);
|
||||
ASSERT_OK(s);
|
||||
std::unique_ptr<SequentialFileReader> freader(
|
||||
new SequentialFileReader(std::move(file), file_name));
|
||||
Status log_read_s;
|
||||
class LogReporter : public log::Reader::Reporter {
|
||||
public:
|
||||
Status* status_;
|
||||
explicit LogReporter(Status* _s) : status_(_s) {}
|
||||
void Corruption(size_t /*bytes*/, const Status& _s) override {
|
||||
if (status_->ok()) {
|
||||
*status_ = _s;
|
||||
}
|
||||
}
|
||||
} reporter(&log_read_s);
|
||||
std::unique_ptr<log::Reader> log_reader(new log::Reader(
|
||||
nullptr, std::move(freader), &reporter, /*checksum=*/true, log_number));
|
||||
Slice record;
|
||||
std::string data;
|
||||
size_t count = 0;
|
||||
while (log_reader->ReadRecord(&record, &data) && log_read_s.ok()) {
|
||||
VersionEdit edit;
|
||||
ASSERT_OK(edit.DecodeFrom(data));
|
||||
++count;
|
||||
}
|
||||
// Verify that only one version edit exists in the file.
|
||||
ASSERT_EQ(1, count);
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
FaultTest, FaultInjectionTest,
|
||||
::testing::Values(std::make_tuple(false, kDefault, kEnd),
|
||||
|
||||
+5
-4
@@ -960,10 +960,11 @@ Status FlushJob::WriteLevel0Table() {
|
||||
edit_->AddFile(0 /* level */, meta_.fd.GetNumber(), meta_.fd.GetPathId(),
|
||||
meta_.fd.GetFileSize(), meta_.smallest, meta_.largest,
|
||||
meta_.fd.smallest_seqno, meta_.fd.largest_seqno,
|
||||
meta_.marked_for_compaction, meta_.oldest_blob_file_number,
|
||||
meta_.oldest_ancester_time, meta_.file_creation_time,
|
||||
meta_.file_checksum, meta_.file_checksum_func_name,
|
||||
meta_.min_timestamp, meta_.max_timestamp);
|
||||
meta_.marked_for_compaction, meta_.temperature,
|
||||
meta_.oldest_blob_file_number, meta_.oldest_ancester_time,
|
||||
meta_.file_creation_time, meta_.file_checksum,
|
||||
meta_.file_checksum_func_name, meta_.min_timestamp,
|
||||
meta_.max_timestamp);
|
||||
|
||||
edit_->SetBlobFileAdditions(std::move(blob_file_additions));
|
||||
}
|
||||
|
||||
@@ -27,7 +27,8 @@ Status ImportColumnFamilyJob::Prepare(uint64_t next_file_number,
|
||||
for (const auto& file_metadata : metadata_) {
|
||||
const auto file_path = file_metadata.db_path + "/" + file_metadata.name;
|
||||
IngestedFileInfo file_to_import;
|
||||
status = GetIngestedFileInfo(file_path, &file_to_import, sv);
|
||||
status =
|
||||
GetIngestedFileInfo(file_path, next_file_number++, &file_to_import, sv);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
@@ -86,8 +87,6 @@ Status ImportColumnFamilyJob::Prepare(uint64_t next_file_number,
|
||||
// Copy/Move external files into DB
|
||||
auto hardlink_files = import_options_.move_files;
|
||||
for (auto& f : files_to_import_) {
|
||||
f.fd = FileDescriptor(next_file_number++, 0, f.file_size);
|
||||
|
||||
const auto path_outside_db = f.external_file_path;
|
||||
const auto path_inside_db = TableFileName(
|
||||
cfd_->ioptions()->cf_paths, f.fd.GetNumber(), f.fd.GetPathId());
|
||||
@@ -153,10 +152,10 @@ Status ImportColumnFamilyJob::Run() {
|
||||
edit_.AddFile(file_metadata.level, f.fd.GetNumber(), f.fd.GetPathId(),
|
||||
f.fd.GetFileSize(), f.smallest_internal_key,
|
||||
f.largest_internal_key, file_metadata.smallest_seqno,
|
||||
file_metadata.largest_seqno, false, kInvalidBlobFileNumber,
|
||||
oldest_ancester_time, current_time, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
file_metadata.largest_seqno, false, file_metadata.temperature,
|
||||
kInvalidBlobFileNumber, oldest_ancester_time, current_time,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
|
||||
// If incoming sequence number is higher, update local sequence number.
|
||||
if (file_metadata.largest_seqno > versions_->LastSequence()) {
|
||||
@@ -198,8 +197,8 @@ void ImportColumnFamilyJob::Cleanup(const Status& status) {
|
||||
}
|
||||
|
||||
Status ImportColumnFamilyJob::GetIngestedFileInfo(
|
||||
const std::string& external_file, IngestedFileInfo* file_to_import,
|
||||
SuperVersion* sv) {
|
||||
const std::string& external_file, uint64_t new_file_number,
|
||||
IngestedFileInfo* file_to_import, SuperVersion* sv) {
|
||||
file_to_import->external_file_path = external_file;
|
||||
|
||||
// Get external file size
|
||||
@@ -209,6 +208,10 @@ Status ImportColumnFamilyJob::GetIngestedFileInfo(
|
||||
return status;
|
||||
}
|
||||
|
||||
// Assign FD with number
|
||||
file_to_import->fd =
|
||||
FileDescriptor(new_file_number, 0, file_to_import->file_size);
|
||||
|
||||
// Create TableReader for external file
|
||||
std::unique_ptr<TableReader> table_reader;
|
||||
std::unique_ptr<FSRandomAccessFile> sst_file;
|
||||
@@ -223,9 +226,14 @@ Status ImportColumnFamilyJob::GetIngestedFileInfo(
|
||||
std::move(sst_file), external_file, nullptr /*Env*/, io_tracer_));
|
||||
|
||||
status = cfd_->ioptions()->table_factory->NewTableReader(
|
||||
TableReaderOptions(*cfd_->ioptions(),
|
||||
sv->mutable_cf_options.prefix_extractor.get(),
|
||||
env_options_, cfd_->internal_comparator()),
|
||||
TableReaderOptions(
|
||||
*cfd_->ioptions(), sv->mutable_cf_options.prefix_extractor.get(),
|
||||
env_options_, cfd_->internal_comparator(),
|
||||
/*skip_filters*/ false, /*immortal*/ false,
|
||||
/*force_direct_prefetch*/ false, /*level*/ -1,
|
||||
/*block_cache_tracer*/ nullptr,
|
||||
/*max_file_size_for_l0_meta_pin*/ 0, versions_->DbSessionId(),
|
||||
/*cur_file_num*/ new_file_number),
|
||||
std::move(sst_file_reader), file_to_import->file_size, &table_reader);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
|
||||
@@ -56,6 +56,7 @@ class ImportColumnFamilyJob {
|
||||
// Open the external file and populate `file_to_import` with all the
|
||||
// external information we need to import this file.
|
||||
Status GetIngestedFileInfo(const std::string& external_file,
|
||||
uint64_t new_file_number,
|
||||
IngestedFileInfo* file_to_import,
|
||||
SuperVersion* sv);
|
||||
|
||||
|
||||
+2
-1
@@ -1246,7 +1246,7 @@ class BlobDBJobLevelEventListenerTest : public EventListener {
|
||||
}
|
||||
|
||||
const VersionStorageInfo::BlobFiles& GetBlobFiles() {
|
||||
VersionSet* const versions = test_->dbfull()->TEST_GetVersionSet();
|
||||
VersionSet* const versions = test_->dbfull()->GetVersionSet();
|
||||
assert(versions);
|
||||
|
||||
ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault();
|
||||
@@ -1548,6 +1548,7 @@ TEST_F(EventListenerTest, BlobDBFileTest) {
|
||||
// On compaction, because of blob_garbage_collection_age_cutoff, it will
|
||||
// delete the oldest blob file and create new blob file during compaction.
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), begin, end));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
blob_event_listener->CheckCounters();
|
||||
}
|
||||
|
||||
+37
-29
@@ -33,6 +33,7 @@
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
#include "rocksdb/slice_transform.h"
|
||||
#include "rocksdb/types.h"
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
#include "table/internal_iterator.h"
|
||||
#include "table/iterator_wrapper.h"
|
||||
@@ -447,11 +448,13 @@ FragmentedRangeTombstoneIterator* MemTable::NewRangeTombstoneIterator(
|
||||
is_range_del_table_empty_.load(std::memory_order_relaxed)) {
|
||||
return nullptr;
|
||||
}
|
||||
return NewRangeTombstoneIteratorInternal(read_options, read_seq);
|
||||
}
|
||||
|
||||
FragmentedRangeTombstoneIterator* MemTable::NewRangeTombstoneIteratorInternal(
|
||||
const ReadOptions& read_options, SequenceNumber read_seq) {
|
||||
auto* unfragmented_iter = new MemTableIterator(
|
||||
*this, read_options, nullptr /* arena */, true /* use_range_del_table */);
|
||||
if (unfragmented_iter == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
auto fragmented_tombstone_list =
|
||||
std::make_shared<FragmentedRangeTombstoneList>(
|
||||
std::unique_ptr<InternalIterator>(unfragmented_iter),
|
||||
@@ -960,53 +963,58 @@ void MemTable::MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
}
|
||||
PERF_TIMER_GUARD(get_from_memtable_time);
|
||||
|
||||
// For now, memtable Bloom filter is effectively disabled if there are any
|
||||
// range tombstones. This is the simplest way to ensure range tombstones are
|
||||
// handled. TODO: allow Bloom checks where max_covering_tombstone_seq==0
|
||||
bool no_range_del = read_options.ignore_range_deletions ||
|
||||
is_range_del_table_empty_.load(std::memory_order_relaxed);
|
||||
MultiGetRange temp_range(*range, range->begin(), range->end());
|
||||
if (bloom_filter_) {
|
||||
std::array<Slice*, MultiGetContext::MAX_BATCH_SIZE> keys;
|
||||
std::array<bool, MultiGetContext::MAX_BATCH_SIZE> may_match = {{true}};
|
||||
autovector<Slice, MultiGetContext::MAX_BATCH_SIZE> prefixes;
|
||||
if (bloom_filter_ && no_range_del) {
|
||||
bool whole_key =
|
||||
!prefix_extractor_ || moptions_.memtable_whole_key_filtering;
|
||||
std::array<Slice, MultiGetContext::MAX_BATCH_SIZE> bloom_keys;
|
||||
std::array<bool, MultiGetContext::MAX_BATCH_SIZE> may_match;
|
||||
std::array<size_t, MultiGetContext::MAX_BATCH_SIZE> range_indexes;
|
||||
int num_keys = 0;
|
||||
for (auto iter = temp_range.begin(); iter != temp_range.end(); ++iter) {
|
||||
if (!prefix_extractor_) {
|
||||
keys[num_keys++] = &iter->ukey_without_ts;
|
||||
if (whole_key) {
|
||||
bloom_keys[num_keys] = iter->ukey_without_ts;
|
||||
range_indexes[num_keys++] = iter.index();
|
||||
} else if (prefix_extractor_->InDomain(iter->ukey_without_ts)) {
|
||||
prefixes.emplace_back(
|
||||
prefix_extractor_->Transform(iter->ukey_without_ts));
|
||||
keys[num_keys++] = &prefixes.back();
|
||||
bloom_keys[num_keys] =
|
||||
prefix_extractor_->Transform(iter->ukey_without_ts);
|
||||
range_indexes[num_keys++] = iter.index();
|
||||
} else {
|
||||
// TODO: consider not counting these as Bloom hits to more closely
|
||||
// match bloom_sst_hit_count
|
||||
PERF_COUNTER_ADD(bloom_memtable_hit_count, 1);
|
||||
}
|
||||
}
|
||||
bloom_filter_->MayContain(num_keys, &keys[0], &may_match[0]);
|
||||
int idx = 0;
|
||||
for (auto iter = temp_range.begin(); iter != temp_range.end(); ++iter) {
|
||||
if (prefix_extractor_ &&
|
||||
!prefix_extractor_->InDomain(iter->ukey_without_ts)) {
|
||||
PERF_COUNTER_ADD(bloom_memtable_hit_count, 1);
|
||||
continue;
|
||||
}
|
||||
if (!may_match[idx]) {
|
||||
temp_range.SkipKey(iter);
|
||||
bloom_filter_->MayContain(num_keys, &bloom_keys[0], &may_match[0]);
|
||||
for (int i = 0; i < num_keys; ++i) {
|
||||
if (!may_match[i]) {
|
||||
temp_range.SkipIndex(range_indexes[i]);
|
||||
PERF_COUNTER_ADD(bloom_memtable_miss_count, 1);
|
||||
} else {
|
||||
PERF_COUNTER_ADD(bloom_memtable_hit_count, 1);
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
for (auto iter = temp_range.begin(); iter != temp_range.end(); ++iter) {
|
||||
SequenceNumber seq = kMaxSequenceNumber;
|
||||
bool found_final_value{false};
|
||||
bool merge_in_progress = iter->s->IsMergeInProgress();
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> range_del_iter(
|
||||
NewRangeTombstoneIterator(
|
||||
read_options, GetInternalKeySeqno(iter->lkey->internal_key())));
|
||||
if (range_del_iter != nullptr) {
|
||||
if (!no_range_del) {
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> range_del_iter(
|
||||
NewRangeTombstoneIteratorInternal(
|
||||
read_options, GetInternalKeySeqno(iter->lkey->internal_key())));
|
||||
iter->max_covering_tombstone_seq = std::max(
|
||||
iter->max_covering_tombstone_seq,
|
||||
range_del_iter->MaxCoveringTombstoneSeqnum(iter->lkey->user_key()));
|
||||
}
|
||||
SequenceNumber dummy_seq;
|
||||
GetFromTable(*(iter->lkey), iter->max_covering_tombstone_seq, true,
|
||||
callback, &iter->is_blob_index, iter->value->GetSelf(),
|
||||
iter->timestamp, iter->s, &(iter->merge_context), &seq,
|
||||
iter->timestamp, iter->s, &(iter->merge_context), &dummy_seq,
|
||||
&found_final_value, &merge_in_progress);
|
||||
|
||||
if (!found_final_value && merge_in_progress) {
|
||||
|
||||
@@ -146,6 +146,13 @@ class MemTable {
|
||||
return approximate_memory_usage_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// used by MemTableListVersion::MemoryAllocatedBytesExcludingLast
|
||||
size_t MemoryAllocatedBytes() const {
|
||||
return table_->ApproximateMemoryUsage() +
|
||||
range_del_table_->ApproximateMemoryUsage() +
|
||||
arena_.MemoryAllocatedBytes();
|
||||
}
|
||||
|
||||
// Returns a vector of unique random memtable entries of size 'sample_size'.
|
||||
//
|
||||
// Note: the entries are stored in the unordered_set as length-prefixed keys,
|
||||
@@ -593,6 +600,10 @@ class MemTable {
|
||||
std::string* value, std::string* timestamp, Status* s,
|
||||
MergeContext* merge_context, SequenceNumber* seq,
|
||||
bool* found_final_value, bool* merge_in_progress);
|
||||
|
||||
// Always returns non-null and assumes certain pre-checks are done
|
||||
FragmentedRangeTombstoneIterator* NewRangeTombstoneIteratorInternal(
|
||||
const ReadOptions& read_options, SequenceNumber read_seq);
|
||||
};
|
||||
|
||||
extern const char* EncodeKey(std::string* scratch, const Slice& target);
|
||||
|
||||
+13
-13
@@ -259,8 +259,8 @@ SequenceNumber MemTableListVersion::GetEarliestSequenceNumber(
|
||||
void MemTableListVersion::Add(MemTable* m, autovector<MemTable*>* to_delete) {
|
||||
assert(refs_ == 1); // only when refs_ == 1 is MemTableListVersion mutable
|
||||
AddMemTable(m);
|
||||
|
||||
TrimHistory(to_delete, m->ApproximateMemoryUsage());
|
||||
// m->MemoryAllocatedBytes() is added in MemoryAllocatedBytesExcludingLast
|
||||
TrimHistory(to_delete, 0);
|
||||
}
|
||||
|
||||
// Removes m from list of memtables not flushed. Caller should NOT Unref m.
|
||||
@@ -282,16 +282,16 @@ void MemTableListVersion::Remove(MemTable* m,
|
||||
}
|
||||
|
||||
// return the total memory usage assuming the oldest flushed memtable is dropped
|
||||
size_t MemTableListVersion::ApproximateMemoryUsageExcludingLast() const {
|
||||
size_t MemTableListVersion::MemoryAllocatedBytesExcludingLast() const {
|
||||
size_t total_memtable_size = 0;
|
||||
for (auto& memtable : memlist_) {
|
||||
total_memtable_size += memtable->ApproximateMemoryUsage();
|
||||
total_memtable_size += memtable->MemoryAllocatedBytes();
|
||||
}
|
||||
for (auto& memtable : memlist_history_) {
|
||||
total_memtable_size += memtable->ApproximateMemoryUsage();
|
||||
total_memtable_size += memtable->MemoryAllocatedBytes();
|
||||
}
|
||||
if (!memlist_history_.empty()) {
|
||||
total_memtable_size -= memlist_history_.back()->ApproximateMemoryUsage();
|
||||
total_memtable_size -= memlist_history_.back()->MemoryAllocatedBytes();
|
||||
}
|
||||
return total_memtable_size;
|
||||
}
|
||||
@@ -301,7 +301,7 @@ bool MemTableListVersion::MemtableLimitExceeded(size_t usage) {
|
||||
// calculate the total memory usage after dropping the oldest flushed
|
||||
// memtable, compare with max_write_buffer_size_to_maintain_ to decide
|
||||
// whether to trim history
|
||||
return ApproximateMemoryUsageExcludingLast() + usage >=
|
||||
return MemoryAllocatedBytesExcludingLast() + usage >=
|
||||
static_cast<size_t>(max_write_buffer_size_to_maintain_);
|
||||
} else if (max_write_buffer_number_to_maintain_ > 0) {
|
||||
return memlist_.size() + memlist_history_.size() >
|
||||
@@ -596,9 +596,9 @@ size_t MemTableList::ApproximateUnflushedMemTablesMemoryUsage() {
|
||||
|
||||
size_t MemTableList::ApproximateMemoryUsage() { return current_memory_usage_; }
|
||||
|
||||
size_t MemTableList::ApproximateMemoryUsageExcludingLast() const {
|
||||
const size_t usage =
|
||||
current_memory_usage_excluding_last_.load(std::memory_order_relaxed);
|
||||
size_t MemTableList::MemoryAllocatedBytesExcludingLast() const {
|
||||
const size_t usage = current_memory_allocted_bytes_excluding_last_.load(
|
||||
std::memory_order_relaxed);
|
||||
return usage;
|
||||
}
|
||||
|
||||
@@ -609,9 +609,9 @@ bool MemTableList::HasHistory() const {
|
||||
|
||||
void MemTableList::UpdateCachedValuesFromMemTableListVersion() {
|
||||
const size_t total_memtable_size =
|
||||
current_->ApproximateMemoryUsageExcludingLast();
|
||||
current_memory_usage_excluding_last_.store(total_memtable_size,
|
||||
std::memory_order_relaxed);
|
||||
current_->MemoryAllocatedBytesExcludingLast();
|
||||
current_memory_allocted_bytes_excluding_last_.store(
|
||||
total_memtable_size, std::memory_order_relaxed);
|
||||
|
||||
const bool has_history = current_->HasHistory();
|
||||
current_has_history_.store(has_history, std::memory_order_relaxed);
|
||||
|
||||
+9
-9
@@ -168,7 +168,7 @@ class MemTableListVersion {
|
||||
// excluding the last MemTable in memlist_history_. The reason for excluding
|
||||
// the last MemTable is to see if dropping the last MemTable will keep total
|
||||
// memory usage above or equal to max_write_buffer_size_to_maintain_
|
||||
size_t ApproximateMemoryUsageExcludingLast() const;
|
||||
size_t MemoryAllocatedBytesExcludingLast() const;
|
||||
|
||||
// Whether this version contains flushed memtables that are only kept around
|
||||
// for transaction conflict checking.
|
||||
@@ -221,7 +221,7 @@ class MemTableList {
|
||||
commit_in_progress_(false),
|
||||
flush_requested_(false),
|
||||
current_memory_usage_(0),
|
||||
current_memory_usage_excluding_last_(0),
|
||||
current_memory_allocted_bytes_excluding_last_(0),
|
||||
current_has_history_(false) {
|
||||
current_->Ref();
|
||||
}
|
||||
@@ -281,15 +281,15 @@ class MemTableList {
|
||||
// Returns an estimate of the number of bytes of data in use.
|
||||
size_t ApproximateMemoryUsage();
|
||||
|
||||
// Returns the cached current_memory_usage_excluding_last_ value.
|
||||
size_t ApproximateMemoryUsageExcludingLast() const;
|
||||
// Returns the cached current_memory_allocted_bytes_excluding_last_ value.
|
||||
size_t MemoryAllocatedBytesExcludingLast() const;
|
||||
|
||||
// Returns the cached current_has_history_ value.
|
||||
bool HasHistory() const;
|
||||
|
||||
// Updates current_memory_usage_excluding_last_ and current_has_history_
|
||||
// from MemTableListVersion. Must be called whenever InstallNewVersion is
|
||||
// called.
|
||||
// Updates current_memory_allocted_bytes_excluding_last_ and
|
||||
// current_has_history_ from MemTableListVersion. Must be called whenever
|
||||
// InstallNewVersion is called.
|
||||
void UpdateCachedValuesFromMemTableListVersion();
|
||||
|
||||
// `usage` is the current size of the mutable Memtable. When
|
||||
@@ -430,8 +430,8 @@ class MemTableList {
|
||||
// The current memory usage.
|
||||
size_t current_memory_usage_;
|
||||
|
||||
// Cached value of current_->ApproximateMemoryUsageExcludingLast().
|
||||
std::atomic<size_t> current_memory_usage_excluding_last_;
|
||||
// Cached value of current_->MemoryAllocatedBytesExcludingLast().
|
||||
std::atomic<size_t> current_memory_allocted_bytes_excluding_last_;
|
||||
|
||||
// Cached value of current_->HasHistory().
|
||||
std::atomic<bool> current_has_history_;
|
||||
|
||||
@@ -344,7 +344,7 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
// Create MemTableList
|
||||
int min_write_buffer_number_to_merge = 2;
|
||||
int max_write_buffer_number_to_maintain = 2;
|
||||
int64_t max_write_buffer_size_to_maintain = 2000;
|
||||
int64_t max_write_buffer_size_to_maintain = 2 * Arena::kInlineSize;
|
||||
MemTableList list(min_write_buffer_number_to_merge,
|
||||
max_write_buffer_number_to_maintain,
|
||||
max_write_buffer_size_to_maintain);
|
||||
|
||||
@@ -189,7 +189,7 @@ TEST_F(ObsoleteFilesTest, DeleteObsoleteOptionsFile) {
|
||||
TEST_F(ObsoleteFilesTest, BlobFiles) {
|
||||
ReopenDB();
|
||||
|
||||
VersionSet* const versions = dbfull()->TEST_GetVersionSet();
|
||||
VersionSet* const versions = dbfull()->GetVersionSet();
|
||||
assert(versions);
|
||||
assert(versions->GetColumnFamilySet());
|
||||
|
||||
|
||||
+1
-1
@@ -633,7 +633,7 @@ class Repairer {
|
||||
table->meta.fd.GetFileSize(), table->meta.smallest,
|
||||
table->meta.largest, table->meta.fd.smallest_seqno,
|
||||
table->meta.fd.largest_seqno, table->meta.marked_for_compaction,
|
||||
table->meta.oldest_blob_file_number,
|
||||
table->meta.temperature, table->meta.oldest_blob_file_number,
|
||||
table->meta.oldest_ancester_time, table->meta.file_creation_time,
|
||||
table->meta.file_checksum, table->meta.file_checksum_func_name,
|
||||
table->meta.min_timestamp, table->meta.max_timestamp);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "file/filename.h"
|
||||
#include "file/random_access_file_reader.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "rocksdb/advanced_options.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "table/block_based/block_based_table_reader.h"
|
||||
#include "table/get_context.h"
|
||||
@@ -108,6 +109,7 @@ Status TableCache::GetTableReader(
|
||||
TableFileName(ioptions_.cf_paths, fd.GetNumber(), fd.GetPathId());
|
||||
std::unique_ptr<FSRandomAccessFile> file;
|
||||
FileOptions fopts = file_options;
|
||||
fopts.temperature = file_temperature;
|
||||
Status s = PrepareIOFromReadOptions(ro, ioptions_.clock, fopts.io_options);
|
||||
if (s.ok()) {
|
||||
s = ioptions_.fs->NewRandomAccessFile(fname, fopts, &file, nullptr);
|
||||
|
||||
@@ -266,6 +266,10 @@ void TransactionLogIteratorImpl::UpdateCurrentWriteBatch(const Slice& record) {
|
||||
sequence_++;
|
||||
return Status::OK();
|
||||
}
|
||||
Status MarkCommitWithTimestamp(const Slice&, const Slice&) override {
|
||||
++sequence_;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status PutCF(uint32_t /*cf*/, const Slice& /*key*/,
|
||||
const Slice& /*val*/) override {
|
||||
|
||||
+108
-99
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "db/version_edit.h"
|
||||
#include "db/version_set.h"
|
||||
#include "rocksdb/advanced_options.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/string_util.h"
|
||||
@@ -67,10 +68,11 @@ class VersionBuilderTest : public testing::Test {
|
||||
FileMetaData* f = new FileMetaData(
|
||||
file_number, path_id, file_size, GetInternalKey(smallest, smallest_seq),
|
||||
GetInternalKey(largest, largest_seq), smallest_seqno, largest_seqno,
|
||||
/* marked_for_compact */ false, oldest_blob_file_number,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
/* marked_for_compact */ false, Temperature::kUnknown,
|
||||
oldest_blob_file_number, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
f->compensated_file_size = file_size;
|
||||
f->num_entries = num_entries;
|
||||
f->num_deletions = num_deletions;
|
||||
@@ -129,10 +131,10 @@ class VersionBuilderTest : public testing::Test {
|
||||
edit->AddFile(level, table_file_number, path_id, file_size,
|
||||
GetInternalKey(smallest), GetInternalKey(largest),
|
||||
smallest_seqno, largest_seqno, marked_for_compaction,
|
||||
blob_file_number, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
Temperature::kUnknown, blob_file_number,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
}
|
||||
|
||||
static std::shared_ptr<BlobFileMetaData> GetBlobFileMetaData(
|
||||
@@ -190,10 +192,10 @@ TEST_F(VersionBuilderTest, ApplyAndSaveTo) {
|
||||
VersionEdit version_edit;
|
||||
version_edit.AddFile(2, 666, 0, 100U, GetInternalKey("301"),
|
||||
GetInternalKey("350"), 200, 200, false,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
Temperature::kUnknown, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
version_edit.DeleteFile(3, 27U);
|
||||
|
||||
EnvOptions env_options;
|
||||
@@ -231,10 +233,10 @@ TEST_F(VersionBuilderTest, ApplyAndSaveToDynamic) {
|
||||
VersionEdit version_edit;
|
||||
version_edit.AddFile(3, 666, 0, 100U, GetInternalKey("301"),
|
||||
GetInternalKey("350"), 200, 200, false,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
Temperature::kUnknown, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
version_edit.DeleteFile(0, 1U);
|
||||
version_edit.DeleteFile(0, 88U);
|
||||
|
||||
@@ -275,10 +277,10 @@ TEST_F(VersionBuilderTest, ApplyAndSaveToDynamic2) {
|
||||
VersionEdit version_edit;
|
||||
version_edit.AddFile(4, 666, 0, 100U, GetInternalKey("301"),
|
||||
GetInternalKey("350"), 200, 200, false,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
Temperature::kUnknown, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
version_edit.DeleteFile(0, 1U);
|
||||
version_edit.DeleteFile(0, 88U);
|
||||
version_edit.DeleteFile(4, 6U);
|
||||
@@ -310,34 +312,34 @@ TEST_F(VersionBuilderTest, ApplyMultipleAndSaveTo) {
|
||||
VersionEdit version_edit;
|
||||
version_edit.AddFile(2, 666, 0, 100U, GetInternalKey("301"),
|
||||
GetInternalKey("350"), 200, 200, false,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
Temperature::kUnknown, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
version_edit.AddFile(2, 676, 0, 100U, GetInternalKey("401"),
|
||||
GetInternalKey("450"), 200, 200, false,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
Temperature::kUnknown, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
version_edit.AddFile(2, 636, 0, 100U, GetInternalKey("601"),
|
||||
GetInternalKey("650"), 200, 200, false,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
Temperature::kUnknown, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
version_edit.AddFile(2, 616, 0, 100U, GetInternalKey("501"),
|
||||
GetInternalKey("550"), 200, 200, false,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
Temperature::kUnknown, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
version_edit.AddFile(2, 606, 0, 100U, GetInternalKey("701"),
|
||||
GetInternalKey("750"), 200, 200, false,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
Temperature::kUnknown, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
|
||||
EnvOptions env_options;
|
||||
constexpr TableCache* table_cache = nullptr;
|
||||
@@ -372,51 +374,51 @@ TEST_F(VersionBuilderTest, ApplyDeleteAndSaveTo) {
|
||||
VersionEdit version_edit;
|
||||
version_edit.AddFile(2, 666, 0, 100U, GetInternalKey("301"),
|
||||
GetInternalKey("350"), 200, 200, false,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
Temperature::kUnknown, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
version_edit.AddFile(2, 676, 0, 100U, GetInternalKey("401"),
|
||||
GetInternalKey("450"), 200, 200, false,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
Temperature::kUnknown, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
version_edit.AddFile(2, 636, 0, 100U, GetInternalKey("601"),
|
||||
GetInternalKey("650"), 200, 200, false,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
Temperature::kUnknown, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
version_edit.AddFile(2, 616, 0, 100U, GetInternalKey("501"),
|
||||
GetInternalKey("550"), 200, 200, false,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
Temperature::kUnknown, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
version_edit.AddFile(2, 606, 0, 100U, GetInternalKey("701"),
|
||||
GetInternalKey("750"), 200, 200, false,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
Temperature::kUnknown, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
ASSERT_OK(version_builder.Apply(&version_edit));
|
||||
|
||||
VersionEdit version_edit2;
|
||||
version_edit.AddFile(2, 808, 0, 100U, GetInternalKey("901"),
|
||||
GetInternalKey("950"), 200, 200, false,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
Temperature::kUnknown, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
version_edit2.DeleteFile(2, 616);
|
||||
version_edit2.DeleteFile(2, 636);
|
||||
version_edit.AddFile(2, 806, 0, 100U, GetInternalKey("801"),
|
||||
GetInternalKey("850"), 200, 200, false,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
Temperature::kUnknown, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
|
||||
ASSERT_OK(version_builder.Apply(&version_edit2));
|
||||
ASSERT_OK(version_builder.SaveTo(&new_vstorage));
|
||||
@@ -515,10 +517,11 @@ TEST_F(VersionBuilderTest, ApplyFileDeletionAndAddition) {
|
||||
addition.AddFile(level, file_number, path_id, file_size,
|
||||
GetInternalKey(smallest, smallest_seq),
|
||||
GetInternalKey(largest, largest_seq), smallest_seqno,
|
||||
largest_seqno, marked_for_compaction, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
largest_seqno, marked_for_compaction, Temperature::kUnknown,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
|
||||
ASSERT_OK(builder.Apply(&addition));
|
||||
|
||||
@@ -560,10 +563,10 @@ TEST_F(VersionBuilderTest, ApplyFileAdditionAlreadyInBase) {
|
||||
edit.AddFile(new_level, file_number, path_id, file_size,
|
||||
GetInternalKey(smallest), GetInternalKey(largest),
|
||||
smallest_seqno, largest_seqno, marked_for_compaction,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
Temperature::kUnknown, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
|
||||
const Status s = builder.Apply(&edit);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
@@ -594,10 +597,11 @@ TEST_F(VersionBuilderTest, ApplyFileAdditionAlreadyApplied) {
|
||||
|
||||
edit.AddFile(level, file_number, path_id, file_size, GetInternalKey(smallest),
|
||||
GetInternalKey(largest), smallest_seqno, largest_seqno,
|
||||
marked_for_compaction, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
marked_for_compaction, Temperature::kUnknown,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
|
||||
ASSERT_OK(builder.Apply(&edit));
|
||||
|
||||
@@ -608,10 +612,10 @@ TEST_F(VersionBuilderTest, ApplyFileAdditionAlreadyApplied) {
|
||||
other_edit.AddFile(new_level, file_number, path_id, file_size,
|
||||
GetInternalKey(smallest), GetInternalKey(largest),
|
||||
smallest_seqno, largest_seqno, marked_for_compaction,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
Temperature::kUnknown, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
|
||||
const Status s = builder.Apply(&other_edit);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
@@ -643,10 +647,10 @@ TEST_F(VersionBuilderTest, ApplyFileAdditionAndDeletion) {
|
||||
addition.AddFile(level, file_number, path_id, file_size,
|
||||
GetInternalKey(smallest), GetInternalKey(largest),
|
||||
smallest_seqno, largest_seqno, marked_for_compaction,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
Temperature::kUnknown, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
|
||||
ASSERT_OK(builder.Apply(&addition));
|
||||
|
||||
@@ -1172,12 +1176,12 @@ TEST_F(VersionBuilderTest, SaveBlobFilesToConcurrentJobs) {
|
||||
constexpr uint64_t total_blob_count = 234;
|
||||
constexpr uint64_t total_blob_bytes = 1 << 22;
|
||||
|
||||
edit.AddFile(level, table_file_number, path_id, file_size,
|
||||
GetInternalKey(smallest), GetInternalKey(largest),
|
||||
smallest_seqno, largest_seqno, marked_for_compaction,
|
||||
blob_file_number, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, checksum_value, checksum_method,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
edit.AddFile(
|
||||
level, table_file_number, path_id, file_size, GetInternalKey(smallest),
|
||||
GetInternalKey(largest), smallest_seqno, largest_seqno,
|
||||
marked_for_compaction, Temperature::kUnknown, blob_file_number,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime, checksum_value,
|
||||
checksum_method, kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
edit.AddBlobFile(blob_file_number, total_blob_count, total_blob_bytes,
|
||||
checksum_method, checksum_value);
|
||||
|
||||
@@ -1259,6 +1263,7 @@ TEST_F(VersionBuilderTest, CheckConsistencyForBlobFiles) {
|
||||
/* file_size */ 100, /* smallest */ GetInternalKey("701"),
|
||||
/* largest */ GetInternalKey("750"), /* smallest_seqno */ 200,
|
||||
/* largest_seqno */ 200, /* marked_for_compaction */ false,
|
||||
Temperature::kUnknown,
|
||||
/* oldest_blob_file_number */ 16, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
@@ -1268,6 +1273,7 @@ TEST_F(VersionBuilderTest, CheckConsistencyForBlobFiles) {
|
||||
/* file_size */ 100, /* smallest */ GetInternalKey("801"),
|
||||
/* largest */ GetInternalKey("850"), /* smallest_seqno */ 200,
|
||||
/* largest_seqno */ 200, /* marked_for_compaction */ false,
|
||||
Temperature::kUnknown,
|
||||
/* oldest_blob_file_number */ 1000, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
@@ -1487,6 +1493,7 @@ TEST_F(VersionBuilderTest, MaintainLinkedSstsForBlobFiles) {
|
||||
/* file_size */ 100, /* smallest */ GetInternalKey("21", 2100),
|
||||
/* largest */ GetInternalKey("21", 2100), /* smallest_seqno */ 2100,
|
||||
/* largest_seqno */ 2100, /* marked_for_compaction */ false,
|
||||
Temperature::kUnknown,
|
||||
/* oldest_blob_file_number */ 1, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
@@ -1498,7 +1505,7 @@ TEST_F(VersionBuilderTest, MaintainLinkedSstsForBlobFiles) {
|
||||
/* file_size */ 100, /* smallest */ GetInternalKey("22", 2200),
|
||||
/* largest */ GetInternalKey("22", 2200), /* smallest_seqno */ 2200,
|
||||
/* largest_seqno */ 2200, /* marked_for_compaction */ false,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
Temperature::kUnknown, kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
@@ -1521,6 +1528,7 @@ TEST_F(VersionBuilderTest, MaintainLinkedSstsForBlobFiles) {
|
||||
/* largest */ GetInternalKey("03", 300),
|
||||
/* smallest_seqno */ 300,
|
||||
/* largest_seqno */ 300, /* marked_for_compaction */ false,
|
||||
Temperature::kUnknown,
|
||||
/* oldest_blob_file_number */ 3, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
@@ -1533,10 +1541,10 @@ TEST_F(VersionBuilderTest, MaintainLinkedSstsForBlobFiles) {
|
||||
/* largest */ GetInternalKey("13", 1300),
|
||||
/* smallest_seqno */ 1300,
|
||||
/* largest_seqno */ 1300, /* marked_for_compaction */ false,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
Temperature::kUnknown, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
|
||||
// Add one more SST file that references a blob file, then promptly
|
||||
// delete it in a second version edit before the new version gets saved.
|
||||
@@ -1546,6 +1554,7 @@ TEST_F(VersionBuilderTest, MaintainLinkedSstsForBlobFiles) {
|
||||
/* largest */ GetInternalKey("23", 2300),
|
||||
/* smallest_seqno */ 2300,
|
||||
/* largest_seqno */ 2300, /* marked_for_compaction */ false,
|
||||
Temperature::kUnknown,
|
||||
/* oldest_blob_file_number */ 5, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
|
||||
+15
-5
@@ -801,16 +801,19 @@ std::string VersionEdit::DebugString(bool hex_key) const {
|
||||
r.append(" blob_file:");
|
||||
AppendNumberTo(&r, f.oldest_blob_file_number);
|
||||
}
|
||||
r.append(" min_timestamp:");
|
||||
r.append(Slice(f.min_timestamp).ToString(true));
|
||||
r.append(" max_timestamp:");
|
||||
r.append(Slice(f.max_timestamp).ToString(true));
|
||||
if (f.min_timestamp != kDisableUserTimestamp) {
|
||||
assert(f.max_timestamp != kDisableUserTimestamp);
|
||||
r.append(" min_timestamp:");
|
||||
r.append(Slice(f.min_timestamp).ToString(true));
|
||||
r.append(" max_timestamp:");
|
||||
r.append(Slice(f.max_timestamp).ToString(true));
|
||||
}
|
||||
r.append(" oldest_ancester_time:");
|
||||
AppendNumberTo(&r, f.oldest_ancester_time);
|
||||
r.append(" file_creation_time:");
|
||||
AppendNumberTo(&r, f.file_creation_time);
|
||||
r.append(" file_checksum:");
|
||||
r.append(f.file_checksum);
|
||||
r.append(Slice(f.file_checksum).ToString(true));
|
||||
r.append(" file_checksum_func_name: ");
|
||||
r.append(f.file_checksum_func_name);
|
||||
if (f.temperature != Temperature::kUnknown) {
|
||||
@@ -923,6 +926,13 @@ std::string VersionEdit::DebugJSON(int edit_num, bool hex_key) const {
|
||||
jw << "MinTimestamp" << Slice(f.min_timestamp).ToString(true);
|
||||
jw << "MaxTimestamp" << Slice(f.max_timestamp).ToString(true);
|
||||
}
|
||||
jw << "OldestAncesterTime" << f.oldest_ancester_time;
|
||||
jw << "FileCreationTime" << f.file_creation_time;
|
||||
jw << "FileChecksum" << Slice(f.file_checksum).ToString(true);
|
||||
jw << "FileChecksumFuncName" << f.file_checksum_func_name;
|
||||
if (f.temperature != Temperature::kUnknown) {
|
||||
jw << "temperature" << ToString(static_cast<int>(f.temperature));
|
||||
}
|
||||
if (f.oldest_blob_file_number != kInvalidBlobFileNumber) {
|
||||
jw << "OldestBlobFile" << f.oldest_blob_file_number;
|
||||
}
|
||||
|
||||
+9
-5
@@ -19,6 +19,7 @@
|
||||
#include "db/dbformat.h"
|
||||
#include "db/wal_edit.h"
|
||||
#include "memory/arena.h"
|
||||
#include "rocksdb/advanced_options.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "table/table_reader.h"
|
||||
#include "util/autovector.h"
|
||||
@@ -222,14 +223,16 @@ struct FileMetaData {
|
||||
const InternalKey& smallest_key, const InternalKey& largest_key,
|
||||
const SequenceNumber& smallest_seq,
|
||||
const SequenceNumber& largest_seq, bool marked_for_compact,
|
||||
uint64_t oldest_blob_file, uint64_t _oldest_ancester_time,
|
||||
uint64_t _file_creation_time, const std::string& _file_checksum,
|
||||
Temperature _temperature, uint64_t oldest_blob_file,
|
||||
uint64_t _oldest_ancester_time, uint64_t _file_creation_time,
|
||||
const std::string& _file_checksum,
|
||||
const std::string& _file_checksum_func_name,
|
||||
std::string _min_timestamp, std::string _max_timestamp)
|
||||
: fd(file, file_path_id, file_size, smallest_seq, largest_seq),
|
||||
smallest(smallest_key),
|
||||
largest(largest_key),
|
||||
marked_for_compaction(marked_for_compact),
|
||||
temperature(_temperature),
|
||||
oldest_blob_file_number(oldest_blob_file),
|
||||
oldest_ancester_time(_oldest_ancester_time),
|
||||
file_creation_time(_file_creation_time),
|
||||
@@ -401,8 +404,9 @@ class VersionEdit {
|
||||
uint64_t file_size, const InternalKey& smallest,
|
||||
const InternalKey& largest, const SequenceNumber& smallest_seqno,
|
||||
const SequenceNumber& largest_seqno, bool marked_for_compaction,
|
||||
uint64_t oldest_blob_file_number, uint64_t oldest_ancester_time,
|
||||
uint64_t file_creation_time, const std::string& file_checksum,
|
||||
Temperature temperature, uint64_t oldest_blob_file_number,
|
||||
uint64_t oldest_ancester_time, uint64_t file_creation_time,
|
||||
const std::string& file_checksum,
|
||||
const std::string& file_checksum_func_name,
|
||||
const std::string& min_timestamp,
|
||||
const std::string& max_timestamp) {
|
||||
@@ -411,7 +415,7 @@ class VersionEdit {
|
||||
level,
|
||||
FileMetaData(file, file_path_id, file_size, smallest, largest,
|
||||
smallest_seqno, largest_seqno, marked_for_compaction,
|
||||
oldest_blob_file_number, oldest_ancester_time,
|
||||
temperature, oldest_blob_file_number, oldest_ancester_time,
|
||||
file_creation_time, file_checksum, file_checksum_func_name,
|
||||
min_timestamp, max_timestamp));
|
||||
}
|
||||
|
||||
+22
-18
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "db/version_edit.h"
|
||||
|
||||
#include "rocksdb/advanced_options.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
@@ -39,8 +40,9 @@ TEST_F(VersionEditTest, EncodeDecode) {
|
||||
edit.AddFile(3, kBig + 300 + i, kBig32Bit + 400 + i, 0,
|
||||
InternalKey("foo", kBig + 500 + i, kTypeValue),
|
||||
InternalKey("zoo", kBig + 600 + i, kTypeDeletion),
|
||||
kBig + 500 + i, kBig + 600 + i, false, kInvalidBlobFileNumber,
|
||||
888, 678, "234", "crc32c", "123", "345");
|
||||
kBig + 500 + i, kBig + 600 + i, false, Temperature::kUnknown,
|
||||
kInvalidBlobFileNumber, 888, 678, "234", "crc32c", "123",
|
||||
"345");
|
||||
edit.DeleteFile(4, kBig + 700 + i);
|
||||
}
|
||||
|
||||
@@ -57,26 +59,27 @@ TEST_F(VersionEditTest, EncodeDecodeNewFile4) {
|
||||
VersionEdit edit;
|
||||
edit.AddFile(3, 300, 3, 100, InternalKey("foo", kBig + 500, kTypeValue),
|
||||
InternalKey("zoo", kBig + 600, kTypeDeletion), kBig + 500,
|
||||
kBig + 600, true, kInvalidBlobFileNumber,
|
||||
kBig + 600, true, Temperature::kUnknown, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName, "123",
|
||||
"234");
|
||||
edit.AddFile(4, 301, 3, 100, InternalKey("foo", kBig + 501, kTypeValue),
|
||||
InternalKey("zoo", kBig + 601, kTypeDeletion), kBig + 501,
|
||||
kBig + 601, false, kInvalidBlobFileNumber,
|
||||
kBig + 601, false, Temperature::kUnknown, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName, "345",
|
||||
"543");
|
||||
edit.AddFile(5, 302, 0, 100, InternalKey("foo", kBig + 502, kTypeValue),
|
||||
InternalKey("zoo", kBig + 602, kTypeDeletion), kBig + 502,
|
||||
kBig + 602, true, kInvalidBlobFileNumber, 666, 888,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName, "456",
|
||||
"567");
|
||||
kBig + 602, true, Temperature::kUnknown, kInvalidBlobFileNumber,
|
||||
666, 888, kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
"456", "567");
|
||||
edit.AddFile(5, 303, 0, 100, InternalKey("foo", kBig + 503, kTypeBlobIndex),
|
||||
InternalKey("zoo", kBig + 603, kTypeBlobIndex), kBig + 503,
|
||||
kBig + 603, true, 1001, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, "678", "789");
|
||||
kBig + 603, true, Temperature::kUnknown, 1001,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName, "678",
|
||||
"789");
|
||||
;
|
||||
|
||||
edit.DeleteFile(4, 700);
|
||||
@@ -123,14 +126,15 @@ TEST_F(VersionEditTest, ForwardCompatibleNewFile4) {
|
||||
VersionEdit edit;
|
||||
edit.AddFile(3, 300, 3, 100, InternalKey("foo", kBig + 500, kTypeValue),
|
||||
InternalKey("zoo", kBig + 600, kTypeDeletion), kBig + 500,
|
||||
kBig + 600, true, kInvalidBlobFileNumber,
|
||||
kBig + 600, true, Temperature::kUnknown, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName, "123",
|
||||
"234");
|
||||
edit.AddFile(4, 301, 3, 100, InternalKey("foo", kBig + 501, kTypeValue),
|
||||
InternalKey("zoo", kBig + 601, kTypeDeletion), kBig + 501,
|
||||
kBig + 601, false, kInvalidBlobFileNumber, 686, 868, "234",
|
||||
"crc32c", kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
kBig + 601, false, Temperature::kUnknown, kInvalidBlobFileNumber,
|
||||
686, 868, "234", "crc32c", kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
edit.DeleteFile(4, 700);
|
||||
|
||||
edit.SetComparatorName("foo");
|
||||
@@ -180,7 +184,7 @@ TEST_F(VersionEditTest, NewFile4NotSupportedField) {
|
||||
VersionEdit edit;
|
||||
edit.AddFile(3, 300, 3, 100, InternalKey("foo", kBig + 500, kTypeValue),
|
||||
InternalKey("zoo", kBig + 600, kTypeDeletion), kBig + 500,
|
||||
kBig + 600, true, kInvalidBlobFileNumber,
|
||||
kBig + 600, true, Temperature::kUnknown, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
@@ -211,10 +215,10 @@ TEST_F(VersionEditTest, NewFile4NotSupportedField) {
|
||||
TEST_F(VersionEditTest, EncodeEmptyFile) {
|
||||
VersionEdit edit;
|
||||
edit.AddFile(0, 0, 0, 0, InternalKey(), InternalKey(), 0, 0, false,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
Temperature::kUnknown, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
std::string buffer;
|
||||
ASSERT_TRUE(!edit.EncodeTo(&buffer));
|
||||
}
|
||||
|
||||
+10
-10
@@ -1271,8 +1271,8 @@ Status Version::GetTableProperties(std::shared_ptr<const TableProperties>* tp,
|
||||
return s;
|
||||
}
|
||||
|
||||
// By setting the magic number to kInvalidTableMagicNumber, we can by
|
||||
// pass the magic number check in the footer.
|
||||
// By setting the magic number to kNullTableMagicNumber, we can bypass
|
||||
// the magic number check in the footer.
|
||||
std::unique_ptr<RandomAccessFileReader> file_reader(
|
||||
new RandomAccessFileReader(
|
||||
std::move(file), file_name, nullptr /* env */, io_tracer_,
|
||||
@@ -1281,7 +1281,7 @@ Status Version::GetTableProperties(std::shared_ptr<const TableProperties>* tp,
|
||||
std::unique_ptr<TableProperties> props;
|
||||
s = ReadTableProperties(
|
||||
file_reader.get(), file_meta->fd.GetFileSize(),
|
||||
Footer::kInvalidTableMagicNumber /* table's magic number */, *ioptions,
|
||||
Footer::kNullTableMagicNumber /* table's magic number */, *ioptions,
|
||||
&props);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -5385,13 +5385,13 @@ Status VersionSet::WriteCurrentStateToManifest(
|
||||
for (int level = 0; level < cfd->NumberLevels(); level++) {
|
||||
for (const auto& f :
|
||||
cfd->current()->storage_info()->LevelFiles(level)) {
|
||||
edit.AddFile(level, f->fd.GetNumber(), f->fd.GetPathId(),
|
||||
f->fd.GetFileSize(), f->smallest, f->largest,
|
||||
f->fd.smallest_seqno, f->fd.largest_seqno,
|
||||
f->marked_for_compaction, f->oldest_blob_file_number,
|
||||
f->oldest_ancester_time, f->file_creation_time,
|
||||
f->file_checksum, f->file_checksum_func_name,
|
||||
f->min_timestamp, f->max_timestamp);
|
||||
edit.AddFile(
|
||||
level, f->fd.GetNumber(), f->fd.GetPathId(), f->fd.GetFileSize(),
|
||||
f->smallest, f->largest, f->fd.smallest_seqno,
|
||||
f->fd.largest_seqno, f->marked_for_compaction, f->temperature,
|
||||
f->oldest_blob_file_number, f->oldest_ancester_time,
|
||||
f->file_creation_time, f->file_checksum,
|
||||
f->file_checksum_func_name, f->min_timestamp, f->max_timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1091,6 +1091,8 @@ class VersionSet {
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
const std::string& DbSessionId() const { return db_session_id_; }
|
||||
|
||||
// Return the current manifest file number
|
||||
uint64_t manifest_file_number() const { return manifest_file_number_; }
|
||||
|
||||
|
||||
+22
-19
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/log_writer.h"
|
||||
#include "rocksdb/advanced_options.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/file_system.h"
|
||||
#include "table/block_based/block_based_table_factory.h"
|
||||
@@ -44,10 +45,11 @@ class GenerateLevelFilesBriefTest : public testing::Test {
|
||||
files_.size() + 1, 0, 0,
|
||||
InternalKey(smallest, smallest_seq, kTypeValue),
|
||||
InternalKey(largest, largest_seq, kTypeValue), smallest_seq,
|
||||
largest_seq, /* marked_for_compact */ false, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
largest_seq, /* marked_for_compact */ false, Temperature::kUnknown,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
files_.push_back(f);
|
||||
}
|
||||
|
||||
@@ -153,10 +155,10 @@ class VersionStorageInfoTestBase : public testing::Test {
|
||||
FileMetaData* f = new FileMetaData(
|
||||
file_number, 0, file_size, smallest, largest, /* smallest_seq */ 0,
|
||||
/* largest_seq */ 0, /* marked_for_compact */ false,
|
||||
oldest_blob_file_number, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kDisableUserTimestamp,
|
||||
kDisableUserTimestamp);
|
||||
Temperature::kUnknown, oldest_blob_file_number,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
f->compensated_file_size = file_size;
|
||||
vstorage_.AddFile(level, f);
|
||||
}
|
||||
@@ -2997,7 +2999,8 @@ class VersionSetTestMissingFiles : public VersionSetTestBase,
|
||||
ASSERT_OK(s);
|
||||
ASSERT_NE(0, file_size);
|
||||
file_metas->emplace_back(file_num, /*file_path_id=*/0, file_size, ikey,
|
||||
ikey, 0, 0, false, 0, 0, 0, kUnknownFileChecksum,
|
||||
ikey, 0, 0, false, Temperature::kUnknown, 0, 0,
|
||||
0, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
}
|
||||
@@ -3051,11 +3054,11 @@ TEST_F(VersionSetTestMissingFiles, ManifestFarBehindSst) {
|
||||
std::string largest_ukey = "b";
|
||||
InternalKey smallest_ikey(smallest_ukey, 1, ValueType::kTypeValue);
|
||||
InternalKey largest_ikey(largest_ukey, 1, ValueType::kTypeValue);
|
||||
FileMetaData meta =
|
||||
FileMetaData(file_num, /*file_path_id=*/0, /*file_size=*/12,
|
||||
smallest_ikey, largest_ikey, 0, 0, false, 0, 0, 0,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
FileMetaData meta = FileMetaData(
|
||||
file_num, /*file_path_id=*/0, /*file_size=*/12, smallest_ikey,
|
||||
largest_ikey, 0, 0, false, Temperature::kUnknown, 0, 0, 0,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
added_files.emplace_back(0, meta);
|
||||
}
|
||||
WriteFileAdditionAndDeletionToManifest(
|
||||
@@ -3107,11 +3110,11 @@ TEST_F(VersionSetTestMissingFiles, ManifestAheadofSst) {
|
||||
std::string largest_ukey = "b";
|
||||
InternalKey smallest_ikey(smallest_ukey, 1, ValueType::kTypeValue);
|
||||
InternalKey largest_ikey(largest_ukey, 1, ValueType::kTypeValue);
|
||||
FileMetaData meta =
|
||||
FileMetaData(file_num, /*file_path_id=*/0, /*file_size=*/12,
|
||||
smallest_ikey, largest_ikey, 0, 0, false, 0, 0, 0,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
FileMetaData meta = FileMetaData(
|
||||
file_num, /*file_path_id=*/0, /*file_size=*/12, smallest_ikey,
|
||||
largest_ikey, 0, 0, false, Temperature::kUnknown, 0, 0, 0,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kDisableUserTimestamp, kDisableUserTimestamp);
|
||||
added_files.emplace_back(0, meta);
|
||||
}
|
||||
WriteFileAdditionAndDeletionToManifest(
|
||||
|
||||
+115
-108
@@ -134,114 +134,17 @@ struct BatchContentClassifier : public WriteBatch::Handler {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status MarkCommitWithTimestamp(const Slice&, const Slice&) override {
|
||||
content_flags |= ContentFlags::HAS_COMMIT;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status MarkRollback(const Slice&) override {
|
||||
content_flags |= ContentFlags::HAS_ROLLBACK;
|
||||
return Status::OK();
|
||||
}
|
||||
};
|
||||
|
||||
class TimestampAssigner : public WriteBatch::Handler {
|
||||
public:
|
||||
explicit TimestampAssigner(const Slice& ts,
|
||||
WriteBatch::ProtectionInfo* prot_info)
|
||||
: timestamp_(ts),
|
||||
timestamps_(kEmptyTimestampList),
|
||||
prot_info_(prot_info) {}
|
||||
explicit TimestampAssigner(const std::vector<Slice>& ts_list,
|
||||
WriteBatch::ProtectionInfo* prot_info)
|
||||
: timestamps_(ts_list), prot_info_(prot_info) {}
|
||||
~TimestampAssigner() override {}
|
||||
|
||||
Status PutCF(uint32_t, const Slice& key, const Slice&) override {
|
||||
AssignTimestamp(key);
|
||||
++idx_;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DeleteCF(uint32_t, const Slice& key) override {
|
||||
AssignTimestamp(key);
|
||||
++idx_;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status SingleDeleteCF(uint32_t, const Slice& key) override {
|
||||
AssignTimestamp(key);
|
||||
++idx_;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DeleteRangeCF(uint32_t, const Slice& begin_key,
|
||||
const Slice& /* end_key */) override {
|
||||
AssignTimestamp(begin_key);
|
||||
++idx_;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status MergeCF(uint32_t, const Slice& key, const Slice&) override {
|
||||
AssignTimestamp(key);
|
||||
++idx_;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status PutBlobIndexCF(uint32_t, const Slice&, const Slice&) override {
|
||||
// TODO (yanqin): support blob db in the future.
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status MarkBeginPrepare(bool) override {
|
||||
// TODO (yanqin): support in the future.
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status MarkEndPrepare(const Slice&) override {
|
||||
// TODO (yanqin): support in the future.
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status MarkCommit(const Slice&) override {
|
||||
// TODO (yanqin): support in the future.
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status MarkRollback(const Slice&) override {
|
||||
// TODO (yanqin): support in the future.
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
private:
|
||||
void AssignTimestamp(const Slice& key) {
|
||||
assert(timestamps_.empty() || idx_ < timestamps_.size());
|
||||
const Slice& ts = timestamps_.empty() ? timestamp_ : timestamps_[idx_];
|
||||
size_t ts_sz = ts.size();
|
||||
if (ts_sz == 0) {
|
||||
// This key does not have timestamp, so skip.
|
||||
return;
|
||||
}
|
||||
if (prot_info_ != nullptr) {
|
||||
SliceParts old_key(&key, 1);
|
||||
Slice key_no_ts(key.data(), key.size() - ts_sz);
|
||||
std::array<Slice, 2> new_key_cmpts{{key_no_ts, ts}};
|
||||
SliceParts new_key(new_key_cmpts.data(), 2);
|
||||
prot_info_->entries_[idx_].UpdateK(old_key, new_key);
|
||||
}
|
||||
char* ptr = const_cast<char*>(key.data() + key.size() - ts_sz);
|
||||
memcpy(ptr, ts.data(), ts_sz);
|
||||
}
|
||||
|
||||
static const std::vector<Slice> kEmptyTimestampList;
|
||||
const Slice timestamp_;
|
||||
const std::vector<Slice>& timestamps_;
|
||||
WriteBatch::ProtectionInfo* const prot_info_;
|
||||
size_t idx_ = 0;
|
||||
|
||||
// No copy or move.
|
||||
TimestampAssigner(const TimestampAssigner&) = delete;
|
||||
TimestampAssigner(TimestampAssigner&&) = delete;
|
||||
TimestampAssigner& operator=(const TimestampAssigner&) = delete;
|
||||
TimestampAssigner&& operator=(TimestampAssigner&&) = delete;
|
||||
};
|
||||
const std::vector<Slice> TimestampAssigner::kEmptyTimestampList;
|
||||
|
||||
} // anon namespace
|
||||
|
||||
struct SavePoints {
|
||||
@@ -518,6 +421,11 @@ Status ReadRecordFromWriteBatch(Slice* input, char* tag,
|
||||
return Status::Corruption("bad EndPrepare XID");
|
||||
}
|
||||
break;
|
||||
case kTypeCommitXIDAndTimestamp:
|
||||
if (!GetLengthPrefixedSlice(input, key)) {
|
||||
return Status::Corruption("bad commit timestamp");
|
||||
}
|
||||
FALLTHROUGH_INTENDED;
|
||||
case kTypeCommitXID:
|
||||
if (!GetLengthPrefixedSlice(input, xid)) {
|
||||
return Status::Corruption("bad Commit XID");
|
||||
@@ -727,6 +635,16 @@ Status WriteBatchInternal::Iterate(const WriteBatch* wb,
|
||||
assert(s.ok());
|
||||
empty_batch = true;
|
||||
break;
|
||||
case kTypeCommitXIDAndTimestamp:
|
||||
assert(wb->content_flags_.load(std::memory_order_relaxed) &
|
||||
(ContentFlags::DEFERRED | ContentFlags::HAS_COMMIT));
|
||||
// key stores the commit timestamp.
|
||||
assert(!key.empty());
|
||||
s = handler->MarkCommitWithTimestamp(xid, key);
|
||||
if (LIKELY(s.ok())) {
|
||||
empty_batch = true;
|
||||
}
|
||||
break;
|
||||
case kTypeRollbackXID:
|
||||
assert(wb->content_flags_.load(std::memory_order_relaxed) &
|
||||
(ContentFlags::DEFERRED | ContentFlags::HAS_ROLLBACK));
|
||||
@@ -926,6 +844,19 @@ Status WriteBatchInternal::MarkCommit(WriteBatch* b, const Slice& xid) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status WriteBatchInternal::MarkCommitWithTimestamp(WriteBatch* b,
|
||||
const Slice& xid,
|
||||
const Slice& commit_ts) {
|
||||
assert(!commit_ts.empty());
|
||||
b->rep_.push_back(static_cast<char>(kTypeCommitXIDAndTimestamp));
|
||||
PutLengthPrefixedSlice(&b->rep_, commit_ts);
|
||||
PutLengthPrefixedSlice(&b->rep_, xid);
|
||||
b->content_flags_.store(b->content_flags_.load(std::memory_order_relaxed) |
|
||||
ContentFlags::HAS_COMMIT,
|
||||
std::memory_order_relaxed);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status WriteBatchInternal::MarkRollback(WriteBatch* b, const Slice& xid) {
|
||||
b->rep_.push_back(static_cast<char>(kTypeRollbackXID));
|
||||
PutLengthPrefixedSlice(&b->rep_, xid);
|
||||
@@ -1292,13 +1223,17 @@ Status WriteBatch::PopSavePoint() {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status WriteBatch::AssignTimestamp(const Slice& ts) {
|
||||
TimestampAssigner ts_assigner(ts, prot_info_.get());
|
||||
Status WriteBatch::AssignTimestamp(
|
||||
const Slice& ts, std::function<Status(uint32_t, size_t&)> checker) {
|
||||
TimestampAssigner ts_assigner(prot_info_.get(), std::move(checker), ts);
|
||||
return Iterate(&ts_assigner);
|
||||
}
|
||||
|
||||
Status WriteBatch::AssignTimestamps(const std::vector<Slice>& ts_list) {
|
||||
TimestampAssigner ts_assigner(ts_list, prot_info_.get());
|
||||
Status WriteBatch::AssignTimestamps(
|
||||
const std::vector<Slice>& ts_list,
|
||||
std::function<Status(uint32_t, size_t&)> checker) {
|
||||
SimpleListTimestampAssigner ts_assigner(prot_info_.get(), std::move(checker),
|
||||
ts_list);
|
||||
return Iterate(&ts_assigner);
|
||||
}
|
||||
|
||||
@@ -2098,8 +2033,8 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
const MemTable* const mem = cfd->mem();
|
||||
assert(mem);
|
||||
|
||||
if (mem->ApproximateMemoryUsageFast() +
|
||||
imm->ApproximateMemoryUsageExcludingLast() >=
|
||||
if (mem->MemoryAllocatedBytes() +
|
||||
imm->MemoryAllocatedBytesExcludingLast() >=
|
||||
size_to_maintain &&
|
||||
imm->MarkTrimHistoryNeeded()) {
|
||||
trim_history_scheduler_->ScheduleWork(cfd);
|
||||
@@ -2184,6 +2119,8 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
Status s;
|
||||
|
||||
if (recovering_log_number_ != 0) {
|
||||
// We must hold db mutex in recovery.
|
||||
db_->mutex()->AssertHeld();
|
||||
// in recovery when we encounter a commit marker
|
||||
// we lookup this transaction in our set of rebuilt transactions
|
||||
// and commit.
|
||||
@@ -2226,6 +2163,76 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
return s;
|
||||
}
|
||||
|
||||
Status MarkCommitWithTimestamp(const Slice& name,
|
||||
const Slice& commit_ts) override {
|
||||
assert(db_);
|
||||
|
||||
Status s;
|
||||
|
||||
if (recovering_log_number_ != 0) {
|
||||
// In recovery, db mutex must be held.
|
||||
db_->mutex()->AssertHeld();
|
||||
// in recovery when we encounter a commit marker
|
||||
// we lookup this transaction in our set of rebuilt transactions
|
||||
// and commit.
|
||||
auto trx = db_->GetRecoveredTransaction(name.ToString());
|
||||
// the log containing the prepared section may have
|
||||
// been released in the last incarnation because the
|
||||
// data was flushed to L0
|
||||
if (trx) {
|
||||
// at this point individual CF lognumbers will prevent
|
||||
// duplicate re-insertion of values.
|
||||
assert(0 == log_number_ref_);
|
||||
if (write_after_commit_) {
|
||||
// write_after_commit_ can only have one batch in trx.
|
||||
assert(trx->batches_.size() == 1);
|
||||
const auto& batch_info = trx->batches_.begin()->second;
|
||||
// all inserts must reference this trx log number
|
||||
log_number_ref_ = batch_info.log_number_;
|
||||
const auto checker = [this](uint32_t cf, size_t& ts_sz) {
|
||||
assert(db_);
|
||||
VersionSet* const vset = db_->GetVersionSet();
|
||||
assert(vset);
|
||||
ColumnFamilySet* const cf_set = vset->GetColumnFamilySet();
|
||||
assert(cf_set);
|
||||
ColumnFamilyData* cfd = cf_set->GetColumnFamily(cf);
|
||||
assert(cfd);
|
||||
const auto* const ucmp = cfd->user_comparator();
|
||||
assert(ucmp);
|
||||
if (ucmp->timestamp_size() == 0) {
|
||||
ts_sz = 0;
|
||||
} else if (ucmp->timestamp_size() != ts_sz) {
|
||||
return Status::InvalidArgument("Timestamp size mismatch");
|
||||
}
|
||||
return Status::OK();
|
||||
};
|
||||
s = batch_info.batch_->AssignTimestamp(commit_ts, checker);
|
||||
if (s.ok()) {
|
||||
s = batch_info.batch_->Iterate(this);
|
||||
log_number_ref_ = 0;
|
||||
}
|
||||
}
|
||||
// else the values are already inserted before the commit
|
||||
|
||||
if (s.ok()) {
|
||||
db_->DeleteRecoveredTransaction(name.ToString());
|
||||
}
|
||||
if (has_valid_writes_) {
|
||||
*has_valid_writes_ = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// When writes are not delayed until commit, there is no connection
|
||||
// between a memtable write and the WAL that supports it. So the commit
|
||||
// need not reference any log as the only log to which it depends.
|
||||
assert(!write_after_commit_ || log_number_ref_ > 0);
|
||||
}
|
||||
constexpr bool batch_boundary = true;
|
||||
MaybeAdvanceSeq(batch_boundary);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Status MarkRollback(const Slice& name) override {
|
||||
assert(db_);
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#pragma once
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "db/flush_scheduler.h"
|
||||
@@ -19,6 +20,7 @@
|
||||
#include "rocksdb/types.h"
|
||||
#include "rocksdb/write_batch.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/cast_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -122,6 +124,9 @@ class WriteBatchInternal {
|
||||
|
||||
static Status MarkCommit(WriteBatch* batch, const Slice& xid);
|
||||
|
||||
static Status MarkCommitWithTimestamp(WriteBatch* batch, const Slice& xid,
|
||||
const Slice& commit_ts);
|
||||
|
||||
static Status InsertNoop(WriteBatch* batch);
|
||||
|
||||
// Return the number of entries in the batch.
|
||||
@@ -260,4 +265,165 @@ class LocalSavePoint {
|
||||
#endif
|
||||
};
|
||||
|
||||
template <typename Derived>
|
||||
class TimestampAssignerBase : public WriteBatch::Handler {
|
||||
public:
|
||||
explicit TimestampAssignerBase(
|
||||
WriteBatch::ProtectionInfo* prot_info,
|
||||
std::function<Status(uint32_t, size_t&)>&& checker)
|
||||
: prot_info_(prot_info), checker_(std::move(checker)) {}
|
||||
|
||||
~TimestampAssignerBase() override {}
|
||||
|
||||
Status PutCF(uint32_t cf, const Slice& key, const Slice&) override {
|
||||
return AssignTimestamp(cf, key);
|
||||
}
|
||||
|
||||
Status DeleteCF(uint32_t cf, const Slice& key) override {
|
||||
return AssignTimestamp(cf, key);
|
||||
}
|
||||
|
||||
Status SingleDeleteCF(uint32_t cf, const Slice& key) override {
|
||||
return AssignTimestamp(cf, key);
|
||||
}
|
||||
|
||||
Status DeleteRangeCF(uint32_t cf, const Slice& begin_key,
|
||||
const Slice&) override {
|
||||
return AssignTimestamp(cf, begin_key);
|
||||
}
|
||||
|
||||
Status MergeCF(uint32_t cf, const Slice& key, const Slice&) override {
|
||||
return AssignTimestamp(cf, key);
|
||||
}
|
||||
|
||||
Status PutBlobIndexCF(uint32_t cf, const Slice& key, const Slice&) override {
|
||||
return AssignTimestamp(cf, key);
|
||||
}
|
||||
|
||||
Status MarkBeginPrepare(bool) override { return Status::OK(); }
|
||||
|
||||
Status MarkEndPrepare(const Slice&) override { return Status::OK(); }
|
||||
|
||||
Status MarkCommit(const Slice&) override { return Status::OK(); }
|
||||
|
||||
Status MarkCommitWithTimestamp(const Slice&, const Slice&) override {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status MarkRollback(const Slice&) override { return Status::OK(); }
|
||||
|
||||
Status MarkNoop(bool /*empty_batch*/) override { return Status::OK(); }
|
||||
|
||||
protected:
|
||||
Status AssignTimestamp(uint32_t cf, const Slice& key) {
|
||||
Status s = static_cast_with_check<Derived>(this)->AssignTimestampImpl(
|
||||
cf, key, idx_);
|
||||
++idx_;
|
||||
return s;
|
||||
}
|
||||
|
||||
Status CheckTimestampSize(uint32_t cf, size_t& ts_sz) {
|
||||
return checker_(cf, ts_sz);
|
||||
}
|
||||
|
||||
Status UpdateTimestampIfNeeded(size_t ts_sz, const Slice& key,
|
||||
const Slice& ts) {
|
||||
if (ts_sz > 0) {
|
||||
assert(ts_sz == ts.size());
|
||||
UpdateProtectionInformationIfNeeded(key, ts);
|
||||
UpdateTimestamp(key, ts);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void UpdateProtectionInformationIfNeeded(const Slice& key, const Slice& ts) {
|
||||
if (prot_info_ != nullptr) {
|
||||
const size_t ts_sz = ts.size();
|
||||
SliceParts old_key(&key, 1);
|
||||
Slice key_no_ts(key.data(), key.size() - ts_sz);
|
||||
std::array<Slice, 2> new_key_cmpts{{key_no_ts, ts}};
|
||||
SliceParts new_key(new_key_cmpts.data(), 2);
|
||||
prot_info_->entries_[idx_].UpdateK(old_key, new_key);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateTimestamp(const Slice& key, const Slice& ts) {
|
||||
const size_t ts_sz = ts.size();
|
||||
char* ptr = const_cast<char*>(key.data() + key.size() - ts_sz);
|
||||
assert(ptr);
|
||||
memcpy(ptr, ts.data(), ts_sz);
|
||||
}
|
||||
|
||||
// No copy or move.
|
||||
TimestampAssignerBase(const TimestampAssignerBase&) = delete;
|
||||
TimestampAssignerBase(TimestampAssignerBase&&) = delete;
|
||||
TimestampAssignerBase& operator=(const TimestampAssignerBase&) = delete;
|
||||
TimestampAssignerBase& operator=(TimestampAssignerBase&&) = delete;
|
||||
|
||||
WriteBatch::ProtectionInfo* const prot_info_ = nullptr;
|
||||
const std::function<Status(uint32_t, size_t&)> checker_{};
|
||||
size_t idx_ = 0;
|
||||
};
|
||||
|
||||
class SimpleListTimestampAssigner
|
||||
: public TimestampAssignerBase<SimpleListTimestampAssigner> {
|
||||
public:
|
||||
explicit SimpleListTimestampAssigner(
|
||||
WriteBatch::ProtectionInfo* prot_info,
|
||||
std::function<Status(uint32_t, size_t&)>&& checker,
|
||||
const std::vector<Slice>& timestamps)
|
||||
: TimestampAssignerBase<SimpleListTimestampAssigner>(prot_info,
|
||||
std::move(checker)),
|
||||
timestamps_(timestamps) {}
|
||||
|
||||
~SimpleListTimestampAssigner() override {}
|
||||
|
||||
private:
|
||||
friend class TimestampAssignerBase<SimpleListTimestampAssigner>;
|
||||
|
||||
Status AssignTimestampImpl(uint32_t cf, const Slice& key, size_t idx) {
|
||||
if (idx >= timestamps_.size()) {
|
||||
return Status::InvalidArgument("Need more timestamps for the assignment");
|
||||
}
|
||||
const Slice& ts = timestamps_[idx];
|
||||
size_t ts_sz = ts.size();
|
||||
const Status s = this->CheckTimestampSize(cf, ts_sz);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
return this->UpdateTimestampIfNeeded(ts_sz, key, ts);
|
||||
}
|
||||
|
||||
const std::vector<Slice>& timestamps_;
|
||||
};
|
||||
|
||||
class TimestampAssigner : public TimestampAssignerBase<TimestampAssigner> {
|
||||
public:
|
||||
explicit TimestampAssigner(WriteBatch::ProtectionInfo* prot_info,
|
||||
std::function<Status(uint32_t, size_t&)>&& checker,
|
||||
const Slice& ts)
|
||||
: TimestampAssignerBase<TimestampAssigner>(prot_info, std::move(checker)),
|
||||
timestamp_(ts) {
|
||||
assert(!timestamp_.empty());
|
||||
}
|
||||
~TimestampAssigner() override {}
|
||||
|
||||
private:
|
||||
friend class TimestampAssignerBase<TimestampAssigner>;
|
||||
|
||||
Status AssignTimestampImpl(uint32_t cf, const Slice& key, size_t /*idx*/) {
|
||||
if (timestamp_.empty()) {
|
||||
return Status::InvalidArgument("Timestamp is empty");
|
||||
}
|
||||
size_t ts_sz = timestamp_.size();
|
||||
const Status s = this->CheckTimestampSize(cf, ts_sz);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
return this->UpdateTimestampIfNeeded(ts_sz, key, timestamp_);
|
||||
}
|
||||
|
||||
const Slice timestamp_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+180
-3
@@ -13,6 +13,7 @@
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/memtable.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/memtablerep.h"
|
||||
@@ -20,6 +21,7 @@
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
#include "table/scoped_arena_iterator.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -316,6 +318,11 @@ namespace {
|
||||
seen += "MarkCommit(" + xid.ToString() + ")";
|
||||
return Status::OK();
|
||||
}
|
||||
Status MarkCommitWithTimestamp(const Slice& xid, const Slice& ts) override {
|
||||
seen += "MarkCommitWithTimestamp(" + xid.ToString() + ", " +
|
||||
ts.ToString(true) + ")";
|
||||
return Status::OK();
|
||||
}
|
||||
Status MarkRollback(const Slice& xid) override {
|
||||
seen += "MarkRollback(" + xid.ToString() + ")";
|
||||
return Status::OK();
|
||||
@@ -627,13 +634,16 @@ class ColumnFamilyHandleImplDummy : public ColumnFamilyHandleImpl {
|
||||
public:
|
||||
explicit ColumnFamilyHandleImplDummy(int id)
|
||||
: ColumnFamilyHandleImpl(nullptr, nullptr, nullptr), id_(id) {}
|
||||
explicit ColumnFamilyHandleImplDummy(int id, const Comparator* ucmp)
|
||||
: ColumnFamilyHandleImpl(nullptr, nullptr, nullptr),
|
||||
id_(id),
|
||||
ucmp_(ucmp) {}
|
||||
uint32_t GetID() const override { return id_; }
|
||||
const Comparator* GetComparator() const override {
|
||||
return BytewiseComparator();
|
||||
}
|
||||
const Comparator* GetComparator() const override { return ucmp_; }
|
||||
|
||||
private:
|
||||
uint32_t id_;
|
||||
const Comparator* const ucmp_ = BytewiseComparator();
|
||||
};
|
||||
} // namespace anonymous
|
||||
|
||||
@@ -899,6 +909,173 @@ TEST_F(WriteBatchTest, MemoryLimitTest) {
|
||||
ASSERT_TRUE(s.IsMemoryLimit());
|
||||
}
|
||||
|
||||
namespace {
|
||||
class TimestampChecker : public WriteBatch::Handler {
|
||||
public:
|
||||
explicit TimestampChecker(
|
||||
std::unordered_map<uint32_t, const Comparator*> cf_to_ucmps, Slice ts)
|
||||
: cf_to_ucmps_(std::move(cf_to_ucmps)), timestamp_(std::move(ts)) {}
|
||||
Status PutCF(uint32_t cf, const Slice& key, const Slice& /*value*/) override {
|
||||
auto cf_iter = cf_to_ucmps_.find(cf);
|
||||
if (cf_iter == cf_to_ucmps_.end()) {
|
||||
return Status::Corruption();
|
||||
}
|
||||
const Comparator* const ucmp = cf_iter->second;
|
||||
assert(ucmp);
|
||||
size_t ts_sz = ucmp->timestamp_size();
|
||||
if (ts_sz == 0) {
|
||||
return Status::OK();
|
||||
}
|
||||
if (key.size() < ts_sz) {
|
||||
return Status::Corruption();
|
||||
}
|
||||
Slice ts = ExtractTimestampFromUserKey(key, ts_sz);
|
||||
if (ts.compare(timestamp_) != 0) {
|
||||
return Status::Corruption();
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<uint32_t, const Comparator*> cf_to_ucmps_;
|
||||
Slice timestamp_;
|
||||
};
|
||||
|
||||
Status CheckTimestampsInWriteBatch(
|
||||
WriteBatch& wb, Slice timestamp,
|
||||
std::unordered_map<uint32_t, const Comparator*> cf_to_ucmps) {
|
||||
TimestampChecker ts_checker(cf_to_ucmps, timestamp);
|
||||
return wb.Iterate(&ts_checker);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_F(WriteBatchTest, AssignTimestamps) {
|
||||
// We assume the last eight bytes of each key is reserved for timestamps.
|
||||
// Therefore, we must make sure each key is longer than eight bytes.
|
||||
constexpr size_t key_size = 16;
|
||||
constexpr size_t num_of_keys = 10;
|
||||
std::vector<std::string> key_strs(num_of_keys, std::string(key_size, '\0'));
|
||||
|
||||
ColumnFamilyHandleImplDummy cf0(0);
|
||||
ColumnFamilyHandleImplDummy cf4(4, test::ComparatorWithU64Ts());
|
||||
ColumnFamilyHandleImplDummy cf5(5, test::ComparatorWithU64Ts());
|
||||
|
||||
const std::unordered_map<uint32_t, const Comparator*> cf_to_ucmps = {
|
||||
{0, cf0.GetComparator()},
|
||||
{4, cf4.GetComparator()},
|
||||
{5, cf5.GetComparator()}};
|
||||
|
||||
WriteBatch batch;
|
||||
// Write to the batch. We will assign timestamps later.
|
||||
for (const auto& key_str : key_strs) {
|
||||
ASSERT_OK(batch.Put(&cf0, key_str, "value"));
|
||||
ASSERT_OK(batch.Put(&cf4, key_str, "value"));
|
||||
ASSERT_OK(batch.Put(&cf5, key_str, "value"));
|
||||
}
|
||||
|
||||
static constexpr size_t timestamp_size = sizeof(uint64_t);
|
||||
const auto checker1 = [](uint32_t cf, size_t& ts_sz) {
|
||||
if (cf == 4 || cf == 5) {
|
||||
if (ts_sz != timestamp_size) {
|
||||
return Status::InvalidArgument("Timestamp size mismatch");
|
||||
}
|
||||
} else if (cf == 0) {
|
||||
ts_sz = 0;
|
||||
return Status::OK();
|
||||
} else {
|
||||
return Status::Corruption("Invalid cf");
|
||||
}
|
||||
return Status::OK();
|
||||
};
|
||||
ASSERT_OK(
|
||||
batch.AssignTimestamp(std::string(timestamp_size, '\xfe'), checker1));
|
||||
ASSERT_OK(CheckTimestampsInWriteBatch(
|
||||
batch, std::string(timestamp_size, '\xfe'), cf_to_ucmps));
|
||||
|
||||
// We use indexed_cf_to_ucmps, non_indexed_cfs_with_ts and timestamp_size to
|
||||
// simulate the case in which a transaction enables indexing for some writes
|
||||
// while disables indexing for other writes. A transaction uses a
|
||||
// WriteBatchWithIndex object to buffer writes (we consider Write-committed
|
||||
// policy only). If indexing is enabled, then writes go through
|
||||
// WriteBatchWithIndex API populating a WBWI internal data structure, i.e. a
|
||||
// mapping from cf to user comparators. If indexing is disabled, a transaction
|
||||
// writes directly to the underlying raw WriteBatch. We will need to track the
|
||||
// comparator information for the column families to which un-indexed writes
|
||||
// are performed. When calling AssignTimestamp(s) API of WriteBatch, we need
|
||||
// indexed_cf_to_ucmps, non_indexed_cfs_with_ts, and timestamp_size to perform
|
||||
// checking.
|
||||
std::unordered_map<uint32_t, const Comparator*> indexed_cf_to_ucmps = {
|
||||
{0, cf0.GetComparator()}, {4, cf4.GetComparator()}};
|
||||
std::unordered_set<uint32_t> non_indexed_cfs_with_ts = {cf5.GetID()};
|
||||
const auto checker2 = [&indexed_cf_to_ucmps, &non_indexed_cfs_with_ts](
|
||||
uint32_t cf, size_t& ts_sz) {
|
||||
if (non_indexed_cfs_with_ts.count(cf) > 0) {
|
||||
if (ts_sz != timestamp_size) {
|
||||
return Status::InvalidArgument("Timestamp size mismatch");
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
auto cf_iter = indexed_cf_to_ucmps.find(cf);
|
||||
if (cf_iter == indexed_cf_to_ucmps.end()) {
|
||||
return Status::Corruption("Unknown cf");
|
||||
}
|
||||
const Comparator* const ucmp = cf_iter->second;
|
||||
assert(ucmp);
|
||||
if (ucmp->timestamp_size() == 0) {
|
||||
ts_sz = 0;
|
||||
} else if (ts_sz != ucmp->timestamp_size()) {
|
||||
return Status::InvalidArgument("Timestamp size mismatch");
|
||||
}
|
||||
return Status::OK();
|
||||
};
|
||||
ASSERT_OK(
|
||||
batch.AssignTimestamp(std::string(timestamp_size, '\xef'), checker2));
|
||||
ASSERT_OK(CheckTimestampsInWriteBatch(
|
||||
batch, std::string(timestamp_size, '\xef'), cf_to_ucmps));
|
||||
|
||||
std::vector<std::string> ts_strs;
|
||||
for (size_t i = 0; i < 3 * key_strs.size(); ++i) {
|
||||
if (0 == (i % 3)) {
|
||||
ts_strs.emplace_back();
|
||||
} else {
|
||||
ts_strs.emplace_back(std::string(timestamp_size, '\xee'));
|
||||
}
|
||||
}
|
||||
std::vector<Slice> ts_vec(ts_strs.size());
|
||||
for (size_t i = 0; i < ts_vec.size(); ++i) {
|
||||
ts_vec[i] = ts_strs[i];
|
||||
}
|
||||
const auto checker3 = [&cf_to_ucmps](uint32_t cf, size_t& ts_sz) {
|
||||
auto cf_iter = cf_to_ucmps.find(cf);
|
||||
if (cf_iter == cf_to_ucmps.end()) {
|
||||
return Status::Corruption("Invalid cf");
|
||||
}
|
||||
const Comparator* const ucmp = cf_iter->second;
|
||||
assert(ucmp);
|
||||
if (ucmp->timestamp_size() != ts_sz) {
|
||||
return Status::InvalidArgument("Timestamp size mismatch");
|
||||
}
|
||||
return Status::OK();
|
||||
};
|
||||
ASSERT_OK(batch.AssignTimestamps(ts_vec, checker3));
|
||||
ASSERT_OK(CheckTimestampsInWriteBatch(
|
||||
batch, std::string(timestamp_size, '\xee'), cf_to_ucmps));
|
||||
}
|
||||
|
||||
TEST_F(WriteBatchTest, CommitWithTimestamp) {
|
||||
WriteBatch wb;
|
||||
const std::string txn_name = "xid1";
|
||||
std::string ts;
|
||||
constexpr uint64_t commit_ts = 23;
|
||||
PutFixed64(&ts, commit_ts);
|
||||
ASSERT_OK(WriteBatchInternal::MarkCommitWithTimestamp(&wb, txn_name, ts));
|
||||
TestHandler handler;
|
||||
ASSERT_OK(wb.Iterate(&handler));
|
||||
ASSERT_EQ("MarkCommitWithTimestamp(" + txn_name + ", " +
|
||||
Slice(ts).ToString(true) + ")",
|
||||
handler.seen);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -7,9 +7,11 @@ add_executable(db_stress${ARTIFACT_SUFFIX}
|
||||
db_stress_gflags.cc
|
||||
db_stress_listener.cc
|
||||
db_stress_shared_state.cc
|
||||
db_stress_stat.cc
|
||||
db_stress_test_base.cc
|
||||
db_stress_tool.cc
|
||||
expected_state.cc
|
||||
multi_ops_txns_stress.cc
|
||||
no_batched_ops_stress.cc)
|
||||
target_link_libraries(db_stress${ARTIFACT_SUFFIX} ${ROCKSDB_LIB} ${THIRDPARTY_LIBS})
|
||||
list(APPEND tool_deps db_stress)
|
||||
@@ -16,6 +16,8 @@ class BatchedOpsStressTest : public StressTest {
|
||||
BatchedOpsStressTest() {}
|
||||
virtual ~BatchedOpsStressTest() {}
|
||||
|
||||
bool IsStateTracked() const override { return false; }
|
||||
|
||||
// Given a key K and value V, this puts ("0"+K, "0"+V), ("1"+K, "1"+V), ...
|
||||
// ("9"+K, "9"+V) in DB atomically i.e in a single batch.
|
||||
// Also refer BatchedOpsStressTest::TestGet
|
||||
|
||||
@@ -18,6 +18,8 @@ class CfConsistencyStressTest : public StressTest {
|
||||
|
||||
~CfConsistencyStressTest() override {}
|
||||
|
||||
bool IsStateTracked() const override { return false; }
|
||||
|
||||
Status TestPut(ThreadState* thread, WriteOptions& write_opts,
|
||||
const ReadOptions& /* read_opts */,
|
||||
const std::vector<int>& rand_column_families,
|
||||
|
||||
@@ -225,7 +225,7 @@ size_t GenerateValue(uint32_t rand, char* v, size_t max_sz) {
|
||||
((rand % kRandomValueMaxFactor) + 1) * FLAGS_value_size_mult;
|
||||
assert(value_sz <= max_sz && value_sz >= sizeof(uint32_t));
|
||||
(void)max_sz;
|
||||
*((uint32_t*)v) = rand;
|
||||
PutUnaligned(reinterpret_cast<uint32_t*>(v), rand);
|
||||
for (size_t i = sizeof(uint32_t); i < value_sz; i++) {
|
||||
v[i] = (char)(rand ^ i);
|
||||
}
|
||||
@@ -233,6 +233,13 @@ size_t GenerateValue(uint32_t rand, char* v, size_t max_sz) {
|
||||
return value_sz; // the size of the value set.
|
||||
}
|
||||
|
||||
uint32_t GetValueBase(Slice s) {
|
||||
assert(s.size() >= sizeof(uint32_t));
|
||||
uint32_t res;
|
||||
GetUnaligned(reinterpret_cast<const uint32_t*>(s.data()), &res);
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string NowNanosStr() {
|
||||
uint64_t t = db_stress_env->NowNanos();
|
||||
std::string ret;
|
||||
|
||||
@@ -87,6 +87,7 @@ DECLARE_int64(active_width);
|
||||
DECLARE_bool(test_batches_snapshots);
|
||||
DECLARE_bool(atomic_flush);
|
||||
DECLARE_bool(test_cf_consistency);
|
||||
DECLARE_bool(test_multi_ops_txns);
|
||||
DECLARE_int32(threads);
|
||||
DECLARE_int32(ttl);
|
||||
DECLARE_int32(value_size_mult);
|
||||
@@ -203,6 +204,7 @@ DECLARE_int32(delrangepercent);
|
||||
DECLARE_int32(nooverwritepercent);
|
||||
DECLARE_int32(iterpercent);
|
||||
DECLARE_uint64(num_iterations);
|
||||
DECLARE_int32(customopspercent);
|
||||
DECLARE_string(compression_type);
|
||||
DECLARE_string(bottommost_compression_type);
|
||||
DECLARE_int32(compression_max_dict_bytes);
|
||||
@@ -269,6 +271,8 @@ DECLARE_uint64(user_timestamp_size);
|
||||
DECLARE_string(secondary_cache_uri);
|
||||
DECLARE_int32(secondary_cache_fault_one_in);
|
||||
|
||||
DECLARE_int32(prepopulate_block_cache);
|
||||
|
||||
constexpr long KB = 1024;
|
||||
constexpr int kRandomValueMaxFactor = 3;
|
||||
constexpr int kValueMaxLen = 100;
|
||||
@@ -563,10 +567,13 @@ extern std::vector<int64_t> GenerateNKeys(ThreadState* thread, int num_keys,
|
||||
uint64_t iteration);
|
||||
|
||||
extern size_t GenerateValue(uint32_t rand, char* v, size_t max_sz);
|
||||
extern uint32_t GetValueBase(Slice s);
|
||||
|
||||
extern StressTest* CreateCfConsistencyStressTest();
|
||||
extern StressTest* CreateBatchedOpsStressTest();
|
||||
extern StressTest* CreateNonBatchedOpsStressTest();
|
||||
extern StressTest* CreateMultiOpsTxnsStressTest();
|
||||
extern void CheckAndSetOptionsForMultiOpsTxnStressTest();
|
||||
extern void InitializeHotKeyGenerator(double alpha);
|
||||
extern int64_t GetOneHotKeyID(double rand_seed, int64_t max_key);
|
||||
|
||||
|
||||
@@ -90,6 +90,11 @@ DEFINE_bool(test_cf_consistency, false,
|
||||
"multiple column families are consistent. Setting this implies "
|
||||
"`atomic_flush=true` is set true if `disable_wal=false`.\n");
|
||||
|
||||
DEFINE_bool(test_multi_ops_txns, false,
|
||||
"If set, runs stress test dedicated to verifying multi-ops "
|
||||
"transactions on a simple relational table with primary and "
|
||||
"secondary index.");
|
||||
|
||||
DEFINE_int32(threads, 32, "Number of concurrent threads to run.");
|
||||
|
||||
DEFINE_int32(ttl, -1,
|
||||
@@ -463,12 +468,13 @@ DEFINE_bool(test_secondary, false, "Test secondary instance.");
|
||||
|
||||
DEFINE_string(
|
||||
expected_values_dir, "",
|
||||
"Dir where file with array of expected uint32_t values will be stored. If "
|
||||
"provided and non-empty, the DB state will be verified against these "
|
||||
"values after recovery. --max_key and --column_family must be kept the "
|
||||
"same across invocations of this program that use the same "
|
||||
"--expected_values_dir. See --seed and --nooverwritepercent for further "
|
||||
"requirements.");
|
||||
"Dir where files containing info about the latest/historical values will "
|
||||
"be stored. If provided and non-empty, the DB state will be verified "
|
||||
"against values from these files after recovery. --max_key and "
|
||||
"--column_family must be kept the same across invocations of this program "
|
||||
"that use the same --expected_values_dir. Currently historical values are "
|
||||
"only tracked when --sync_fault_injection is set. See --seed and "
|
||||
"--nooverwritepercent for further requirements.");
|
||||
|
||||
DEFINE_bool(verify_checksum, false,
|
||||
"Verify checksum for every block read from storage");
|
||||
@@ -674,6 +680,10 @@ DEFINE_uint64(num_iterations, 10, "Number of iterations per MultiIterate run");
|
||||
static const bool FLAGS_num_iterations_dummy __attribute__((__unused__)) =
|
||||
RegisterFlagValidator(&FLAGS_num_iterations, &ValidateUint32Range);
|
||||
|
||||
DEFINE_int32(
|
||||
customopspercent, 0,
|
||||
"Ratio of custom operations to total workload (expressed as a percentage)");
|
||||
|
||||
DEFINE_string(compression_type, "snappy",
|
||||
"Algorithm to use to compress the database");
|
||||
|
||||
@@ -805,7 +815,10 @@ DEFINE_int32(get_property_one_in, 1000,
|
||||
|
||||
DEFINE_bool(sync_fault_injection, false,
|
||||
"If true, FaultInjectionTestFS will be used for write operations, "
|
||||
" and unsynced data in DB will lost after crash.");
|
||||
"and unsynced data in DB will lost after crash. In such a case we "
|
||||
"track DB changes in a trace file (\"*.trace\") in "
|
||||
"--expected_values_dir for verifying there are no holes in the "
|
||||
"recovered data.");
|
||||
|
||||
DEFINE_bool(best_efforts_recovery, false,
|
||||
"If true, use best efforts recovery.");
|
||||
@@ -860,5 +873,10 @@ DEFINE_int32(injest_error_severity, 1,
|
||||
"The severity of the injested IO Error. 1 is soft error (e.g. "
|
||||
"retryable error), 2 is fatal error, and the default is "
|
||||
"retryable error.");
|
||||
DEFINE_int32(prepopulate_block_cache,
|
||||
static_cast<int32_t>(ROCKSDB_NAMESPACE::BlockBasedTableOptions::
|
||||
PrepopulateBlockCache::kDisable),
|
||||
"Options related to cache warming (see `enum "
|
||||
"PrepopulateBlockCache` in table.h)");
|
||||
|
||||
#endif // GFLAGS
|
||||
@@ -31,6 +31,13 @@ UniqueIdVerifier::UniqueIdVerifier(const std::string& db_name)
|
||||
FileSystem& fs = *FileSystem::Default();
|
||||
IOOptions opts;
|
||||
|
||||
Status st = fs.CreateDirIfMissing(db_name, opts, nullptr);
|
||||
if (!st.ok()) {
|
||||
fprintf(stderr, "Failed to create directory %s: %s\n", db_name.c_str(),
|
||||
st.ToString().c_str());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_ptr<FSSequentialFile> reader;
|
||||
Status s =
|
||||
|
||||
@@ -250,6 +250,14 @@ class SharedState {
|
||||
}
|
||||
}
|
||||
|
||||
Status SaveAtAndAfter(DB* db) {
|
||||
return expected_state_manager_->SaveAtAndAfter(db);
|
||||
}
|
||||
|
||||
bool HasHistory() { return expected_state_manager_->HasHistory(); }
|
||||
|
||||
Status Restore(DB* db) { return expected_state_manager_->Restore(db); }
|
||||
|
||||
// Requires external locking covering all keys in `cf`.
|
||||
void ClearColumnFamily(int cf) {
|
||||
return expected_state_manager_->ClearColumnFamily(cf);
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
// 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 "db_stress_tool/db_stress_stat.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
std::shared_ptr<ROCKSDB_NAMESPACE::Statistics> dbstats;
|
||||
std::shared_ptr<ROCKSDB_NAMESPACE::Statistics> dbstats_secondaries;
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#endif // GFLAGS
|
||||
@@ -21,9 +21,10 @@ DECLARE_bool(histogram);
|
||||
DECLARE_bool(progress_reports);
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// Database statistics
|
||||
static std::shared_ptr<ROCKSDB_NAMESPACE::Statistics> dbstats;
|
||||
static std::shared_ptr<ROCKSDB_NAMESPACE::Statistics> dbstats_secondaries;
|
||||
extern std::shared_ptr<ROCKSDB_NAMESPACE::Statistics> dbstats;
|
||||
extern std::shared_ptr<ROCKSDB_NAMESPACE::Statistics> dbstats_secondaries;
|
||||
|
||||
class Stats {
|
||||
private:
|
||||
|
||||
@@ -151,7 +151,8 @@ std::shared_ptr<Cache> StressTest::NewCache(size_t capacity,
|
||||
}
|
||||
if (FLAGS_secondary_cache_fault_one_in > 0) {
|
||||
secondary_cache = std::make_shared<FaultInjectionSecondaryCache>(
|
||||
secondary_cache, FLAGS_seed, FLAGS_secondary_cache_fault_one_in);
|
||||
secondary_cache, static_cast<uint32_t>(FLAGS_seed),
|
||||
FLAGS_secondary_cache_fault_one_in);
|
||||
}
|
||||
opts.secondary_cache = secondary_cache;
|
||||
}
|
||||
@@ -306,6 +307,28 @@ void StressTest::FinishInitDb(SharedState* shared) {
|
||||
fprintf(stdout, "Compaction filter factory: %s\n",
|
||||
compaction_filter_factory->Name());
|
||||
}
|
||||
|
||||
if (shared->HasHistory() && IsStateTracked()) {
|
||||
// The way it works right now is, if there's any history, that means the
|
||||
// previous run mutating the DB had all its operations traced, in which case
|
||||
// we should always be able to `Restore()` the expected values to match the
|
||||
// `db_`'s current seqno.
|
||||
Status s = shared->Restore(db_);
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "Error restoring historical expected values: %s\n",
|
||||
s.ToString().c_str());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (FLAGS_sync_fault_injection && IsStateTracked()) {
|
||||
Status s = shared->SaveAtAndAfter(db_);
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "Error enabling history tracing: %s\n",
|
||||
s.ToString().c_str());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool StressTest::VerifySecondaries() {
|
||||
@@ -615,11 +638,15 @@ void StressTest::OperateDb(ThreadState* thread) {
|
||||
write_opts.sync = true;
|
||||
}
|
||||
write_opts.disableWAL = FLAGS_disable_wal;
|
||||
const int prefixBound = static_cast<int>(FLAGS_readpercent) +
|
||||
static_cast<int>(FLAGS_prefixpercent);
|
||||
const int writeBound = prefixBound + static_cast<int>(FLAGS_writepercent);
|
||||
const int delBound = writeBound + static_cast<int>(FLAGS_delpercent);
|
||||
const int delRangeBound = delBound + static_cast<int>(FLAGS_delrangepercent);
|
||||
const int prefix_bound = static_cast<int>(FLAGS_readpercent) +
|
||||
static_cast<int>(FLAGS_prefixpercent);
|
||||
const int write_bound = prefix_bound + static_cast<int>(FLAGS_writepercent);
|
||||
const int del_bound = write_bound + static_cast<int>(FLAGS_delpercent);
|
||||
const int delrange_bound =
|
||||
del_bound + static_cast<int>(FLAGS_delrangepercent);
|
||||
const int iterate_bound =
|
||||
delrange_bound + static_cast<int>(FLAGS_iterpercent);
|
||||
|
||||
const uint64_t ops_per_open = FLAGS_ops_per_thread / (FLAGS_reopen + 1);
|
||||
|
||||
#ifndef NDEBUG
|
||||
@@ -875,7 +902,7 @@ void StressTest::OperateDb(ThreadState* thread) {
|
||||
} else {
|
||||
TestGet(thread, read_opts, rand_column_families, rand_keys);
|
||||
}
|
||||
} else if (prob_op < prefixBound) {
|
||||
} else if (prob_op < prefix_bound) {
|
||||
assert(static_cast<int>(FLAGS_readpercent) <= prob_op);
|
||||
// OPERATION prefix scan
|
||||
// keys are 8 bytes long, prefix size is FLAGS_prefix_size. There are
|
||||
@@ -883,22 +910,22 @@ void StressTest::OperateDb(ThreadState* thread) {
|
||||
// be 2 ^ ((8 - FLAGS_prefix_size) * 8) possible keys with the same
|
||||
// prefix
|
||||
TestPrefixScan(thread, read_opts, rand_column_families, rand_keys);
|
||||
} else if (prob_op < writeBound) {
|
||||
assert(prefixBound <= prob_op);
|
||||
} else if (prob_op < write_bound) {
|
||||
assert(prefix_bound <= prob_op);
|
||||
// OPERATION write
|
||||
TestPut(thread, write_opts, read_opts, rand_column_families, rand_keys,
|
||||
value, lock);
|
||||
} else if (prob_op < delBound) {
|
||||
assert(writeBound <= prob_op);
|
||||
} else if (prob_op < del_bound) {
|
||||
assert(write_bound <= prob_op);
|
||||
// OPERATION delete
|
||||
TestDelete(thread, write_opts, rand_column_families, rand_keys, lock);
|
||||
} else if (prob_op < delRangeBound) {
|
||||
assert(delBound <= prob_op);
|
||||
} else if (prob_op < delrange_bound) {
|
||||
assert(del_bound <= prob_op);
|
||||
// OPERATION delete range
|
||||
TestDeleteRange(thread, write_opts, rand_column_families, rand_keys,
|
||||
lock);
|
||||
} else {
|
||||
assert(delRangeBound <= prob_op);
|
||||
} else if (prob_op < iterate_bound) {
|
||||
assert(delrange_bound <= prob_op);
|
||||
// OPERATION iterate
|
||||
int num_seeks = static_cast<int>(
|
||||
std::min(static_cast<uint64_t>(thread->rand.Uniform(4)),
|
||||
@@ -906,6 +933,9 @@ void StressTest::OperateDb(ThreadState* thread) {
|
||||
rand_keys = GenerateNKeys(thread, num_seeks, i);
|
||||
i += num_seeks - 1;
|
||||
TestIterate(thread, read_opts, rand_column_families, rand_keys);
|
||||
} else {
|
||||
assert(iterate_bound <= prob_op);
|
||||
TestCustomOperations(thread, rand_column_families);
|
||||
}
|
||||
thread->stats.FinishedSingleOp();
|
||||
#ifndef ROCKSDB_LITE
|
||||
@@ -2112,6 +2142,7 @@ void StressTest::PrintEnv() const {
|
||||
fprintf(stdout, "No overwrite percentage : %d%%\n",
|
||||
FLAGS_nooverwritepercent);
|
||||
fprintf(stdout, "Iterate percentage : %d%%\n", FLAGS_iterpercent);
|
||||
fprintf(stdout, "Custom ops percentage : %d%%\n", FLAGS_customopspercent);
|
||||
fprintf(stdout, "DB-write-buffer-size : %" PRIu64 "\n",
|
||||
FLAGS_db_write_buffer_size);
|
||||
fprintf(stdout, "Write-buffer-size : %d\n", FLAGS_write_buffer_size);
|
||||
@@ -2226,6 +2257,9 @@ void StressTest::Open() {
|
||||
FLAGS_optimize_filters_for_memory;
|
||||
block_based_options.index_type =
|
||||
static_cast<BlockBasedTableOptions::IndexType>(FLAGS_index_type);
|
||||
block_based_options.prepopulate_block_cache =
|
||||
static_cast<BlockBasedTableOptions::PrepopulateBlockCache>(
|
||||
FLAGS_prepopulate_block_cache);
|
||||
options_.table_factory.reset(
|
||||
NewBlockBasedTableFactory(block_based_options));
|
||||
options_.db_write_buffer_size = FLAGS_db_write_buffer_size;
|
||||
@@ -2739,7 +2773,7 @@ void StressTest::Reopen(ThreadState* thread) {
|
||||
// the db via a callbac ii) they hold on to a snapshot and the upcoming
|
||||
// ::Close would complain about it.
|
||||
const bool write_prepared = FLAGS_use_txn && FLAGS_txn_write_policy != 0;
|
||||
bool bg_canceled = false;
|
||||
bool bg_canceled __attribute__((unused)) = false;
|
||||
if (write_prepared || thread->rand.OneIn(2)) {
|
||||
const bool wait =
|
||||
write_prepared || static_cast<bool>(thread->rand.OneIn(2));
|
||||
@@ -2747,7 +2781,6 @@ void StressTest::Reopen(ThreadState* thread) {
|
||||
bg_canceled = wait;
|
||||
}
|
||||
assert(!write_prepared || bg_canceled);
|
||||
(void) bg_canceled;
|
||||
#else
|
||||
(void) thread;
|
||||
#endif
|
||||
@@ -2789,6 +2822,15 @@ void StressTest::Reopen(ThreadState* thread) {
|
||||
fprintf(stdout, "%s Reopening database for the %dth time\n",
|
||||
clock_->TimeToString(now / 1000000).c_str(), num_times_reopened_);
|
||||
Open();
|
||||
|
||||
if (FLAGS_sync_fault_injection && IsStateTracked()) {
|
||||
Status s = thread->shared->SaveAtAndAfter(db_);
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "Error enabling history tracing: %s\n",
|
||||
s.ToString().c_str());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StressTest::CheckAndSetOptionsForUserTimestamp() {
|
||||
|
||||
@@ -32,7 +32,7 @@ class StressTest {
|
||||
void InitDb();
|
||||
// The initialization work is split into two parts to avoid a circular
|
||||
// dependency with `SharedState`.
|
||||
void FinishInitDb(SharedState*);
|
||||
virtual void FinishInitDb(SharedState*);
|
||||
|
||||
// Return false if verification fails.
|
||||
bool VerifySecondaries();
|
||||
@@ -65,6 +65,9 @@ class StressTest {
|
||||
|
||||
virtual bool ShouldAcquireMutexOnKey() const { return false; }
|
||||
|
||||
// Returns true if DB state is tracked by the stress test.
|
||||
virtual bool IsStateTracked() const = 0;
|
||||
|
||||
virtual std::vector<int> GenerateColumnFamilies(
|
||||
const int /* num_column_families */, int rand_column_family) const {
|
||||
return {rand_column_family};
|
||||
@@ -200,6 +203,12 @@ class StressTest {
|
||||
const std::vector<int64_t>& rand_keys);
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
virtual Status TestCustomOperations(
|
||||
ThreadState* /*thread*/,
|
||||
const std::vector<int>& /*rand_column_families*/) {
|
||||
return Status::NotSupported("TestCustomOperations() must be overridden");
|
||||
}
|
||||
|
||||
void VerificationAbort(SharedState* shared, std::string msg, Status s) const;
|
||||
|
||||
void VerificationAbort(SharedState* shared, std::string msg, int cf,
|
||||
|
||||
@@ -150,14 +150,18 @@ int db_stress_tool(int argc, char** argv) {
|
||||
exit(1);
|
||||
}
|
||||
if ((FLAGS_readpercent + FLAGS_prefixpercent + FLAGS_writepercent +
|
||||
FLAGS_delpercent + FLAGS_delrangepercent + FLAGS_iterpercent) != 100) {
|
||||
fprintf(stderr,
|
||||
"Error: "
|
||||
"Read(%d)+Prefix(%d)+Write(%d)+Delete(%d)+DeleteRange(%d)"
|
||||
"+Iterate(%d) percents != "
|
||||
"100!\n",
|
||||
FLAGS_readpercent, FLAGS_prefixpercent, FLAGS_writepercent,
|
||||
FLAGS_delpercent, FLAGS_delrangepercent, FLAGS_iterpercent);
|
||||
FLAGS_delpercent + FLAGS_delrangepercent + FLAGS_iterpercent +
|
||||
FLAGS_customopspercent) != 100) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"Error: "
|
||||
"Read(-readpercent=%d)+Prefix(-prefixpercent=%d)+Write(-writepercent=%"
|
||||
"d)+Delete(-delpercent=%d)+DeleteRange(-delrangepercent=%d)"
|
||||
"+Iterate(-iterpercent=%d)+CustomOps(-customopspercent=%d) percents != "
|
||||
"100!\n",
|
||||
FLAGS_readpercent, FLAGS_prefixpercent, FLAGS_writepercent,
|
||||
FLAGS_delpercent, FLAGS_delrangepercent, FLAGS_iterpercent,
|
||||
FLAGS_customopspercent);
|
||||
exit(1);
|
||||
}
|
||||
if (FLAGS_disable_wal == 1 && FLAGS_reopen > 0) {
|
||||
@@ -287,6 +291,9 @@ int db_stress_tool(int argc, char** argv) {
|
||||
"batch_protection_bytes_per_key > 0\n");
|
||||
exit(1);
|
||||
}
|
||||
if (FLAGS_test_multi_ops_txns) {
|
||||
CheckAndSetOptionsForMultiOpsTxnStressTest();
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
KillPoint* kp = KillPoint::GetInstance();
|
||||
@@ -331,6 +338,8 @@ int db_stress_tool(int argc, char** argv) {
|
||||
stress.reset(CreateCfConsistencyStressTest());
|
||||
} else if (FLAGS_test_batches_snapshots) {
|
||||
stress.reset(CreateBatchedOpsStressTest());
|
||||
} else if (FLAGS_test_multi_ops_txns) {
|
||||
stress.reset(CreateMultiOpsTxnsStressTest());
|
||||
} else {
|
||||
stress.reset(CreateNonBatchedOpsStressTest());
|
||||
}
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
|
||||
#include "db_stress_tool/expected_state.h"
|
||||
|
||||
#include "db_stress_tool/db_stress_common.h"
|
||||
#include "db_stress_tool/db_stress_shared_state.h"
|
||||
#include "rocksdb/trace_reader_writer.h"
|
||||
#include "rocksdb/trace_record_result.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -145,7 +148,11 @@ ExpectedStateManager::ExpectedStateManager(size_t max_key,
|
||||
|
||||
ExpectedStateManager::~ExpectedStateManager() {}
|
||||
|
||||
const std::string FileExpectedStateManager::kLatestFilename = "LATEST.state";
|
||||
const std::string FileExpectedStateManager::kLatestBasename = "LATEST";
|
||||
const std::string FileExpectedStateManager::kStateFilenameSuffix = ".state";
|
||||
const std::string FileExpectedStateManager::kTraceFilenameSuffix = ".trace";
|
||||
const std::string FileExpectedStateManager::kTempFilenamePrefix = ".";
|
||||
const std::string FileExpectedStateManager::kTempFilenameSuffix = ".tmp";
|
||||
|
||||
FileExpectedStateManager::FileExpectedStateManager(
|
||||
size_t max_key, size_t num_column_families,
|
||||
@@ -156,9 +163,58 @@ FileExpectedStateManager::FileExpectedStateManager(
|
||||
}
|
||||
|
||||
Status FileExpectedStateManager::Open() {
|
||||
Status s = Clean();
|
||||
// Before doing anything, sync directory state with ours. That is, determine
|
||||
// `saved_seqno_`, and create any necessary missing files.
|
||||
std::vector<std::string> expected_state_dir_children;
|
||||
Status s = Env::Default()->GetChildren(expected_state_dir_path_,
|
||||
&expected_state_dir_children);
|
||||
bool found_trace = false;
|
||||
if (s.ok()) {
|
||||
for (size_t i = 0; i < expected_state_dir_children.size(); ++i) {
|
||||
const auto& filename = expected_state_dir_children[i];
|
||||
if (filename.size() >= kStateFilenameSuffix.size() &&
|
||||
filename.rfind(kStateFilenameSuffix) ==
|
||||
filename.size() - kStateFilenameSuffix.size() &&
|
||||
filename.rfind(kLatestBasename, 0) == std::string::npos) {
|
||||
SequenceNumber found_seqno = ParseUint64(
|
||||
filename.substr(0, filename.size() - kStateFilenameSuffix.size()));
|
||||
if (saved_seqno_ == kMaxSequenceNumber || found_seqno > saved_seqno_) {
|
||||
saved_seqno_ = found_seqno;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check if crash happened after creating state file but before creating
|
||||
// trace file.
|
||||
if (saved_seqno_ != kMaxSequenceNumber) {
|
||||
std::string saved_seqno_trace_path =
|
||||
GetPathForFilename(ToString(saved_seqno_) + kTraceFilenameSuffix);
|
||||
Status exists_status = Env::Default()->FileExists(saved_seqno_trace_path);
|
||||
if (exists_status.ok()) {
|
||||
found_trace = true;
|
||||
} else if (exists_status.IsNotFound()) {
|
||||
found_trace = false;
|
||||
} else {
|
||||
s = exists_status;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s.ok() && saved_seqno_ != kMaxSequenceNumber && !found_trace) {
|
||||
// Create an empty trace file so later logic does not need to distinguish
|
||||
// missing vs. empty trace file.
|
||||
std::unique_ptr<WritableFile> wfile;
|
||||
const EnvOptions soptions;
|
||||
std::string saved_seqno_trace_path =
|
||||
GetPathForFilename(ToString(saved_seqno_) + kTraceFilenameSuffix);
|
||||
s = Env::Default()->NewWritableFile(saved_seqno_trace_path, &wfile,
|
||||
soptions);
|
||||
}
|
||||
|
||||
std::string expected_state_file_path = GetPathForFilename(kLatestFilename);
|
||||
if (s.ok()) {
|
||||
s = Clean();
|
||||
}
|
||||
|
||||
std::string expected_state_file_path =
|
||||
GetPathForFilename(kLatestBasename + kStateFilenameSuffix);
|
||||
bool found = false;
|
||||
if (s.ok()) {
|
||||
Status exists_status = Env::Default()->FileExists(expected_state_file_path);
|
||||
@@ -176,7 +232,7 @@ Status FileExpectedStateManager::Open() {
|
||||
// this process is killed during setup, `Clean()` will take care of removing
|
||||
// the incomplete expected values file.
|
||||
std::string temp_expected_state_file_path =
|
||||
GetTempPathForFilename(kLatestFilename);
|
||||
GetTempPathForFilename(kLatestBasename + kStateFilenameSuffix);
|
||||
FileExpectedState temp_expected_state(temp_expected_state_file_path,
|
||||
max_key_, num_column_families_);
|
||||
if (s.ok()) {
|
||||
@@ -196,23 +252,321 @@ Status FileExpectedStateManager::Open() {
|
||||
return s;
|
||||
}
|
||||
|
||||
Status FileExpectedStateManager::Clean() {
|
||||
// An incomplete `Open()` could have left behind an invalid temporary file.
|
||||
std::string temp_path = GetTempPathForFilename(kLatestFilename);
|
||||
Status s = Env::Default()->FileExists(temp_path);
|
||||
#ifndef ROCKSDB_LITE
|
||||
Status FileExpectedStateManager::SaveAtAndAfter(DB* db) {
|
||||
SequenceNumber seqno = db->GetLatestSequenceNumber();
|
||||
|
||||
std::string state_filename = ToString(seqno) + kStateFilenameSuffix;
|
||||
std::string state_file_temp_path = GetTempPathForFilename(state_filename);
|
||||
std::string state_file_path = GetPathForFilename(state_filename);
|
||||
|
||||
std::string latest_file_path =
|
||||
GetPathForFilename(kLatestBasename + kStateFilenameSuffix);
|
||||
|
||||
std::string trace_filename = ToString(seqno) + kTraceFilenameSuffix;
|
||||
std::string trace_file_path = GetPathForFilename(trace_filename);
|
||||
|
||||
// Populate a tempfile and then rename it to atomically create "<seqno>.state"
|
||||
// with contents from "LATEST.state"
|
||||
Status s =
|
||||
CopyFile(FileSystem::Default(), latest_file_path, state_file_temp_path,
|
||||
0 /* size */, false /* use_fsync */);
|
||||
if (s.ok()) {
|
||||
s = Env::Default()->DeleteFile(temp_path);
|
||||
} else if (s.IsNotFound()) {
|
||||
s = Status::OK();
|
||||
s = FileSystem::Default()->RenameFile(state_file_temp_path, state_file_path,
|
||||
IOOptions(), nullptr /* dbg */);
|
||||
}
|
||||
SequenceNumber old_saved_seqno = 0;
|
||||
if (s.ok()) {
|
||||
old_saved_seqno = saved_seqno_;
|
||||
saved_seqno_ = seqno;
|
||||
}
|
||||
|
||||
// If there is a crash now, i.e., after "<seqno>.state" was created but before
|
||||
// "<seqno>.trace" is created, it will be treated as if "<seqno>.trace" were
|
||||
// present but empty.
|
||||
|
||||
// Create "<seqno>.trace" directly. It is initially empty so no need for
|
||||
// tempfile.
|
||||
std::unique_ptr<TraceWriter> trace_writer;
|
||||
if (s.ok()) {
|
||||
EnvOptions soptions;
|
||||
// Disable buffering so traces will not get stuck in application buffer.
|
||||
soptions.writable_file_max_buffer_size = 0;
|
||||
s = NewFileTraceWriter(Env::Default(), soptions, trace_file_path,
|
||||
&trace_writer);
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = db->StartTrace(TraceOptions(), std::move(trace_writer));
|
||||
}
|
||||
|
||||
// Delete old state/trace files. Deletion order does not matter since we only
|
||||
// delete after successfully saving new files, so old files will never be used
|
||||
// again, even if we crash.
|
||||
if (s.ok() && old_saved_seqno != kMaxSequenceNumber &&
|
||||
old_saved_seqno != saved_seqno_) {
|
||||
s = Env::Default()->DeleteFile(
|
||||
GetPathForFilename(ToString(old_saved_seqno) + kStateFilenameSuffix));
|
||||
}
|
||||
if (s.ok() && old_saved_seqno != kMaxSequenceNumber &&
|
||||
old_saved_seqno != saved_seqno_) {
|
||||
s = Env::Default()->DeleteFile(
|
||||
GetPathForFilename(ToString(old_saved_seqno) + kTraceFilenameSuffix));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
#else // ROCKSDB_LITE
|
||||
Status FileExpectedStateManager::SaveAtAndAfter(DB* /* db */) {
|
||||
return Status::NotSupported();
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
bool FileExpectedStateManager::HasHistory() {
|
||||
return saved_seqno_ != kMaxSequenceNumber;
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
// An `ExpectedStateTraceRecordHandler` applies a configurable number of
|
||||
// write operation trace records to the configured expected state. It is used in
|
||||
// `FileExpectedStateManager::Restore()` to sync the expected state with the
|
||||
// DB's post-recovery state.
|
||||
class ExpectedStateTraceRecordHandler : public TraceRecord::Handler,
|
||||
public WriteBatch::Handler {
|
||||
public:
|
||||
ExpectedStateTraceRecordHandler(uint64_t max_write_ops, ExpectedState* state)
|
||||
: max_write_ops_(max_write_ops), state_(state) {}
|
||||
|
||||
~ExpectedStateTraceRecordHandler() {
|
||||
assert(num_write_ops_ == max_write_ops_);
|
||||
}
|
||||
|
||||
Status Handle(const WriteQueryTraceRecord& record,
|
||||
std::unique_ptr<TraceRecordResult>* /* result */) override {
|
||||
if (num_write_ops_ == max_write_ops_) {
|
||||
return Status::OK();
|
||||
}
|
||||
WriteBatch batch(record.GetWriteBatchRep().ToString());
|
||||
return batch.Iterate(this);
|
||||
}
|
||||
|
||||
// Ignore reads.
|
||||
Status Handle(const GetQueryTraceRecord& /* record */,
|
||||
std::unique_ptr<TraceRecordResult>* /* result */) override {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Ignore reads.
|
||||
Status Handle(const IteratorSeekQueryTraceRecord& /* record */,
|
||||
std::unique_ptr<TraceRecordResult>* /* result */) override {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Ignore reads.
|
||||
Status Handle(const MultiGetQueryTraceRecord& /* record */,
|
||||
std::unique_ptr<TraceRecordResult>* /* result */) override {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Below are the WriteBatch::Handler overrides. We could use a separate
|
||||
// object, but it's convenient and works to share state with the
|
||||
// `TraceRecord::Handler`.
|
||||
|
||||
Status PutCF(uint32_t column_family_id, const Slice& key,
|
||||
const Slice& value) override {
|
||||
uint64_t key_id;
|
||||
if (!GetIntVal(key.ToString(), &key_id)) {
|
||||
return Status::Corruption("unable to parse key", key.ToString());
|
||||
}
|
||||
uint32_t value_id = GetValueBase(value);
|
||||
|
||||
state_->Put(column_family_id, static_cast<int64_t>(key_id), value_id,
|
||||
false /* pending */);
|
||||
++num_write_ops_;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DeleteCF(uint32_t column_family_id, const Slice& key) override {
|
||||
uint64_t key_id;
|
||||
if (!GetIntVal(key.ToString(), &key_id)) {
|
||||
return Status::Corruption("unable to parse key", key.ToString());
|
||||
}
|
||||
|
||||
state_->Delete(column_family_id, static_cast<int64_t>(key_id),
|
||||
false /* pending */);
|
||||
++num_write_ops_;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status SingleDeleteCF(uint32_t column_family_id, const Slice& key) override {
|
||||
return DeleteCF(column_family_id, key);
|
||||
}
|
||||
|
||||
Status DeleteRangeCF(uint32_t column_family_id, const Slice& begin_key,
|
||||
const Slice& end_key) override {
|
||||
uint64_t begin_key_id, end_key_id;
|
||||
if (!GetIntVal(begin_key.ToString(), &begin_key_id)) {
|
||||
return Status::Corruption("unable to parse begin key",
|
||||
begin_key.ToString());
|
||||
}
|
||||
if (!GetIntVal(end_key.ToString(), &end_key_id)) {
|
||||
return Status::Corruption("unable to parse end key", end_key.ToString());
|
||||
}
|
||||
|
||||
state_->DeleteRange(column_family_id, static_cast<int64_t>(begin_key_id),
|
||||
static_cast<int64_t>(end_key_id), false /* pending */);
|
||||
++num_write_ops_;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status MergeCF(uint32_t column_family_id, const Slice& key,
|
||||
const Slice& value) override {
|
||||
return PutCF(column_family_id, key, value);
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t num_write_ops_ = 0;
|
||||
uint64_t max_write_ops_;
|
||||
ExpectedState* state_;
|
||||
};
|
||||
|
||||
Status FileExpectedStateManager::Restore(DB* db) {
|
||||
assert(HasHistory());
|
||||
SequenceNumber seqno = db->GetLatestSequenceNumber();
|
||||
if (seqno < saved_seqno_) {
|
||||
return Status::Corruption("DB is older than any restorable expected state");
|
||||
}
|
||||
|
||||
std::string state_filename = ToString(saved_seqno_) + kStateFilenameSuffix;
|
||||
std::string state_file_path = GetPathForFilename(state_filename);
|
||||
|
||||
std::string latest_file_temp_path =
|
||||
GetTempPathForFilename(kLatestBasename + kStateFilenameSuffix);
|
||||
std::string latest_file_path =
|
||||
GetPathForFilename(kLatestBasename + kStateFilenameSuffix);
|
||||
|
||||
std::string trace_filename = ToString(saved_seqno_) + kTraceFilenameSuffix;
|
||||
std::string trace_file_path = GetPathForFilename(trace_filename);
|
||||
|
||||
std::unique_ptr<TraceReader> trace_reader;
|
||||
Status s = NewFileTraceReader(Env::Default(), EnvOptions(), trace_file_path,
|
||||
&trace_reader);
|
||||
|
||||
if (s.ok()) {
|
||||
// We are going to replay on top of "`seqno`.state" to create a new
|
||||
// "LATEST.state". Start off by creating a tempfile so we can later make the
|
||||
// new "LATEST.state" appear atomically using `RenameFile()`.
|
||||
s = CopyFile(FileSystem::Default(), state_file_path, latest_file_temp_path,
|
||||
0 /* size */, false /* use_fsync */);
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_ptr<Replayer> replayer;
|
||||
std::unique_ptr<ExpectedState> state;
|
||||
std::unique_ptr<TraceRecord::Handler> handler;
|
||||
if (s.ok()) {
|
||||
state.reset(new FileExpectedState(latest_file_temp_path, max_key_,
|
||||
num_column_families_));
|
||||
s = state->Open(false /* create */);
|
||||
}
|
||||
if (s.ok()) {
|
||||
handler.reset(new ExpectedStateTraceRecordHandler(seqno - saved_seqno_,
|
||||
state.get()));
|
||||
// TODO(ajkr): An API limitation requires we provide `handles` although
|
||||
// they will be unused since we only use the replayer for reading records.
|
||||
// Just give a default CFH for now to satisfy the requirement.
|
||||
s = db->NewDefaultReplayer({db->DefaultColumnFamily()} /* handles */,
|
||||
std::move(trace_reader), &replayer);
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
s = replayer->Prepare();
|
||||
}
|
||||
for (;;) {
|
||||
std::unique_ptr<TraceRecord> record;
|
||||
s = replayer->Next(&record);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
std::unique_ptr<TraceRecordResult> res;
|
||||
record->Accept(handler.get(), &res);
|
||||
}
|
||||
if (s.IsIncomplete()) {
|
||||
// OK because `Status::Incomplete` is expected upon finishing all the
|
||||
// trace records.
|
||||
s = Status::OK();
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
s = FileSystem::Default()->RenameFile(latest_file_temp_path,
|
||||
latest_file_path, IOOptions(),
|
||||
nullptr /* dbg */);
|
||||
}
|
||||
if (s.ok()) {
|
||||
latest_.reset(new FileExpectedState(latest_file_path, max_key_,
|
||||
num_column_families_));
|
||||
s = latest_->Open(false /* create */);
|
||||
}
|
||||
|
||||
// Delete old state/trace files. We must delete the state file first.
|
||||
// Otherwise, a crash-recovery immediately after deleting the trace file could
|
||||
// lead to `Restore()` unable to replay to `seqno`.
|
||||
if (s.ok()) {
|
||||
s = Env::Default()->DeleteFile(state_file_path);
|
||||
}
|
||||
if (s.ok()) {
|
||||
saved_seqno_ = kMaxSequenceNumber;
|
||||
s = Env::Default()->DeleteFile(trace_file_path);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
#else // ROCKSDB_LITE
|
||||
Status FileExpectedStateManager::Restore(DB* /* db */) {
|
||||
return Status::NotSupported();
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
Status FileExpectedStateManager::Clean() {
|
||||
std::vector<std::string> expected_state_dir_children;
|
||||
Status s = Env::Default()->GetChildren(expected_state_dir_path_,
|
||||
&expected_state_dir_children);
|
||||
// An incomplete `Open()` or incomplete `SaveAtAndAfter()` could have left
|
||||
// behind invalid temporary files. An incomplete `SaveAtAndAfter()` could have
|
||||
// also left behind stale state/trace files. An incomplete `Restore()` could
|
||||
// have left behind stale trace files.
|
||||
for (size_t i = 0; s.ok() && i < expected_state_dir_children.size(); ++i) {
|
||||
const auto& filename = expected_state_dir_children[i];
|
||||
if (filename.rfind(kTempFilenamePrefix, 0 /* pos */) == 0 &&
|
||||
filename.size() >= kTempFilenameSuffix.size() &&
|
||||
filename.rfind(kTempFilenameSuffix) ==
|
||||
filename.size() - kTempFilenameSuffix.size()) {
|
||||
// Delete all temp files.
|
||||
s = Env::Default()->DeleteFile(GetPathForFilename(filename));
|
||||
} else if (filename.size() >= kStateFilenameSuffix.size() &&
|
||||
filename.rfind(kStateFilenameSuffix) ==
|
||||
filename.size() - kStateFilenameSuffix.size() &&
|
||||
filename.rfind(kLatestBasename, 0) == std::string::npos &&
|
||||
ParseUint64(filename.substr(
|
||||
0, filename.size() - kStateFilenameSuffix.size())) <
|
||||
saved_seqno_) {
|
||||
assert(saved_seqno_ != kMaxSequenceNumber);
|
||||
// Delete stale state files.
|
||||
s = Env::Default()->DeleteFile(GetPathForFilename(filename));
|
||||
} else if (filename.size() >= kTraceFilenameSuffix.size() &&
|
||||
filename.rfind(kTraceFilenameSuffix) ==
|
||||
filename.size() - kTraceFilenameSuffix.size() &&
|
||||
ParseUint64(filename.substr(
|
||||
0, filename.size() - kTraceFilenameSuffix.size())) <
|
||||
saved_seqno_) {
|
||||
// Delete stale trace files.
|
||||
s = Env::Default()->DeleteFile(GetPathForFilename(filename));
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string FileExpectedStateManager::GetTempPathForFilename(
|
||||
const std::string& filename) {
|
||||
static const std::string kTempFilenamePrefix = ".";
|
||||
static const std::string kTempFilenameSuffix = ".tmp";
|
||||
|
||||
assert(!expected_state_dir_path_.empty());
|
||||
std::string expected_state_dir_path_slash =
|
||||
expected_state_dir_path_.back() == '/' ? expected_state_dir_path_
|
||||
|
||||
@@ -12,8 +12,14 @@
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "file/file_util.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/file_system.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
#include "rocksdb/types.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -126,6 +132,31 @@ class ExpectedStateManager {
|
||||
// member function.
|
||||
virtual Status Open() = 0;
|
||||
|
||||
// Saves expected values for the current state of `db` and begins tracking
|
||||
// changes. Following a successful `SaveAtAndAfter()`, `Restore()` can be
|
||||
// called on the same DB, as long as its state does not roll back to before
|
||||
// its current state.
|
||||
//
|
||||
// Requires external locking preventing concurrent execution with any other
|
||||
// member function. Furthermore, `db` must not be mutated while this function
|
||||
// is executing.
|
||||
virtual Status SaveAtAndAfter(DB* db) = 0;
|
||||
|
||||
// Returns true if at least one state of historical expected values can be
|
||||
// restored.
|
||||
//
|
||||
// Requires external locking preventing concurrent execution with any other
|
||||
// member function.
|
||||
virtual bool HasHistory() = 0;
|
||||
|
||||
// Restores expected values according to the current state of `db`. See
|
||||
// `SaveAtAndAfter()` for conditions where this can be called.
|
||||
//
|
||||
// Requires external locking preventing concurrent execution with any other
|
||||
// member function. Furthermore, `db` must not be mutated while this function
|
||||
// is executing.
|
||||
virtual Status Restore(DB* db) = 0;
|
||||
|
||||
// Requires external locking covering all keys in `cf`.
|
||||
void ClearColumnFamily(int cf) { return latest_->ClearColumnFamily(cf); }
|
||||
|
||||
@@ -186,6 +217,25 @@ class FileExpectedStateManager : public ExpectedStateManager {
|
||||
// member function.
|
||||
Status Open() override;
|
||||
|
||||
// See `ExpectedStateManager::SaveAtAndAfter()` API doc.
|
||||
//
|
||||
// This implementation makes a copy of "LATEST.state" into
|
||||
// "<current seqno>.state", and starts a trace in "<current seqno>.trace".
|
||||
// Due to using external files, a following `Restore()` can happen even
|
||||
// from a different process.
|
||||
Status SaveAtAndAfter(DB* db) override;
|
||||
|
||||
// See `ExpectedStateManager::HasHistory()` API doc.
|
||||
bool HasHistory() override;
|
||||
|
||||
// See `ExpectedStateManager::Restore()` API doc.
|
||||
//
|
||||
// Say `db->GetLatestSequenceNumber()` was `a` last time `SaveAtAndAfter()`
|
||||
// was called and now it is `b`. Then this function replays `b - a` write
|
||||
// operations from "`a`.trace" onto "`a`.state", and then copies the resulting
|
||||
// file into "LATEST.state".
|
||||
Status Restore(DB* db) override;
|
||||
|
||||
private:
|
||||
// Requires external locking preventing concurrent execution with any other
|
||||
// member function.
|
||||
@@ -194,9 +244,14 @@ class FileExpectedStateManager : public ExpectedStateManager {
|
||||
std::string GetTempPathForFilename(const std::string& filename);
|
||||
std::string GetPathForFilename(const std::string& filename);
|
||||
|
||||
static const std::string kLatestFilename;
|
||||
static const std::string kLatestBasename;
|
||||
static const std::string kStateFilenameSuffix;
|
||||
static const std::string kTraceFilenameSuffix;
|
||||
static const std::string kTempFilenamePrefix;
|
||||
static const std::string kTempFilenameSuffix;
|
||||
|
||||
const std::string expected_state_dir_path_;
|
||||
SequenceNumber saved_seqno_ = kMaxSequenceNumber;
|
||||
};
|
||||
|
||||
// An `AnonExpectedStateManager` implements an `ExpectedStateManager` backed by
|
||||
@@ -205,6 +260,23 @@ class AnonExpectedStateManager : public ExpectedStateManager {
|
||||
public:
|
||||
explicit AnonExpectedStateManager(size_t max_key, size_t num_column_families);
|
||||
|
||||
// See `ExpectedStateManager::SaveAtAndAfter()` API doc.
|
||||
//
|
||||
// This implementation returns `Status::NotSupported` since we do not
|
||||
// currently have a need to keep history of expected state within a process.
|
||||
Status SaveAtAndAfter(DB* /* db */) override {
|
||||
return Status::NotSupported();
|
||||
}
|
||||
|
||||
// See `ExpectedStateManager::HasHistory()` API doc.
|
||||
bool HasHistory() override { return false; }
|
||||
|
||||
// See `ExpectedStateManager::Restore()` API doc.
|
||||
//
|
||||
// This implementation returns `Status::NotSupported` since we do not
|
||||
// currently have a need to keep history of expected state within a process.
|
||||
Status Restore(DB* /* db */) override { return Status::NotSupported(); }
|
||||
|
||||
// Requires external locking preventing concurrent execution with any other
|
||||
// member function.
|
||||
Status Open() override;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,302 @@
|
||||
// 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.
|
||||
|
||||
#ifdef GFLAGS
|
||||
#include "db_stress_tool/db_stress_common.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// This file defines MultiOpsTxnsStress so that we can stress test RocksDB
|
||||
// transactions on a simple, emulated relational table.
|
||||
//
|
||||
// The record format is similar to the example found at
|
||||
// https://github.com/facebook/mysql-5.6/wiki/MyRocks-record-format.
|
||||
//
|
||||
// The table is created by
|
||||
// ```
|
||||
// create table t1 (
|
||||
// a int primary key,
|
||||
// b int,
|
||||
// c int,
|
||||
// key(c),
|
||||
// )
|
||||
// ```
|
||||
//
|
||||
// (For simplicity, we use uint32_t for int here.)
|
||||
//
|
||||
// For this table, there is a primary index using `a`, as well as a secondary
|
||||
// index using `c` and `a`.
|
||||
//
|
||||
// Primary key format:
|
||||
// | index id | M(a) |
|
||||
// Primary index value:
|
||||
// | b | c |
|
||||
// M(a) represents the big-endian format of a.
|
||||
//
|
||||
// Secondary key format:
|
||||
// | index id | M(c) | M(a) |
|
||||
// Secondary index value:
|
||||
// | crc32 |
|
||||
// Similarly to M(a), M(c) is the big-endian format of c.
|
||||
//
|
||||
// The in-memory representation of a record is defined in class
|
||||
// MultiOpsTxnsStress:Record that includes a number of helper methods to
|
||||
// encode/decode primary index keys, primary index values, secondary index keys,
|
||||
// secondary index values, etc.
|
||||
//
|
||||
// Sometimes primary index and secondary index reside on different column
|
||||
// families, but sometimes they colocate in the same column family. Current
|
||||
// implementation puts them in the same (default) column family, and this is
|
||||
// subject to future change if we find it interesting to test the other case.
|
||||
//
|
||||
// Class MultiOpsTxnsStressTest has the following transactions for testing.
|
||||
//
|
||||
// 1. Primary key update
|
||||
// UPDATE t1 SET a = 3 WHERE a = 2;
|
||||
// ```
|
||||
// tx->GetForUpdate(primary key a=2)
|
||||
// tx->GetForUpdate(primary key a=3)
|
||||
// tx->Delete(primary key a=2)
|
||||
// tx->Put(primary key a=3, value)
|
||||
// tx->batch->SingleDelete(secondary key a=2)
|
||||
// tx->batch->Put(secondary key a=3, value)
|
||||
// tx->Prepare()
|
||||
// Tx->Commit()
|
||||
// ```
|
||||
//
|
||||
// 2. Secondary key update
|
||||
// UPDATE t1 SET c = 3 WHERE c = 2;
|
||||
// ```
|
||||
// iter->Seek(secondary key)
|
||||
// // Get corresponding primary key value(s) from iterator
|
||||
// tx->GetForUpdate(primary key)
|
||||
// tx->Put(primary key, value c=3)
|
||||
// tx->batch->SingleDelete(secondary key c=2)
|
||||
// tx->batch->Put(secondary key c=3)
|
||||
// tx->Prepare()
|
||||
// tx->Commit()
|
||||
// ```
|
||||
//
|
||||
// 3. Primary index value update
|
||||
// UPDATE t1 SET b = b + 1 WHERE a = 2;
|
||||
// ```
|
||||
// tx->GetForUpdate(primary key a=2)
|
||||
// tx->Put(primary key a=2, value b=b+1)
|
||||
// tx->Prepare()
|
||||
// tx->Commit()
|
||||
// ```
|
||||
//
|
||||
// 4. Point lookup
|
||||
// SELECT * FROM t1 WHERE a = 3;
|
||||
// ```
|
||||
// tx->Get(primary key a=3)
|
||||
// tx->Commit()
|
||||
// ```
|
||||
//
|
||||
// 5. Range scan
|
||||
// SELECT * FROM t1 WHERE c = 2;
|
||||
// ```
|
||||
// it = tx->GetIterator()
|
||||
// it->Seek(secondary key c=2)
|
||||
// tx->Commit()
|
||||
// ```
|
||||
|
||||
class MultiOpsTxnsStressTest : public StressTest {
|
||||
public:
|
||||
class Record {
|
||||
public:
|
||||
static constexpr uint32_t kPrimaryIndexId = 1;
|
||||
static constexpr uint32_t kSecondaryIndexId = 2;
|
||||
|
||||
static constexpr size_t kPrimaryIndexEntrySize = 8 + 8;
|
||||
static constexpr size_t kSecondaryIndexEntrySize = 12 + 4;
|
||||
|
||||
static_assert(kPrimaryIndexId < kSecondaryIndexId,
|
||||
"kPrimaryIndexId must be smaller than kSecondaryIndexId");
|
||||
|
||||
static_assert(sizeof(kPrimaryIndexId) == sizeof(uint32_t),
|
||||
"kPrimaryIndexId must be 4 bytes");
|
||||
static_assert(sizeof(kSecondaryIndexId) == sizeof(uint32_t),
|
||||
"kSecondaryIndexId must be 4 bytes");
|
||||
|
||||
// Used for generating search key to probe primary index.
|
||||
static std::string EncodePrimaryKey(uint32_t a);
|
||||
// Used for generating search prefix to probe secondary index.
|
||||
static std::string EncodeSecondaryKey(uint32_t c);
|
||||
// Used for generating search key to probe secondary index.
|
||||
static std::string EncodeSecondaryKey(uint32_t c, uint32_t a);
|
||||
|
||||
static std::tuple<Status, uint32_t, uint32_t> DecodePrimaryIndexValue(
|
||||
Slice primary_index_value);
|
||||
|
||||
static std::pair<Status, uint32_t> DecodeSecondaryIndexValue(
|
||||
Slice secondary_index_value);
|
||||
|
||||
Record() = default;
|
||||
Record(uint32_t _a, uint32_t _b, uint32_t _c) : a_(_a), b_(_b), c_(_c) {}
|
||||
|
||||
bool operator==(const Record& other) const {
|
||||
return a_ == other.a_ && b_ == other.b_ && c_ == other.c_;
|
||||
}
|
||||
|
||||
bool operator!=(const Record& other) const { return !(*this == other); }
|
||||
|
||||
std::pair<std::string, std::string> EncodePrimaryIndexEntry() const;
|
||||
|
||||
std::string EncodePrimaryKey() const;
|
||||
|
||||
std::string EncodePrimaryIndexValue() const;
|
||||
|
||||
std::pair<std::string, std::string> EncodeSecondaryIndexEntry() const;
|
||||
|
||||
std::string EncodeSecondaryKey() const;
|
||||
|
||||
Status DecodePrimaryIndexEntry(Slice primary_index_key,
|
||||
Slice primary_index_value);
|
||||
|
||||
Status DecodeSecondaryIndexEntry(Slice secondary_index_key,
|
||||
Slice secondary_index_value);
|
||||
|
||||
uint32_t a_value() const { return a_; }
|
||||
uint32_t b_value() const { return b_; }
|
||||
uint32_t c_value() const { return c_; }
|
||||
|
||||
void SetA(uint32_t _a) { a_ = _a; }
|
||||
void SetB(uint32_t _b) { b_ = _b; }
|
||||
void SetC(uint32_t _c) { c_ = _c; }
|
||||
|
||||
std::string ToString() const {
|
||||
std::string ret("(");
|
||||
ret.append(std::to_string(a_));
|
||||
ret.append(",");
|
||||
ret.append(std::to_string(b_));
|
||||
ret.append(",");
|
||||
ret.append(std::to_string(c_));
|
||||
ret.append(")");
|
||||
return ret;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class InvariantChecker;
|
||||
|
||||
uint32_t a_{0};
|
||||
uint32_t b_{0};
|
||||
uint32_t c_{0};
|
||||
};
|
||||
|
||||
MultiOpsTxnsStressTest() {}
|
||||
|
||||
~MultiOpsTxnsStressTest() override {}
|
||||
|
||||
void FinishInitDb(SharedState*) override;
|
||||
|
||||
void ReopenAndPreloadDb(SharedState* shared);
|
||||
|
||||
bool IsStateTracked() const override { return false; }
|
||||
|
||||
Status TestGet(ThreadState* thread, const ReadOptions& read_opts,
|
||||
const std::vector<int>& rand_column_families,
|
||||
const std::vector<int64_t>& rand_keys) override;
|
||||
|
||||
std::vector<Status> TestMultiGet(
|
||||
ThreadState* thread, const ReadOptions& read_opts,
|
||||
const std::vector<int>& rand_column_families,
|
||||
const std::vector<int64_t>& rand_keys) override;
|
||||
|
||||
Status TestPrefixScan(ThreadState* thread, const ReadOptions& read_opts,
|
||||
const std::vector<int>& rand_column_families,
|
||||
const std::vector<int64_t>& rand_keys) override;
|
||||
|
||||
// Given a key K, this creates an iterator which scans to K and then
|
||||
// does a random sequence of Next/Prev operations.
|
||||
Status TestIterate(ThreadState* thread, const ReadOptions& read_opts,
|
||||
const std::vector<int>& rand_column_families,
|
||||
const std::vector<int64_t>& rand_keys) override;
|
||||
|
||||
Status TestPut(ThreadState* thread, WriteOptions& write_opts,
|
||||
const ReadOptions& read_opts, const std::vector<int>& cf_ids,
|
||||
const std::vector<int64_t>& keys, char (&value)[100],
|
||||
std::unique_ptr<MutexLock>& lock) override;
|
||||
|
||||
Status TestDelete(ThreadState* thread, WriteOptions& write_opts,
|
||||
const std::vector<int>& rand_column_families,
|
||||
const std::vector<int64_t>& rand_keys,
|
||||
std::unique_ptr<MutexLock>& lock) override;
|
||||
|
||||
Status TestDeleteRange(ThreadState* thread, WriteOptions& write_opts,
|
||||
const std::vector<int>& rand_column_families,
|
||||
const std::vector<int64_t>& rand_keys,
|
||||
std::unique_ptr<MutexLock>& lock) override;
|
||||
|
||||
void TestIngestExternalFile(ThreadState* thread,
|
||||
const std::vector<int>& rand_column_families,
|
||||
const std::vector<int64_t>& rand_keys,
|
||||
std::unique_ptr<MutexLock>& lock) override;
|
||||
|
||||
void TestCompactRange(ThreadState* thread, int64_t rand_key,
|
||||
const Slice& start_key,
|
||||
ColumnFamilyHandle* column_family) override;
|
||||
|
||||
Status TestBackupRestore(ThreadState* thread,
|
||||
const std::vector<int>& rand_column_families,
|
||||
const std::vector<int64_t>& rand_keys) override;
|
||||
|
||||
Status TestCheckpoint(ThreadState* thread,
|
||||
const std::vector<int>& rand_column_families,
|
||||
const std::vector<int64_t>& rand_keys) override;
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
Status TestApproximateSize(ThreadState* thread, uint64_t iteration,
|
||||
const std::vector<int>& rand_column_families,
|
||||
const std::vector<int64_t>& rand_keys) override;
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
Status TestCustomOperations(
|
||||
ThreadState* thread,
|
||||
const std::vector<int>& rand_column_families) override;
|
||||
|
||||
Status PrimaryKeyUpdateTxn(ThreadState* thread, uint32_t old_a,
|
||||
uint32_t new_a);
|
||||
|
||||
Status SecondaryKeyUpdateTxn(ThreadState* thread, uint32_t old_c,
|
||||
uint32_t new_c);
|
||||
|
||||
Status UpdatePrimaryIndexValueTxn(ThreadState* thread, uint32_t a,
|
||||
uint32_t b_delta);
|
||||
|
||||
Status PointLookupTxn(ThreadState* thread, ReadOptions ropts, uint32_t a);
|
||||
|
||||
Status RangeScanTxn(ThreadState* thread, ReadOptions ropts, uint32_t c);
|
||||
|
||||
void VerifyDb(ThreadState* thread) const override;
|
||||
|
||||
protected:
|
||||
uint32_t ChooseA(ThreadState* thread);
|
||||
|
||||
uint32_t GenerateNextA();
|
||||
|
||||
private:
|
||||
void PreloadDb(SharedState* shared, size_t num_c);
|
||||
|
||||
// TODO (yanqin) encapsulate the selection of keys a separate class.
|
||||
std::atomic<uint32_t> next_a_{0};
|
||||
};
|
||||
|
||||
class InvariantChecker {
|
||||
public:
|
||||
static_assert(sizeof(MultiOpsTxnsStressTest::Record().a_) == sizeof(uint32_t),
|
||||
"MultiOpsTxnsStressTest::Record::a_ must be 4 bytes");
|
||||
static_assert(sizeof(MultiOpsTxnsStressTest::Record().b_) == sizeof(uint32_t),
|
||||
"MultiOpsTxnsStressTest::Record::b_ must be 4 bytes");
|
||||
static_assert(sizeof(MultiOpsTxnsStressTest::Record().c_) == sizeof(uint32_t),
|
||||
"MultiOpsTxnsStressTest::Record::c_ must be 4 bytes");
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
#endif // GFLAGS
|
||||
@@ -179,6 +179,8 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
|
||||
bool ShouldAcquireMutexOnKey() const override { return true; }
|
||||
|
||||
bool IsStateTracked() const override { return true; }
|
||||
|
||||
Status TestGet(ThreadState* thread, const ReadOptions& read_opts,
|
||||
const std::vector<int>& rand_column_families,
|
||||
const std::vector<int64_t>& rand_keys) override {
|
||||
|
||||
Vendored
+1
-1
@@ -734,7 +734,7 @@ Status Env::GetChildrenFileAttributes(const std::string& dir,
|
||||
}
|
||||
|
||||
Status Env::GetHostNameString(std::string* result) {
|
||||
std::array<char, kMaxHostNameLen> hostname_buf;
|
||||
std::array<char, kMaxHostNameLen> hostname_buf{};
|
||||
Status s = GetHostName(hostname_buf.data(), hostname_buf.size());
|
||||
if (s.ok()) {
|
||||
hostname_buf[hostname_buf.size() - 1] = '\0';
|
||||
|
||||
Vendored
+2
-1
@@ -1543,7 +1543,8 @@ PosixDirectory::PosixDirectory(int fd) : fd_(fd) {
|
||||
#ifdef OS_LINUX
|
||||
struct statfs buf;
|
||||
int ret = fstatfs(fd, &buf);
|
||||
is_btrfs_ = (ret == 0 && buf.f_type == BTRFS_SUPER_MAGIC);
|
||||
is_btrfs_ = (ret == 0 && buf.f_type == static_cast<decltype(buf.f_type)>(
|
||||
BTRFS_SUPER_MAGIC));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
Loaded 100 of 239 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user