mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-07 03:22:34 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2898067de8 | ||
|
|
238a9c3f68 | ||
|
|
2511b42c7e | ||
|
|
1920121cef | ||
|
|
7c393c3fc6 | ||
|
|
7513e9011b | ||
|
|
bf117c54ef | ||
|
|
c04a86a0e9 | ||
|
|
3455ab0e2b | ||
|
|
39a07c9651 | ||
|
|
a379dae4f7 | ||
|
|
ac37bfded0 | ||
|
|
df5dc73bec | ||
|
|
1e5b631e51 | ||
|
|
0229a88dfe | ||
|
|
42ba60b3ba | ||
|
|
aeb913dd01 | ||
|
|
b678cb1f86 | ||
|
|
206845c057 | ||
|
|
4e4ec16957 | ||
|
|
5ad3227650 | ||
|
|
31193a73a4 | ||
|
|
803a40d412 | ||
|
|
870033291a | ||
|
|
7b9ecd4067 | ||
|
|
e8e911a11c | ||
|
|
a53d6d25e0 | ||
|
|
da90e23998 | ||
|
|
955b80e84f | ||
|
|
0b75b22321 | ||
|
|
bbdc4f2e9a | ||
|
|
c8665611bc | ||
|
|
5afd1e309c | ||
|
|
d1b70b05a6 | ||
|
|
837705ad80 | ||
|
|
bb485e986a | ||
|
|
5dd18a8d8e | ||
|
|
f127d459ad | ||
|
|
ac3f3f3719 | ||
|
|
b1a53db327 | ||
|
|
ed8eb436db | ||
|
|
714ce5041d | ||
|
|
df4197ca6e | ||
|
|
fcd8088333 | ||
|
|
f33611d5e9 | ||
|
|
1ae026c400 | ||
|
|
570248aeff | ||
|
|
9dc887ece0 | ||
|
|
c76778e2bd | ||
|
|
b20737709f | ||
|
|
a95a776d75 | ||
|
|
41d32152ce | ||
|
|
d45b837701 | ||
|
|
ba224b75c7 | ||
|
|
41c4b665f4 | ||
|
|
a0cbb69421 | ||
|
|
93a7389442 | ||
|
|
3503f28982 | ||
|
|
89f66d4484 | ||
|
|
bac399449d | ||
|
|
390c5246d2 | ||
|
|
373e3a154d | ||
|
|
be219089ad | ||
|
|
6f9ed59b1d | ||
|
|
be8199cdb9 | ||
|
|
a904c62d28 | ||
|
|
3d844dff1d | ||
|
|
95d0ee95fa | ||
|
|
66b62a123a | ||
|
|
68d8b28389 | ||
|
|
75741eb0ce | ||
|
|
d44ef2ed4d | ||
|
|
6adc39e1bf | ||
|
|
3f20925dc4 | ||
|
|
3ab0eae860 | ||
|
|
005c046734 | ||
|
|
0a1aed4e71 | ||
|
|
f89423a57a | ||
|
|
b511b042f4 | ||
|
|
cbb3b25915 | ||
|
|
54d73d6429 | ||
|
|
065bea1587 | ||
|
|
a50da404be | ||
|
|
e9b627fd56 | ||
|
|
6f33901899 | ||
|
|
7ed69fbe72 |
No files matched your search
+25
-9
@@ -91,6 +91,13 @@ commands:
|
||||
command: |
|
||||
sudo apt-get update -y && sudo apt-get install -y libgflags-dev
|
||||
|
||||
install-benchmark:
|
||||
steps:
|
||||
- run: # currently doesn't support ubuntu-1604 which doesn't have libbenchmark package, user can still install by building it youself
|
||||
name: Install benchmark
|
||||
command: |
|
||||
sudo apt-get update -y && sudo apt-get install -y libbenchmark-dev
|
||||
|
||||
upgrade-cmake:
|
||||
steps:
|
||||
- run:
|
||||
@@ -304,7 +311,7 @@ jobs:
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- upgrade-cmake
|
||||
- run: (mkdir build && cd build && cmake -DWITH_GFLAGS=1 .. && make V=1 -j20 && ctest -j20) | .circleci/cat_ignore_eagain
|
||||
@@ -315,9 +322,10 @@ jobs:
|
||||
image: ubuntu-2004:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- run: (mkdir build && cd build && cmake -DWITH_GFLAGS=1 .. && make V=1 -j20 && ctest -j20) | .circleci/cat_ignore_eagain
|
||||
- install-benchmark
|
||||
- run: (mkdir build && cd build && cmake -DWITH_GFLAGS=1 -DWITH_BENCHMARK=1 .. && make V=1 -j20 && ctest -j20 && make microbench) | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-unity:
|
||||
@@ -370,6 +378,17 @@ jobs:
|
||||
- run: CC=gcc-10 CXX=g++-10 V=1 SKIP_LINK=1 ROCKSDB_CXX_STANDARD=c++20 make -j16 all | .circleci/cat_ignore_eagain # Linking broken because libgflags compiled with newer ABI
|
||||
- post-steps
|
||||
|
||||
# This job is only to make sure the microbench tests are able to run, the benchmark result is not meaningful as the CI host is changing.
|
||||
build-linux-microbench:
|
||||
machine:
|
||||
image: ubuntu-2004:202010-01
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
- install-benchmark
|
||||
- run: DEBUG_LEVEL=0 make microbench | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-windows:
|
||||
executor: windows-2xlarge
|
||||
parameters:
|
||||
@@ -729,12 +748,6 @@ workflows:
|
||||
name: "build-windows-vs2017"
|
||||
vs_year: "2017"
|
||||
cmake_generator: "Visual Studio 15 Win64"
|
||||
# build-windows-vs2015:
|
||||
# jobs:
|
||||
# - build-windows:
|
||||
# name: "build-windows-vs2015"
|
||||
# vs_year: "2015"
|
||||
# cmake_generator: "Visual Studio 14 Win64"
|
||||
build-java:
|
||||
jobs:
|
||||
- build-linux-java
|
||||
@@ -778,6 +791,9 @@ workflows:
|
||||
build-linux-arm:
|
||||
jobs:
|
||||
- build-linux-arm
|
||||
build-microbench:
|
||||
jobs:
|
||||
- build-linux-microbench
|
||||
nightly:
|
||||
triggers:
|
||||
- schedule:
|
||||
|
||||
+10
-7
@@ -635,6 +635,7 @@ set(SOURCES
|
||||
db/blob/blob_file_garbage.cc
|
||||
db/blob/blob_file_meta.cc
|
||||
db/blob/blob_file_reader.cc
|
||||
db/blob/blob_garbage_meter.cc
|
||||
db/blob/blob_log_format.cc
|
||||
db/blob/blob_log_sequential_reader.cc
|
||||
db/blob/blob_log_writer.cc
|
||||
@@ -933,13 +934,8 @@ if(WIN32)
|
||||
port/win/env_win.cc
|
||||
port/win/env_default.cc
|
||||
port/win/port_win.cc
|
||||
port/win/win_logger.cc)
|
||||
if(NOT MINGW)
|
||||
# Mingw only supports std::thread when using
|
||||
# posix threads.
|
||||
list(APPEND SOURCES
|
||||
port/win/win_thread.cc)
|
||||
endif()
|
||||
port/win/win_logger.cc
|
||||
port/win/win_thread.cc)
|
||||
if(WITH_XPRESS)
|
||||
list(APPEND SOURCES
|
||||
port/win/xpress_win.cc)
|
||||
@@ -1123,11 +1119,13 @@ if(WITH_TESTS)
|
||||
list(APPEND TESTS
|
||||
cache/cache_test.cc
|
||||
cache/lru_cache_test.cc
|
||||
db/blob/blob_counting_iterator_test.cc
|
||||
db/blob/blob_file_addition_test.cc
|
||||
db/blob/blob_file_builder_test.cc
|
||||
db/blob/blob_file_cache_test.cc
|
||||
db/blob/blob_file_garbage_test.cc
|
||||
db/blob/blob_file_reader_test.cc
|
||||
db/blob/blob_garbage_meter_test.cc
|
||||
db/blob/db_blob_basic_test.cc
|
||||
db/blob/db_blob_compaction_test.cc
|
||||
db/blob/db_blob_corruption_test.cc
|
||||
@@ -1424,3 +1422,8 @@ option(WITH_EXAMPLES "build with examples" OFF)
|
||||
if(WITH_EXAMPLES)
|
||||
add_subdirectory(examples)
|
||||
endif()
|
||||
|
||||
option(WITH_BENCHMARK "build benchmark tests" OFF)
|
||||
if(WITH_BENCHMARK)
|
||||
add_subdirectory(${PROJECT_SOURCE_DIR}/microbench/)
|
||||
endif()
|
||||
+42
-4
@@ -1,17 +1,57 @@
|
||||
# Rocksdb Change Log
|
||||
## Unreleased
|
||||
## 6.23.2 (2021-08-04)
|
||||
### Bug Fixes
|
||||
* Fixed a race related to the destruction of `ColumnFamilyData` objects. The earlier logic unlocked the DB mutex before destroying the thread-local `SuperVersion` pointers, which could result in a process crash if another thread managed to get a reference to the `ColumnFamilyData` object.
|
||||
* Fixed an issue where `OnFlushCompleted` was not called for atomic flush.
|
||||
|
||||
## 6.23.1 (2021-07-22)
|
||||
### Bug Fixes
|
||||
* Fix a race condition during multiple DB instances opening.
|
||||
|
||||
## 6.23.0 (2021-07-16)
|
||||
### Behavior Changes
|
||||
* Obsolete keys in the bottommost level that were preserved for a snapshot will now be cleaned upon snapshot release in all cases. This form of compaction (snapshot release triggered compaction) previously had an artificial limitation that multiple tombstones needed to be present.
|
||||
|
||||
### Bug Fixes
|
||||
* Blob file checksums are now printed in hexadecimal format when using the `manifest_dump` `ldb` command.
|
||||
* `GetLiveFilesMetaData()` now populates the `temperature`, `oldest_ancester_time`, and `file_creation_time` fields of its `LiveFileMetaData` results when the information is available. Previously these fields always contained zero indicating unknown.
|
||||
* Fix mismatches of OnCompaction{Begin,Completed} in case of DisableManualCompaction().
|
||||
* Fix continuous logging of an existing background error on every user write
|
||||
* Fix a bug that `Get()` return Status::OK() and an empty value for non-existent key when `read_options.read_tier = kBlockCacheTier`.
|
||||
* Fix a bug that stat in `get_context` didn't accumulate to statistics when query is failed.
|
||||
|
||||
### New Features
|
||||
* ldb has a new feature, `list_live_files_metadata`, that shows the live SST files, as well as their LSM storage level and the column family they belong to.
|
||||
* The new BlobDB implementation now tracks the amount of garbage in each blob file in the MANIFEST.
|
||||
* Integrated BlobDB now supports Merge with base values (Put/Delete etc.).
|
||||
* RemoteCompaction supports sub-compaction, the job_id in the user interface is changed from `int` to `uint64_t` to support sub-compaction id.
|
||||
* Expose statistics option in RemoteCompaction worker.
|
||||
|
||||
### Public API change
|
||||
* Added APIs to the Customizable class to allow developers to create their own Customizable classes. Created the utilities/customizable_util.h file to contain helper methods for developing new Customizable classes.
|
||||
* Change signature of SecondaryCache::Name(). Make SecondaryCache customizable and add SecondaryCache::CreateFromString method.
|
||||
## 6.22.0 (2021-06-18)
|
||||
### Behavior Changes
|
||||
* Added two additional tickers, MEMTABLE_PAYLOAD_BYTES_AT_FLUSH and MEMTABLE_GARBAGE_BYTES_AT_FLUSH. These stats can be used to estimate the ratio of "garbage" (outdated) bytes in the memtable that are discarded at flush time.
|
||||
* Added API comments clarifying safe usage of Disable/EnableManualCompaction and EventListener callbacks for compaction.
|
||||
|
||||
### Bug Fixes
|
||||
* fs_posix.cc GetFreeSpace() always report disk space available to root even when running as non-root. Linux defaults often have disk mounts with 5 to 10 percent of total space reserved only for root. Out of space could result for non-root users.
|
||||
* Subcompactions are now disabled when user-defined timestamps are used, since the subcompaction boundary picking logic is currently not timestamp-aware, which could lead to incorrect results when different subcompactions process keys that only differ by timestamp.
|
||||
* Fix an issue that `DeleteFilesInRange()` may cause ongoing compaction reports corruption exception, or ASSERT for debug build. There's no actual data loss or corruption that we find.
|
||||
* Fixed confusingly duplicated output in LOG for periodic stats ("DUMPING STATS"), including "Compaction Stats" and "File Read Latency Histogram By Level".
|
||||
* Fixed performance bugs in background gathering of block cache entry statistics, that could consume a lot of CPU when there are many column families with a shared block cache.
|
||||
|
||||
### New Features
|
||||
* Marked the Ribbon filter and optimize_filters_for_memory features as production-ready, each enabling memory savings for Bloom-like filters. Use `NewRibbonFilterPolicy` in place of `NewBloomFilterPolicy` to use Ribbon filters instead of Bloom, or `ribbonfilter` in place of `bloomfilter` in configuration string.
|
||||
* Allow `DBWithTTL` to use `DeleteRange` api just like other DBs. `DeleteRangeCF()` which executes `WriteBatchInternal::DeleteRange()` has been added to the handler in `DBWithTTLImpl::Write()` to implement it.
|
||||
* Add BlockBasedTableOptions.prepopulate_block_cache. If enabled, it prepopulate warm/hot data blocks which are already in memory into block cache at the time of flush. On a flush, the data block that is in memory (in memtables) get flushed to the device. If using Direct IO, additional IO is incurred to read this data back into memory again, which is avoided by enabling this option and it also helps with Distributed FileSystem. More details in include/rocksdb/table.h.
|
||||
* Added a `cancel` field to `CompactRangeOptions`, allowing individual in-process manual range compactions to be cancelled.
|
||||
|
||||
### New Features
|
||||
* Added BlobMetaData to the ColumnFamilyMetaData to return information about blob files
|
||||
|
||||
### Public API change
|
||||
* Added GetAllColumnFamilyMetaData API to retrieve the ColumnFamilyMetaData about all column families.
|
||||
|
||||
## 6.21.0 (2021-05-21)
|
||||
### Bug Fixes
|
||||
@@ -23,7 +63,6 @@
|
||||
* Handle return code by io_uring_submit_and_wait() and io_uring_wait_cqe().
|
||||
* In the IngestExternalFile() API, only try to sync the ingested file if the file is linked and the FileSystem/Env supports reopening a writable file.
|
||||
* Fixed a bug that `AdvancedColumnFamilyOptions.max_compaction_bytes` is under-calculated for manual compaction (`CompactRange()`). Manual compaction is split to multiple compactions if the compaction size exceed the `max_compaction_bytes`. The bug creates much larger compaction which size exceed the user setting. On the other hand, larger manual compaction size can increase the subcompaction parallelism, you can tune that by setting `max_compaction_bytes`.
|
||||
* Fixed confusingly duplicated output in LOG for periodic stats ("DUMPING STATS"), including "Compaction Stats" and "File Read Latency Histogram By Level".
|
||||
|
||||
### Behavior Changes
|
||||
* Due to the fix of false-postive alert of "SST file is ahead of WAL", all the CFs with no SST file (CF empty) will bypass the consistency check. We fixed a false-positive, but introduced a very rare true-negative which will be triggered in the following conditions: A CF with some delete operations in the last a few queries which will result in an empty CF (those are flushed to SST file and a compaction triggered which combines this file and all other SST files and generates an empty CF, or there is another reason to write a manifest entry for this CF after a flush that generates no SST file from an empty CF). The deletion entries are logged in a WAL and this WAL was corrupted, while the CF's log number points to the next WAL (due to the flush). Therefore, the DB can only recover to the point without these trailing deletions and cause the inconsistent DB status.
|
||||
@@ -36,7 +75,6 @@
|
||||
* Add an experimental Remote Compaction feature, which allows the user to run Compaction on a different host or process. The feature is still under development, currently only works on some basic use cases. The interface will be changed without backward/forward compatibility support.
|
||||
* RocksDB would validate total entries read in flush, and compare with counter inserted into it. If flush_verify_memtable_count = true (default), flush will fail. Otherwise, only log to info logs.
|
||||
* Add `TableProperties::num_filter_entries`, which can be used with `TableProperties::filter_size` to calculate the effective bits per filter entry (unique user key or prefix) for a table file.
|
||||
* Added a `cancel` field to `CompactRangeOptions`, allowing individual in-process manual range compactions to be cancelled.
|
||||
|
||||
### Performance Improvements
|
||||
* BlockPrefetcher is used by iterators to prefetch data if they anticipate more data to be used in future. It is enabled implicitly by rocksdb. Added change to take in account read pattern if reads are sequential. This would disable prefetching for random reads in MultiGet and iterators as readahead_size is increased exponential doing large prefetches.
|
||||
|
||||
@@ -344,6 +344,12 @@ ifdef ROCKSDB_VALGRIND_RUN
|
||||
PLATFORM_CCFLAGS += -DROCKSDB_VALGRIND_RUN
|
||||
PLATFORM_CXXFLAGS += -DROCKSDB_VALGRIND_RUN
|
||||
endif
|
||||
ifdef ROCKSDB_FULL_VALGRIND_RUN
|
||||
# Some tests are slow when run under valgrind and are only run when
|
||||
# explicitly requested via the ROCKSDB_FULL_VALGRIND_RUN compiler flag.
|
||||
PLATFORM_CCFLAGS += -DROCKSDB_VALGRIND_RUN -DROCKSDB_FULL_VALGRIND_RUN
|
||||
PLATFORM_CXXFLAGS += -DROCKSDB_VALGRIND_RUN -DROCKSDB_FULL_VALGRIND_RUN
|
||||
endif
|
||||
|
||||
ifndef DISABLE_JEMALLOC
|
||||
ifdef JEMALLOC
|
||||
@@ -488,6 +494,7 @@ VALGRIND_ERROR = 2
|
||||
VALGRIND_VER := $(join $(VALGRIND_VER),valgrind)
|
||||
|
||||
VALGRIND_OPTS = --error-exitcode=$(VALGRIND_ERROR) --leak-check=full
|
||||
# Not yet supported: --show-leak-kinds=definite,possible,reachable --errors-for-leak-kinds=definite,possible,reachable
|
||||
|
||||
TEST_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(TEST_LIB_SOURCES) $(MOCK_LIB_SOURCES)) $(GTEST)
|
||||
BENCH_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(BENCH_LIB_SOURCES))
|
||||
@@ -516,8 +523,10 @@ endif
|
||||
ifdef ASSERT_STATUS_CHECKED
|
||||
# TODO: finish fixing all tests to pass this check
|
||||
TESTS_FAILING_ASC = \
|
||||
c_test \
|
||||
db_test \
|
||||
db_test2 \
|
||||
env_test \
|
||||
range_locking_test \
|
||||
testutil_test \
|
||||
|
||||
@@ -593,6 +602,8 @@ TEST_LIBS = \
|
||||
# TODO: add back forward_iterator_bench, after making it build in all environemnts.
|
||||
BENCHMARKS = $(patsubst %.cc, %, $(notdir $(BENCH_MAIN_SOURCES)))
|
||||
|
||||
MICROBENCHS = $(patsubst %.cc, %, $(notdir $(MICROBENCH_SOURCES)))
|
||||
|
||||
# if user didn't config LIBNAME, set the default
|
||||
ifeq ($(LIBNAME),)
|
||||
LIBNAME=librocksdb
|
||||
@@ -731,6 +742,9 @@ test_libs: $(TEST_LIBS)
|
||||
|
||||
benchmarks: $(BENCHMARKS)
|
||||
|
||||
microbench: $(MICROBENCHS)
|
||||
for t in $(MICROBENCHS); do echo "===== Running benchmark $$t (`date`)"; ./$$t || exit 1; done;
|
||||
|
||||
dbg: $(LIBRARY) $(BENCHMARKS) tools $(TESTS)
|
||||
|
||||
# creates library and programs
|
||||
@@ -1044,6 +1058,12 @@ ubsan_crash_test_with_best_efforts_recovery: clean
|
||||
COMPILE_WITH_UBSAN=1 $(MAKE) crash_test_with_best_efforts_recovery
|
||||
$(MAKE) clean
|
||||
|
||||
full_valgrind_test:
|
||||
ROCKSDB_FULL_VALGRIND_RUN=1 DISABLE_JEMALLOC=1 $(MAKE) valgrind_check
|
||||
|
||||
full_valgrind_test_some:
|
||||
ROCKSDB_FULL_VALGRIND_RUN=1 DISABLE_JEMALLOC=1 $(MAKE) valgrind_check_some
|
||||
|
||||
valgrind_test:
|
||||
ROCKSDB_VALGRIND_RUN=1 DISABLE_JEMALLOC=1 $(MAKE) valgrind_check
|
||||
|
||||
@@ -1162,7 +1182,7 @@ clean-not-downloaded: clean-ext-libraries-bin clean-rocks clean-not-downloaded-r
|
||||
clean-rocks:
|
||||
echo shared=$(ALL_SHARED_LIBS)
|
||||
echo static=$(ALL_STATIC_LIBS)
|
||||
rm -f $(BENCHMARKS) $(TOOLS) $(TESTS) $(PARALLEL_TEST) $(ALL_STATIC_LIBS) $(ALL_SHARED_LIBS)
|
||||
rm -f $(BENCHMARKS) $(TOOLS) $(TESTS) $(PARALLEL_TEST) $(ALL_STATIC_LIBS) $(ALL_SHARED_LIBS) $(MICROBENCHS)
|
||||
rm -rf $(CLEAN_FILES) ios-x86 ios-arm scan_build_report
|
||||
$(FIND) . -name "*.[oda]" -exec rm -f {} \;
|
||||
$(FIND) . -type f -regex ".*\.\(\(gcda\)\|\(gcno\)\)" -exec rm -f {} \;
|
||||
@@ -1825,6 +1845,9 @@ block_cache_trace_analyzer_test: $(OBJ_DIR)/tools/block_cache_analyzer/block_cac
|
||||
defer_test: $(OBJ_DIR)/util/defer_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
blob_counting_iterator_test: $(OBJ_DIR)/db/blob/blob_counting_iterator_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
blob_file_addition_test: $(OBJ_DIR)/db/blob/blob_file_addition_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1840,6 +1863,9 @@ blob_file_garbage_test: $(OBJ_DIR)/db/blob/blob_file_garbage_test.o $(TEST_LIBRA
|
||||
blob_file_reader_test: $(OBJ_DIR)/db/blob/blob_file_reader_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
blob_garbage_meter_test: $(OBJ_DIR)/db/blob/blob_garbage_meter_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
timer_test: $(OBJ_DIR)/util/timer_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1870,6 +1896,9 @@ db_write_buffer_manager_test: $(OBJ_DIR)/db/db_write_buffer_manager_test.o $(TES
|
||||
clipping_iterator_test: $(OBJ_DIR)/db/compaction/clipping_iterator_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
ribbon_bench: $(OBJ_DIR)/microbench/ribbon_bench.o $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
#-------------------------------------------------
|
||||
# make install related stuff
|
||||
PREFIX ?= /usr/local
|
||||
|
||||
@@ -144,6 +144,7 @@ cpp_library(
|
||||
"db/blob/blob_file_garbage.cc",
|
||||
"db/blob/blob_file_meta.cc",
|
||||
"db/blob/blob_file_reader.cc",
|
||||
"db/blob/blob_garbage_meter.cc",
|
||||
"db/blob/blob_log_format.cc",
|
||||
"db/blob/blob_log_sequential_reader.cc",
|
||||
"db/blob/blob_log_writer.cc",
|
||||
@@ -456,6 +457,7 @@ cpp_library(
|
||||
"db/blob/blob_file_garbage.cc",
|
||||
"db/blob/blob_file_meta.cc",
|
||||
"db/blob/blob_file_reader.cc",
|
||||
"db/blob/blob_garbage_meter.cc",
|
||||
"db/blob/blob_log_format.cc",
|
||||
"db/blob/blob_log_sequential_reader.cc",
|
||||
"db/blob/blob_log_writer.cc",
|
||||
@@ -906,6 +908,13 @@ ROCKS_TESTS = [
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"blob_counting_iterator_test",
|
||||
"db/blob/blob_counting_iterator_test.cc",
|
||||
"parallel",
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"blob_db_test",
|
||||
"utilities/blob_db/blob_db_test.cc",
|
||||
@@ -948,6 +957,13 @@ ROCKS_TESTS = [
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"blob_garbage_meter_test",
|
||||
"db/blob/blob_garbage_meter_test.cc",
|
||||
"parallel",
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"block_based_filter_block_test",
|
||||
"table/block_based/block_based_filter_block_test.cc",
|
||||
|
||||
@@ -596,6 +596,16 @@ EOF
|
||||
PLATFORM_CXXFLAGS="$PLATFORM_CXXFLAGS -faligned-new -DHAVE_ALIGNED_NEW"
|
||||
fi
|
||||
fi
|
||||
if ! test $ROCKSDB_DISABLE_BENCHMARK; then
|
||||
# Test whether google benchmark is available
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null -lbenchmark 2>/dev/null <<EOF
|
||||
#include <benchmark/benchmark.h>
|
||||
int main() {}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lbenchmark"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# TODO(tec): Fix -Wshorten-64-to-32 errors on FreeBSD and enable the warning.
|
||||
|
||||
@@ -1195,6 +1195,40 @@ JAVA_BUILD_TEST_COMMANDS="[
|
||||
}
|
||||
]"
|
||||
|
||||
#
|
||||
# RocksDB fbcode stress/crash test
|
||||
#
|
||||
FBCODE_STRESS_CRASH_TEST_COMMANDS="[
|
||||
{
|
||||
'name':'Rocksdb Fbcode Stress and Crash Test',
|
||||
'oncall':'$ONCALL',
|
||||
'executeLocal': 'true',
|
||||
'timeout': 86400,
|
||||
'steps': [
|
||||
{
|
||||
'name':'Copy RocksDB code to fbcode repo',
|
||||
'shell':'cd internal_repo_rocksdb/repo && git init && git add * && git commit -a -m \"Make internal_repo_rocksdb/repo a git repo\" && cd ../.. && echo Y | python3 rocks/release_script/release_to_fbcode.py -u internal_repo_rocksdb/repo master || $CONTRUN_NAME=db_stress_fbcode $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
{
|
||||
'name':'Build RocksDB fbcode stress tests',
|
||||
'shell':'cd $WORKING_DIR; buck build @mode/dbg rocks/tools:rocks_db_stress || $CONTRUN_NAME=db_stress_fbcode $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
{
|
||||
'name':'Run RocksDB whitebox crash tests',
|
||||
'timeout': 86400,
|
||||
'shell':'cd $WORKING_DIR; mkdir /dev/shm/rocksdb_whitebox_crash_test && TEST_TMPDIR=\$(mktemp -d --tmpdir=/dev/shm/rocksdb_whitebox_crash_test) python3 rocksdb/src/tools/db_crashtest.py --stress_cmd=buck-out/dbg/gen/rocks/tools/rocks_db_stress -secondary_cache_uri=\"$SECONDARY_CACHE_URI\" --env_uri=$ENV_URI -checkpoint_one_in=0 -backup_one_in=0 -cache_size=134217728 -cache_numshardbits=4 -logtostderr=false whitebox || $CONTRUN_NAME=whitebox_crash_test $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
]"
|
||||
|
||||
|
||||
case $1 in
|
||||
unit)
|
||||
@@ -1332,6 +1366,11 @@ case $1 in
|
||||
java_build)
|
||||
echo $JAVA_BUILD_TEST_COMMANDS
|
||||
;;
|
||||
fbcode_stress_crash)
|
||||
set -f
|
||||
echo $FBCODE_STRESS_CRASH_TEST_COMMANDS
|
||||
set +f
|
||||
;;
|
||||
*)
|
||||
echo "Invalid determinator command"
|
||||
exit 1
|
||||
|
||||
Vendored
+9
@@ -10,6 +10,8 @@
|
||||
#include "rocksdb/cache.h"
|
||||
|
||||
#include "cache/lru_cache.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
#include "rocksdb/utilities/customizable_util.h"
|
||||
#include "rocksdb/utilities/options_type.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
@@ -34,6 +36,13 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
};
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
Status SecondaryCache::CreateFromString(
|
||||
const ConfigOptions& config_options, const std::string& value,
|
||||
std::shared_ptr<SecondaryCache>* result) {
|
||||
return LoadSharedObject<SecondaryCache>(config_options, value, nullptr,
|
||||
result);
|
||||
}
|
||||
|
||||
Status Cache::CreateFromString(const ConfigOptions& config_options,
|
||||
const std::string& value,
|
||||
std::shared_ptr<Cache>* result) {
|
||||
|
||||
Vendored
+3
-4
@@ -13,11 +13,11 @@
|
||||
#include "monitoring/histogram.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
#include "rocksdb/system_clock.h"
|
||||
#include "rocksdb/utilities/object_registry.h"
|
||||
#include "table/block_based/cachable_entry.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/gflags_compat.h"
|
||||
@@ -233,9 +233,8 @@ class CacheBench {
|
||||
LRUCacheOptions opts(FLAGS_cache_size, FLAGS_num_shard_bits, false, 0.5);
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (!FLAGS_secondary_cache_uri.empty()) {
|
||||
Status s =
|
||||
ObjectRegistry::NewInstance()->NewSharedObject<SecondaryCache>(
|
||||
FLAGS_secondary_cache_uri, &secondary_cache);
|
||||
Status s = SecondaryCache::CreateFromString(
|
||||
ConfigOptions(), FLAGS_secondary_cache_uri, &secondary_cache);
|
||||
if (secondary_cache == nullptr) {
|
||||
fprintf(
|
||||
stderr,
|
||||
|
||||
Vendored
+25
-10
@@ -51,7 +51,8 @@ namespace ROCKSDB_NAMESPACE {
|
||||
template <class Stats>
|
||||
class CacheEntryStatsCollector {
|
||||
public:
|
||||
// Gathers stats and saves results into `stats`
|
||||
// Gather and save stats if saved stats are too old. (Use GetStats() to
|
||||
// read saved stats.)
|
||||
//
|
||||
// Maximum allowed age for a "hit" on saved results is determined by the
|
||||
// two interval parameters. Both set to 0 forces a re-scan. For example
|
||||
@@ -61,10 +62,9 @@ class CacheEntryStatsCollector {
|
||||
// Justification: scans can vary wildly in duration, e.g. from 0.02 sec
|
||||
// to as much as 20 seconds, so we want to be able to cap the absolute
|
||||
// and relative frequency of scans.
|
||||
void GetStats(Stats *stats, int min_interval_seconds,
|
||||
int min_interval_factor) {
|
||||
void CollectStats(int min_interval_seconds, int min_interval_factor) {
|
||||
// Waits for any pending reader or writer (collector)
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
std::lock_guard<std::mutex> lock(working_mutex_);
|
||||
|
||||
uint64_t max_age_micros =
|
||||
static_cast<uint64_t>(std::max(min_interval_seconds, 0)) * 1000000U;
|
||||
@@ -79,19 +79,28 @@ class CacheEntryStatsCollector {
|
||||
uint64_t start_time_micros = clock_->NowMicros();
|
||||
if ((start_time_micros - last_end_time_micros_) > max_age_micros) {
|
||||
last_start_time_micros_ = start_time_micros;
|
||||
saved_stats_.BeginCollection(cache_, clock_, start_time_micros);
|
||||
working_stats_.BeginCollection(cache_, clock_, start_time_micros);
|
||||
|
||||
cache_->ApplyToAllEntries(saved_stats_.GetEntryCallback(), {});
|
||||
cache_->ApplyToAllEntries(working_stats_.GetEntryCallback(), {});
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"CacheEntryStatsCollector::GetStats:AfterApplyToAllEntries", nullptr);
|
||||
|
||||
uint64_t end_time_micros = clock_->NowMicros();
|
||||
last_end_time_micros_ = end_time_micros;
|
||||
saved_stats_.EndCollection(cache_, clock_, end_time_micros);
|
||||
working_stats_.EndCollection(cache_, clock_, end_time_micros);
|
||||
} else {
|
||||
saved_stats_.SkippedCollection();
|
||||
working_stats_.SkippedCollection();
|
||||
}
|
||||
// Copy to caller
|
||||
|
||||
// Save so that we don't need to wait for an outstanding collection in
|
||||
// order to make of copy of the last saved stats
|
||||
std::lock_guard<std::mutex> lock2(saved_mutex_);
|
||||
saved_stats_ = working_stats_;
|
||||
}
|
||||
|
||||
// Gets saved stats, regardless of age
|
||||
void GetStats(Stats *stats) {
|
||||
std::lock_guard<std::mutex> lock(saved_mutex_);
|
||||
*stats = saved_stats_;
|
||||
}
|
||||
|
||||
@@ -129,6 +138,7 @@ class CacheEntryStatsCollector {
|
||||
Cache::Priority::HIGH);
|
||||
if (!s.ok()) {
|
||||
assert(h == nullptr);
|
||||
delete new_ptr;
|
||||
return s;
|
||||
}
|
||||
}
|
||||
@@ -145,6 +155,7 @@ class CacheEntryStatsCollector {
|
||||
private:
|
||||
explicit CacheEntryStatsCollector(Cache *cache, SystemClock *clock)
|
||||
: saved_stats_(),
|
||||
working_stats_(),
|
||||
last_start_time_micros_(0),
|
||||
last_end_time_micros_(/*pessimistic*/ 10000000),
|
||||
cache_(cache),
|
||||
@@ -154,10 +165,14 @@ class CacheEntryStatsCollector {
|
||||
delete static_cast<CacheEntryStatsCollector *>(value);
|
||||
}
|
||||
|
||||
std::mutex mutex_;
|
||||
std::mutex saved_mutex_;
|
||||
Stats saved_stats_;
|
||||
|
||||
std::mutex working_mutex_;
|
||||
Stats working_stats_;
|
||||
uint64_t last_start_time_micros_;
|
||||
uint64_t last_end_time_micros_;
|
||||
|
||||
Cache *const cache_;
|
||||
SystemClock *const clock_;
|
||||
};
|
||||
|
||||
Vendored
+12
-8
@@ -407,11 +407,10 @@ void LRUCacheShard::Promote(LRUHandle* e) {
|
||||
}
|
||||
} else {
|
||||
// Since the secondary cache lookup failed, mark the item as not in cache
|
||||
// and charge the cache only for metadata usage, i.e handle, key etc
|
||||
// Don't charge the cache as its only metadata that'll shortly be released
|
||||
MutexLock l(&mutex_);
|
||||
e->charge = 0;
|
||||
e->SetInCache(false);
|
||||
usage_ += e->CalcTotalCharge(metadata_charge_policy_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -522,7 +521,12 @@ bool LRUCacheShard::Release(Cache::Handle* handle, bool force_erase) {
|
||||
last_reference = false;
|
||||
}
|
||||
}
|
||||
if (last_reference) {
|
||||
// If it was the last reference, and the entry is either not secondary
|
||||
// cache compatible (i.e a dummy entry for accounting), or is secondary
|
||||
// cache compatible and has a non-null value, then decrement the cache
|
||||
// usage. If value is null in the latter case, taht means the lookup
|
||||
// failed and we didn't charge the cache.
|
||||
if (last_reference && (!e->IsSecondaryCacheCompatible() || e->value)) {
|
||||
size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
|
||||
assert(usage_ >= total_charge);
|
||||
usage_ -= total_charge;
|
||||
@@ -553,6 +557,9 @@ Status LRUCacheShard::Insert(const Slice& key, uint32_t hash, void* value,
|
||||
e->SetSecondaryCacheCompatible(true);
|
||||
e->info_.helper = helper;
|
||||
} else {
|
||||
#ifdef __SANITIZE_THREAD__
|
||||
e->is_secondary_cache_compatible_for_tsan = false;
|
||||
#endif // __SANITIZE_THREAD__
|
||||
e->info_.deleter = deleter;
|
||||
}
|
||||
e->charge = charge;
|
||||
@@ -596,15 +603,12 @@ void LRUCacheShard::Erase(const Slice& key, uint32_t hash) {
|
||||
|
||||
bool LRUCacheShard::IsReady(Cache::Handle* handle) {
|
||||
LRUHandle* e = reinterpret_cast<LRUHandle*>(handle);
|
||||
MutexLock l(&mutex_);
|
||||
bool ready = true;
|
||||
if (e->IsPending()) {
|
||||
assert(secondary_cache_);
|
||||
assert(e->sec_handle);
|
||||
if (e->sec_handle->IsReady()) {
|
||||
Promote(e);
|
||||
} else {
|
||||
ready = false;
|
||||
}
|
||||
ready = e->sec_handle->IsReady();
|
||||
}
|
||||
return ready;
|
||||
}
|
||||
|
||||
Vendored
+28
-7
@@ -12,6 +12,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "cache/sharded_cache.h"
|
||||
#include "port/lang.h"
|
||||
#include "port/malloc.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
@@ -80,8 +81,8 @@ struct LRUHandle {
|
||||
IN_HIGH_PRI_POOL = (1 << 2),
|
||||
// Whether this entry has had any lookups (hits).
|
||||
HAS_HIT = (1 << 3),
|
||||
// Can this be inserted into the tiered cache
|
||||
IS_TIERED_CACHE_COMPATIBLE = (1 << 4),
|
||||
// Can this be inserted into the secondary cache
|
||||
IS_SECONDARY_CACHE_COMPATIBLE = (1 << 4),
|
||||
// Is the handle still being read from a lower tier
|
||||
IS_PENDING = (1 << 5),
|
||||
// Has the item been promoted from a lower tier
|
||||
@@ -90,6 +91,14 @@ struct LRUHandle {
|
||||
|
||||
uint8_t flags;
|
||||
|
||||
#ifdef __SANITIZE_THREAD__
|
||||
// TSAN can report a false data race on flags, where one thread is writing
|
||||
// to one of the mutable bits and another thread is reading this immutable
|
||||
// bit. So precisely suppress that TSAN warning, we separate out this bit
|
||||
// during TSAN runs.
|
||||
bool is_secondary_cache_compatible_for_tsan;
|
||||
#endif // __SANITIZE_THREAD__
|
||||
|
||||
// Beginning of the key (MUST BE THE LAST FIELD IN THIS STRUCT!)
|
||||
char key_data[1];
|
||||
|
||||
@@ -113,7 +122,11 @@ struct LRUHandle {
|
||||
bool InHighPriPool() const { return flags & IN_HIGH_PRI_POOL; }
|
||||
bool HasHit() const { return flags & HAS_HIT; }
|
||||
bool IsSecondaryCacheCompatible() const {
|
||||
return flags & IS_TIERED_CACHE_COMPATIBLE;
|
||||
#ifdef __SANITIZE_THREAD__
|
||||
return is_secondary_cache_compatible_for_tsan;
|
||||
#else
|
||||
return flags & IS_SECONDARY_CACHE_COMPATIBLE;
|
||||
#endif // __SANITIZE_THREAD__
|
||||
}
|
||||
bool IsPending() const { return flags & IS_PENDING; }
|
||||
bool IsPromoted() const { return flags & IS_PROMOTED; }
|
||||
@@ -144,12 +157,15 @@ struct LRUHandle {
|
||||
|
||||
void SetHit() { flags |= HAS_HIT; }
|
||||
|
||||
void SetSecondaryCacheCompatible(bool tiered) {
|
||||
if (tiered) {
|
||||
flags |= IS_TIERED_CACHE_COMPATIBLE;
|
||||
void SetSecondaryCacheCompatible(bool compat) {
|
||||
if (compat) {
|
||||
flags |= IS_SECONDARY_CACHE_COMPATIBLE;
|
||||
} else {
|
||||
flags &= ~IS_TIERED_CACHE_COMPATIBLE;
|
||||
flags &= ~IS_SECONDARY_CACHE_COMPATIBLE;
|
||||
}
|
||||
#ifdef __SANITIZE_THREAD__
|
||||
is_secondary_cache_compatible_for_tsan = compat;
|
||||
#endif // __SANITIZE_THREAD__
|
||||
}
|
||||
|
||||
void SetIncomplete(bool incomp) {
|
||||
@@ -170,6 +186,11 @@ struct LRUHandle {
|
||||
|
||||
void Free() {
|
||||
assert(refs == 0);
|
||||
#ifdef __SANITIZE_THREAD__
|
||||
// Here we can safely assert they are the same without a data race reported
|
||||
assert(((flags & IS_SECONDARY_CACHE_COMPATIBLE) != 0) ==
|
||||
is_secondary_cache_compatible_for_tsan);
|
||||
#endif // __SANITIZE_THREAD__
|
||||
if (!IsSecondaryCacheCompatible() && info_.deleter) {
|
||||
(*info_.deleter)(key(), value);
|
||||
} else if (IsSecondaryCacheCompatible()) {
|
||||
|
||||
Vendored
+1
-1
@@ -224,7 +224,7 @@ class TestSecondaryCache : public SecondaryCache {
|
||||
}
|
||||
~TestSecondaryCache() override { cache_.reset(); }
|
||||
|
||||
std::string Name() override { return "TestSecondaryCache"; }
|
||||
const char* Name() const override { return "TestSecondaryCache"; }
|
||||
|
||||
void InjectFailure() { inject_failure_ = true; }
|
||||
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "db/blob/blob_garbage_meter.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "table/internal_iterator.h"
|
||||
#include "test_util/sync_point.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// An internal iterator that passes each key-value encountered to
|
||||
// BlobGarbageMeter as inflow in order to measure the total number and size of
|
||||
// blobs in the compaction input on a per-blob file basis.
|
||||
class BlobCountingIterator : public InternalIterator {
|
||||
public:
|
||||
BlobCountingIterator(InternalIterator* iter,
|
||||
BlobGarbageMeter* blob_garbage_meter)
|
||||
: iter_(iter), blob_garbage_meter_(blob_garbage_meter) {
|
||||
assert(iter_);
|
||||
assert(blob_garbage_meter_);
|
||||
|
||||
UpdateAndCountBlobIfNeeded();
|
||||
}
|
||||
|
||||
bool Valid() const override { return iter_->Valid() && status_.ok(); }
|
||||
|
||||
void SeekToFirst() override {
|
||||
iter_->SeekToFirst();
|
||||
UpdateAndCountBlobIfNeeded();
|
||||
}
|
||||
|
||||
void SeekToLast() override {
|
||||
iter_->SeekToLast();
|
||||
UpdateAndCountBlobIfNeeded();
|
||||
}
|
||||
|
||||
void Seek(const Slice& target) override {
|
||||
iter_->Seek(target);
|
||||
UpdateAndCountBlobIfNeeded();
|
||||
}
|
||||
|
||||
void SeekForPrev(const Slice& target) override {
|
||||
iter_->SeekForPrev(target);
|
||||
UpdateAndCountBlobIfNeeded();
|
||||
}
|
||||
|
||||
void Next() override {
|
||||
assert(Valid());
|
||||
|
||||
iter_->Next();
|
||||
UpdateAndCountBlobIfNeeded();
|
||||
}
|
||||
|
||||
bool NextAndGetResult(IterateResult* result) override {
|
||||
assert(Valid());
|
||||
|
||||
const bool res = iter_->NextAndGetResult(result);
|
||||
UpdateAndCountBlobIfNeeded();
|
||||
return res;
|
||||
}
|
||||
|
||||
void Prev() override {
|
||||
assert(Valid());
|
||||
|
||||
iter_->Prev();
|
||||
UpdateAndCountBlobIfNeeded();
|
||||
}
|
||||
|
||||
Slice key() const override {
|
||||
assert(Valid());
|
||||
return iter_->key();
|
||||
}
|
||||
|
||||
Slice user_key() const override {
|
||||
assert(Valid());
|
||||
return iter_->user_key();
|
||||
}
|
||||
|
||||
Slice value() const override {
|
||||
assert(Valid());
|
||||
return iter_->value();
|
||||
}
|
||||
|
||||
Status status() const override { return status_; }
|
||||
|
||||
bool PrepareValue() override {
|
||||
assert(Valid());
|
||||
return iter_->PrepareValue();
|
||||
}
|
||||
|
||||
bool MayBeOutOfLowerBound() override {
|
||||
assert(Valid());
|
||||
return iter_->MayBeOutOfLowerBound();
|
||||
}
|
||||
|
||||
IterBoundCheck UpperBoundCheckResult() override {
|
||||
assert(Valid());
|
||||
return iter_->UpperBoundCheckResult();
|
||||
}
|
||||
|
||||
void SetPinnedItersMgr(PinnedIteratorsManager* pinned_iters_mgr) override {
|
||||
iter_->SetPinnedItersMgr(pinned_iters_mgr);
|
||||
}
|
||||
|
||||
bool IsKeyPinned() const override {
|
||||
assert(Valid());
|
||||
return iter_->IsKeyPinned();
|
||||
}
|
||||
|
||||
bool IsValuePinned() const override {
|
||||
assert(Valid());
|
||||
return iter_->IsValuePinned();
|
||||
}
|
||||
|
||||
Status GetProperty(std::string prop_name, std::string* prop) override {
|
||||
return iter_->GetProperty(prop_name, prop);
|
||||
}
|
||||
|
||||
private:
|
||||
void UpdateAndCountBlobIfNeeded() {
|
||||
assert(!iter_->Valid() || iter_->status().ok());
|
||||
|
||||
if (!iter_->Valid()) {
|
||||
status_ = iter_->status();
|
||||
return;
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT(
|
||||
"BlobCountingIterator::UpdateAndCountBlobIfNeeded:ProcessInFlow");
|
||||
|
||||
status_ = blob_garbage_meter_->ProcessInFlow(key(), value());
|
||||
}
|
||||
|
||||
InternalIterator* iter_;
|
||||
BlobGarbageMeter* blob_garbage_meter_;
|
||||
Status status_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -0,0 +1,325 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/blob/blob_counting_iterator.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/blob/blob_garbage_meter.h"
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
void CheckInFlow(const BlobGarbageMeter& blob_garbage_meter,
|
||||
uint64_t blob_file_number, uint64_t count, uint64_t bytes) {
|
||||
const auto& flows = blob_garbage_meter.flows();
|
||||
|
||||
const auto it = flows.find(blob_file_number);
|
||||
if (it == flows.end()) {
|
||||
ASSERT_EQ(count, 0);
|
||||
ASSERT_EQ(bytes, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& in = it->second.GetInFlow();
|
||||
|
||||
ASSERT_EQ(in.GetCount(), count);
|
||||
ASSERT_EQ(in.GetBytes(), bytes);
|
||||
}
|
||||
|
||||
TEST(BlobCountingIteratorTest, CountBlobs) {
|
||||
// Note: the input consists of three key-values: two are blob references to
|
||||
// different blob files, while the third one is a plain value.
|
||||
constexpr char user_key0[] = "key0";
|
||||
constexpr char user_key1[] = "key1";
|
||||
constexpr char user_key2[] = "key2";
|
||||
|
||||
const std::vector<std::string> keys{
|
||||
test::KeyStr(user_key0, 1, kTypeBlobIndex),
|
||||
test::KeyStr(user_key1, 2, kTypeBlobIndex),
|
||||
test::KeyStr(user_key2, 3, kTypeValue)};
|
||||
|
||||
constexpr uint64_t first_blob_file_number = 4;
|
||||
constexpr uint64_t first_offset = 1000;
|
||||
constexpr uint64_t first_size = 2000;
|
||||
|
||||
std::string first_blob_index;
|
||||
BlobIndex::EncodeBlob(&first_blob_index, first_blob_file_number, first_offset,
|
||||
first_size, kNoCompression);
|
||||
|
||||
constexpr uint64_t second_blob_file_number = 6;
|
||||
constexpr uint64_t second_offset = 2000;
|
||||
constexpr uint64_t second_size = 4000;
|
||||
|
||||
std::string second_blob_index;
|
||||
BlobIndex::EncodeBlob(&second_blob_index, second_blob_file_number,
|
||||
second_offset, second_size, kNoCompression);
|
||||
|
||||
const std::vector<std::string> values{first_blob_index, second_blob_index,
|
||||
"raw_value"};
|
||||
|
||||
assert(keys.size() == values.size());
|
||||
|
||||
test::VectorIterator input(keys, values);
|
||||
BlobGarbageMeter blob_garbage_meter;
|
||||
|
||||
BlobCountingIterator blob_counter(&input, &blob_garbage_meter);
|
||||
|
||||
constexpr uint64_t first_expected_bytes =
|
||||
first_size +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(sizeof(user_key0) - 1);
|
||||
constexpr uint64_t second_expected_bytes =
|
||||
second_size +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(sizeof(user_key1) - 1);
|
||||
|
||||
// Call SeekToFirst and iterate forward
|
||||
blob_counter.SeekToFirst();
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[0]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key0);
|
||||
ASSERT_EQ(blob_counter.value(), values[0]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 1,
|
||||
first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 0, 0);
|
||||
|
||||
blob_counter.Next();
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[1]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key1);
|
||||
ASSERT_EQ(blob_counter.value(), values[1]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 1,
|
||||
first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 1,
|
||||
second_expected_bytes);
|
||||
|
||||
blob_counter.Next();
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[2]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key2);
|
||||
ASSERT_EQ(blob_counter.value(), values[2]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 1,
|
||||
first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 1,
|
||||
second_expected_bytes);
|
||||
|
||||
blob_counter.Next();
|
||||
ASSERT_FALSE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 1,
|
||||
first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 1,
|
||||
second_expected_bytes);
|
||||
|
||||
// Do it again using NextAndGetResult
|
||||
blob_counter.SeekToFirst();
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[0]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key0);
|
||||
ASSERT_EQ(blob_counter.value(), values[0]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 2,
|
||||
2 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 1,
|
||||
second_expected_bytes);
|
||||
|
||||
{
|
||||
IterateResult result;
|
||||
ASSERT_TRUE(blob_counter.NextAndGetResult(&result));
|
||||
ASSERT_EQ(result.key, keys[1]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key1);
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[1]);
|
||||
ASSERT_EQ(blob_counter.value(), values[1]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 2,
|
||||
2 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 2,
|
||||
2 * second_expected_bytes);
|
||||
}
|
||||
|
||||
{
|
||||
IterateResult result;
|
||||
ASSERT_TRUE(blob_counter.NextAndGetResult(&result));
|
||||
ASSERT_EQ(result.key, keys[2]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key2);
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[2]);
|
||||
ASSERT_EQ(blob_counter.value(), values[2]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 2,
|
||||
2 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 2,
|
||||
2 * second_expected_bytes);
|
||||
}
|
||||
|
||||
{
|
||||
IterateResult result;
|
||||
ASSERT_FALSE(blob_counter.NextAndGetResult(&result));
|
||||
ASSERT_FALSE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 2,
|
||||
2 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 2,
|
||||
2 * second_expected_bytes);
|
||||
}
|
||||
|
||||
// Call SeekToLast and iterate backward
|
||||
blob_counter.SeekToLast();
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[2]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key2);
|
||||
ASSERT_EQ(blob_counter.value(), values[2]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 2,
|
||||
2 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 2,
|
||||
2 * second_expected_bytes);
|
||||
|
||||
blob_counter.Prev();
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[1]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key1);
|
||||
ASSERT_EQ(blob_counter.value(), values[1]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 2,
|
||||
2 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 3,
|
||||
3 * second_expected_bytes);
|
||||
|
||||
blob_counter.Prev();
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[0]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key0);
|
||||
ASSERT_EQ(blob_counter.value(), values[0]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 3,
|
||||
3 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 3,
|
||||
3 * second_expected_bytes);
|
||||
|
||||
blob_counter.Prev();
|
||||
ASSERT_FALSE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 3,
|
||||
3 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 3,
|
||||
3 * second_expected_bytes);
|
||||
|
||||
// Call Seek for all keys (plus one that's greater than all of them)
|
||||
blob_counter.Seek(keys[0]);
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[0]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key0);
|
||||
ASSERT_EQ(blob_counter.value(), values[0]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 4,
|
||||
4 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 3,
|
||||
3 * second_expected_bytes);
|
||||
|
||||
blob_counter.Seek(keys[1]);
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[1]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key1);
|
||||
ASSERT_EQ(blob_counter.value(), values[1]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 4,
|
||||
4 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 4,
|
||||
4 * second_expected_bytes);
|
||||
|
||||
blob_counter.Seek(keys[2]);
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[2]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key2);
|
||||
ASSERT_EQ(blob_counter.value(), values[2]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 4,
|
||||
4 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 4,
|
||||
4 * second_expected_bytes);
|
||||
|
||||
blob_counter.Seek("zzz");
|
||||
ASSERT_FALSE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 4,
|
||||
4 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 4,
|
||||
4 * second_expected_bytes);
|
||||
|
||||
// Call SeekForPrev for all keys (plus one that's less than all of them)
|
||||
blob_counter.SeekForPrev("aaa");
|
||||
ASSERT_FALSE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 4,
|
||||
4 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 4,
|
||||
4 * second_expected_bytes);
|
||||
|
||||
blob_counter.SeekForPrev(keys[0]);
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[0]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key0);
|
||||
ASSERT_EQ(blob_counter.value(), values[0]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 5,
|
||||
5 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 4,
|
||||
4 * second_expected_bytes);
|
||||
|
||||
blob_counter.SeekForPrev(keys[1]);
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[1]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key1);
|
||||
ASSERT_EQ(blob_counter.value(), values[1]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 5,
|
||||
5 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 5,
|
||||
5 * second_expected_bytes);
|
||||
|
||||
blob_counter.SeekForPrev(keys[2]);
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[2]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key2);
|
||||
ASSERT_EQ(blob_counter.value(), values[2]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 5,
|
||||
5 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 5,
|
||||
5 * second_expected_bytes);
|
||||
}
|
||||
|
||||
TEST(BlobCountingIteratorTest, CorruptBlobIndex) {
|
||||
const std::vector<std::string> keys{
|
||||
test::KeyStr("user_key", 1, kTypeBlobIndex)};
|
||||
const std::vector<std::string> values{"i_am_not_a_blob_index"};
|
||||
|
||||
assert(keys.size() == values.size());
|
||||
|
||||
test::VectorIterator input(keys, values);
|
||||
BlobGarbageMeter blob_garbage_meter;
|
||||
|
||||
BlobCountingIterator blob_counter(&input, &blob_garbage_meter);
|
||||
|
||||
blob_counter.SeekToFirst();
|
||||
ASSERT_FALSE(blob_counter.Valid());
|
||||
ASSERT_NOK(blob_counter.status());
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -135,7 +135,8 @@ std::ostream& operator<<(std::ostream& os,
|
||||
<< " total_blob_count: " << blob_file_addition.GetTotalBlobCount()
|
||||
<< " total_blob_bytes: " << blob_file_addition.GetTotalBlobBytes()
|
||||
<< " checksum_method: " << blob_file_addition.GetChecksumMethod()
|
||||
<< " checksum_value: " << blob_file_addition.GetChecksumValue();
|
||||
<< " checksum_value: "
|
||||
<< Slice(blob_file_addition.GetChecksumValue()).ToString(/* hex */ true);
|
||||
|
||||
return os;
|
||||
}
|
||||
@@ -146,7 +147,8 @@ JSONWriter& operator<<(JSONWriter& jw,
|
||||
<< "TotalBlobCount" << blob_file_addition.GetTotalBlobCount()
|
||||
<< "TotalBlobBytes" << blob_file_addition.GetTotalBlobBytes()
|
||||
<< "ChecksumMethod" << blob_file_addition.GetChecksumMethod()
|
||||
<< "ChecksumValue" << blob_file_addition.GetChecksumValue();
|
||||
<< "ChecksumValue"
|
||||
<< Slice(blob_file_addition.GetChecksumValue()).ToString(/* hex */ true);
|
||||
|
||||
return jw;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,9 @@ TEST_F(BlobFileAdditionTest, NonEmpty) {
|
||||
constexpr uint64_t total_blob_count = 2;
|
||||
constexpr uint64_t total_blob_bytes = 123456;
|
||||
const std::string checksum_method("SHA1");
|
||||
const std::string checksum_value("bdb7f34a59dfa1592ce7f52e99f98c570c525cbd");
|
||||
const std::string checksum_value(
|
||||
"\xbd\xb7\xf3\x4a\x59\xdf\xa1\x59\x2c\xe7\xf5\x2e\x99\xf9\x8c\x57\x0c\x52"
|
||||
"\x5c\xbd");
|
||||
|
||||
BlobFileAddition blob_file_addition(blob_file_number, total_blob_count,
|
||||
total_blob_bytes, checksum_method,
|
||||
@@ -113,7 +115,9 @@ TEST_F(BlobFileAdditionTest, DecodeErrors) {
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "checksum value"));
|
||||
}
|
||||
|
||||
constexpr char checksum_value[] = "bdb7f34a59dfa1592ce7f52e99f98c570c525cbd";
|
||||
constexpr char checksum_value[] =
|
||||
"\xbd\xb7\xf3\x4a\x59\xdf\xa1\x59\x2c\xe7\xf5\x2e\x99\xf9\x8c\x57\x0c\x52"
|
||||
"\x5c\xbd";
|
||||
PutLengthPrefixedSlice(&str, checksum_value);
|
||||
slice = str;
|
||||
|
||||
@@ -150,7 +154,7 @@ TEST_F(BlobFileAdditionTest, ForwardCompatibleCustomField) {
|
||||
constexpr uint64_t total_blob_count = 9999;
|
||||
constexpr uint64_t total_blob_bytes = 100000000;
|
||||
const std::string checksum_method("CRC32");
|
||||
const std::string checksum_value("3d87ff57");
|
||||
const std::string checksum_value("\x3d\x87\xff\x57");
|
||||
|
||||
BlobFileAddition blob_file_addition(blob_file_number, total_blob_count,
|
||||
total_blob_bytes, checksum_method,
|
||||
@@ -178,7 +182,7 @@ TEST_F(BlobFileAdditionTest, ForwardIncompatibleCustomField) {
|
||||
constexpr uint64_t total_blob_count = 100;
|
||||
constexpr uint64_t total_blob_bytes = 2000000;
|
||||
const std::string checksum_method("CRC32B");
|
||||
const std::string checksum_value("6dbdf23a");
|
||||
const std::string checksum_value("\x6d\xbd\xf2\x3a");
|
||||
|
||||
BlobFileAddition blob_file_addition(blob_file_number, total_blob_count,
|
||||
total_blob_bytes, checksum_method,
|
||||
|
||||
@@ -191,7 +191,7 @@ Status BlobFileBuilder::OpenBlobFileIfNeeded() {
|
||||
immutable_options_->clock, io_tracer_, statistics,
|
||||
immutable_options_->listeners,
|
||||
immutable_options_->file_checksum_gen_factory.get(),
|
||||
tmp_set.Contains(FileType::kBlobFile)));
|
||||
tmp_set.Contains(FileType::kBlobFile), false));
|
||||
|
||||
constexpr bool do_flush = false;
|
||||
|
||||
|
||||
@@ -8,7 +8,13 @@
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "rocksdb/slice.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
uint64_t SharedBlobFileMetaData::GetBlobFileSize() const {
|
||||
return BlobLogHeader::kSize + total_blob_bytes_ + BlobLogFooter::kSize;
|
||||
}
|
||||
|
||||
std::string SharedBlobFileMetaData::DebugString() const {
|
||||
std::ostringstream oss;
|
||||
@@ -23,7 +29,8 @@ std::ostream& operator<<(std::ostream& os,
|
||||
<< " total_blob_count: " << shared_meta.GetTotalBlobCount()
|
||||
<< " total_blob_bytes: " << shared_meta.GetTotalBlobBytes()
|
||||
<< " checksum_method: " << shared_meta.GetChecksumMethod()
|
||||
<< " checksum_value: " << shared_meta.GetChecksumValue();
|
||||
<< " checksum_value: "
|
||||
<< Slice(shared_meta.GetChecksumValue()).ToString(/* hex */ true);
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ class SharedBlobFileMetaData {
|
||||
SharedBlobFileMetaData(SharedBlobFileMetaData&&) = delete;
|
||||
SharedBlobFileMetaData& operator=(SharedBlobFileMetaData&&) = delete;
|
||||
|
||||
uint64_t GetBlobFileSize() const;
|
||||
uint64_t GetBlobFileNumber() const { return blob_file_number_; }
|
||||
uint64_t GetTotalBlobCount() const { return total_blob_count_; }
|
||||
uint64_t GetTotalBlobBytes() const { return total_blob_bytes_; }
|
||||
@@ -112,6 +113,11 @@ class BlobFileMetaData {
|
||||
return shared_meta_;
|
||||
}
|
||||
|
||||
uint64_t GetBlobFileSize() const {
|
||||
assert(shared_meta_);
|
||||
return shared_meta_->GetBlobFileSize();
|
||||
}
|
||||
|
||||
uint64_t GetBlobFileNumber() const {
|
||||
assert(shared_meta_);
|
||||
return shared_meta_->GetBlobFileNumber();
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/blob/blob_garbage_meter.h"
|
||||
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "db/dbformat.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
Status BlobGarbageMeter::ProcessInFlow(const Slice& key, const Slice& value) {
|
||||
uint64_t blob_file_number = kInvalidBlobFileNumber;
|
||||
uint64_t bytes = 0;
|
||||
|
||||
const Status s = Parse(key, value, &blob_file_number, &bytes);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
if (blob_file_number == kInvalidBlobFileNumber) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
flows_[blob_file_number].AddInFlow(bytes);
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status BlobGarbageMeter::ProcessOutFlow(const Slice& key, const Slice& value) {
|
||||
uint64_t blob_file_number = kInvalidBlobFileNumber;
|
||||
uint64_t bytes = 0;
|
||||
|
||||
const Status s = Parse(key, value, &blob_file_number, &bytes);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
if (blob_file_number == kInvalidBlobFileNumber) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Note: in order to measure the amount of additional garbage, we only need to
|
||||
// track the outflow for preexisting files, i.e. those that also had inflow.
|
||||
// (Newly written files would only have outflow.)
|
||||
auto it = flows_.find(blob_file_number);
|
||||
if (it == flows_.end()) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
it->second.AddOutFlow(bytes);
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status BlobGarbageMeter::Parse(const Slice& key, const Slice& value,
|
||||
uint64_t* blob_file_number, uint64_t* bytes) {
|
||||
assert(blob_file_number);
|
||||
assert(*blob_file_number == kInvalidBlobFileNumber);
|
||||
assert(bytes);
|
||||
assert(*bytes == 0);
|
||||
|
||||
ParsedInternalKey ikey;
|
||||
|
||||
{
|
||||
constexpr bool log_err_key = false;
|
||||
const Status s = ParseInternalKey(key, &ikey, log_err_key);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
if (ikey.type != kTypeBlobIndex) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
BlobIndex blob_index;
|
||||
|
||||
{
|
||||
const Status s = blob_index.DecodeFrom(value);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
if (blob_index.IsInlined() || blob_index.HasTTL()) {
|
||||
return Status::Corruption("Unexpected TTL/inlined blob index");
|
||||
}
|
||||
|
||||
*blob_file_number = blob_index.file_number();
|
||||
*bytes =
|
||||
blob_index.size() +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(ikey.user_key.size());
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -0,0 +1,102 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "db/blob/blob_constants.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class Slice;
|
||||
|
||||
// A class that can be used to compute the amount of additional garbage
|
||||
// generated by a compaction. It parses the keys and blob references in the
|
||||
// input and output of a compaction, and aggregates the "inflow" and "outflow"
|
||||
// on a per-blob file basis. The amount of additional garbage for any given blob
|
||||
// file can then be computed by subtracting the outflow from the inflow.
|
||||
class BlobGarbageMeter {
|
||||
public:
|
||||
// A class to store the number and total size of blobs on a per-blob file
|
||||
// basis.
|
||||
class BlobStats {
|
||||
public:
|
||||
void Add(uint64_t bytes) {
|
||||
++count_;
|
||||
bytes_ += bytes;
|
||||
}
|
||||
void Add(uint64_t count, uint64_t bytes) {
|
||||
count_ += count;
|
||||
bytes_ += bytes;
|
||||
}
|
||||
|
||||
uint64_t GetCount() const { return count_; }
|
||||
uint64_t GetBytes() const { return bytes_; }
|
||||
|
||||
private:
|
||||
uint64_t count_ = 0;
|
||||
uint64_t bytes_ = 0;
|
||||
};
|
||||
|
||||
// A class to keep track of the "inflow" and the "outflow" and to compute the
|
||||
// amount of additional garbage for a given blob file.
|
||||
class BlobInOutFlow {
|
||||
public:
|
||||
void AddInFlow(uint64_t bytes) {
|
||||
in_flow_.Add(bytes);
|
||||
assert(IsValid());
|
||||
}
|
||||
void AddOutFlow(uint64_t bytes) {
|
||||
out_flow_.Add(bytes);
|
||||
assert(IsValid());
|
||||
}
|
||||
|
||||
const BlobStats& GetInFlow() const { return in_flow_; }
|
||||
const BlobStats& GetOutFlow() const { return out_flow_; }
|
||||
|
||||
bool IsValid() const {
|
||||
return in_flow_.GetCount() >= out_flow_.GetCount() &&
|
||||
in_flow_.GetBytes() >= out_flow_.GetBytes();
|
||||
}
|
||||
bool HasGarbage() const {
|
||||
assert(IsValid());
|
||||
return in_flow_.GetCount() > out_flow_.GetCount();
|
||||
}
|
||||
uint64_t GetGarbageCount() const {
|
||||
assert(IsValid());
|
||||
assert(HasGarbage());
|
||||
return in_flow_.GetCount() - out_flow_.GetCount();
|
||||
}
|
||||
uint64_t GetGarbageBytes() const {
|
||||
assert(IsValid());
|
||||
assert(HasGarbage());
|
||||
return in_flow_.GetBytes() - out_flow_.GetBytes();
|
||||
}
|
||||
|
||||
private:
|
||||
BlobStats in_flow_;
|
||||
BlobStats out_flow_;
|
||||
};
|
||||
|
||||
Status ProcessInFlow(const Slice& key, const Slice& value);
|
||||
Status ProcessOutFlow(const Slice& key, const Slice& value);
|
||||
|
||||
const std::unordered_map<uint64_t, BlobInOutFlow>& flows() const {
|
||||
return flows_;
|
||||
}
|
||||
|
||||
private:
|
||||
static Status Parse(const Slice& key, const Slice& value,
|
||||
uint64_t* blob_file_number, uint64_t* bytes);
|
||||
|
||||
std::unordered_map<uint64_t, BlobInOutFlow> flows_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -0,0 +1,196 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/blob/blob_garbage_meter.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "test_util/testharness.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
TEST(BlobGarbageMeterTest, MeasureGarbage) {
|
||||
BlobGarbageMeter blob_garbage_meter;
|
||||
|
||||
struct BlobDescriptor {
|
||||
std::string user_key;
|
||||
uint64_t blob_file_number;
|
||||
uint64_t offset;
|
||||
uint64_t size;
|
||||
CompressionType compression_type;
|
||||
bool has_in_flow;
|
||||
bool has_out_flow;
|
||||
|
||||
uint64_t GetExpectedBytes() const {
|
||||
return size +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(user_key.size());
|
||||
}
|
||||
};
|
||||
|
||||
// Note: blob file 4 has the same inflow and outflow and hence no additional
|
||||
// garbage. Blob file 5 has less outflow than inflow and thus it does have
|
||||
// additional garbage. Blob file 6 is a newly written file (i.e. no inflow,
|
||||
// only outflow) and is thus not tracked by the meter.
|
||||
std::vector<BlobDescriptor> blobs{
|
||||
{"key", 4, 1234, 555, kLZ4Compression, true, true},
|
||||
{"other_key", 4, 6789, 101010, kLZ4Compression, true, true},
|
||||
{"yet_another_key", 5, 22222, 3456, kLZ4Compression, true, true},
|
||||
{"foo_key", 5, 77777, 8888, kLZ4Compression, true, true},
|
||||
{"bar_key", 5, 999999, 1212, kLZ4Compression, true, false},
|
||||
{"baz_key", 5, 1234567, 890, kLZ4Compression, true, false},
|
||||
{"new_key", 6, 7777, 9999, kNoCompression, false, true}};
|
||||
|
||||
for (const auto& blob : blobs) {
|
||||
constexpr SequenceNumber seq = 123;
|
||||
const InternalKey key(blob.user_key, seq, kTypeBlobIndex);
|
||||
const Slice key_slice = key.Encode();
|
||||
|
||||
std::string value;
|
||||
BlobIndex::EncodeBlob(&value, blob.blob_file_number, blob.offset, blob.size,
|
||||
blob.compression_type);
|
||||
const Slice value_slice(value);
|
||||
|
||||
if (blob.has_in_flow) {
|
||||
ASSERT_OK(blob_garbage_meter.ProcessInFlow(key_slice, value_slice));
|
||||
}
|
||||
if (blob.has_out_flow) {
|
||||
ASSERT_OK(blob_garbage_meter.ProcessOutFlow(key_slice, value_slice));
|
||||
}
|
||||
}
|
||||
|
||||
const auto& flows = blob_garbage_meter.flows();
|
||||
ASSERT_EQ(flows.size(), 2);
|
||||
|
||||
{
|
||||
const auto it = flows.find(4);
|
||||
ASSERT_NE(it, flows.end());
|
||||
|
||||
const auto& flow = it->second;
|
||||
|
||||
constexpr uint64_t expected_count = 2;
|
||||
const uint64_t expected_bytes =
|
||||
blobs[0].GetExpectedBytes() + blobs[1].GetExpectedBytes();
|
||||
|
||||
const auto& in = flow.GetInFlow();
|
||||
ASSERT_EQ(in.GetCount(), expected_count);
|
||||
ASSERT_EQ(in.GetBytes(), expected_bytes);
|
||||
|
||||
const auto& out = flow.GetOutFlow();
|
||||
ASSERT_EQ(out.GetCount(), expected_count);
|
||||
ASSERT_EQ(out.GetBytes(), expected_bytes);
|
||||
|
||||
ASSERT_TRUE(flow.IsValid());
|
||||
ASSERT_FALSE(flow.HasGarbage());
|
||||
}
|
||||
|
||||
{
|
||||
const auto it = flows.find(5);
|
||||
ASSERT_NE(it, flows.end());
|
||||
|
||||
const auto& flow = it->second;
|
||||
|
||||
const auto& in = flow.GetInFlow();
|
||||
|
||||
constexpr uint64_t expected_in_count = 4;
|
||||
const uint64_t expected_in_bytes =
|
||||
blobs[2].GetExpectedBytes() + blobs[3].GetExpectedBytes() +
|
||||
blobs[4].GetExpectedBytes() + blobs[5].GetExpectedBytes();
|
||||
|
||||
ASSERT_EQ(in.GetCount(), expected_in_count);
|
||||
ASSERT_EQ(in.GetBytes(), expected_in_bytes);
|
||||
|
||||
const auto& out = flow.GetOutFlow();
|
||||
|
||||
constexpr uint64_t expected_out_count = 2;
|
||||
const uint64_t expected_out_bytes =
|
||||
blobs[2].GetExpectedBytes() + blobs[3].GetExpectedBytes();
|
||||
|
||||
ASSERT_EQ(out.GetCount(), expected_out_count);
|
||||
ASSERT_EQ(out.GetBytes(), expected_out_bytes);
|
||||
|
||||
ASSERT_TRUE(flow.IsValid());
|
||||
ASSERT_TRUE(flow.HasGarbage());
|
||||
ASSERT_EQ(flow.GetGarbageCount(), expected_in_count - expected_out_count);
|
||||
ASSERT_EQ(flow.GetGarbageBytes(), expected_in_bytes - expected_out_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BlobGarbageMeterTest, PlainValue) {
|
||||
constexpr char user_key[] = "user_key";
|
||||
constexpr SequenceNumber seq = 123;
|
||||
|
||||
const InternalKey key(user_key, seq, kTypeValue);
|
||||
const Slice key_slice = key.Encode();
|
||||
|
||||
constexpr char value[] = "value";
|
||||
const Slice value_slice(value);
|
||||
|
||||
BlobGarbageMeter blob_garbage_meter;
|
||||
|
||||
ASSERT_OK(blob_garbage_meter.ProcessInFlow(key_slice, value_slice));
|
||||
ASSERT_OK(blob_garbage_meter.ProcessOutFlow(key_slice, value_slice));
|
||||
ASSERT_TRUE(blob_garbage_meter.flows().empty());
|
||||
}
|
||||
|
||||
TEST(BlobGarbageMeterTest, CorruptInternalKey) {
|
||||
constexpr char corrupt_key[] = "i_am_corrupt";
|
||||
const Slice key_slice(corrupt_key);
|
||||
|
||||
constexpr char value[] = "value";
|
||||
const Slice value_slice(value);
|
||||
|
||||
BlobGarbageMeter blob_garbage_meter;
|
||||
|
||||
ASSERT_NOK(blob_garbage_meter.ProcessInFlow(key_slice, value_slice));
|
||||
ASSERT_NOK(blob_garbage_meter.ProcessOutFlow(key_slice, value_slice));
|
||||
}
|
||||
|
||||
TEST(BlobGarbageMeterTest, CorruptBlobIndex) {
|
||||
constexpr char user_key[] = "user_key";
|
||||
constexpr SequenceNumber seq = 123;
|
||||
|
||||
const InternalKey key(user_key, seq, kTypeBlobIndex);
|
||||
const Slice key_slice = key.Encode();
|
||||
|
||||
constexpr char value[] = "i_am_not_a_blob_index";
|
||||
const Slice value_slice(value);
|
||||
|
||||
BlobGarbageMeter blob_garbage_meter;
|
||||
|
||||
ASSERT_NOK(blob_garbage_meter.ProcessInFlow(key_slice, value_slice));
|
||||
ASSERT_NOK(blob_garbage_meter.ProcessOutFlow(key_slice, value_slice));
|
||||
}
|
||||
|
||||
TEST(BlobGarbageMeterTest, InlinedTTLBlobIndex) {
|
||||
constexpr char user_key[] = "user_key";
|
||||
constexpr SequenceNumber seq = 123;
|
||||
|
||||
const InternalKey key(user_key, seq, kTypeBlobIndex);
|
||||
const Slice key_slice = key.Encode();
|
||||
|
||||
constexpr uint64_t expiration = 1234567890;
|
||||
constexpr char inlined_value[] = "inlined";
|
||||
|
||||
std::string value;
|
||||
BlobIndex::EncodeInlinedTTL(&value, expiration, inlined_value);
|
||||
|
||||
const Slice value_slice(value);
|
||||
|
||||
BlobGarbageMeter blob_garbage_meter;
|
||||
|
||||
ASSERT_NOK(blob_garbage_meter.ProcessInFlow(key_slice, value_slice));
|
||||
ASSERT_NOK(blob_garbage_meter.ProcessOutFlow(key_slice, value_slice));
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -107,7 +107,8 @@ struct BlobLogRecord {
|
||||
// Note that the offset field of BlobIndex actually points to the blob value
|
||||
// as opposed to the start of the blob record. The following method can
|
||||
// be used to calculate the adjustment needed to read the blob record header.
|
||||
static uint64_t CalculateAdjustmentForRecordHeader(uint64_t key_size) {
|
||||
static constexpr uint64_t CalculateAdjustmentForRecordHeader(
|
||||
uint64_t key_size) {
|
||||
return key_size + kHeaderSize;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "test_util/sync_point.h"
|
||||
@@ -152,6 +153,31 @@ class AlwaysKeepFilter : public CompactionFilter {
|
||||
return CompactionFilter::Decision::kKeep;
|
||||
}
|
||||
};
|
||||
|
||||
class SkipUntilFilter : public CompactionFilter {
|
||||
public:
|
||||
explicit SkipUntilFilter(std::string skip_until)
|
||||
: skip_until_(std::move(skip_until)) {}
|
||||
|
||||
const char* Name() const override {
|
||||
return "rocksdb.compaction.filter.skip.until";
|
||||
}
|
||||
|
||||
CompactionFilter::Decision FilterV2(int /* level */, const Slice& /* key */,
|
||||
ValueType /* value_type */,
|
||||
const Slice& /* existing_value */,
|
||||
std::string* /* new_value */,
|
||||
std::string* skip_until) const override {
|
||||
assert(skip_until);
|
||||
*skip_until = skip_until_;
|
||||
|
||||
return CompactionFilter::Decision::kRemoveAndSkipUntil;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string skip_until_;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
class DBBlobBadCompactionFilterTest
|
||||
@@ -254,6 +280,49 @@ TEST_F(DBBlobCompactionTest, BlindWriteFilter) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBlobCompactionTest, SkipUntilFilter) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
|
||||
std::unique_ptr<CompactionFilter> compaction_filter_guard(
|
||||
new SkipUntilFilter("z"));
|
||||
options.compaction_filter = compaction_filter_guard.get();
|
||||
|
||||
Reopen(options);
|
||||
|
||||
const std::vector<std::string> keys{"a", "b", "c"};
|
||||
const std::vector<std::string> values{"a_value", "b_value", "c_value"};
|
||||
assert(keys.size() == values.size());
|
||||
|
||||
for (size_t i = 0; i < keys.size(); ++i) {
|
||||
ASSERT_OK(Put(keys[i], values[i]));
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
int process_in_flow_called = 0;
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlobCountingIterator::UpdateAndCountBlobIfNeeded:ProcessInFlow",
|
||||
[&process_in_flow_called](void* /* arg */) { ++process_in_flow_called; });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), /* begin */ nullptr,
|
||||
/* end */ nullptr));
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
for (const auto& key : keys) {
|
||||
ASSERT_EQ(Get(key), "NOT_FOUND");
|
||||
}
|
||||
|
||||
// Make sure SkipUntil was performed using iteration rather than Seek
|
||||
ASSERT_EQ(process_in_flow_called, keys.size());
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_P(DBBlobBadCompactionFilterTest, BadDecisionFromCompactionFilter) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
@@ -390,6 +459,138 @@ TEST_F(DBBlobCompactionTest, CompactionFilterReadBlobAndKeep) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBlobCompactionTest, TrackGarbage) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
// First table+blob file pair: 4 blobs with different keys
|
||||
constexpr char first_key[] = "first_key";
|
||||
constexpr char first_value[] = "first_value";
|
||||
constexpr char second_key[] = "second_key";
|
||||
constexpr char second_value[] = "second_value";
|
||||
constexpr char third_key[] = "third_key";
|
||||
constexpr char third_value[] = "third_value";
|
||||
constexpr char fourth_key[] = "fourth_key";
|
||||
constexpr char fourth_value[] = "fourth_value";
|
||||
|
||||
ASSERT_OK(Put(first_key, first_value));
|
||||
ASSERT_OK(Put(second_key, second_value));
|
||||
ASSERT_OK(Put(third_key, third_value));
|
||||
ASSERT_OK(Put(fourth_key, fourth_value));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Second table+blob file pair: overwrite 2 existing keys
|
||||
constexpr char new_first_value[] = "new_first_value";
|
||||
constexpr char new_second_value[] = "new_second_value";
|
||||
|
||||
ASSERT_OK(Put(first_key, new_first_value));
|
||||
ASSERT_OK(Put(second_key, new_second_value));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Compact them together. The first blob file should have 2 garbage blobs
|
||||
// corresponding to the 2 overwritten keys.
|
||||
constexpr Slice* begin = nullptr;
|
||||
constexpr Slice* end = nullptr;
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), begin, end));
|
||||
|
||||
VersionSet* const versions = dbfull()->TEST_GetVersionSet();
|
||||
assert(versions);
|
||||
assert(versions->GetColumnFamilySet());
|
||||
|
||||
ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault();
|
||||
assert(cfd);
|
||||
|
||||
Version* const current = cfd->current();
|
||||
assert(current);
|
||||
|
||||
const VersionStorageInfo* const storage_info = current->storage_info();
|
||||
assert(storage_info);
|
||||
|
||||
const auto& blob_files = storage_info->GetBlobFiles();
|
||||
ASSERT_EQ(blob_files.size(), 2);
|
||||
|
||||
{
|
||||
auto it = blob_files.begin();
|
||||
const auto& meta = it->second;
|
||||
assert(meta);
|
||||
|
||||
constexpr uint64_t first_expected_bytes =
|
||||
sizeof(first_value) - 1 +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(sizeof(first_key) -
|
||||
1);
|
||||
constexpr uint64_t second_expected_bytes =
|
||||
sizeof(second_value) - 1 +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(sizeof(second_key) -
|
||||
1);
|
||||
constexpr uint64_t third_expected_bytes =
|
||||
sizeof(third_value) - 1 +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(sizeof(third_key) -
|
||||
1);
|
||||
constexpr uint64_t fourth_expected_bytes =
|
||||
sizeof(fourth_value) - 1 +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(sizeof(fourth_key) -
|
||||
1);
|
||||
|
||||
ASSERT_EQ(meta->GetTotalBlobCount(), 4);
|
||||
ASSERT_EQ(meta->GetTotalBlobBytes(),
|
||||
first_expected_bytes + second_expected_bytes +
|
||||
third_expected_bytes + fourth_expected_bytes);
|
||||
ASSERT_EQ(meta->GetGarbageBlobCount(), 2);
|
||||
ASSERT_EQ(meta->GetGarbageBlobBytes(),
|
||||
first_expected_bytes + second_expected_bytes);
|
||||
}
|
||||
|
||||
{
|
||||
auto it = blob_files.rbegin();
|
||||
const auto& meta = it->second;
|
||||
assert(meta);
|
||||
|
||||
constexpr uint64_t new_first_expected_bytes =
|
||||
sizeof(new_first_value) - 1 +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(sizeof(first_key) -
|
||||
1);
|
||||
constexpr uint64_t new_second_expected_bytes =
|
||||
sizeof(new_second_value) - 1 +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(sizeof(second_key) -
|
||||
1);
|
||||
|
||||
ASSERT_EQ(meta->GetTotalBlobCount(), 2);
|
||||
ASSERT_EQ(meta->GetTotalBlobBytes(),
|
||||
new_first_expected_bytes + new_second_expected_bytes);
|
||||
ASSERT_EQ(meta->GetGarbageBlobCount(), 0);
|
||||
ASSERT_EQ(meta->GetGarbageBlobBytes(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBlobCompactionTest, MergeBlobWithBase) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
options.disable_auto_compactions = true;
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("Key1", "v1_1"));
|
||||
ASSERT_OK(Put("Key2", "v2_1"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(Merge("Key1", "v1_2"));
|
||||
ASSERT_OK(Merge("Key2", "v2_2"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(Merge("Key1", "v1_3"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr));
|
||||
ASSERT_EQ(Get("Key1"), "v1_1,v1_2,v1_3");
|
||||
ASSERT_EQ(Get("Key2"), "v2_1,v2_2");
|
||||
Close();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+3
-1
@@ -165,7 +165,7 @@ Status BuildTable(
|
||||
std::move(file), fname, file_options, ioptions.clock, io_tracer,
|
||||
ioptions.stats, ioptions.listeners,
|
||||
ioptions.file_checksum_gen_factory.get(),
|
||||
tmp_set.Contains(FileType::kTableFile)));
|
||||
tmp_set.Contains(FileType::kTableFile), false));
|
||||
|
||||
builder = NewTableBuilder(tboptions, file_writer.get());
|
||||
}
|
||||
@@ -204,6 +204,8 @@ Status BuildTable(
|
||||
const Slice& value = c_iter.value();
|
||||
const ParsedInternalKey& ikey = c_iter.ikey();
|
||||
// Generate a rolling 64-bit hash of the key and values
|
||||
// Note :
|
||||
// Here "key" integrates 'sequence_number'+'kType'+'user key'.
|
||||
s = output_validator.Add(key, value);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
|
||||
@@ -88,6 +88,7 @@ using ROCKSDB_NAMESPACE::NewBloomFilterPolicy;
|
||||
using ROCKSDB_NAMESPACE::NewCompactOnDeletionCollectorFactory;
|
||||
using ROCKSDB_NAMESPACE::NewGenericRateLimiter;
|
||||
using ROCKSDB_NAMESPACE::NewLRUCache;
|
||||
using ROCKSDB_NAMESPACE::NewRibbonFilterPolicy;
|
||||
using ROCKSDB_NAMESPACE::OptimisticTransactionDB;
|
||||
using ROCKSDB_NAMESPACE::OptimisticTransactionOptions;
|
||||
using ROCKSDB_NAMESPACE::Options;
|
||||
@@ -3028,6 +3029,11 @@ unsigned char rocksdb_options_get_advise_random_on_open(
|
||||
return opt->rep.advise_random_on_open;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_experimental_allow_mempurge(rocksdb_options_t* opt,
|
||||
unsigned char v) {
|
||||
opt->rep.experimental_allow_mempurge = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_access_hint_on_compaction_start(
|
||||
rocksdb_options_t* opt, int v) {
|
||||
switch(v) {
|
||||
@@ -3877,6 +3883,47 @@ rocksdb_filterpolicy_t* rocksdb_filterpolicy_create_bloom(int bits_per_key) {
|
||||
return rocksdb_filterpolicy_create_bloom_format(bits_per_key, true);
|
||||
}
|
||||
|
||||
rocksdb_filterpolicy_t* rocksdb_filterpolicy_create_ribbon_format(
|
||||
int bloom_equivalent_bits_per_key) {
|
||||
// Make a rocksdb_filterpolicy_t, but override all of its methods so
|
||||
// they delegate to a NewRibbonFilterPolicy() instead of user
|
||||
// supplied C functions.
|
||||
struct Wrapper : public rocksdb_filterpolicy_t {
|
||||
const FilterPolicy* rep_;
|
||||
~Wrapper() override { delete rep_; }
|
||||
const char* Name() const override { return rep_->Name(); }
|
||||
void CreateFilter(const Slice* keys, int n,
|
||||
std::string* dst) const override {
|
||||
return rep_->CreateFilter(keys, n, dst);
|
||||
}
|
||||
bool KeyMayMatch(const Slice& key, const Slice& filter) const override {
|
||||
return rep_->KeyMayMatch(key, filter);
|
||||
}
|
||||
ROCKSDB_NAMESPACE::FilterBitsBuilder* GetBuilderWithContext(
|
||||
const ROCKSDB_NAMESPACE::FilterBuildingContext& context)
|
||||
const override {
|
||||
return rep_->GetBuilderWithContext(context);
|
||||
}
|
||||
ROCKSDB_NAMESPACE::FilterBitsReader* GetFilterBitsReader(
|
||||
const Slice& contents) const override {
|
||||
return rep_->GetFilterBitsReader(contents);
|
||||
}
|
||||
static void DoNothing(void*) {}
|
||||
};
|
||||
Wrapper* wrapper = new Wrapper;
|
||||
wrapper->rep_ = NewRibbonFilterPolicy(bloom_equivalent_bits_per_key);
|
||||
wrapper->state_ = nullptr;
|
||||
wrapper->delete_filter_ = nullptr;
|
||||
wrapper->destructor_ = &Wrapper::DoNothing;
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
rocksdb_filterpolicy_t* rocksdb_filterpolicy_create_ribbon(
|
||||
int bloom_equivalent_bits_per_key) {
|
||||
return rocksdb_filterpolicy_create_ribbon_format(
|
||||
bloom_equivalent_bits_per_key);
|
||||
}
|
||||
|
||||
rocksdb_mergeoperator_t* rocksdb_mergeoperator_create(
|
||||
void* state, void (*destructor)(void*),
|
||||
char* (*full_merge)(void*, const char* key, size_t key_length,
|
||||
|
||||
+8
-3
@@ -1043,7 +1043,7 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
StartPhase("filter");
|
||||
for (run = 0; run <= 2; run++) {
|
||||
for (run = 0; run <= 3; run++) {
|
||||
// First run uses custom filter
|
||||
// Second run uses old block-based bloom filter
|
||||
// Third run uses full bloom filter
|
||||
@@ -1054,8 +1054,10 @@ int main(int argc, char** argv) {
|
||||
FilterKeyMatch, NULL, FilterName);
|
||||
} else if (run == 1) {
|
||||
policy = rocksdb_filterpolicy_create_bloom(8);
|
||||
} else {
|
||||
} else if (run == 2) {
|
||||
policy = rocksdb_filterpolicy_create_bloom_full(8);
|
||||
} else {
|
||||
policy = rocksdb_filterpolicy_create_ribbon(8);
|
||||
}
|
||||
rocksdb_block_based_options_set_filter_policy(table_options, policy);
|
||||
|
||||
@@ -1121,9 +1123,12 @@ int main(int argc, char** argv) {
|
||||
} else if (run == 1) {
|
||||
// Essentially a fingerprint of the block-based Bloom schema
|
||||
CheckCondition(hits == 241);
|
||||
} else {
|
||||
} else if (run == 2) {
|
||||
// Essentially a fingerprint of full Bloom schema, format_version=5
|
||||
CheckCondition(hits == 188);
|
||||
} else {
|
||||
// Essentially a fingerprint of Ribbon schema
|
||||
CheckCondition(hits == 226);
|
||||
}
|
||||
CheckCondition(
|
||||
(keys_to_query - hits) ==
|
||||
|
||||
+23
-23
@@ -445,6 +445,9 @@ bool SuperVersion::Unref() {
|
||||
|
||||
void SuperVersion::Cleanup() {
|
||||
assert(refs.load(std::memory_order_relaxed) == 0);
|
||||
// Since this SuperVersion object is being deleted,
|
||||
// decrement reference to the immutable MemtableList
|
||||
// this SV object was pointing to.
|
||||
imm->Unref(&to_delete);
|
||||
MemTable* m = mem->Unref();
|
||||
if (m != nullptr) {
|
||||
@@ -454,7 +457,7 @@ void SuperVersion::Cleanup() {
|
||||
to_delete.push_back(m);
|
||||
}
|
||||
current->Unref();
|
||||
cfd->UnrefAndTryDelete(this);
|
||||
cfd->UnrefAndTryDelete();
|
||||
}
|
||||
|
||||
void SuperVersion::Init(ColumnFamilyData* new_cfd, MemTable* new_mem,
|
||||
@@ -472,10 +475,10 @@ void SuperVersion::Init(ColumnFamilyData* new_cfd, MemTable* new_mem,
|
||||
|
||||
namespace {
|
||||
void SuperVersionUnrefHandle(void* ptr) {
|
||||
// UnrefHandle is called when a thread exists or a ThreadLocalPtr gets
|
||||
// destroyed. When former happens, the thread shouldn't see kSVInUse.
|
||||
// When latter happens, we are in ~ColumnFamilyData(), no get should happen as
|
||||
// well.
|
||||
// UnrefHandle is called when a thread exits or a ThreadLocalPtr gets
|
||||
// destroyed. When the former happens, the thread shouldn't see kSVInUse.
|
||||
// When the latter happens, only super_version_ holds a reference
|
||||
// to ColumnFamilyData, so no further queries are possible.
|
||||
SuperVersion* sv = static_cast<SuperVersion*>(ptr);
|
||||
bool was_last_ref __attribute__((__unused__));
|
||||
was_last_ref = sv->Unref();
|
||||
@@ -665,7 +668,7 @@ ColumnFamilyData::~ColumnFamilyData() {
|
||||
}
|
||||
}
|
||||
|
||||
bool ColumnFamilyData::UnrefAndTryDelete(SuperVersion* sv_under_cleanup) {
|
||||
bool ColumnFamilyData::UnrefAndTryDelete() {
|
||||
int old_refs = refs_.fetch_sub(1);
|
||||
assert(old_refs > 0);
|
||||
|
||||
@@ -675,22 +678,17 @@ bool ColumnFamilyData::UnrefAndTryDelete(SuperVersion* sv_under_cleanup) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If called under SuperVersion::Cleanup, we should not re-enter Cleanup on
|
||||
// the same SuperVersion. (But while installing a new SuperVersion, this
|
||||
// cfd could be referenced only by two SuperVersions.)
|
||||
if (old_refs == 2 && super_version_ != nullptr &&
|
||||
super_version_ != sv_under_cleanup) {
|
||||
if (old_refs == 2 && super_version_ != nullptr) {
|
||||
// Only the super_version_ holds me
|
||||
SuperVersion* sv = super_version_;
|
||||
super_version_ = nullptr;
|
||||
// Release SuperVersion reference kept in ThreadLocalPtr.
|
||||
// This must be done outside of mutex_ since unref handler can lock mutex.
|
||||
sv->db_mutex->Unlock();
|
||||
|
||||
// Release SuperVersion references kept in ThreadLocalPtr.
|
||||
local_sv_.reset();
|
||||
sv->db_mutex->Lock();
|
||||
|
||||
if (sv->Unref()) {
|
||||
// May delete this ColumnFamilyData after calling Cleanup()
|
||||
// Note: sv will delete this ColumnFamilyData during Cleanup()
|
||||
assert(sv->cfd == this);
|
||||
sv->Cleanup();
|
||||
delete sv;
|
||||
return true;
|
||||
@@ -1056,17 +1054,20 @@ uint64_t ColumnFamilyData::GetLiveSstFilesSize() const {
|
||||
}
|
||||
|
||||
MemTable* ColumnFamilyData::ConstructNewMemtable(
|
||||
const MutableCFOptions& mutable_cf_options, SequenceNumber earliest_seq) {
|
||||
const MutableCFOptions& mutable_cf_options, SequenceNumber earliest_seq,
|
||||
uint64_t log_number) {
|
||||
return new MemTable(internal_comparator_, ioptions_, mutable_cf_options,
|
||||
write_buffer_manager_, earliest_seq, id_);
|
||||
write_buffer_manager_, earliest_seq, id_, log_number);
|
||||
}
|
||||
|
||||
void ColumnFamilyData::CreateNewMemtable(
|
||||
const MutableCFOptions& mutable_cf_options, SequenceNumber earliest_seq) {
|
||||
const MutableCFOptions& mutable_cf_options, SequenceNumber earliest_seq,
|
||||
uint64_t log_number) {
|
||||
if (mem_ != nullptr) {
|
||||
delete mem_->Unref();
|
||||
}
|
||||
SetMemtable(ConstructNewMemtable(mutable_cf_options, earliest_seq));
|
||||
SetMemtable(
|
||||
ConstructNewMemtable(mutable_cf_options, earliest_seq, log_number));
|
||||
mem_->Ref();
|
||||
}
|
||||
|
||||
@@ -1255,14 +1256,13 @@ bool ColumnFamilyData::ReturnThreadLocalSuperVersion(SuperVersion* sv) {
|
||||
void ColumnFamilyData::InstallSuperVersion(
|
||||
SuperVersionContext* sv_context, InstrumentedMutex* db_mutex) {
|
||||
db_mutex->AssertHeld();
|
||||
return InstallSuperVersion(sv_context, db_mutex, mutable_cf_options_);
|
||||
return InstallSuperVersion(sv_context, mutable_cf_options_);
|
||||
}
|
||||
|
||||
void ColumnFamilyData::InstallSuperVersion(
|
||||
SuperVersionContext* sv_context, InstrumentedMutex* db_mutex,
|
||||
SuperVersionContext* sv_context,
|
||||
const MutableCFOptions& mutable_cf_options) {
|
||||
SuperVersion* new_superversion = sv_context->new_superversion.release();
|
||||
new_superversion->db_mutex = db_mutex;
|
||||
new_superversion->mutable_cf_options = mutable_cf_options;
|
||||
new_superversion->Init(this, mem_, imm_.current(), current_);
|
||||
SuperVersion* old_superversion = super_version_;
|
||||
|
||||
+5
-7
@@ -208,8 +208,6 @@ struct SuperVersion {
|
||||
uint64_t version_number;
|
||||
WriteStallCondition write_stall_condition;
|
||||
|
||||
InstrumentedMutex* db_mutex;
|
||||
|
||||
// should be called outside the mutex
|
||||
SuperVersion() = default;
|
||||
~SuperVersion();
|
||||
@@ -281,8 +279,7 @@ class ColumnFamilyData {
|
||||
// UnrefAndTryDelete() decreases the reference count and do free if needed,
|
||||
// return true if this is freed else false, UnrefAndTryDelete() can only
|
||||
// be called while holding a DB mutex, or during single-threaded recovery.
|
||||
// sv_under_cleanup is only provided when called from SuperVersion::Cleanup.
|
||||
bool UnrefAndTryDelete(SuperVersion* sv_under_cleanup = nullptr);
|
||||
bool UnrefAndTryDelete();
|
||||
|
||||
// SetDropped() can only be called under following conditions:
|
||||
// 1) Holding a DB mutex,
|
||||
@@ -371,9 +368,10 @@ class ColumnFamilyData {
|
||||
|
||||
// See Memtable constructor for explanation of earliest_seq param.
|
||||
MemTable* ConstructNewMemtable(const MutableCFOptions& mutable_cf_options,
|
||||
SequenceNumber earliest_seq);
|
||||
SequenceNumber earliest_seq,
|
||||
uint64_t log_number = 0);
|
||||
void CreateNewMemtable(const MutableCFOptions& mutable_cf_options,
|
||||
SequenceNumber earliest_seq);
|
||||
SequenceNumber earliest_seq, uint64_t log_number = 0);
|
||||
|
||||
TableCache* table_cache() const { return table_cache_.get(); }
|
||||
BlobFileCache* blob_file_cache() const { return blob_file_cache_.get(); }
|
||||
@@ -453,7 +451,6 @@ class ColumnFamilyData {
|
||||
// the clients to allocate SuperVersion outside of mutex.
|
||||
// IMPORTANT: Only call this from DBImpl::InstallSuperVersion()
|
||||
void InstallSuperVersion(SuperVersionContext* sv_context,
|
||||
InstrumentedMutex* db_mutex,
|
||||
const MutableCFOptions& mutable_cf_options);
|
||||
void InstallSuperVersion(SuperVersionContext* sv_context,
|
||||
InstrumentedMutex* db_mutex);
|
||||
@@ -520,6 +517,7 @@ class ColumnFamilyData {
|
||||
}
|
||||
|
||||
ThreadLocalPtr* TEST_GetLocalSV() { return local_sv_.get(); }
|
||||
WriteBufferManager* write_buffer_mgr() { return write_buffer_manager_; }
|
||||
|
||||
private:
|
||||
friend class ColumnFamilySet;
|
||||
|
||||
@@ -237,7 +237,8 @@ Compaction::Compaction(
|
||||
is_full_compaction_(IsFullCompaction(vstorage, inputs_)),
|
||||
is_manual_compaction_(_manual_compaction),
|
||||
is_trivial_move_(false),
|
||||
compaction_reason_(_compaction_reason) {
|
||||
compaction_reason_(_compaction_reason),
|
||||
notify_on_compaction_completion_(false) {
|
||||
MarkFilesBeingCompacted(true);
|
||||
if (is_manual_compaction_) {
|
||||
compaction_reason_ = CompactionReason::kManualCompaction;
|
||||
@@ -581,6 +582,29 @@ bool Compaction::ShouldFormSubcompactions() const {
|
||||
}
|
||||
}
|
||||
|
||||
bool Compaction::DoesInputReferenceBlobFiles() const {
|
||||
assert(input_version_);
|
||||
|
||||
const VersionStorageInfo* storage_info = input_version_->storage_info();
|
||||
assert(storage_info);
|
||||
|
||||
if (storage_info->GetBlobFiles().empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < inputs_.size(); ++i) {
|
||||
for (const FileMetaData* meta : inputs_[i].files) {
|
||||
assert(meta);
|
||||
|
||||
if (meta->oldest_blob_file_number != kInvalidBlobFileNumber) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t Compaction::MinInputFileOldestAncesterTime() const {
|
||||
uint64_t min_oldest_ancester_time = port::kMaxUint64;
|
||||
for (const auto& level_files : inputs_) {
|
||||
|
||||
@@ -266,6 +266,11 @@ class Compaction {
|
||||
// Should this compaction be broken up into smaller ones run in parallel?
|
||||
bool ShouldFormSubcompactions() const;
|
||||
|
||||
// Returns true iff at least one input file references a blob file.
|
||||
//
|
||||
// PRE: input version has been set.
|
||||
bool DoesInputReferenceBlobFiles() const;
|
||||
|
||||
// test function to validate the functionality of IsBottommostLevel()
|
||||
// function -- determines if compaction with inputs and storage is bottommost
|
||||
static bool TEST_IsBottommostLevel(
|
||||
@@ -298,6 +303,16 @@ class Compaction {
|
||||
|
||||
uint64_t MinInputFileOldestAncesterTime() const;
|
||||
|
||||
// Called by DBImpl::NotifyOnCompactionCompleted to make sure number of
|
||||
// compaction begin and compaction completion callbacks match.
|
||||
void SetNotifyOnCompactionCompleted() {
|
||||
notify_on_compaction_completion_ = true;
|
||||
}
|
||||
|
||||
bool ShouldNotifyOnCompactionCompleted() const {
|
||||
return notify_on_compaction_completion_;
|
||||
}
|
||||
|
||||
private:
|
||||
// mark (or clear) all files that are being compacted
|
||||
void MarkFilesBeingCompacted(bool mark_as_compacted);
|
||||
@@ -381,6 +396,10 @@ class Compaction {
|
||||
|
||||
// Reason for compaction
|
||||
CompactionReason compaction_reason_;
|
||||
|
||||
// Notify on compaction completion only if listener was notified on compaction
|
||||
// begin.
|
||||
bool notify_on_compaction_completion_;
|
||||
};
|
||||
|
||||
// Return sum of sizes of all files in `files`.
|
||||
|
||||
@@ -75,10 +75,8 @@ CompactionIterator::CompactionIterator(
|
||||
const std::atomic<bool>* manual_compaction_canceled,
|
||||
const std::shared_ptr<Logger> info_log,
|
||||
const std::string* full_history_ts_low)
|
||||
: input_(
|
||||
input, cmp,
|
||||
compaction ==
|
||||
nullptr), // Now only need to count number of entries in flush.
|
||||
: input_(input, cmp,
|
||||
!compaction || compaction->DoesInputReferenceBlobFiles()),
|
||||
cmp_(cmp),
|
||||
merge_helper_(merge_helper),
|
||||
snapshots_(snapshots),
|
||||
@@ -756,13 +754,15 @@ void CompactionIterator::NextFromInput() {
|
||||
}
|
||||
|
||||
pinned_iters_mgr_.StartPinning();
|
||||
Version* version = compaction_ ? compaction_->input_version() : nullptr;
|
||||
|
||||
// We know the merge type entry is not hidden, otherwise we would
|
||||
// have hit (A)
|
||||
// We encapsulate the merge related state machine in a different
|
||||
// object to minimize change to the existing flow.
|
||||
Status s =
|
||||
merge_helper_->MergeUntil(&input_, range_del_agg_, prev_snapshot,
|
||||
bottommost_level_, allow_data_in_errors_);
|
||||
Status s = merge_helper_->MergeUntil(&input_, range_del_agg_,
|
||||
prev_snapshot, bottommost_level_,
|
||||
allow_data_in_errors_, version);
|
||||
merge_out_iter_.SeekToFirst();
|
||||
|
||||
if (!s.ok() && !s.IsMergeInProgress()) {
|
||||
|
||||
@@ -97,6 +97,8 @@ class CompactionIterator {
|
||||
virtual double blob_garbage_collection_age_cutoff() const = 0;
|
||||
|
||||
virtual Version* input_version() const = 0;
|
||||
|
||||
virtual bool DoesInputReferenceBlobFiles() const = 0;
|
||||
};
|
||||
|
||||
class RealCompaction : public CompactionProxy {
|
||||
@@ -146,6 +148,10 @@ class CompactionIterator {
|
||||
return compaction_->input_version();
|
||||
}
|
||||
|
||||
bool DoesInputReferenceBlobFiles() const override {
|
||||
return compaction_->DoesInputReferenceBlobFiles();
|
||||
}
|
||||
|
||||
private:
|
||||
const Compaction* compaction_;
|
||||
};
|
||||
|
||||
@@ -182,6 +182,8 @@ class FakeCompaction : public CompactionIterator::CompactionProxy {
|
||||
|
||||
Version* input_version() const override { return nullptr; }
|
||||
|
||||
bool DoesInputReferenceBlobFiles() const override { return false; }
|
||||
|
||||
bool key_not_exists_beyond_output_level = false;
|
||||
|
||||
bool is_bottommost_level = false;
|
||||
|
||||
@@ -20,8 +20,10 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "db/blob/blob_counting_iterator.h"
|
||||
#include "db/blob/blob_file_addition.h"
|
||||
#include "db/blob/blob_file_builder.h"
|
||||
#include "db/blob/blob_garbage_meter.h"
|
||||
#include "db/builder.h"
|
||||
#include "db/compaction/clipping_iterator.h"
|
||||
#include "db/db_impl/db_impl.h"
|
||||
@@ -147,6 +149,7 @@ struct CompactionJob::SubcompactionState {
|
||||
// State kept for output being generated
|
||||
std::vector<Output> outputs;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
std::unique_ptr<BlobGarbageMeter> blob_garbage_meter;
|
||||
std::unique_ptr<WritableFileWriter> outfile;
|
||||
std::unique_ptr<TableBuilder> builder;
|
||||
|
||||
@@ -177,9 +180,17 @@ struct CompactionJob::SubcompactionState {
|
||||
uint64_t overlapped_bytes = 0;
|
||||
// A flag determine whether the key has been seen in ShouldStopBefore()
|
||||
bool seen_key = false;
|
||||
// sub compaction job id, which is used to identify different sub-compaction
|
||||
// within the same compaction job.
|
||||
const uint32_t sub_job_id;
|
||||
|
||||
SubcompactionState(Compaction* c, Slice* _start, Slice* _end, uint64_t size)
|
||||
: compaction(c), start(_start), end(_end), approx_size(size) {
|
||||
SubcompactionState(Compaction* c, Slice* _start, Slice* _end, uint64_t size,
|
||||
uint32_t _sub_job_id)
|
||||
: compaction(c),
|
||||
start(_start),
|
||||
end(_end),
|
||||
approx_size(size),
|
||||
sub_job_id(_sub_job_id) {
|
||||
assert(compaction != nullptr);
|
||||
}
|
||||
|
||||
@@ -229,6 +240,14 @@ struct CompactionJob::SubcompactionState {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Status ProcessOutFlowIfNeeded(const Slice& key, const Slice& value) {
|
||||
if (!blob_garbage_meter) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
return blob_garbage_meter->ProcessOutFlow(key, value);
|
||||
}
|
||||
};
|
||||
|
||||
// Maintains state for the entire compaction
|
||||
@@ -438,7 +457,8 @@ void CompactionJob::Prepare() {
|
||||
for (size_t i = 0; i <= boundaries_.size(); i++) {
|
||||
Slice* start = i == 0 ? nullptr : &boundaries_[i - 1];
|
||||
Slice* end = i == boundaries_.size() ? nullptr : &boundaries_[i];
|
||||
compact_->sub_compact_states.emplace_back(c, start, end, sizes_[i]);
|
||||
compact_->sub_compact_states.emplace_back(c, start, end, sizes_[i],
|
||||
static_cast<uint32_t>(i));
|
||||
}
|
||||
RecordInHistogram(stats_, NUM_SUBCOMPACTIONS_SCHEDULED,
|
||||
compact_->sub_compact_states.size());
|
||||
@@ -447,7 +467,8 @@ void CompactionJob::Prepare() {
|
||||
constexpr Slice* end = nullptr;
|
||||
constexpr uint64_t size = 0;
|
||||
|
||||
compact_->sub_compact_states.emplace_back(c, start, end, size);
|
||||
compact_->sub_compact_states.emplace_back(c, start, end, size,
|
||||
/*sub_job_id*/ 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -962,7 +983,8 @@ void CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
compaction_input.column_family.name.c_str(), job_id_,
|
||||
compaction_input.output_level, input_files_oss.str().c_str());
|
||||
CompactionServiceJobStatus compaction_status =
|
||||
db_options_.compaction_service->Start(compaction_input_binary, job_id_);
|
||||
db_options_.compaction_service->Start(compaction_input_binary,
|
||||
GetCompactionId(sub_compact));
|
||||
if (compaction_status != CompactionServiceJobStatus::kSuccess) {
|
||||
sub_compact->status =
|
||||
Status::Incomplete("CompactionService failed to start compaction job.");
|
||||
@@ -971,7 +993,7 @@ void CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
|
||||
std::string compaction_result_binary;
|
||||
compaction_status = db_options_.compaction_service->WaitForComplete(
|
||||
job_id_, &compaction_result_binary);
|
||||
GetCompactionId(sub_compact), &compaction_result_binary);
|
||||
|
||||
CompactionServiceResult compaction_result;
|
||||
s = CompactionServiceResult::Read(compaction_result_binary,
|
||||
@@ -1136,6 +1158,15 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
input = clip.get();
|
||||
}
|
||||
|
||||
std::unique_ptr<InternalIterator> blob_counter;
|
||||
|
||||
if (sub_compact->compaction->DoesInputReferenceBlobFiles()) {
|
||||
sub_compact->blob_garbage_meter.reset(new BlobGarbageMeter);
|
||||
blob_counter.reset(
|
||||
new BlobCountingIterator(input, sub_compact->blob_garbage_meter.get()));
|
||||
input = blob_counter.get();
|
||||
}
|
||||
|
||||
input->SeekToFirst();
|
||||
|
||||
AutoThreadOperationStageUpdater stage_updater(
|
||||
@@ -1248,6 +1279,11 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
break;
|
||||
}
|
||||
|
||||
status = sub_compact->ProcessOutFlowIfNeeded(key, value);
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
}
|
||||
|
||||
sub_compact->current_output_file_size =
|
||||
sub_compact->builder->EstimatedFileSize();
|
||||
const ParsedInternalKey& ikey = c_iter->ikey();
|
||||
@@ -1415,11 +1451,16 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
#endif // ROCKSDB_ASSERT_STATUS_CHECKED
|
||||
|
||||
sub_compact->c_iter.reset();
|
||||
blob_counter.reset();
|
||||
clip.reset();
|
||||
raw_input.reset();
|
||||
sub_compact->status = status;
|
||||
}
|
||||
|
||||
uint64_t CompactionJob::GetCompactionId(SubcompactionState* sub_compact) {
|
||||
return (uint64_t)job_id_ << 32 | sub_compact->sub_job_id;
|
||||
}
|
||||
|
||||
void CompactionJob::RecordDroppedKeys(
|
||||
const CompactionIterationStats& c_iter_stats,
|
||||
CompactionJobStats* compaction_job_stats) {
|
||||
@@ -1784,19 +1825,6 @@ Status CompactionJob::InstallCompactionResults(
|
||||
auto* compaction = compact_->compaction;
|
||||
assert(compaction);
|
||||
|
||||
// paranoia: verify that the files that we started with
|
||||
// still exist in the current version and in the same original level.
|
||||
// This ensures that a concurrent compaction did not erroneously
|
||||
// pick the same files to compact_.
|
||||
if (!versions_->VerifyCompactionFileConsistency(compaction)) {
|
||||
Compaction::InputLevelSummaryBuffer inputs_summary;
|
||||
|
||||
ROCKS_LOG_ERROR(db_options_.info_log, "[%s] [JOB %d] Compaction %s aborted",
|
||||
compaction->column_family_data()->GetName().c_str(),
|
||||
job_id_, compaction->InputLevelSummary(&inputs_summary));
|
||||
return Status::Corruption("Compaction input files inconsistent");
|
||||
}
|
||||
|
||||
{
|
||||
Compaction::InputLevelSummaryBuffer inputs_summary;
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
@@ -1812,6 +1840,8 @@ Status CompactionJob::InstallCompactionResults(
|
||||
// Add compaction inputs
|
||||
compaction->AddInputDeletions(edit);
|
||||
|
||||
std::unordered_map<uint64_t, BlobGarbageMeter::BlobStats> blob_total_garbage;
|
||||
|
||||
for (const auto& sub_compact : compact_->sub_compact_states) {
|
||||
for (const auto& out : sub_compact.outputs) {
|
||||
edit->AddFile(compaction->output_level(), out.meta);
|
||||
@@ -1820,6 +1850,29 @@ Status CompactionJob::InstallCompactionResults(
|
||||
for (const auto& blob : sub_compact.blob_file_additions) {
|
||||
edit->AddBlobFile(blob);
|
||||
}
|
||||
|
||||
if (sub_compact.blob_garbage_meter) {
|
||||
const auto& flows = sub_compact.blob_garbage_meter->flows();
|
||||
|
||||
for (const auto& pair : flows) {
|
||||
const uint64_t blob_file_number = pair.first;
|
||||
const BlobGarbageMeter::BlobInOutFlow& flow = pair.second;
|
||||
|
||||
assert(flow.IsValid());
|
||||
if (flow.HasGarbage()) {
|
||||
blob_total_garbage[blob_file_number].Add(flow.GetGarbageCount(),
|
||||
flow.GetGarbageBytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& pair : blob_total_garbage) {
|
||||
const uint64_t blob_file_number = pair.first;
|
||||
const BlobGarbageMeter::BlobStats& stats = pair.second;
|
||||
|
||||
edit->AddBlobFileGarbage(blob_file_number, stats.GetCount(),
|
||||
stats.GetBytes());
|
||||
}
|
||||
|
||||
return versions_->LogAndApply(compaction->column_family_data(),
|
||||
@@ -1948,7 +2001,7 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
std::move(writable_file), fname, file_options_, db_options_.clock,
|
||||
io_tracer_, db_options_.stats, listeners,
|
||||
db_options_.file_checksum_gen_factory.get(),
|
||||
tmp_set.Contains(FileType::kTableFile)));
|
||||
tmp_set.Contains(FileType::kTableFile), false));
|
||||
|
||||
TableBuilderOptions tboptions(
|
||||
*cfd->ioptions(), *(sub_compact->compaction->mutable_cf_options()),
|
||||
@@ -2179,8 +2232,8 @@ Status CompactionServiceCompactionJob::Run() {
|
||||
Slice end = compaction_input_.end;
|
||||
compact_->sub_compact_states.emplace_back(
|
||||
c, compaction_input_.has_begin ? &begin : nullptr,
|
||||
compaction_input_.has_end ? &end : nullptr,
|
||||
compaction_input_.approx_size);
|
||||
compaction_input_.has_end ? &end : nullptr, compaction_input_.approx_size,
|
||||
/*sub_job_id*/ 0);
|
||||
|
||||
log_buffer_->FlushBufferToLog();
|
||||
LogCompaction();
|
||||
@@ -2219,7 +2272,7 @@ Status CompactionServiceCompactionJob::Run() {
|
||||
status = io_s;
|
||||
}
|
||||
if (status.ok()) {
|
||||
// TODO: Add verify_table() and VerifyCompactionFileConsistency()
|
||||
// TODO: Add verify_table()
|
||||
}
|
||||
|
||||
// Finish up all book-keeping to unify the subcompaction results
|
||||
|
||||
@@ -168,7 +168,7 @@ class CompactionJob {
|
||||
void UpdateCompactionInputStatsHelper(
|
||||
int* num_files, uint64_t* bytes_read, int input_level);
|
||||
|
||||
int job_id_;
|
||||
uint32_t job_id_;
|
||||
|
||||
CompactionJobStats* compaction_job_stats_;
|
||||
|
||||
@@ -219,6 +219,8 @@ class CompactionJob {
|
||||
std::string full_history_ts_low_;
|
||||
BlobFileCompletionCallback* blob_callback_;
|
||||
|
||||
uint64_t GetCompactionId(SubcompactionState* sub_compact);
|
||||
|
||||
// Get table file name in where it's outputting to, which should also be in
|
||||
// `output_directory_`.
|
||||
virtual std::string GetTableFileName(uint64_t file_number);
|
||||
|
||||
@@ -12,16 +12,16 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class MyTestCompactionService : public CompactionService {
|
||||
public:
|
||||
MyTestCompactionService(const std::string& db_path,
|
||||
std::shared_ptr<FileSystem> fs, Options& options)
|
||||
: db_path_(db_path), fs_(fs), options_(options) {}
|
||||
MyTestCompactionService(const std::string& db_path, Options& options,
|
||||
std::shared_ptr<Statistics> statistics = nullptr)
|
||||
: db_path_(db_path), options_(options), statistics_(statistics) {}
|
||||
|
||||
static const char* kClassName() { return "MyTestCompactionService"; }
|
||||
|
||||
const char* Name() const override { return kClassName(); }
|
||||
|
||||
CompactionServiceJobStatus Start(const std::string& compaction_service_input,
|
||||
int job_id) override {
|
||||
uint64_t job_id) override {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
jobs_.emplace(job_id, compaction_service_input);
|
||||
CompactionServiceJobStatus s = CompactionServiceJobStatus::kSuccess;
|
||||
@@ -30,7 +30,7 @@ class MyTestCompactionService : public CompactionService {
|
||||
}
|
||||
|
||||
CompactionServiceJobStatus WaitForComplete(
|
||||
int job_id, std::string* compaction_service_result) override {
|
||||
uint64_t job_id, std::string* compaction_service_result) override {
|
||||
std::string compaction_input;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
@@ -54,6 +54,7 @@ class MyTestCompactionService : public CompactionService {
|
||||
options_override.prefix_extractor = options_.prefix_extractor;
|
||||
options_override.table_factory = options_.table_factory;
|
||||
options_override.sst_partitioner_factory = options_.sst_partitioner_factory;
|
||||
options_override.statistics = statistics_;
|
||||
|
||||
Status s = DB::OpenAndCompact(
|
||||
db_path_, db_path_ + "/" + ROCKSDB_NAMESPACE::ToString(job_id),
|
||||
@@ -73,10 +74,10 @@ class MyTestCompactionService : public CompactionService {
|
||||
private:
|
||||
InstrumentedMutex mutex_;
|
||||
std::atomic_int compaction_num_{0};
|
||||
std::map<int, std::string> jobs_;
|
||||
std::map<uint64_t, std::string> jobs_;
|
||||
const std::string db_path_;
|
||||
std::shared_ptr<FileSystem> fs_;
|
||||
Options options_;
|
||||
std::shared_ptr<Statistics> statistics_;
|
||||
};
|
||||
|
||||
class CompactionServiceTest : public DBTestBase {
|
||||
@@ -123,8 +124,10 @@ class CompactionServiceTest : public DBTestBase {
|
||||
TEST_F(CompactionServiceTest, BasicCompactions) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.statistics = CreateDBStatistics();
|
||||
std::shared_ptr<Statistics> compactor_statistics = CreateDBStatistics();
|
||||
options.compaction_service = std::make_shared<MyTestCompactionService>(
|
||||
dbname_, env_->GetFileSystem(), options);
|
||||
dbname_, options, compactor_statistics);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
@@ -158,6 +161,12 @@ TEST_F(CompactionServiceTest, BasicCompactions) {
|
||||
dynamic_cast<MyTestCompactionService*>(options.compaction_service.get());
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), 1);
|
||||
|
||||
// make sure the compaction statistics is only recorded on remote side
|
||||
ASSERT_GE(
|
||||
compactor_statistics->getTickerCount(COMPACTION_KEY_DROP_NEWER_ENTRY), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(COMPACTION_KEY_DROP_NEWER_ENTRY),
|
||||
0);
|
||||
|
||||
// Test failed compaction
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImplSecondary::CompactWithoutInstallation::End", [&](void* status) {
|
||||
@@ -195,8 +204,8 @@ TEST_F(CompactionServiceTest, ManualCompaction) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.disable_auto_compactions = true;
|
||||
options.compaction_service = std::make_shared<MyTestCompactionService>(
|
||||
dbname_, env_->GetFileSystem(), options);
|
||||
options.compaction_service =
|
||||
std::make_shared<MyTestCompactionService>(dbname_, options);
|
||||
DestroyAndReopen(options);
|
||||
GenerateTestData();
|
||||
|
||||
@@ -236,8 +245,8 @@ TEST_F(CompactionServiceTest, FailedToStart) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.disable_auto_compactions = true;
|
||||
options.compaction_service = std::make_shared<MyTestCompactionService>(
|
||||
dbname_, env_->GetFileSystem(), options);
|
||||
options.compaction_service =
|
||||
std::make_shared<MyTestCompactionService>(dbname_, options);
|
||||
DestroyAndReopen(options);
|
||||
GenerateTestData();
|
||||
|
||||
@@ -261,8 +270,8 @@ TEST_F(CompactionServiceTest, InvalidResult) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.disable_auto_compactions = true;
|
||||
options.compaction_service = std::make_shared<MyTestCompactionService>(
|
||||
dbname_, env_->GetFileSystem(), options);
|
||||
options.compaction_service =
|
||||
std::make_shared<MyTestCompactionService>(dbname_, options);
|
||||
DestroyAndReopen(options);
|
||||
GenerateTestData();
|
||||
|
||||
@@ -282,22 +291,31 @@ TEST_F(CompactionServiceTest, InvalidResult) {
|
||||
ASSERT_FALSE(s.ok());
|
||||
}
|
||||
|
||||
// TODO: support sub-compaction
|
||||
TEST_F(CompactionServiceTest, DISABLED_SubCompaction) {
|
||||
TEST_F(CompactionServiceTest, SubCompaction) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.max_subcompactions = 10;
|
||||
options.target_file_size_base = 1 << 10; // 1KB
|
||||
options.disable_auto_compactions = true;
|
||||
options.compaction_service = std::make_shared<MyTestCompactionService>(
|
||||
dbname_, env_->GetFileSystem(), options);
|
||||
options.compaction_service =
|
||||
std::make_shared<MyTestCompactionService>(dbname_, options);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
GenerateTestData();
|
||||
VerifyTestData();
|
||||
|
||||
auto my_cs =
|
||||
dynamic_cast<MyTestCompactionService*>(options.compaction_service.get());
|
||||
int compaction_num_before = my_cs->GetCompactionNum();
|
||||
|
||||
auto cro = CompactRangeOptions();
|
||||
cro.max_subcompactions = 10;
|
||||
db_->CompactRange(cro, nullptr, nullptr);
|
||||
Status s = db_->CompactRange(cro, nullptr, nullptr);
|
||||
ASSERT_OK(s);
|
||||
VerifyTestData();
|
||||
int compaction_num = my_cs->GetCompactionNum() - compaction_num_before;
|
||||
// make sure there's sub-compaction by checking the compaction number
|
||||
ASSERT_GE(compaction_num, 2);
|
||||
}
|
||||
|
||||
class PartialDeleteCompactionFilter : public CompactionFilter {
|
||||
@@ -321,8 +339,8 @@ TEST_F(CompactionServiceTest, CompactionFilter) {
|
||||
options.env = env_;
|
||||
auto delete_comp_filter = PartialDeleteCompactionFilter();
|
||||
options.compaction_filter = &delete_comp_filter;
|
||||
options.compaction_service = std::make_shared<MyTestCompactionService>(
|
||||
dbname_, env_->GetFileSystem(), options);
|
||||
options.compaction_service =
|
||||
std::make_shared<MyTestCompactionService>(dbname_, options);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
@@ -364,8 +382,8 @@ TEST_F(CompactionServiceTest, CompactionFilter) {
|
||||
TEST_F(CompactionServiceTest, Snapshot) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.compaction_service = std::make_shared<MyTestCompactionService>(
|
||||
dbname_, env_->GetFileSystem(), options);
|
||||
options.compaction_service =
|
||||
std::make_shared<MyTestCompactionService>(dbname_, options);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
@@ -391,8 +409,8 @@ TEST_F(CompactionServiceTest, ConcurrentCompaction) {
|
||||
Options options = CurrentOptions();
|
||||
options.level0_file_num_compaction_trigger = 100;
|
||||
options.env = env_;
|
||||
options.compaction_service = std::make_shared<MyTestCompactionService>(
|
||||
dbname_, env_->GetFileSystem(), options);
|
||||
options.compaction_service =
|
||||
std::make_shared<MyTestCompactionService>(dbname_, options);
|
||||
options.max_background_jobs = 20;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "cache/cache_entry_roles.h"
|
||||
#include "cache/lru_cache.h"
|
||||
#include "db/column_family.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/table.h"
|
||||
@@ -152,13 +153,15 @@ class DBBlockCacheTest : public DBTestBase {
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
const std::array<size_t, kNumCacheEntryRoles>& GetCacheEntryRoleCountsBg() {
|
||||
const std::array<size_t, kNumCacheEntryRoles> GetCacheEntryRoleCountsBg() {
|
||||
// Verify in cache entry role stats
|
||||
ColumnFamilyHandleImpl* cfh =
|
||||
static_cast<ColumnFamilyHandleImpl*>(dbfull()->DefaultColumnFamily());
|
||||
InternalStats* internal_stats_ptr = cfh->cfd()->internal_stats();
|
||||
return internal_stats_ptr->TEST_GetCacheEntryRoleStats(/*foreground=*/false)
|
||||
.entry_counts;
|
||||
InternalStats::CacheEntryRoleStats stats;
|
||||
internal_stats_ptr->TEST_GetCacheEntryRoleStats(&stats,
|
||||
/*foreground=*/false);
|
||||
return stats.entry_counts;
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
};
|
||||
@@ -170,7 +173,13 @@ TEST_F(DBBlockCacheTest, IteratorBlockCacheUsage) {
|
||||
auto options = GetOptions(table_options);
|
||||
InitTable(options);
|
||||
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(0, 0, false);
|
||||
LRUCacheOptions co;
|
||||
co.capacity = 0;
|
||||
co.num_shard_bits = 0;
|
||||
co.strict_capacity_limit = false;
|
||||
// Needed not to count entry stats collector
|
||||
co.metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(co);
|
||||
table_options.block_cache = cache;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
Reopen(options);
|
||||
@@ -194,7 +203,13 @@ TEST_F(DBBlockCacheTest, TestWithoutCompressedBlockCache) {
|
||||
auto options = GetOptions(table_options);
|
||||
InitTable(options);
|
||||
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(0, 0, false);
|
||||
LRUCacheOptions co;
|
||||
co.capacity = 0;
|
||||
co.num_shard_bits = 0;
|
||||
co.strict_capacity_limit = false;
|
||||
// Needed not to count entry stats collector
|
||||
co.metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(co);
|
||||
table_options.block_cache = cache;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
Reopen(options);
|
||||
@@ -265,7 +280,13 @@ TEST_F(DBBlockCacheTest, TestWithCompressedBlockCache) {
|
||||
|
||||
ReadOptions read_options;
|
||||
std::shared_ptr<Cache> compressed_cache = NewLRUCache(1 << 25, 0, false);
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(0, 0, false);
|
||||
LRUCacheOptions co;
|
||||
co.capacity = 0;
|
||||
co.num_shard_bits = 0;
|
||||
co.strict_capacity_limit = false;
|
||||
// Needed not to count entry stats collector
|
||||
co.metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(co);
|
||||
table_options.block_cache = cache;
|
||||
table_options.no_block_cache = false;
|
||||
table_options.block_cache_compressed = compressed_cache;
|
||||
@@ -299,8 +320,8 @@ TEST_F(DBBlockCacheTest, TestWithCompressedBlockCache) {
|
||||
// Load last key block.
|
||||
ASSERT_EQ("Result incomplete: Insert failed due to LRU cache being full.",
|
||||
Get(ToString(kNumBlocks - 1)));
|
||||
// Failure won't record the miss counter.
|
||||
CheckCacheCounters(options, 0, 0, 0, 1);
|
||||
// Failure will also record the miss counter.
|
||||
CheckCacheCounters(options, 1, 0, 0, 1);
|
||||
CheckCompressedCacheCounters(options, 1, 0, 1, 0);
|
||||
|
||||
// Clear strict capacity limit flag. This time we shall hit compressed block
|
||||
@@ -944,10 +965,15 @@ TEST_F(DBBlockCacheTest, CacheCompressionDict) {
|
||||
}
|
||||
|
||||
static void ClearCache(Cache* cache) {
|
||||
auto roles = CopyCacheDeleterRoleMap();
|
||||
std::deque<std::string> keys;
|
||||
Cache::ApplyToAllEntriesOptions opts;
|
||||
auto callback = [&](const Slice& key, void* /*value*/, size_t /*charge*/,
|
||||
Cache::DeleterFn /*deleter*/) {
|
||||
Cache::DeleterFn deleter) {
|
||||
if (roles.find(deleter) == roles.end()) {
|
||||
// Keep the stats collector
|
||||
return;
|
||||
}
|
||||
keys.push_back(key.ToString());
|
||||
};
|
||||
cache->ApplyToAllEntries(callback, opts);
|
||||
@@ -1126,6 +1152,9 @@ TEST_F(DBBlockCacheTest, CacheEntryRoleStats) {
|
||||
&h, Cache::Priority::HIGH));
|
||||
ASSERT_GT(cache->GetUsage(), cache->GetCapacity());
|
||||
expected = {};
|
||||
// For CacheEntryStatsCollector
|
||||
expected[static_cast<size_t>(CacheEntryRole::kMisc)] = 1;
|
||||
// For Fill-it-up
|
||||
expected[static_cast<size_t>(CacheEntryRole::kMisc)]++;
|
||||
// Still able to hit on saved stats
|
||||
EXPECT_EQ(prev_expected, GetCacheEntryRoleCountsBg());
|
||||
@@ -1134,6 +1163,48 @@ TEST_F(DBBlockCacheTest, CacheEntryRoleStats) {
|
||||
EXPECT_EQ(expected, GetCacheEntryRoleCountsBg());
|
||||
|
||||
cache->Release(h);
|
||||
|
||||
// Now we test that the DB mutex is not held during scans, for the ways
|
||||
// we know how to (possibly) trigger them. Without a better good way to
|
||||
// check this, we simply inject an acquire & release of the DB mutex
|
||||
// deep in the stat collection code. If we were already holding the
|
||||
// mutex, that is UB that would at least be found by TSAN.
|
||||
int scan_count = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"CacheEntryStatsCollector::GetStats:AfterApplyToAllEntries",
|
||||
[this, &scan_count](void*) {
|
||||
dbfull()->TEST_LockMutex();
|
||||
dbfull()->TEST_UnlockMutex();
|
||||
++scan_count;
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Different things that might trigger a scan, with mock sleeps to
|
||||
// force a miss.
|
||||
env_->MockSleepForSeconds(10000);
|
||||
dbfull()->DumpStats();
|
||||
ASSERT_EQ(scan_count, 1);
|
||||
|
||||
env_->MockSleepForSeconds(10000);
|
||||
ASSERT_TRUE(
|
||||
db_->GetMapProperty(DB::Properties::kBlockCacheEntryStats, &values));
|
||||
ASSERT_EQ(scan_count, 2);
|
||||
|
||||
env_->MockSleepForSeconds(10000);
|
||||
std::string value_str;
|
||||
ASSERT_TRUE(
|
||||
db_->GetProperty(DB::Properties::kBlockCacheEntryStats, &value_str));
|
||||
ASSERT_EQ(scan_count, 3);
|
||||
|
||||
env_->MockSleepForSeconds(10000);
|
||||
ASSERT_TRUE(db_->GetProperty(DB::Properties::kCFStats, &value_str));
|
||||
// To match historical speed, querying this property no longer triggers
|
||||
// a scan, even if results are old. But periodic dump stats should keep
|
||||
// things reasonably updated.
|
||||
ASSERT_EQ(scan_count, /*unchanged*/ 3);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
EXPECT_GE(iterations_tested, 1);
|
||||
}
|
||||
|
||||
@@ -527,7 +527,7 @@ TEST_P(DBBloomFilterTestWithParam, BloomFilter) {
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_VALGRIND_RUN
|
||||
#if !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
FormatDef, DBBloomFilterTestDefFormatVersion,
|
||||
::testing::Values(
|
||||
@@ -551,7 +551,7 @@ INSTANTIATE_TEST_CASE_P(
|
||||
test::kLatestFormatVersion),
|
||||
std::make_tuple(BFP::kAutoBloom, true, test::kLatestFormatVersion),
|
||||
std::make_tuple(BFP::kAutoBloom, false, test::kLatestFormatVersion)));
|
||||
#endif // ROCKSDB_VALGRIND_RUN
|
||||
#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
|
||||
TEST_F(DBBloomFilterTest, BloomFilterRate) {
|
||||
while (ChangeFilterOptions()) {
|
||||
|
||||
@@ -46,7 +46,7 @@ class DBTestCompactionFilterWithCompactParam
|
||||
}
|
||||
};
|
||||
|
||||
#ifndef ROCKSDB_VALGRIND_RUN
|
||||
#if !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
CompactionFilterWithOption, DBTestCompactionFilterWithCompactParam,
|
||||
::testing::Values(DBTestBase::OptionConfig::kDefault,
|
||||
@@ -55,11 +55,11 @@ INSTANTIATE_TEST_CASE_P(
|
||||
DBTestBase::OptionConfig::kLevelSubcompactions,
|
||||
DBTestBase::OptionConfig::kUniversalSubcompactions));
|
||||
#else
|
||||
// Run fewer cases in valgrind
|
||||
// Run fewer cases in non-full valgrind to save time.
|
||||
INSTANTIATE_TEST_CASE_P(CompactionFilterWithOption,
|
||||
DBTestCompactionFilterWithCompactParam,
|
||||
::testing::Values(DBTestBase::OptionConfig::kDefault));
|
||||
#endif // ROCKSDB_VALGRIND_RUN
|
||||
#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
|
||||
class KeepFilter : public CompactionFilter {
|
||||
public:
|
||||
|
||||
@@ -301,7 +301,7 @@ const SstFileMetaData* PickFileRandomly(
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
#ifndef ROCKSDB_VALGRIND_RUN
|
||||
#if !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
// All the TEST_P tests run once with sub_compactions disabled (i.e.
|
||||
// options.max_subcompactions = 1) and once with it enabled
|
||||
TEST_P(DBCompactionTestWithParam, CompactionDeletionTrigger) {
|
||||
@@ -366,7 +366,7 @@ TEST_P(DBCompactionTestWithParam, CompactionDeletionTrigger) {
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // ROCKSDB_VALGRIND_RUN
|
||||
#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
|
||||
TEST_P(DBCompactionTestWithParam, CompactionsPreserveDeletes) {
|
||||
// For each options type we test following
|
||||
@@ -3600,6 +3600,41 @@ TEST_F(DBCompactionTest, CompactFilesOverlapInL0Bug) {
|
||||
ASSERT_EQ("new_val", Get(Key(0)));
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, DeleteFilesInRangeConflictWithCompaction) {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
const Snapshot* snapshot = nullptr;
|
||||
const int kMaxKey = 10;
|
||||
|
||||
for (int i = 0; i < kMaxKey; i++) {
|
||||
ASSERT_OK(Put(Key(i), Key(i)));
|
||||
ASSERT_OK(Delete(Key(i)));
|
||||
if (!snapshot) {
|
||||
snapshot = db_->GetSnapshot();
|
||||
}
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(1);
|
||||
ASSERT_OK(Put(Key(kMaxKey), Key(kMaxKey)));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
// test DeleteFilesInRange() deletes the files already picked for compaction
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"VersionSet::LogAndApply:WriteManifestStart",
|
||||
"BackgroundCallCompaction:0"},
|
||||
{"DBImpl::BackgroundCompaction:Finish",
|
||||
"VersionSet::LogAndApply:WriteManifestDone"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// release snapshot which mark bottommost file for compaction
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
std::string begin_string = Key(0);
|
||||
std::string end_string = Key(kMaxKey + 1);
|
||||
Slice begin(begin_string);
|
||||
Slice end(end_string);
|
||||
ASSERT_OK(DeleteFilesInRange(db_, db_->DefaultColumnFamily(), &begin, &end));
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactBottomLevelFilesWithDeletions) {
|
||||
// bottom-level files may contain deletions due to snapshots protecting the
|
||||
// deleted keys. Once the snapshot is released, we should see files with many
|
||||
|
||||
@@ -24,6 +24,10 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// This is a static filter used for filtering
|
||||
// kvs during the compaction process.
|
||||
static std::string NEW_VALUE = "NewValue";
|
||||
|
||||
class DBFlushTest : public DBTestBase {
|
||||
public:
|
||||
DBFlushTest() : DBTestBase("/db_flush_test", /*env_do_fsync=*/true) {}
|
||||
@@ -658,6 +662,620 @@ TEST_F(DBFlushTest, StatisticsGarbageRangeDeletes) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBFlushTest, MemPurgeBasic) {
|
||||
Options options = CurrentOptions();
|
||||
|
||||
// The following options are used to enforce several values that
|
||||
// may already exist as default values to make this test resilient
|
||||
// to default value updates in the future.
|
||||
options.statistics = CreateDBStatistics();
|
||||
|
||||
// Record all statistics.
|
||||
options.statistics->set_stats_level(StatsLevel::kAll);
|
||||
|
||||
// create the DB if it's not already present
|
||||
options.create_if_missing = true;
|
||||
|
||||
// Useful for now as we are trying to compare uncompressed data savings on
|
||||
// flush().
|
||||
options.compression = kNoCompression;
|
||||
|
||||
// Prevent memtable in place updates. Should already be disabled
|
||||
// (from Wiki:
|
||||
// In place updates can be enabled by toggling on the bool
|
||||
// inplace_update_support flag. However, this flag is by default set to
|
||||
// false
|
||||
// because this thread-safe in-place update support is not compatible
|
||||
// with concurrent memtable writes. Note that the bool
|
||||
// allow_concurrent_memtable_write is set to true by default )
|
||||
options.inplace_update_support = false;
|
||||
options.allow_concurrent_memtable_write = true;
|
||||
|
||||
// Enforce size of a single MemTable to 64MB (64MB = 67108864 bytes).
|
||||
options.write_buffer_size = 1 << 20;
|
||||
// Activate the MemPurge prototype.
|
||||
options.experimental_allow_mempurge = true;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
uint32_t mempurge_count = 0;
|
||||
uint32_t sst_count = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::FlushJob:MemPurgeSuccessful",
|
||||
[&](void* /*arg*/) { mempurge_count++; });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::FlushJob:SSTFileCreated", [&](void* /*arg*/) { sst_count++; });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::string KEY1 = "IamKey1";
|
||||
std::string KEY2 = "IamKey2";
|
||||
std::string KEY3 = "IamKey3";
|
||||
std::string KEY4 = "IamKey4";
|
||||
std::string KEY5 = "IamKey5";
|
||||
std::string KEY6 = "IamKey6";
|
||||
std::string KEY7 = "IamKey7";
|
||||
std::string KEY8 = "IamKey8";
|
||||
std::string KEY9 = "IamKey9";
|
||||
std::string RNDKEY1, RNDKEY2, RNDKEY3;
|
||||
const std::string NOT_FOUND = "NOT_FOUND";
|
||||
|
||||
// Heavy overwrite workload,
|
||||
// more than would fit in maximum allowed memtables.
|
||||
Random rnd(719);
|
||||
const size_t NUM_REPEAT = 100;
|
||||
const size_t RAND_KEYS_LENGTH = 57;
|
||||
const size_t RAND_VALUES_LENGTH = 10240;
|
||||
std::string p_v1, p_v2, p_v3, p_v4, p_v5, p_v6, p_v7, p_v8, p_v9, p_rv1,
|
||||
p_rv2, p_rv3;
|
||||
|
||||
// Insert a very first set of keys that will be
|
||||
// mempurged at least once.
|
||||
p_v1 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
p_v2 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
p_v3 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
p_v4 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
ASSERT_OK(Put(KEY1, p_v1));
|
||||
ASSERT_OK(Put(KEY2, p_v2));
|
||||
ASSERT_OK(Put(KEY3, p_v3));
|
||||
ASSERT_OK(Put(KEY4, p_v4));
|
||||
ASSERT_EQ(Get(KEY1), p_v1);
|
||||
ASSERT_EQ(Get(KEY2), p_v2);
|
||||
ASSERT_EQ(Get(KEY3), p_v3);
|
||||
ASSERT_EQ(Get(KEY4), p_v4);
|
||||
|
||||
// Insertion of of K-V pairs, multiple times (overwrites).
|
||||
for (size_t i = 0; i < NUM_REPEAT; i++) {
|
||||
// Create value strings of arbitrary length RAND_VALUES_LENGTH bytes.
|
||||
p_v5 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
p_v6 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
p_v7 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
p_v8 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
p_v9 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
|
||||
ASSERT_OK(Put(KEY5, p_v5));
|
||||
ASSERT_OK(Put(KEY6, p_v6));
|
||||
ASSERT_OK(Put(KEY7, p_v7));
|
||||
ASSERT_OK(Put(KEY8, p_v8));
|
||||
ASSERT_OK(Put(KEY9, p_v9));
|
||||
|
||||
ASSERT_EQ(Get(KEY1), p_v1);
|
||||
ASSERT_EQ(Get(KEY2), p_v2);
|
||||
ASSERT_EQ(Get(KEY3), p_v3);
|
||||
ASSERT_EQ(Get(KEY4), p_v4);
|
||||
ASSERT_EQ(Get(KEY5), p_v5);
|
||||
ASSERT_EQ(Get(KEY6), p_v6);
|
||||
ASSERT_EQ(Get(KEY7), p_v7);
|
||||
ASSERT_EQ(Get(KEY8), p_v8);
|
||||
ASSERT_EQ(Get(KEY9), p_v9);
|
||||
}
|
||||
|
||||
// Check that there was at least one mempurge
|
||||
const uint32_t EXPECTED_MIN_MEMPURGE_COUNT = 1;
|
||||
// Check that there was no SST files created during flush.
|
||||
const uint32_t EXPECTED_SST_COUNT = 0;
|
||||
|
||||
EXPECT_GE(mempurge_count, EXPECTED_MIN_MEMPURGE_COUNT);
|
||||
EXPECT_EQ(sst_count, EXPECTED_SST_COUNT);
|
||||
|
||||
const uint32_t mempurge_count_record = mempurge_count;
|
||||
|
||||
// Insertion of of K-V pairs, no overwrites.
|
||||
for (size_t i = 0; i < NUM_REPEAT; i++) {
|
||||
// Create value strings of arbitrary length RAND_VALUES_LENGTH bytes.
|
||||
RNDKEY1 = rnd.RandomString(RAND_KEYS_LENGTH);
|
||||
RNDKEY2 = rnd.RandomString(RAND_KEYS_LENGTH);
|
||||
RNDKEY3 = rnd.RandomString(RAND_KEYS_LENGTH);
|
||||
p_rv1 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
p_rv2 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
p_rv3 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
|
||||
ASSERT_OK(Put(RNDKEY1, p_rv1));
|
||||
ASSERT_OK(Put(RNDKEY2, p_rv2));
|
||||
ASSERT_OK(Put(RNDKEY3, p_rv3));
|
||||
|
||||
ASSERT_EQ(Get(KEY1), p_v1);
|
||||
ASSERT_EQ(Get(KEY2), p_v2);
|
||||
ASSERT_EQ(Get(KEY3), p_v3);
|
||||
ASSERT_EQ(Get(KEY4), p_v4);
|
||||
ASSERT_EQ(Get(KEY5), p_v5);
|
||||
ASSERT_EQ(Get(KEY6), p_v6);
|
||||
ASSERT_EQ(Get(KEY7), p_v7);
|
||||
ASSERT_EQ(Get(KEY8), p_v8);
|
||||
ASSERT_EQ(Get(KEY9), p_v9);
|
||||
ASSERT_EQ(Get(RNDKEY1), p_rv1);
|
||||
ASSERT_EQ(Get(RNDKEY2), p_rv2);
|
||||
ASSERT_EQ(Get(RNDKEY3), p_rv3);
|
||||
}
|
||||
|
||||
// Assert that at least one flush to storage has been performed
|
||||
ASSERT_GT(sst_count, EXPECTED_SST_COUNT);
|
||||
// (which will consequently increase the number of mempurges recorded too).
|
||||
ASSERT_EQ(mempurge_count, mempurge_count_record);
|
||||
|
||||
// Assert that there is no data corruption, even with
|
||||
// a flush to storage.
|
||||
ASSERT_EQ(Get(KEY1), p_v1);
|
||||
ASSERT_EQ(Get(KEY2), p_v2);
|
||||
ASSERT_EQ(Get(KEY3), p_v3);
|
||||
ASSERT_EQ(Get(KEY4), p_v4);
|
||||
ASSERT_EQ(Get(KEY5), p_v5);
|
||||
ASSERT_EQ(Get(KEY6), p_v6);
|
||||
ASSERT_EQ(Get(KEY7), p_v7);
|
||||
ASSERT_EQ(Get(KEY8), p_v8);
|
||||
ASSERT_EQ(Get(KEY9), p_v9);
|
||||
ASSERT_EQ(Get(RNDKEY1), p_rv1);
|
||||
ASSERT_EQ(Get(RNDKEY2), p_rv2);
|
||||
ASSERT_EQ(Get(RNDKEY3), p_rv3);
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBFlushTest, MemPurgeDeleteAndDeleteRange) {
|
||||
Options options = CurrentOptions();
|
||||
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.statistics->set_stats_level(StatsLevel::kAll);
|
||||
options.create_if_missing = true;
|
||||
options.compression = kNoCompression;
|
||||
options.inplace_update_support = false;
|
||||
options.allow_concurrent_memtable_write = true;
|
||||
|
||||
// Enforce size of a single MemTable to 64MB (64MB = 67108864 bytes).
|
||||
options.write_buffer_size = 1 << 20;
|
||||
// Activate the MemPurge prototype.
|
||||
options.experimental_allow_mempurge = true;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
uint32_t mempurge_count = 0;
|
||||
uint32_t sst_count = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::FlushJob:MemPurgeSuccessful",
|
||||
[&](void* /*arg*/) { mempurge_count++; });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::FlushJob:SSTFileCreated", [&](void* /*arg*/) { sst_count++; });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::string KEY1 = "ThisIsKey1";
|
||||
std::string KEY2 = "ThisIsKey2";
|
||||
std::string KEY3 = "ThisIsKey3";
|
||||
std::string KEY4 = "ThisIsKey4";
|
||||
std::string KEY5 = "ThisIsKey5";
|
||||
const std::string NOT_FOUND = "NOT_FOUND";
|
||||
|
||||
Random rnd(117);
|
||||
const size_t NUM_REPEAT = 100;
|
||||
const size_t RAND_VALUES_LENGTH = 10240;
|
||||
|
||||
std::string key, value, p_v1, p_v2, p_v3, p_v3b, p_v4, p_v5;
|
||||
int count = 0;
|
||||
const int EXPECTED_COUNT_FORLOOP = 3;
|
||||
const int EXPECTED_COUNT_END = 4;
|
||||
|
||||
ReadOptions ropt;
|
||||
ropt.pin_data = true;
|
||||
ropt.total_order_seek = true;
|
||||
Iterator* iter = nullptr;
|
||||
|
||||
// Insertion of of K-V pairs, multiple times.
|
||||
// Also insert DeleteRange
|
||||
for (size_t i = 0; i < NUM_REPEAT; i++) {
|
||||
// Create value strings of arbitrary length RAND_VALUES_LENGTH bytes.
|
||||
p_v1 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
p_v2 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
p_v3 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
p_v3b = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
p_v4 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
p_v5 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
ASSERT_OK(Put(KEY1, p_v1));
|
||||
ASSERT_OK(Put(KEY2, p_v2));
|
||||
ASSERT_OK(Put(KEY3, p_v3));
|
||||
ASSERT_OK(Put(KEY4, p_v4));
|
||||
ASSERT_OK(Put(KEY5, p_v5));
|
||||
ASSERT_OK(Delete(KEY2));
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), KEY2,
|
||||
KEY4));
|
||||
ASSERT_OK(Put(KEY3, p_v3b));
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), KEY1,
|
||||
KEY3));
|
||||
ASSERT_OK(Delete(KEY1));
|
||||
|
||||
ASSERT_EQ(Get(KEY1), NOT_FOUND);
|
||||
ASSERT_EQ(Get(KEY2), NOT_FOUND);
|
||||
ASSERT_EQ(Get(KEY3), p_v3b);
|
||||
ASSERT_EQ(Get(KEY4), p_v4);
|
||||
ASSERT_EQ(Get(KEY5), p_v5);
|
||||
|
||||
iter = db_->NewIterator(ropt);
|
||||
iter->SeekToFirst();
|
||||
count = 0;
|
||||
for (; iter->Valid(); iter->Next()) {
|
||||
ASSERT_OK(iter->status());
|
||||
key = (iter->key()).ToString(false);
|
||||
value = (iter->value()).ToString(false);
|
||||
if (key.compare(KEY3) == 0)
|
||||
ASSERT_EQ(value, p_v3b);
|
||||
else if (key.compare(KEY4) == 0)
|
||||
ASSERT_EQ(value, p_v4);
|
||||
else if (key.compare(KEY5) == 0)
|
||||
ASSERT_EQ(value, p_v5);
|
||||
else
|
||||
ASSERT_EQ(value, NOT_FOUND);
|
||||
count++;
|
||||
}
|
||||
|
||||
// Expected count here is 3: KEY3, KEY4, KEY5.
|
||||
ASSERT_EQ(count, EXPECTED_COUNT_FORLOOP);
|
||||
if (iter) {
|
||||
delete iter;
|
||||
}
|
||||
}
|
||||
|
||||
// Check that there was at least one mempurge
|
||||
const uint32_t EXPECTED_MIN_MEMPURGE_COUNT = 1;
|
||||
// Check that there was no SST files created during flush.
|
||||
const uint32_t EXPECTED_SST_COUNT = 0;
|
||||
|
||||
EXPECT_GE(mempurge_count, EXPECTED_MIN_MEMPURGE_COUNT);
|
||||
EXPECT_EQ(sst_count, EXPECTED_SST_COUNT);
|
||||
|
||||
// Additional test for the iterator+memPurge.
|
||||
ASSERT_OK(Put(KEY2, p_v2));
|
||||
iter = db_->NewIterator(ropt);
|
||||
iter->SeekToFirst();
|
||||
ASSERT_OK(Put(KEY4, p_v4));
|
||||
count = 0;
|
||||
for (; iter->Valid(); iter->Next()) {
|
||||
ASSERT_OK(iter->status());
|
||||
key = (iter->key()).ToString(false);
|
||||
value = (iter->value()).ToString(false);
|
||||
if (key.compare(KEY2) == 0)
|
||||
ASSERT_EQ(value, p_v2);
|
||||
else if (key.compare(KEY3) == 0)
|
||||
ASSERT_EQ(value, p_v3b);
|
||||
else if (key.compare(KEY4) == 0)
|
||||
ASSERT_EQ(value, p_v4);
|
||||
else if (key.compare(KEY5) == 0)
|
||||
ASSERT_EQ(value, p_v5);
|
||||
else
|
||||
ASSERT_EQ(value, NOT_FOUND);
|
||||
count++;
|
||||
}
|
||||
|
||||
// Expected count here is 4: KEY2, KEY3, KEY4, KEY5.
|
||||
ASSERT_EQ(count, EXPECTED_COUNT_END);
|
||||
if (iter) delete iter;
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
// Create a Compaction Fitler that will be invoked
|
||||
// at flush time and will update the value of a KV pair
|
||||
// if the key string is "lower" than the filter_key_ string.
|
||||
class ConditionalUpdateFilter : public CompactionFilter {
|
||||
public:
|
||||
explicit ConditionalUpdateFilter(const std::string* filtered_key)
|
||||
: filtered_key_(filtered_key) {}
|
||||
bool Filter(int /*level*/, const Slice& key, const Slice& /*value*/,
|
||||
std::string* new_value, bool* value_changed) const override {
|
||||
// If key<filtered_key_, update the value of the KV-pair.
|
||||
if (key.compare(*filtered_key_) < 0) {
|
||||
assert(new_value != nullptr);
|
||||
*new_value = NEW_VALUE;
|
||||
*value_changed = true;
|
||||
}
|
||||
return false /*do not remove this KV-pair*/;
|
||||
}
|
||||
|
||||
const char* Name() const override { return "ConditionalUpdateFilter"; }
|
||||
|
||||
private:
|
||||
const std::string* filtered_key_;
|
||||
};
|
||||
|
||||
class ConditionalUpdateFilterFactory : public CompactionFilterFactory {
|
||||
public:
|
||||
explicit ConditionalUpdateFilterFactory(const Slice& filtered_key)
|
||||
: filtered_key_(filtered_key.ToString()) {}
|
||||
|
||||
std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& /*context*/) override {
|
||||
return std::unique_ptr<CompactionFilter>(
|
||||
new ConditionalUpdateFilter(&filtered_key_));
|
||||
}
|
||||
|
||||
const char* Name() const override { return "ConditionalUpdateFilterFactory"; }
|
||||
|
||||
bool ShouldFilterTableFileCreation(
|
||||
TableFileCreationReason reason) const override {
|
||||
// This compaction filter will be invoked
|
||||
// at flush time (and therefore at MemPurge time).
|
||||
return (reason == TableFileCreationReason::kFlush);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string filtered_key_;
|
||||
};
|
||||
|
||||
TEST_F(DBFlushTest, MemPurgeAndCompactionFilter) {
|
||||
Options options = CurrentOptions();
|
||||
|
||||
std::string KEY1 = "ThisIsKey1";
|
||||
std::string KEY2 = "ThisIsKey2";
|
||||
std::string KEY3 = "ThisIsKey3";
|
||||
std::string KEY4 = "ThisIsKey4";
|
||||
std::string KEY5 = "ThisIsKey5";
|
||||
std::string KEY6 = "ThisIsKey6";
|
||||
std::string KEY7 = "ThisIsKey7";
|
||||
std::string KEY8 = "ThisIsKey8";
|
||||
std::string KEY9 = "ThisIsKey9";
|
||||
const std::string NOT_FOUND = "NOT_FOUND";
|
||||
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.statistics->set_stats_level(StatsLevel::kAll);
|
||||
options.create_if_missing = true;
|
||||
options.compression = kNoCompression;
|
||||
options.inplace_update_support = false;
|
||||
options.allow_concurrent_memtable_write = true;
|
||||
|
||||
// Create a ConditionalUpdate compaction filter
|
||||
// that will update all the values of the KV pairs
|
||||
// where the keys are "lower" than KEY4.
|
||||
options.compaction_filter_factory =
|
||||
std::make_shared<ConditionalUpdateFilterFactory>(KEY4);
|
||||
|
||||
// Enforce size of a single MemTable to 64MB (64MB = 67108864 bytes).
|
||||
options.write_buffer_size = 1 << 20;
|
||||
// Activate the MemPurge prototype.
|
||||
options.experimental_allow_mempurge = true;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
uint32_t mempurge_count = 0;
|
||||
uint32_t sst_count = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::FlushJob:MemPurgeSuccessful",
|
||||
[&](void* /*arg*/) { mempurge_count++; });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::FlushJob:SSTFileCreated", [&](void* /*arg*/) { sst_count++; });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Random rnd(53);
|
||||
const size_t NUM_REPEAT = 1000;
|
||||
const size_t RAND_VALUES_LENGTH = 10240;
|
||||
std::string p_v1, p_v2, p_v3, p_v4, p_v5, p_v6, p_v7, p_v8, p_v9;
|
||||
|
||||
p_v1 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
p_v2 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
p_v3 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
p_v4 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
p_v5 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
ASSERT_OK(Put(KEY1, p_v1));
|
||||
ASSERT_OK(Put(KEY2, p_v2));
|
||||
ASSERT_OK(Put(KEY3, p_v3));
|
||||
ASSERT_OK(Put(KEY4, p_v4));
|
||||
ASSERT_OK(Put(KEY5, p_v5));
|
||||
ASSERT_OK(Delete(KEY1));
|
||||
|
||||
// Insertion of of K-V pairs, multiple times.
|
||||
for (size_t i = 0; i < NUM_REPEAT; i++) {
|
||||
// Create value strings of arbitrary
|
||||
// length RAND_VALUES_LENGTH bytes.
|
||||
p_v6 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
p_v7 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
p_v8 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
p_v9 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
ASSERT_OK(Put(KEY6, p_v6));
|
||||
ASSERT_OK(Put(KEY7, p_v7));
|
||||
ASSERT_OK(Put(KEY8, p_v8));
|
||||
ASSERT_OK(Put(KEY9, p_v9));
|
||||
|
||||
ASSERT_OK(Delete(KEY7));
|
||||
}
|
||||
|
||||
// Check that there was at least one mempurge
|
||||
const uint32_t EXPECTED_MIN_MEMPURGE_COUNT = 1;
|
||||
// Check that there was no SST files created during flush.
|
||||
const uint32_t EXPECTED_SST_COUNT = 0;
|
||||
|
||||
EXPECT_GE(mempurge_count, EXPECTED_MIN_MEMPURGE_COUNT);
|
||||
EXPECT_EQ(sst_count, EXPECTED_SST_COUNT);
|
||||
|
||||
// Verify that the ConditionalUpdateCompactionFilter
|
||||
// updated the values of KEY2 and KEY3, and not KEY4 and KEY5.
|
||||
ASSERT_EQ(Get(KEY1), NOT_FOUND);
|
||||
ASSERT_EQ(Get(KEY2), NEW_VALUE);
|
||||
ASSERT_EQ(Get(KEY3), NEW_VALUE);
|
||||
ASSERT_EQ(Get(KEY4), p_v4);
|
||||
ASSERT_EQ(Get(KEY5), p_v5);
|
||||
}
|
||||
|
||||
TEST_F(DBFlushTest, MemPurgeWALSupport) {
|
||||
Options options = CurrentOptions();
|
||||
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.statistics->set_stats_level(StatsLevel::kAll);
|
||||
options.create_if_missing = true;
|
||||
options.compression = kNoCompression;
|
||||
options.inplace_update_support = false;
|
||||
options.allow_concurrent_memtable_write = true;
|
||||
|
||||
// Enforce size of a single MemTable to 1MB.
|
||||
options.write_buffer_size = 128 << 10;
|
||||
// Activate the MemPurge prototype.
|
||||
options.experimental_allow_mempurge = true;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
const size_t KVSIZE = 10;
|
||||
|
||||
do {
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
ASSERT_OK(Put(1, "foo", "v1"));
|
||||
ASSERT_OK(Put(1, "baz", "v5"));
|
||||
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
||||
ASSERT_EQ("v1", Get(1, "foo"));
|
||||
|
||||
ASSERT_EQ("v1", Get(1, "foo"));
|
||||
ASSERT_EQ("v5", Get(1, "baz"));
|
||||
ASSERT_OK(Put(0, "bar", "v2"));
|
||||
ASSERT_OK(Put(1, "bar", "v2"));
|
||||
ASSERT_OK(Put(1, "foo", "v3"));
|
||||
uint32_t mempurge_count = 0;
|
||||
uint32_t sst_count = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::FlushJob:MemPurgeSuccessful",
|
||||
[&](void* /*arg*/) { mempurge_count++; });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::FlushJob:SSTFileCreated", [&](void* /*arg*/) { sst_count++; });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::vector<std::string> keys;
|
||||
for (size_t k = 0; k < KVSIZE; k++) {
|
||||
keys.push_back("IamKey" + std::to_string(k));
|
||||
}
|
||||
|
||||
std::string RNDKEY, RNDVALUE;
|
||||
const std::string NOT_FOUND = "NOT_FOUND";
|
||||
|
||||
// Heavy overwrite workload,
|
||||
// more than would fit in maximum allowed memtables.
|
||||
Random rnd(719);
|
||||
const size_t NUM_REPEAT = 100;
|
||||
const size_t RAND_KEY_LENGTH = 8192;
|
||||
const size_t RAND_VALUES_LENGTH = 1024;
|
||||
std::vector<std::string> values_default(KVSIZE), values_pikachu(KVSIZE);
|
||||
|
||||
// Insert a very first set of keys that will be
|
||||
// mempurged at least once.
|
||||
for (size_t k = 0; k < KVSIZE / 2; k++) {
|
||||
values_default[k] = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
values_pikachu[k] = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
}
|
||||
|
||||
// Insert keys[0:KVSIZE/2] to
|
||||
// both 'default' and 'pikachu' CFs.
|
||||
for (size_t k = 0; k < KVSIZE / 2; k++) {
|
||||
ASSERT_OK(Put(0, keys[k], values_default[k]));
|
||||
ASSERT_OK(Put(1, keys[k], values_pikachu[k]));
|
||||
}
|
||||
|
||||
// Check that the insertion was seamless.
|
||||
for (size_t k = 0; k < KVSIZE / 2; k++) {
|
||||
ASSERT_EQ(Get(0, keys[k]), values_default[k]);
|
||||
ASSERT_EQ(Get(1, keys[k]), values_pikachu[k]);
|
||||
}
|
||||
|
||||
// Insertion of of K-V pairs, multiple times (overwrites)
|
||||
// into 'default' CF. Will trigger mempurge.
|
||||
for (size_t j = 0; j < NUM_REPEAT; j++) {
|
||||
// Create value strings of arbitrary length RAND_VALUES_LENGTH bytes.
|
||||
for (size_t k = KVSIZE / 2; k < KVSIZE; k++) {
|
||||
values_default[k] = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
}
|
||||
|
||||
// Insert K-V into default CF.
|
||||
for (size_t k = KVSIZE / 2; k < KVSIZE; k++) {
|
||||
ASSERT_OK(Put(0, keys[k], values_default[k]));
|
||||
}
|
||||
|
||||
// Check key validity, for all keys, both in
|
||||
// default and pikachu CFs.
|
||||
for (size_t k = 0; k < KVSIZE; k++) {
|
||||
ASSERT_EQ(Get(0, keys[k]), values_default[k]);
|
||||
}
|
||||
// Note that at this point, only keys[0:KVSIZE/2]
|
||||
// have been inserted into Pikachu.
|
||||
for (size_t k = 0; k < KVSIZE / 2; k++) {
|
||||
ASSERT_EQ(Get(1, keys[k]), values_pikachu[k]);
|
||||
}
|
||||
}
|
||||
|
||||
// Insertion of of K-V pairs, multiple times (overwrites)
|
||||
// into 'pikachu' CF. Will trigger mempurge.
|
||||
// Check that we keep the older logs for 'default' imm().
|
||||
for (size_t j = 0; j < NUM_REPEAT; j++) {
|
||||
// Create value strings of arbitrary length RAND_VALUES_LENGTH bytes.
|
||||
for (size_t k = KVSIZE / 2; k < KVSIZE; k++) {
|
||||
values_pikachu[k] = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
}
|
||||
|
||||
// Insert K-V into pikachu CF.
|
||||
for (size_t k = KVSIZE / 2; k < KVSIZE; k++) {
|
||||
ASSERT_OK(Put(1, keys[k], values_pikachu[k]));
|
||||
}
|
||||
|
||||
// Check key validity, for all keys,
|
||||
// both in default and pikachu.
|
||||
for (size_t k = 0; k < KVSIZE; k++) {
|
||||
ASSERT_EQ(Get(0, keys[k]), values_default[k]);
|
||||
ASSERT_EQ(Get(1, keys[k]), values_pikachu[k]);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that there was at least one mempurge
|
||||
const uint32_t EXPECTED_MIN_MEMPURGE_COUNT = 1;
|
||||
// Check that there was no SST files created during flush.
|
||||
const uint32_t EXPECTED_SST_COUNT = 0;
|
||||
|
||||
EXPECT_GE(mempurge_count, EXPECTED_MIN_MEMPURGE_COUNT);
|
||||
EXPECT_EQ(sst_count, EXPECTED_SST_COUNT);
|
||||
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
||||
// Check that there was no data corruption anywhere,
|
||||
// not in 'default' nor in 'Pikachu' CFs.
|
||||
ASSERT_EQ("v3", Get(1, "foo"));
|
||||
ASSERT_OK(Put(1, "foo", "v4"));
|
||||
ASSERT_EQ("v4", Get(1, "foo"));
|
||||
ASSERT_EQ("v2", Get(1, "bar"));
|
||||
ASSERT_EQ("v5", Get(1, "baz"));
|
||||
// Check keys in 'Default' and 'Pikachu'.
|
||||
// keys[0:KVSIZE/2] were for sure contained
|
||||
// in the imm() at Reopen/recovery time.
|
||||
for (size_t k = 0; k < KVSIZE; k++) {
|
||||
ASSERT_EQ(Get(0, keys[k]), values_default[k]);
|
||||
ASSERT_EQ(Get(1, keys[k]), values_pikachu[k]);
|
||||
}
|
||||
// Insertion of random K-V pairs to trigger
|
||||
// a flush in the Pikachu CF.
|
||||
for (size_t j = 0; j < NUM_REPEAT; j++) {
|
||||
RNDKEY = rnd.RandomString(RAND_KEY_LENGTH);
|
||||
RNDVALUE = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
ASSERT_OK(Put(1, RNDKEY, RNDVALUE));
|
||||
}
|
||||
// ASsert than there was at least one flush to storage.
|
||||
EXPECT_GT(sst_count, EXPECTED_SST_COUNT);
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
||||
ASSERT_EQ("v4", Get(1, "foo"));
|
||||
ASSERT_EQ("v2", Get(1, "bar"));
|
||||
ASSERT_EQ("v5", Get(1, "baz"));
|
||||
// Since values in default are held in mutable mem()
|
||||
// and imm(), check if the flush in pikachu didn't
|
||||
// affect these values.
|
||||
for (size_t k = 0; k < KVSIZE; k++) {
|
||||
ASSERT_EQ(Get(0, keys[k]), values_default[k]);
|
||||
ASSERT_EQ(Get(1, keys[k]), values_pikachu[k]);
|
||||
}
|
||||
ASSERT_EQ(Get(1, RNDKEY), RNDVALUE);
|
||||
} while (ChangeWalOptions());
|
||||
}
|
||||
|
||||
TEST_P(DBFlushDirectIOTest, DirectIO) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
|
||||
+83
-12
@@ -63,6 +63,7 @@
|
||||
#include "memtable/hash_linklist_rep.h"
|
||||
#include "memtable/hash_skiplist_rep.h"
|
||||
#include "monitoring/in_memory_stats_history.h"
|
||||
#include "monitoring/instrumented_mutex.h"
|
||||
#include "monitoring/iostats_context_imp.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "monitoring/persistent_stats_history.h"
|
||||
@@ -548,12 +549,45 @@ Status DBImpl::CloseHelper() {
|
||||
flush_scheduler_.Clear();
|
||||
trim_history_scheduler_.Clear();
|
||||
|
||||
// For now, simply trigger a manual flush at close time
|
||||
// on all the column families.
|
||||
// TODO(bjlemaire): Check if this is needed. Also, in the
|
||||
// future we can contemplate doing a more fine-grained
|
||||
// flushing by first checking if there is a need for
|
||||
// flushing (but need to implement something
|
||||
// else than imm()->IsFlushPending() because the output
|
||||
// memtables added to imm() dont trigger flushes).
|
||||
if (immutable_db_options_.experimental_allow_mempurge) {
|
||||
Status flush_ret;
|
||||
mutex_.Unlock();
|
||||
for (ColumnFamilyData* cf : *versions_->GetColumnFamilySet()) {
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
flush_ret = AtomicFlushMemTables({cf}, FlushOptions(),
|
||||
FlushReason::kManualFlush);
|
||||
if (!flush_ret.ok()) {
|
||||
ROCKS_LOG_INFO(
|
||||
immutable_db_options_.info_log,
|
||||
"Atomic flush memtables failed upon closing (mempurge).");
|
||||
}
|
||||
} else {
|
||||
flush_ret =
|
||||
FlushMemTable(cf, FlushOptions(), FlushReason::kManualFlush);
|
||||
if (!flush_ret.ok()) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Flush memtables failed upon closing (mempurge).");
|
||||
}
|
||||
}
|
||||
}
|
||||
mutex_.Lock();
|
||||
}
|
||||
|
||||
while (!flush_queue_.empty()) {
|
||||
const FlushRequest& flush_req = PopFirstFromFlushQueue();
|
||||
for (const auto& iter : flush_req) {
|
||||
iter.first->UnrefAndTryDelete();
|
||||
}
|
||||
}
|
||||
|
||||
while (!compaction_queue_.empty()) {
|
||||
auto cfd = PopFirstFromCompactionQueue();
|
||||
cfd->UnrefAndTryDelete();
|
||||
@@ -911,18 +945,31 @@ void DBImpl::DumpStats() {
|
||||
if (shutdown_initiated_) {
|
||||
return;
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT("DBImpl::DumpStats:StartRunning");
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (cfd->initialized()) {
|
||||
// Release DB mutex for gathering cache entry stats. Pass over all
|
||||
// column families for this first so that other stats are dumped
|
||||
// near-atomically.
|
||||
InstrumentedMutexUnlock u(&mutex_);
|
||||
cfd->internal_stats()->CollectCacheEntryStats(/*foreground=*/false);
|
||||
}
|
||||
}
|
||||
|
||||
const std::string* property = &DB::Properties::kDBStats;
|
||||
const DBPropertyInfo* property_info = GetPropertyInfo(*property);
|
||||
assert(property_info != nullptr);
|
||||
assert(!property_info->need_out_of_mutex);
|
||||
default_cf_internal_stats_->GetStringProperty(*property_info, *property,
|
||||
&stats);
|
||||
|
||||
property = &DB::Properties::kCFStatsNoFileHistogram;
|
||||
property_info = GetPropertyInfo(*property);
|
||||
assert(property_info != nullptr);
|
||||
assert(!property_info->need_out_of_mutex);
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (cfd->initialized()) {
|
||||
cfd->internal_stats()->GetStringProperty(*property_info, *property,
|
||||
@@ -933,6 +980,7 @@ void DBImpl::DumpStats() {
|
||||
property = &DB::Properties::kCFFileHistogram;
|
||||
property_info = GetPropertyInfo(*property);
|
||||
assert(property_info != nullptr);
|
||||
assert(!property_info->need_out_of_mutex);
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (cfd->initialized()) {
|
||||
cfd->internal_stats()->GetStringProperty(*property_info, *property,
|
||||
@@ -3231,16 +3279,21 @@ bool DBImpl::GetProperty(ColumnFamilyHandle* column_family,
|
||||
}
|
||||
return ret_value;
|
||||
} else if (property_info->handle_string) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
return cfd->internal_stats()->GetStringProperty(*property_info, property,
|
||||
value);
|
||||
} else if (property_info->handle_string_dbimpl) {
|
||||
std::string tmp_value;
|
||||
bool ret_value = (this->*(property_info->handle_string_dbimpl))(&tmp_value);
|
||||
if (ret_value) {
|
||||
*value = tmp_value;
|
||||
if (property_info->need_out_of_mutex) {
|
||||
return cfd->internal_stats()->GetStringProperty(*property_info, property,
|
||||
value);
|
||||
} else {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
return cfd->internal_stats()->GetStringProperty(*property_info, property,
|
||||
value);
|
||||
}
|
||||
} else if (property_info->handle_string_dbimpl) {
|
||||
if (property_info->need_out_of_mutex) {
|
||||
return (this->*(property_info->handle_string_dbimpl))(value);
|
||||
} else {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
return (this->*(property_info->handle_string_dbimpl))(value);
|
||||
}
|
||||
return ret_value;
|
||||
}
|
||||
// Shouldn't reach here since exactly one of handle_string and handle_int
|
||||
// should be non-nullptr.
|
||||
@@ -3258,9 +3311,14 @@ bool DBImpl::GetMapProperty(ColumnFamilyHandle* column_family,
|
||||
if (property_info == nullptr) {
|
||||
return false;
|
||||
} else if (property_info->handle_map) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
return cfd->internal_stats()->GetMapProperty(*property_info, property,
|
||||
value);
|
||||
if (property_info->need_out_of_mutex) {
|
||||
return cfd->internal_stats()->GetMapProperty(*property_info, property,
|
||||
value);
|
||||
} else {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
return cfd->internal_stats()->GetMapProperty(*property_info, property,
|
||||
value);
|
||||
}
|
||||
}
|
||||
// If we reach this point it means that handle_map is not provided for the
|
||||
// requested property
|
||||
@@ -3709,6 +3767,8 @@ Status DBImpl::DeleteFilesInRanges(ColumnFamilyHandle* column_family,
|
||||
deleted_files.insert(level_file);
|
||||
level_file->being_compacted = true;
|
||||
}
|
||||
vstorage->ComputeCompactionScore(*cfd->ioptions(),
|
||||
*cfd->GetLatestMutableCFOptions());
|
||||
}
|
||||
}
|
||||
if (edit.GetDeletedFiles().empty()) {
|
||||
@@ -3772,6 +3832,17 @@ void DBImpl::GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
|
||||
ReturnAndCleanupSuperVersion(cfd, sv);
|
||||
}
|
||||
|
||||
void DBImpl::GetAllColumnFamilyMetaData(
|
||||
std::vector<ColumnFamilyMetaData>* metadata) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
for (auto cfd : *(versions_->GetColumnFamilySet())) {
|
||||
{
|
||||
metadata->emplace_back();
|
||||
cfd->current()->GetColumnFamilyMetaData(&metadata->back());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
Status DBImpl::CheckConsistency() {
|
||||
|
||||
+10
-10
@@ -20,6 +20,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "db/column_family.h"
|
||||
#include "db/compaction/compaction_iterator.h"
|
||||
#include "db/compaction/compaction_job.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/error_handler.h"
|
||||
@@ -53,6 +54,7 @@
|
||||
#include "rocksdb/trace_reader_writer.h"
|
||||
#include "rocksdb/transaction_log.h"
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
#include "table/merging_iterator.h"
|
||||
#include "table/scoped_arena_iterator.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/hash.h"
|
||||
@@ -400,12 +402,13 @@ class DBImpl : public DB {
|
||||
FileChecksumList* checksum_list) override;
|
||||
|
||||
// Obtains the meta data of the specified column family of the DB.
|
||||
// Status::NotFound() will be returned if the current DB does not have
|
||||
// any column family match the specified name.
|
||||
// TODO(yhchiang): output parameter is placed in the end in this codebase.
|
||||
virtual void GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
|
||||
ColumnFamilyMetaData* metadata) override;
|
||||
|
||||
void GetAllColumnFamilyMetaData(
|
||||
std::vector<ColumnFamilyMetaData>* metadata) override;
|
||||
|
||||
Status SuggestCompactRange(ColumnFamilyHandle* column_family,
|
||||
const Slice* begin, const Slice* end) override;
|
||||
|
||||
@@ -962,7 +965,7 @@ class DBImpl : public DB {
|
||||
|
||||
// Return the maximum overlapping data (in bytes) at next level for any
|
||||
// file at a level >= 1.
|
||||
int64_t TEST_MaxNextLevelOverlappingBytes(
|
||||
uint64_t TEST_MaxNextLevelOverlappingBytes(
|
||||
ColumnFamilyHandle* column_family = nullptr);
|
||||
|
||||
// Return the current manifest file no.
|
||||
@@ -974,8 +977,10 @@ class DBImpl : public DB {
|
||||
// get total level0 file size. Only for testing.
|
||||
uint64_t TEST_GetLevel0TotalSize();
|
||||
|
||||
void TEST_GetFilesMetaData(ColumnFamilyHandle* column_family,
|
||||
std::vector<std::vector<FileMetaData>>* metadata);
|
||||
void TEST_GetFilesMetaData(
|
||||
ColumnFamilyHandle* column_family,
|
||||
std::vector<std::vector<FileMetaData>>* metadata,
|
||||
std::vector<std::shared_ptr<BlobFileMetaData>>* blob_metadata = nullptr);
|
||||
|
||||
void TEST_LockMutex();
|
||||
|
||||
@@ -1184,11 +1189,6 @@ class DBImpl : public DB {
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice* begin, const Slice* end);
|
||||
|
||||
Status GetApproximateSizesInternal(const SizeApproximationOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Range* range, int n,
|
||||
uint64_t* sizes);
|
||||
|
||||
// The following two functions can only be called when:
|
||||
// 1. WriteThread::Writer::EnterUnbatched() is used.
|
||||
// 2. db_mutex is NOT held
|
||||
|
||||
@@ -151,7 +151,6 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
assert(cfd);
|
||||
assert(cfd->imm()->NumNotFlushed() != 0);
|
||||
assert(cfd->imm()->IsFlushPending());
|
||||
|
||||
FlushJob flush_job(
|
||||
dbname_, cfd, immutable_db_options_, mutable_cf_options,
|
||||
port::kMaxUint64 /* memtable_id */, file_options_for_compaction_,
|
||||
@@ -591,6 +590,8 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
autovector<const autovector<MemTable*>*> mems_list;
|
||||
autovector<const MutableCFOptions*> mutable_cf_options_list;
|
||||
autovector<FileMetaData*> tmp_file_meta;
|
||||
autovector<std::list<std::unique_ptr<FlushJobInfo>>*>
|
||||
committed_flush_jobs_info;
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
const auto& mems = jobs[i]->GetMemTables();
|
||||
if (!cfds[i]->IsDropped() && !mems.empty()) {
|
||||
@@ -598,13 +599,18 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
mems_list.emplace_back(&mems);
|
||||
mutable_cf_options_list.emplace_back(&all_mutable_cf_options[i]);
|
||||
tmp_file_meta.emplace_back(&file_meta[i]);
|
||||
#ifndef ROCKSDB_LITE
|
||||
committed_flush_jobs_info.emplace_back(
|
||||
jobs[i]->GetCommittedFlushJobsInfo());
|
||||
#endif //! ROCKSDB_LITE
|
||||
}
|
||||
}
|
||||
|
||||
s = InstallMemtableAtomicFlushResults(
|
||||
nullptr /* imm_lists */, tmp_cfds, mutable_cf_options_list, mems_list,
|
||||
versions_.get(), &logs_with_prep_tracker_, &mutex_, tmp_file_meta,
|
||||
&job_context->memtables_to_free, directories_.GetDbDir(), log_buffer);
|
||||
committed_flush_jobs_info, &job_context->memtables_to_free,
|
||||
directories_.GetDbDir(), log_buffer);
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
@@ -1395,6 +1401,8 @@ void DBImpl::NotifyOnCompactionBegin(ColumnFamilyData* cfd, Compaction* c,
|
||||
manual_compaction_paused_.load(std::memory_order_acquire) > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
c->SetNotifyOnCompactionCompleted();
|
||||
Version* current = cfd->current();
|
||||
current->Ref();
|
||||
// release lock while notifying events
|
||||
@@ -1430,10 +1438,8 @@ void DBImpl::NotifyOnCompactionCompleted(
|
||||
if (shutting_down_.load(std::memory_order_acquire)) {
|
||||
return;
|
||||
}
|
||||
// TODO: Should disabling manual compaction squash compaction completed
|
||||
// notifications that aren't the result of a shutdown?
|
||||
if (c->is_manual_compaction() &&
|
||||
manual_compaction_paused_.load(std::memory_order_acquire) > 0) {
|
||||
|
||||
if (c->ShouldNotifyOnCompactionCompleted() == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2398,6 +2404,17 @@ void DBImpl::SchedulePendingFlush(const FlushRequest& flush_req,
|
||||
assert(flush_req.size() == 1);
|
||||
ColumnFamilyData* cfd = flush_req[0].first;
|
||||
assert(cfd);
|
||||
// Note: SchedulePendingFlush is always preceded
|
||||
// with an imm()->FlushRequested() call. However,
|
||||
// we want to make this code snipper more resilient to
|
||||
// future changes. Therefore, we add the following if
|
||||
// statement - note that calling it twice (or more)
|
||||
// doesn't break anything.
|
||||
if (immutable_db_options_.experimental_allow_mempurge) {
|
||||
// If imm() contains silent memtables,
|
||||
// requesting a flush will mark the imm_needed as true.
|
||||
cfd->imm()->FlushRequested();
|
||||
}
|
||||
if (!cfd->queued_for_flush() && cfd->imm()->IsFlushPending()) {
|
||||
cfd->Ref();
|
||||
cfd->set_queued_for_flush(true);
|
||||
@@ -2539,6 +2556,11 @@ Status DBImpl::BackgroundFlush(bool* made_progress, JobContext* job_context,
|
||||
|
||||
for (const auto& iter : flush_req) {
|
||||
ColumnFamilyData* cfd = iter.first;
|
||||
if (immutable_db_options_.experimental_allow_mempurge) {
|
||||
// If imm() contains silent memtables,
|
||||
// requesting a flush will mark the imm_needed as true.
|
||||
cfd->imm()->FlushRequested();
|
||||
}
|
||||
if (cfd->IsDropped() || !cfd->imm()->IsFlushPending()) {
|
||||
// can't flush this CF, try next one
|
||||
column_families_not_to_flush.push_back(cfd);
|
||||
@@ -3452,7 +3474,7 @@ void DBImpl::InstallSuperVersionAndScheduleWork(
|
||||
if (UNLIKELY(sv_context->new_superversion == nullptr)) {
|
||||
sv_context->NewSuperVersion();
|
||||
}
|
||||
cfd->InstallSuperVersion(sv_context, &mutex_, mutable_cf_options);
|
||||
cfd->InstallSuperVersion(sv_context, mutable_cf_options);
|
||||
|
||||
// There may be a small data race here. The snapshot tricking bottommost
|
||||
// compaction may already be released here. But assuming there will always be
|
||||
|
||||
@@ -43,7 +43,7 @@ bool DBImpl::TEST_WALBufferIsEmpty(bool lock) {
|
||||
return res;
|
||||
}
|
||||
|
||||
int64_t DBImpl::TEST_MaxNextLevelOverlappingBytes(
|
||||
uint64_t DBImpl::TEST_MaxNextLevelOverlappingBytes(
|
||||
ColumnFamilyHandle* column_family) {
|
||||
ColumnFamilyData* cfd;
|
||||
if (column_family == nullptr) {
|
||||
@@ -58,7 +58,8 @@ int64_t DBImpl::TEST_MaxNextLevelOverlappingBytes(
|
||||
|
||||
void DBImpl::TEST_GetFilesMetaData(
|
||||
ColumnFamilyHandle* column_family,
|
||||
std::vector<std::vector<FileMetaData>>* metadata) {
|
||||
std::vector<std::vector<FileMetaData>>* metadata,
|
||||
std::vector<std::shared_ptr<BlobFileMetaData>>* blob_metadata) {
|
||||
auto cfh = static_cast_with_check<ColumnFamilyHandleImpl>(column_family);
|
||||
auto cfd = cfh->cfd();
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
@@ -72,6 +73,12 @@ void DBImpl::TEST_GetFilesMetaData(
|
||||
(*metadata)[level].push_back(*f);
|
||||
}
|
||||
}
|
||||
if (blob_metadata != nullptr) {
|
||||
blob_metadata->clear();
|
||||
for (const auto& blob : cfd->current()->storage_info()->GetBlobFiles()) {
|
||||
blob_metadata->push_back(blob.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t DBImpl::TEST_Current_Manifest_FileNo() {
|
||||
|
||||
@@ -300,7 +300,8 @@ Status DBImpl::NewDB(std::vector<std::string>* new_filenames) {
|
||||
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
|
||||
std::move(file), manifest, file_options, immutable_db_options_.clock,
|
||||
io_tracer_, nullptr /* stats */, immutable_db_options_.listeners,
|
||||
nullptr, tmp_set.Contains(FileType::kDescriptorFile)));
|
||||
nullptr, tmp_set.Contains(FileType::kDescriptorFile),
|
||||
tmp_set.Contains(FileType::kDescriptorFile)));
|
||||
log::Writer log(std::move(file_writer), 0, false);
|
||||
std::string record;
|
||||
new_db.EncodeTo(&record);
|
||||
@@ -629,7 +630,7 @@ Status DBImpl::Recover(
|
||||
// Clear memtables if recovery failed
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
cfd->CreateNewMemtable(*cfd->GetLatestMutableCFOptions(),
|
||||
kMaxSequenceNumber);
|
||||
kMaxSequenceNumber, cfd->GetLogNumber());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1065,7 +1066,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& wal_numbers,
|
||||
flushed = true;
|
||||
|
||||
cfd->CreateNewMemtable(*cfd->GetLatestMutableCFOptions(),
|
||||
*next_sequence);
|
||||
*next_sequence, cfd->GetLogNumber());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1203,7 +1204,8 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& wal_numbers,
|
||||
flushed = true;
|
||||
|
||||
cfd->CreateNewMemtable(*cfd->GetLatestMutableCFOptions(),
|
||||
versions_->LastSequence());
|
||||
versions_->LastSequence(),
|
||||
cfd->GetLogNumber());
|
||||
}
|
||||
data_seen = true;
|
||||
}
|
||||
@@ -1540,7 +1542,8 @@ IOStatus DBImpl::CreateWAL(uint64_t log_file_num, uint64_t recycle_log_number,
|
||||
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
|
||||
std::move(lfile), log_fname, opt_file_options,
|
||||
immutable_db_options_.clock, io_tracer_, nullptr /* stats */, listeners,
|
||||
nullptr, tmp_set.Contains(FileType::kWalFile)));
|
||||
nullptr, tmp_set.Contains(FileType::kWalFile),
|
||||
tmp_set.Contains(FileType::kWalFile)));
|
||||
*new_log = new log::Writer(std::move(file_writer), log_file_num,
|
||||
immutable_db_options_.recycle_log_file_num > 0,
|
||||
immutable_db_options_.manual_wal_flush);
|
||||
|
||||
@@ -253,8 +253,8 @@ Status DBImplSecondary::RecoverLogFiles(
|
||||
curr_log_num != log_number)) {
|
||||
const MutableCFOptions mutable_cf_options =
|
||||
*cfd->GetLatestMutableCFOptions();
|
||||
MemTable* new_mem =
|
||||
cfd->ConstructNewMemtable(mutable_cf_options, seq_of_batch);
|
||||
MemTable* new_mem = cfd->ConstructNewMemtable(
|
||||
mutable_cf_options, seq_of_batch, log_number);
|
||||
cfd->mem()->SetNextLogNumber(log_number);
|
||||
cfd->imm()->Add(cfd->mem(), &job_context->memtables_to_free);
|
||||
new_mem->Ref();
|
||||
@@ -767,6 +767,7 @@ Status DB::OpenAndCompact(
|
||||
compaction_input.db_options.env = override_options.env;
|
||||
compaction_input.db_options.file_checksum_gen_factory =
|
||||
override_options.file_checksum_gen_factory;
|
||||
compaction_input.db_options.statistics = override_options.statistics;
|
||||
compaction_input.column_family.options.comparator =
|
||||
override_options.comparator;
|
||||
compaction_input.column_family.options.merge_operator =
|
||||
|
||||
@@ -278,6 +278,16 @@ class DBImplSecondary : public DBImpl {
|
||||
return AddColumnFamilyId(column_family_id);
|
||||
}
|
||||
|
||||
Status MarkBeginPrepare(bool) override { return Status::OK(); }
|
||||
|
||||
Status MarkEndPrepare(const Slice&) override { return Status::OK(); }
|
||||
|
||||
Status MarkRollback(const Slice&) override { return Status::OK(); }
|
||||
|
||||
Status MarkCommit(const Slice&) override { return Status::OK(); }
|
||||
|
||||
Status MarkNoop(bool) override { return Status::OK(); }
|
||||
|
||||
const std::unordered_set<uint32_t>& column_families() const {
|
||||
return column_family_ids_;
|
||||
}
|
||||
|
||||
@@ -251,6 +251,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
write_thread_.EnterAsBatchGroupLeader(&w, &write_group);
|
||||
|
||||
IOStatus io_s;
|
||||
Status pre_release_cb_status;
|
||||
if (status.ok()) {
|
||||
// Rules for when we can update the memtable concurrently
|
||||
// 1. supported by memtable
|
||||
@@ -361,7 +362,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
writer->sequence, disable_memtable, writer->log_used, index++,
|
||||
pre_release_callback_cnt);
|
||||
if (!ws.ok()) {
|
||||
status = ws;
|
||||
status = pre_release_cb_status = ws;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -414,10 +415,13 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
|
||||
if (!w.CallbackFailed()) {
|
||||
if (!io_s.ok()) {
|
||||
assert(pre_release_cb_status.ok());
|
||||
IOStatusCheck(io_s);
|
||||
} else {
|
||||
WriteStatusCheck(status);
|
||||
WriteStatusCheck(pre_release_cb_status);
|
||||
}
|
||||
} else {
|
||||
assert(io_s.ok() && pre_release_cb_status.ok());
|
||||
}
|
||||
|
||||
if (need_log_sync) {
|
||||
@@ -1801,7 +1805,8 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
}
|
||||
if (s.ok()) {
|
||||
SequenceNumber seq = versions_->LastSequence();
|
||||
new_mem = cfd->ConstructNewMemtable(mutable_cf_options, seq);
|
||||
new_mem =
|
||||
cfd->ConstructNewMemtable(mutable_cf_options, seq, new_log_number);
|
||||
context->superversion_context.NewSuperVersion();
|
||||
}
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
@@ -1911,6 +1916,8 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
for (auto cf : empty_cfs) {
|
||||
if (cf->IsEmpty()) {
|
||||
cf->SetLogNumber(logfile_number_);
|
||||
// MEMPURGE: No need to change this, because new adds
|
||||
// should still receive new sequence numbers.
|
||||
cf->mem()->SetCreationSeq(versions_->LastSequence());
|
||||
} // cf may become non-empty.
|
||||
}
|
||||
@@ -1933,11 +1940,13 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
}
|
||||
|
||||
cfd->mem()->SetNextLogNumber(logfile_number_);
|
||||
assert(new_mem != nullptr);
|
||||
cfd->imm()->Add(cfd->mem(), &context->memtables_to_free_);
|
||||
new_mem->Ref();
|
||||
cfd->SetMemtable(new_mem);
|
||||
InstallSuperVersionAndScheduleWork(cfd, &context->superversion_context,
|
||||
mutable_cf_options);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
mutex_.Unlock();
|
||||
// Notify client that memtable is sealed, now that we have successfully
|
||||
|
||||
@@ -1536,9 +1536,11 @@ class DBIteratorTestForPinnedData : public DBIteratorTest {
|
||||
}
|
||||
};
|
||||
|
||||
#if !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
TEST_P(DBIteratorTestForPinnedData, PinnedDataIteratorRandomizedNormal) {
|
||||
PinnedDataIteratorRandomized(TestConfig::NORMAL);
|
||||
}
|
||||
#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
|
||||
TEST_P(DBIteratorTestForPinnedData, PinnedDataIteratorRandomizedCLoseAndOpen) {
|
||||
PinnedDataIteratorRandomized(TestConfig::CLOSE_AND_OPEN);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "db/db_impl/db_impl_secondary.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/utilities/transaction_db.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
|
||||
@@ -1115,6 +1116,39 @@ TEST_F(DBSecondaryTest, InconsistencyDuringCatchUp) {
|
||||
Status s = db_secondary_->TryCatchUpWithPrimary();
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, OpenWithTransactionDB) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
|
||||
// Destroy the DB to recreate as a TransactionDB.
|
||||
Close();
|
||||
Destroy(options, true);
|
||||
|
||||
// Create a TransactionDB.
|
||||
TransactionDB* txn_db = nullptr;
|
||||
TransactionDBOptions txn_db_opts;
|
||||
ASSERT_OK(TransactionDB::Open(options, txn_db_opts, dbname_, &txn_db));
|
||||
ASSERT_NE(txn_db, nullptr);
|
||||
db_ = txn_db;
|
||||
|
||||
std::vector<std::string> cfs = {"new_CF"};
|
||||
CreateColumnFamilies(cfs, options);
|
||||
ASSERT_EQ(handles_.size(), 1);
|
||||
|
||||
WriteOptions wopts;
|
||||
TransactionOptions txn_opts;
|
||||
Transaction* txn1 = txn_db->BeginTransaction(wopts, txn_opts, nullptr);
|
||||
ASSERT_NE(txn1, nullptr);
|
||||
ASSERT_OK(txn1->Put(handles_[0], "k1", "v1"));
|
||||
ASSERT_OK(txn1->Commit());
|
||||
delete txn1;
|
||||
|
||||
options = CurrentOptions();
|
||||
options.max_open_files = -1;
|
||||
ASSERT_OK(TryOpenSecondary(options));
|
||||
}
|
||||
|
||||
#endif //! ROCKSDB_LITE
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
+130
-33
@@ -25,6 +25,7 @@
|
||||
|
||||
#include "cache/lru_cache.h"
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/dbformat.h"
|
||||
@@ -1027,10 +1028,10 @@ TEST_F(DBTest, FailMoreDbPaths) {
|
||||
}
|
||||
|
||||
void CheckColumnFamilyMeta(
|
||||
const ColumnFamilyMetaData& cf_meta,
|
||||
const ColumnFamilyMetaData& cf_meta, const std::string& cf_name,
|
||||
const std::vector<std::vector<FileMetaData>>& files_by_level,
|
||||
uint64_t start_time, uint64_t end_time) {
|
||||
ASSERT_EQ(cf_meta.name, kDefaultColumnFamilyName);
|
||||
ASSERT_EQ(cf_meta.name, cf_name);
|
||||
ASSERT_EQ(cf_meta.levels.size(), files_by_level.size());
|
||||
|
||||
uint64_t cf_size = 0;
|
||||
@@ -1124,6 +1125,53 @@ void CheckLiveFilesMeta(
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
void AddBlobFile(const ColumnFamilyHandle* cfh, uint64_t blob_file_number,
|
||||
uint64_t total_blob_count, uint64_t total_blob_bytes,
|
||||
const std::string& checksum_method,
|
||||
const std::string& checksum_value,
|
||||
uint64_t garbage_blob_count = 0,
|
||||
uint64_t garbage_blob_bytes = 0) {
|
||||
ColumnFamilyData* cfd =
|
||||
(static_cast<const ColumnFamilyHandleImpl*>(cfh))->cfd();
|
||||
assert(cfd);
|
||||
|
||||
Version* const version = cfd->current();
|
||||
assert(version);
|
||||
|
||||
VersionStorageInfo* const storage_info = version->storage_info();
|
||||
assert(storage_info);
|
||||
|
||||
// Add a live blob file.
|
||||
|
||||
auto shared_meta = SharedBlobFileMetaData::Create(
|
||||
blob_file_number, total_blob_count, total_blob_bytes, checksum_method,
|
||||
checksum_value);
|
||||
|
||||
auto meta = BlobFileMetaData::Create(std::move(shared_meta),
|
||||
BlobFileMetaData::LinkedSsts(),
|
||||
garbage_blob_count, garbage_blob_bytes);
|
||||
|
||||
storage_info->AddBlobFile(std::move(meta));
|
||||
}
|
||||
|
||||
static void CheckBlobMetaData(
|
||||
const BlobMetaData& bmd, uint64_t blob_file_number,
|
||||
uint64_t total_blob_count, uint64_t total_blob_bytes,
|
||||
const std::string& checksum_method, const std::string& checksum_value,
|
||||
uint64_t garbage_blob_count = 0, uint64_t garbage_blob_bytes = 0) {
|
||||
ASSERT_EQ(bmd.blob_file_number, blob_file_number);
|
||||
ASSERT_EQ(bmd.blob_file_name, BlobFileName("", blob_file_number));
|
||||
ASSERT_EQ(bmd.blob_file_size,
|
||||
total_blob_bytes + BlobLogHeader::kSize + BlobLogFooter::kSize);
|
||||
|
||||
ASSERT_EQ(bmd.total_blob_count, total_blob_count);
|
||||
ASSERT_EQ(bmd.total_blob_bytes, total_blob_bytes);
|
||||
ASSERT_EQ(bmd.garbage_blob_count, garbage_blob_count);
|
||||
ASSERT_EQ(bmd.garbage_blob_bytes, garbage_blob_bytes);
|
||||
ASSERT_EQ(bmd.checksum_method, checksum_method);
|
||||
ASSERT_EQ(bmd.checksum_value, checksum_value);
|
||||
}
|
||||
|
||||
TEST_F(DBTest, MetaDataTest) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
@@ -1164,13 +1212,71 @@ TEST_F(DBTest, MetaDataTest) {
|
||||
|
||||
ColumnFamilyMetaData cf_meta;
|
||||
db_->GetColumnFamilyMetaData(&cf_meta);
|
||||
CheckColumnFamilyMeta(cf_meta, files_by_level, start_time, end_time);
|
||||
|
||||
CheckColumnFamilyMeta(cf_meta, kDefaultColumnFamilyName, files_by_level,
|
||||
start_time, end_time);
|
||||
std::vector<LiveFileMetaData> live_file_meta;
|
||||
db_->GetLiveFilesMetaData(&live_file_meta);
|
||||
CheckLiveFilesMeta(live_file_meta, files_by_level);
|
||||
}
|
||||
|
||||
TEST_F(DBTest, AllMetaDataTest) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
constexpr uint64_t blob_file_number = 234;
|
||||
constexpr uint64_t total_blob_count = 555;
|
||||
constexpr uint64_t total_blob_bytes = 66666;
|
||||
constexpr char checksum_method[] = "CRC32";
|
||||
constexpr char checksum_value[] = "\x3d\x87\xff\x57";
|
||||
|
||||
int64_t temp_time = 0;
|
||||
options.env->GetCurrentTime(&temp_time).PermitUncheckedError();
|
||||
uint64_t start_time = static_cast<uint64_t>(temp_time);
|
||||
|
||||
Random rnd(301);
|
||||
dbfull()->TEST_LockMutex();
|
||||
for (int cf = 0; cf < 2; cf++) {
|
||||
AddBlobFile(handles_[cf], blob_file_number * (cf + 1),
|
||||
total_blob_count * (cf + 1), total_blob_bytes * (cf + 1),
|
||||
checksum_method, checksum_value);
|
||||
}
|
||||
dbfull()->TEST_UnlockMutex();
|
||||
|
||||
std::vector<ColumnFamilyMetaData> all_meta;
|
||||
db_->GetAllColumnFamilyMetaData(&all_meta);
|
||||
|
||||
std::vector<std::vector<FileMetaData>> default_files_by_level;
|
||||
std::vector<std::vector<FileMetaData>> pikachu_files_by_level;
|
||||
dbfull()->TEST_GetFilesMetaData(handles_[0], &default_files_by_level);
|
||||
dbfull()->TEST_GetFilesMetaData(handles_[1], &pikachu_files_by_level);
|
||||
|
||||
options.env->GetCurrentTime(&temp_time).PermitUncheckedError();
|
||||
uint64_t end_time = static_cast<uint64_t>(temp_time);
|
||||
|
||||
ASSERT_EQ(all_meta.size(), 2);
|
||||
for (int cf = 0; cf < 2; cf++) {
|
||||
const auto& cfmd = all_meta[cf];
|
||||
if (cf == 0) {
|
||||
CheckColumnFamilyMeta(cfmd, "default", default_files_by_level, start_time,
|
||||
end_time);
|
||||
} else {
|
||||
CheckColumnFamilyMeta(cfmd, "pikachu", pikachu_files_by_level, start_time,
|
||||
end_time);
|
||||
}
|
||||
ASSERT_EQ(cfmd.blob_files.size(), 1U);
|
||||
const auto& bmd = cfmd.blob_files[0];
|
||||
ASSERT_EQ(cfmd.blob_file_count, 1U);
|
||||
ASSERT_EQ(cfmd.blob_file_size, bmd.blob_file_size);
|
||||
ASSERT_EQ(NormalizePath(bmd.blob_file_path), NormalizePath(dbname_));
|
||||
CheckBlobMetaData(bmd, blob_file_number * (cf + 1),
|
||||
total_blob_count * (cf + 1), total_blob_bytes * (cf + 1),
|
||||
checksum_method, checksum_value);
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
void MinLevelHelper(DBTest* self, Options& options) {
|
||||
Random rnd(301);
|
||||
@@ -2344,41 +2450,19 @@ TEST_F(DBTest, GetLiveBlobFiles) {
|
||||
Options options = CurrentOptions();
|
||||
options.stats_dump_period_sec = 0;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
VersionSet* const versions = dbfull()->TEST_GetVersionSet();
|
||||
assert(versions);
|
||||
assert(versions->GetColumnFamilySet());
|
||||
|
||||
ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault();
|
||||
assert(cfd);
|
||||
|
||||
Version* const version = cfd->current();
|
||||
assert(version);
|
||||
|
||||
VersionStorageInfo* const storage_info = version->storage_info();
|
||||
assert(storage_info);
|
||||
|
||||
// Add a live blob file.
|
||||
constexpr uint64_t blob_file_number = 234;
|
||||
constexpr uint64_t total_blob_count = 555;
|
||||
constexpr uint64_t total_blob_bytes = 66666;
|
||||
constexpr char checksum_method[] = "CRC32";
|
||||
constexpr char checksum_value[] = "3d87ff57";
|
||||
|
||||
auto shared_meta = SharedBlobFileMetaData::Create(
|
||||
blob_file_number, total_blob_count, total_blob_bytes, checksum_method,
|
||||
checksum_value);
|
||||
|
||||
constexpr char checksum_value[] = "\x3d\x87\xff\x57";
|
||||
constexpr uint64_t garbage_blob_count = 0;
|
||||
constexpr uint64_t garbage_blob_bytes = 0;
|
||||
|
||||
auto meta = BlobFileMetaData::Create(std::move(shared_meta),
|
||||
BlobFileMetaData::LinkedSsts(),
|
||||
garbage_blob_count, garbage_blob_bytes);
|
||||
|
||||
storage_info->AddBlobFile(std::move(meta));
|
||||
Reopen(options);
|
||||
|
||||
AddBlobFile(db_->DefaultColumnFamily(), blob_file_number, total_blob_count,
|
||||
total_blob_bytes, checksum_method, checksum_value,
|
||||
garbage_blob_count, garbage_blob_bytes);
|
||||
// Make sure it appears in the results returned by GetLiveFiles.
|
||||
uint64_t manifest_size = 0;
|
||||
std::vector<std::string> files;
|
||||
@@ -2386,6 +2470,19 @@ TEST_F(DBTest, GetLiveBlobFiles) {
|
||||
|
||||
ASSERT_FALSE(files.empty());
|
||||
ASSERT_EQ(files[0], BlobFileName("", blob_file_number));
|
||||
|
||||
ColumnFamilyMetaData cfmd;
|
||||
|
||||
db_->GetColumnFamilyMetaData(&cfmd);
|
||||
ASSERT_EQ(cfmd.blob_files.size(), 1);
|
||||
const BlobMetaData& bmd = cfmd.blob_files[0];
|
||||
|
||||
CheckBlobMetaData(bmd, blob_file_number, total_blob_count, total_blob_bytes,
|
||||
checksum_method, checksum_value, garbage_blob_count,
|
||||
garbage_blob_bytes);
|
||||
ASSERT_EQ(NormalizePath(bmd.blob_file_path), NormalizePath(dbname_));
|
||||
ASSERT_EQ(cfmd.blob_file_count, 1U);
|
||||
ASSERT_EQ(cfmd.blob_file_size, bmd.blob_file_size);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -3114,7 +3211,7 @@ class ModelDB : public DB {
|
||||
std::string name_ = "";
|
||||
};
|
||||
|
||||
#ifndef ROCKSDB_VALGRIND_RUN
|
||||
#if !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
static std::string RandomKey(Random* rnd, int minimum = 0) {
|
||||
int len;
|
||||
do {
|
||||
@@ -3269,7 +3366,7 @@ TEST_P(DBTestRandomized, Randomized) {
|
||||
if (model_snap != nullptr) model.ReleaseSnapshot(model_snap);
|
||||
if (db_snap != nullptr) db_->ReleaseSnapshot(db_snap);
|
||||
}
|
||||
#endif // ROCKSDB_VALGRIND_RUN
|
||||
#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
|
||||
TEST_F(DBTest, BlockBasedTablePrefixIndexTest) {
|
||||
// create a DB with block prefix index
|
||||
|
||||
+170
@@ -3376,6 +3376,176 @@ TEST_F(DBTest2, CancelManualCompaction2) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
class CancelCompactionListener : public EventListener {
|
||||
public:
|
||||
CancelCompactionListener()
|
||||
: num_compaction_started_(0), num_compaction_ended_(0) {}
|
||||
|
||||
void OnCompactionBegin(DB* /*db*/, const CompactionJobInfo& ci) override {
|
||||
ASSERT_EQ(ci.cf_name, "default");
|
||||
ASSERT_EQ(ci.base_input_level, 0);
|
||||
num_compaction_started_++;
|
||||
}
|
||||
|
||||
void OnCompactionCompleted(DB* /*db*/, const CompactionJobInfo& ci) override {
|
||||
ASSERT_EQ(ci.cf_name, "default");
|
||||
ASSERT_EQ(ci.base_input_level, 0);
|
||||
ASSERT_EQ(ci.status.code(), code_);
|
||||
ASSERT_EQ(ci.status.subcode(), subcode_);
|
||||
num_compaction_ended_++;
|
||||
}
|
||||
|
||||
std::atomic<size_t> num_compaction_started_;
|
||||
std::atomic<size_t> num_compaction_ended_;
|
||||
Status::Code code_;
|
||||
Status::SubCode subcode_;
|
||||
};
|
||||
|
||||
TEST_F(DBTest2, CancelManualCompactionWithListener) {
|
||||
CompactRangeOptions compact_options;
|
||||
auto canceledPtr =
|
||||
std::unique_ptr<std::atomic<bool>>(new std::atomic<bool>{true});
|
||||
compact_options.canceled = canceledPtr.get();
|
||||
compact_options.max_subcompactions = 1;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
CancelCompactionListener* listener = new CancelCompactionListener();
|
||||
options.listeners.emplace_back(listener);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
ASSERT_OK(Put(Key(i + j * 10), rnd.RandomString(50)));
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionIterator:ProcessKV", [&](void* /*arg*/) {
|
||||
compact_options.canceled->store(true, std::memory_order_release);
|
||||
});
|
||||
|
||||
int running_compaction = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionJob::FinishCompactionOutputFile1",
|
||||
[&](void* /*arg*/) { running_compaction++; });
|
||||
|
||||
// Case I: 1 Notify begin compaction, 2 DisableManualCompaction, 3 Compaction
|
||||
// not run, 4 Notify compaction end.
|
||||
listener->code_ = Status::kIncomplete;
|
||||
listener->subcode_ = Status::SubCode::kManualCompactionPaused;
|
||||
|
||||
compact_options.canceled->store(false, std::memory_order_release);
|
||||
dbfull()->CompactRange(compact_options, nullptr, nullptr);
|
||||
dbfull()->TEST_WaitForCompact(true);
|
||||
|
||||
ASSERT_GT(listener->num_compaction_started_, 0);
|
||||
ASSERT_EQ(listener->num_compaction_started_, listener->num_compaction_ended_);
|
||||
ASSERT_EQ(running_compaction, 0);
|
||||
|
||||
listener->num_compaction_started_ = 0;
|
||||
listener->num_compaction_ended_ = 0;
|
||||
|
||||
// Case II: 1 DisableManualCompaction, 2 Notify begin compaction (return
|
||||
// without notifying), 3 Notify compaction end (return without notifying).
|
||||
dbfull()->CompactRange(compact_options, nullptr, nullptr);
|
||||
dbfull()->TEST_WaitForCompact(true);
|
||||
|
||||
ASSERT_EQ(listener->num_compaction_started_, 0);
|
||||
ASSERT_EQ(listener->num_compaction_started_, listener->num_compaction_ended_);
|
||||
ASSERT_EQ(running_compaction, 0);
|
||||
|
||||
// Case III: 1 Notify begin compaction, 2 Compaction in between
|
||||
// 3. DisableManualCompaction, , 4 Notify compaction end.
|
||||
// compact_options.canceled->store(false, std::memory_order_release);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearCallBack(
|
||||
"CompactionIterator:ProcessKV");
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionJob::Run:BeforeVerify", [&](void* /*arg*/) {
|
||||
compact_options.canceled->store(true, std::memory_order_release);
|
||||
});
|
||||
|
||||
listener->code_ = Status::kOk;
|
||||
listener->subcode_ = Status::SubCode::kNone;
|
||||
|
||||
compact_options.canceled->store(false, std::memory_order_release);
|
||||
dbfull()->CompactRange(compact_options, nullptr, nullptr);
|
||||
dbfull()->TEST_WaitForCompact(true);
|
||||
|
||||
ASSERT_GT(listener->num_compaction_started_, 0);
|
||||
ASSERT_EQ(listener->num_compaction_started_, listener->num_compaction_ended_);
|
||||
|
||||
// Compaction job will succeed.
|
||||
ASSERT_GT(running_compaction, 0);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, CompactionOnBottomPriorityWithListener) {
|
||||
int num_levels = 3;
|
||||
const int kNumFilesTrigger = 4;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
env_->SetBackgroundThreads(0, Env::Priority::HIGH);
|
||||
env_->SetBackgroundThreads(0, Env::Priority::LOW);
|
||||
env_->SetBackgroundThreads(1, Env::Priority::BOTTOM);
|
||||
options.env = env_;
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.num_levels = num_levels;
|
||||
options.write_buffer_size = 100 << 10; // 100KB
|
||||
options.target_file_size_base = 32 << 10; // 32KB
|
||||
options.level0_file_num_compaction_trigger = kNumFilesTrigger;
|
||||
// Trigger compaction if size amplification exceeds 110%
|
||||
options.compaction_options_universal.max_size_amplification_percent = 110;
|
||||
|
||||
CancelCompactionListener* listener = new CancelCompactionListener();
|
||||
options.listeners.emplace_back(listener);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
int num_bottom_thread_compaction_scheduled = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:ForwardToBottomPriPool",
|
||||
[&](void* /*arg*/) { num_bottom_thread_compaction_scheduled++; });
|
||||
|
||||
int num_compaction_jobs = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionJob::Run():End",
|
||||
[&](void* /*arg*/) { num_compaction_jobs++; });
|
||||
|
||||
listener->code_ = Status::kOk;
|
||||
listener->subcode_ = Status::SubCode::kNone;
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < 1; ++i) {
|
||||
for (int num = 0; num < kNumFilesTrigger; num++) {
|
||||
int key_idx = 0;
|
||||
GenerateNewFile(&rnd, &key_idx, true /* no_wait */);
|
||||
// use no_wait above because that one waits for flush and compaction. We
|
||||
// don't want to wait for compaction because the full compaction is
|
||||
// intentionally blocked while more files are flushed.
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
}
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_GT(num_bottom_thread_compaction_scheduled, 0);
|
||||
ASSERT_EQ(num_compaction_jobs, 1);
|
||||
ASSERT_GT(listener->num_compaction_started_, 0);
|
||||
ASSERT_EQ(listener->num_compaction_started_, listener->num_compaction_ended_);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, OptimizeForPointLookup) {
|
||||
Options options = CurrentOptions();
|
||||
Close();
|
||||
|
||||
+7
-3
@@ -68,9 +68,13 @@ DBTestBase::DBTestBase(const std::string path, bool env_do_fsync)
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (getenv("ENCRYPTED_ENV")) {
|
||||
std::shared_ptr<EncryptionProvider> provider;
|
||||
Status s = EncryptionProvider::CreateFromString(
|
||||
config_options, std::string("test://") + getenv("ENCRYPTED_ENV"),
|
||||
&provider);
|
||||
std::string provider_id = getenv("ENCRYPTED_ENV");
|
||||
if (provider_id.find("=") == std::string::npos &&
|
||||
!EndsWith(provider_id, "://test")) {
|
||||
provider_id = provider_id + "://test";
|
||||
}
|
||||
EXPECT_OK(EncryptionProvider::CreateFromString(ConfigOptions(), provider_id,
|
||||
&provider));
|
||||
encrypted_env_ = NewEncryptedEnv(mem_env_ ? mem_env_ : base_env, provider);
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
@@ -635,7 +635,7 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionTargetLevel) {
|
||||
ASSERT_EQ("0,0,0,0,1", FilesPerLevel(0));
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_VALGRIND_RUN
|
||||
#if !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
class DBTestUniversalCompactionMultiLevels
|
||||
: public DBTestUniversalCompactionBase {
|
||||
public:
|
||||
@@ -912,7 +912,7 @@ TEST_P(DBTestUniversalCompactionParallel, PickByFileNumberBug) {
|
||||
INSTANTIATE_TEST_CASE_P(Parallel, DBTestUniversalCompactionParallel,
|
||||
::testing::Combine(::testing::Values(1, 10),
|
||||
::testing::Values(false)));
|
||||
#endif // ROCKSDB_VALGRIND_RUN
|
||||
#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
|
||||
TEST_P(DBTestUniversalCompaction, UniversalCompactionOptions) {
|
||||
Options options = CurrentOptions();
|
||||
@@ -1128,7 +1128,7 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionCompressRatio2) {
|
||||
ASSERT_LT(TotalSize(), 120000U * 12 * 0.82 + 120000 * 2);
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_VALGRIND_RUN
|
||||
#if !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
// Test that checks trivial move in universal compaction
|
||||
TEST_P(DBTestUniversalCompaction, UniversalCompactionTrivialMoveTest1) {
|
||||
int32_t trivial_move = 0;
|
||||
@@ -1221,7 +1221,7 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionTrivialMoveTest2) {
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
#endif // ROCKSDB_VALGRIND_RUN
|
||||
#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
|
||||
TEST_P(DBTestUniversalCompaction, UniversalCompactionFourPaths) {
|
||||
Options options = CurrentOptions();
|
||||
|
||||
@@ -1494,6 +1494,7 @@ TEST_F(DBBasicTestWithTimestamp, CompactDeletionWithTimestampMarkerToBottom) {
|
||||
Close();
|
||||
}
|
||||
|
||||
#if !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
class DBBasicTestWithTimestampFilterPrefixSettings
|
||||
: public DBBasicTestWithTimestampBase,
|
||||
public testing::WithParamInterface<
|
||||
@@ -1625,6 +1626,7 @@ INSTANTIATE_TEST_CASE_P(
|
||||
BlockBasedTableOptions::IndexType::kHashSearch,
|
||||
BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch,
|
||||
BlockBasedTableOptions::IndexType::kBinarySearchWithFirstKey)));
|
||||
#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
|
||||
class DataVisibilityTest : public DBBasicTestWithTimestampBase {
|
||||
public:
|
||||
@@ -2215,6 +2217,7 @@ TEST_F(DataVisibilityTest, MultiGetCrossCF) {
|
||||
Close();
|
||||
}
|
||||
|
||||
#if !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
class DBBasicTestWithTimestampCompressionSettings
|
||||
: public DBBasicTestWithTimestampBase,
|
||||
public testing::WithParamInterface<
|
||||
@@ -2954,6 +2957,7 @@ INSTANTIATE_TEST_CASE_P(
|
||||
BlockBasedTableOptions::IndexType::kHashSearch,
|
||||
BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch,
|
||||
BlockBasedTableOptions::IndexType::kBinarySearchWithFirstKey)));
|
||||
#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
|
||||
@@ -1398,6 +1398,7 @@ TEST_F(ExternalSSTFileTest, IngestNonExistingFile) {
|
||||
ASSERT_EQ(1, num_sst_files);
|
||||
}
|
||||
|
||||
#if !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
TEST_F(ExternalSSTFileTest, CompactDuringAddFileRandom) {
|
||||
env_->skip_fsync_ = true;
|
||||
Options options = CurrentOptions();
|
||||
@@ -1455,6 +1456,7 @@ TEST_F(ExternalSSTFileTest, CompactDuringAddFileRandom) {
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
|
||||
TEST_F(ExternalSSTFileTest, PickedLevelDynamic) {
|
||||
env_->skip_fsync_ = true;
|
||||
@@ -1716,6 +1718,7 @@ TEST_F(ExternalSSTFileTest, WithUnorderedWrite) {
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
#if !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoRandomized) {
|
||||
env_->skip_fsync_ = true;
|
||||
Options options = CurrentOptions();
|
||||
@@ -1757,6 +1760,7 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoRandomized) {
|
||||
VerifyDBFromMap(true_data, &kcnt, false);
|
||||
}
|
||||
}
|
||||
#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
|
||||
TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedLevel) {
|
||||
Options options = CurrentOptions();
|
||||
|
||||
+290
-5
@@ -227,9 +227,33 @@ Status FlushJob::Run(LogsWithPrepTracker* prep_tracker,
|
||||
prev_cpu_write_nanos = IOSTATS(cpu_write_nanos);
|
||||
prev_cpu_read_nanos = IOSTATS(cpu_read_nanos);
|
||||
}
|
||||
|
||||
// This will release and re-acquire the mutex.
|
||||
Status s = WriteLevel0Table();
|
||||
Status mempurge_s = Status::NotFound("No MemPurge.");
|
||||
if (db_options_.experimental_allow_mempurge &&
|
||||
(cfd_->GetFlushReason() == FlushReason::kWriteBufferFull) &&
|
||||
(!mems_.empty())) {
|
||||
mempurge_s = MemPurge();
|
||||
if (!mempurge_s.ok()) {
|
||||
// Mempurge is typically aborted when the new_mem output memtable
|
||||
// is filled at more than XX % capacity (currently: 60%).
|
||||
if (mempurge_s.IsAborted()) {
|
||||
ROCKS_LOG_INFO(db_options_.info_log, "Mempurge process aborted: %s\n",
|
||||
mempurge_s.ToString().c_str());
|
||||
} else {
|
||||
// However the mempurge process can also fail for
|
||||
// other reasons (eg: new_mem->Add() fails).
|
||||
ROCKS_LOG_WARN(db_options_.info_log, "Mempurge process failed: %s\n",
|
||||
mempurge_s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
Status s;
|
||||
if (mempurge_s.ok()) {
|
||||
base_->Unref();
|
||||
s = Status::OK();
|
||||
} else {
|
||||
// This will release and re-acquire the mutex.
|
||||
s = WriteLevel0Table();
|
||||
}
|
||||
|
||||
if (s.ok() && cfd_->IsDropped()) {
|
||||
s = Status::ColumnFamilyDropped("Column family dropped during compaction");
|
||||
@@ -248,7 +272,10 @@ Status FlushJob::Run(LogsWithPrepTracker* prep_tracker,
|
||||
s = cfd_->imm()->TryInstallMemtableFlushResults(
|
||||
cfd_, mutable_cf_options_, mems_, prep_tracker, versions_, db_mutex_,
|
||||
meta_.fd.GetNumber(), &job_context_->memtables_to_free, db_directory_,
|
||||
log_buffer_, &committed_flush_jobs_info_, &tmp_io_s);
|
||||
log_buffer_, &committed_flush_jobs_info_, &tmp_io_s,
|
||||
!(mempurge_s.ok()) /* write_edit : true if no mempurge happened (or if aborted),
|
||||
but 'false' if mempurge successful: no new min log number
|
||||
or new level 0 file path to write to manifest. */);
|
||||
if (!tmp_io_s.ok()) {
|
||||
io_status_ = tmp_io_s;
|
||||
}
|
||||
@@ -306,6 +333,262 @@ void FlushJob::Cancel() {
|
||||
base_->Unref();
|
||||
}
|
||||
|
||||
uint64_t FlushJob::ExtractEarliestLogFileNumber() {
|
||||
uint64_t earliest_logno = 0;
|
||||
for (MemTable* m : mems_) {
|
||||
uint64_t logno = m->GetEarliestLogFileNumber();
|
||||
if (logno > 0 && (earliest_logno == 0 || logno < earliest_logno)) {
|
||||
earliest_logno = logno;
|
||||
}
|
||||
}
|
||||
return earliest_logno;
|
||||
}
|
||||
|
||||
Status FlushJob::MemPurge() {
|
||||
Status s;
|
||||
db_mutex_->AssertHeld();
|
||||
db_mutex_->Unlock();
|
||||
assert(!mems_.empty());
|
||||
|
||||
MemTable* new_mem = nullptr;
|
||||
|
||||
// Create two iterators, one for the memtable data (contains
|
||||
// info from puts + deletes), and one for the memtable
|
||||
// Range Tombstones (from DeleteRanges).
|
||||
ReadOptions ro;
|
||||
ro.total_order_seek = true;
|
||||
Arena arena;
|
||||
std::vector<InternalIterator*> memtables;
|
||||
std::vector<std::unique_ptr<FragmentedRangeTombstoneIterator>>
|
||||
range_del_iters;
|
||||
for (MemTable* m : mems_) {
|
||||
memtables.push_back(m->NewIterator(ro, &arena));
|
||||
auto* range_del_iter = m->NewRangeTombstoneIterator(ro, kMaxSequenceNumber);
|
||||
if (range_del_iter != nullptr) {
|
||||
range_del_iters.emplace_back(range_del_iter);
|
||||
}
|
||||
}
|
||||
|
||||
assert(!memtables.empty());
|
||||
SequenceNumber first_seqno = kMaxSequenceNumber;
|
||||
SequenceNumber earliest_seqno = kMaxSequenceNumber;
|
||||
// Pick first and earliest seqno as min of all first_seqno
|
||||
// and earliest_seqno of the mempurged memtables.
|
||||
for (const auto& mem : mems_) {
|
||||
first_seqno = mem->GetFirstSequenceNumber() < first_seqno
|
||||
? mem->GetFirstSequenceNumber()
|
||||
: first_seqno;
|
||||
earliest_seqno = mem->GetEarliestSequenceNumber() < earliest_seqno
|
||||
? mem->GetEarliestSequenceNumber()
|
||||
: earliest_seqno;
|
||||
}
|
||||
|
||||
ScopedArenaIterator iter(
|
||||
NewMergingIterator(&(cfd_->internal_comparator()), memtables.data(),
|
||||
static_cast<int>(memtables.size()), &arena));
|
||||
|
||||
uint64_t earliest_logno = ExtractEarliestLogFileNumber();
|
||||
|
||||
auto* ioptions = cfd_->ioptions();
|
||||
|
||||
// Place iterator at the First (meaning most recent) key node.
|
||||
iter->SeekToFirst();
|
||||
|
||||
std::unique_ptr<CompactionRangeDelAggregator> range_del_agg(
|
||||
new CompactionRangeDelAggregator(&(cfd_->internal_comparator()),
|
||||
existing_snapshots_));
|
||||
for (auto& rd_iter : range_del_iters) {
|
||||
range_del_agg->AddTombstones(std::move(rd_iter));
|
||||
}
|
||||
|
||||
// If there is valid data in the memtable,
|
||||
// or at least range tombstones, copy over the info
|
||||
// to the new memtable.
|
||||
if (iter->Valid() || !range_del_agg->IsEmpty()) {
|
||||
// Arbitrary heuristic: maxSize is 60% cpacity.
|
||||
size_t maxSize = ((mutable_cf_options_.write_buffer_size + 6U) / 10U);
|
||||
std::unique_ptr<CompactionFilter> compaction_filter;
|
||||
if (ioptions->compaction_filter_factory != nullptr &&
|
||||
ioptions->compaction_filter_factory->ShouldFilterTableFileCreation(
|
||||
TableFileCreationReason::kFlush)) {
|
||||
CompactionFilter::Context ctx;
|
||||
ctx.is_full_compaction = false;
|
||||
ctx.is_manual_compaction = false;
|
||||
ctx.column_family_id = cfd_->GetID();
|
||||
ctx.reason = TableFileCreationReason::kFlush;
|
||||
compaction_filter =
|
||||
ioptions->compaction_filter_factory->CreateCompactionFilter(ctx);
|
||||
if (compaction_filter != nullptr &&
|
||||
!compaction_filter->IgnoreSnapshots()) {
|
||||
s = Status::NotSupported(
|
||||
"CompactionFilter::IgnoreSnapshots() = false is not supported "
|
||||
"anymore.");
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
new_mem = new MemTable((cfd_->internal_comparator()), *(cfd_->ioptions()),
|
||||
mutable_cf_options_, cfd_->write_buffer_mgr(),
|
||||
earliest_seqno, cfd_->GetID(), earliest_logno);
|
||||
assert(new_mem != nullptr);
|
||||
|
||||
Env* env = db_options_.env;
|
||||
assert(env);
|
||||
MergeHelper merge(
|
||||
env, (cfd_->internal_comparator()).user_comparator(),
|
||||
(ioptions->merge_operator).get(), compaction_filter.get(),
|
||||
ioptions->logger, true /* internal key corruption is not ok */,
|
||||
existing_snapshots_.empty() ? 0 : existing_snapshots_.back(),
|
||||
snapshot_checker_);
|
||||
CompactionIterator c_iter(
|
||||
iter.get(), (cfd_->internal_comparator()).user_comparator(), &merge,
|
||||
kMaxSequenceNumber, &existing_snapshots_,
|
||||
earliest_write_conflict_snapshot_, snapshot_checker_, env,
|
||||
ShouldReportDetailedTime(env, ioptions->stats),
|
||||
true /* internal key corruption is not ok */, range_del_agg.get(),
|
||||
nullptr, ioptions->allow_data_in_errors,
|
||||
/*compaction=*/nullptr, compaction_filter.get(),
|
||||
/*shutting_down=*/nullptr,
|
||||
/*preserve_deletes_seqnum=*/0, /*manual_compaction_paused=*/nullptr,
|
||||
/*manual_compaction_canceled=*/nullptr, ioptions->info_log,
|
||||
&(cfd_->GetFullHistoryTsLow()));
|
||||
|
||||
// Set earliest sequence number in the new memtable
|
||||
// to be equal to the earliest sequence number of the
|
||||
// memtable being flushed (See later if there is a need
|
||||
// to update this number!).
|
||||
new_mem->SetEarliestSequenceNumber(earliest_seqno);
|
||||
// Likewise for first seq number.
|
||||
new_mem->SetFirstSequenceNumber(first_seqno);
|
||||
SequenceNumber new_first_seqno = kMaxSequenceNumber;
|
||||
|
||||
c_iter.SeekToFirst();
|
||||
|
||||
// Key transfer
|
||||
for (; c_iter.Valid(); c_iter.Next()) {
|
||||
const ParsedInternalKey ikey = c_iter.ikey();
|
||||
const Slice value = c_iter.value();
|
||||
new_first_seqno =
|
||||
ikey.sequence < new_first_seqno ? ikey.sequence : new_first_seqno;
|
||||
|
||||
// Should we update "OldestKeyTime" ???? -> timestamp appear
|
||||
// to still be an "experimental" feature.
|
||||
s = new_mem->Add(
|
||||
ikey.sequence, ikey.type, ikey.user_key, value,
|
||||
nullptr, // KV protection info set as nullptr since it
|
||||
// should only be useful for the first add to
|
||||
// the original memtable.
|
||||
false, // : allow concurrent_memtable_writes_
|
||||
// Not seen as necessary for now.
|
||||
nullptr, // get_post_process_info(m) must be nullptr
|
||||
// when concurrent_memtable_writes is switched off.
|
||||
nullptr); // hint, only used when concurrent_memtable_writes_
|
||||
// is switched on.
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// If new_mem has size greater than maxSize,
|
||||
// then rollback to regular flush operation,
|
||||
// and destroy new_mem.
|
||||
if (new_mem->ApproximateMemoryUsage() > maxSize) {
|
||||
s = Status::Aborted("Mempurge filled more than one memtable.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check status and propagate
|
||||
// potential error status from c_iter
|
||||
if (!s.ok()) {
|
||||
c_iter.status().PermitUncheckedError();
|
||||
} else if (!c_iter.status().ok()) {
|
||||
s = c_iter.status();
|
||||
}
|
||||
|
||||
// Range tombstone transfer.
|
||||
if (s.ok()) {
|
||||
auto range_del_it = range_del_agg->NewIterator();
|
||||
for (range_del_it->SeekToFirst(); range_del_it->Valid();
|
||||
range_del_it->Next()) {
|
||||
auto tombstone = range_del_it->Tombstone();
|
||||
new_first_seqno =
|
||||
tombstone.seq_ < new_first_seqno ? tombstone.seq_ : new_first_seqno;
|
||||
s = new_mem->Add(
|
||||
tombstone.seq_, // Sequence number
|
||||
kTypeRangeDeletion, // KV type
|
||||
tombstone.start_key_, // Key is start key.
|
||||
tombstone.end_key_, // Value is end key.
|
||||
nullptr, // KV protection info set as nullptr since it
|
||||
// should only be useful for the first add to
|
||||
// the original memtable.
|
||||
false, // : allow concurrent_memtable_writes_
|
||||
// Not seen as necessary for now.
|
||||
nullptr, // get_post_process_info(m) must be nullptr
|
||||
// when concurrent_memtable_writes is switched off.
|
||||
nullptr); // hint, only used when concurrent_memtable_writes_
|
||||
// is switched on.
|
||||
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// If new_mem has size greater than maxSize,
|
||||
// then rollback to regular flush operation,
|
||||
// and destroy new_mem.
|
||||
if (new_mem->ApproximateMemoryUsage() > maxSize) {
|
||||
s = Status::Aborted(Slice("Mempurge filled more than one memtable."));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If everything happened smoothly and new_mem contains valid data,
|
||||
// decide if it is flushed to storage or kept in the imm()
|
||||
// memtable list (memory).
|
||||
if (s.ok() && (new_first_seqno != kMaxSequenceNumber)) {
|
||||
// Rectify the first sequence number, which (unlike the earliest seq
|
||||
// number) needs to be present in the new memtable.
|
||||
new_mem->SetFirstSequenceNumber(new_first_seqno);
|
||||
|
||||
// The new_mem is added to the list of immutable memtables
|
||||
// only if it filled at less than 60% capacity (arbitrary heuristic).
|
||||
if (new_mem->ApproximateMemoryUsage() < maxSize) {
|
||||
db_mutex_->Lock();
|
||||
cfd_->imm()->Add(new_mem,
|
||||
&job_context_->memtables_to_free,
|
||||
false /* -> trigger_flush=false:
|
||||
* adding this memtable
|
||||
* will not trigger a flush.
|
||||
*/);
|
||||
new_mem->Ref();
|
||||
db_mutex_->Unlock();
|
||||
} else {
|
||||
s = Status::Aborted(Slice("Mempurge filled more than one memtable."));
|
||||
if (new_mem) {
|
||||
job_context_->memtables_to_free.push_back(new_mem);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// In this case, the newly allocated new_mem is empty.
|
||||
assert(new_mem != nullptr);
|
||||
job_context_->memtables_to_free.push_back(new_mem);
|
||||
}
|
||||
}
|
||||
|
||||
// Reacquire the mutex for WriteLevel0 function.
|
||||
db_mutex_->Lock();
|
||||
|
||||
// If mempurge successful, don't write input tables to level0,
|
||||
// but write any full output table to level0.
|
||||
if (s.ok()) {
|
||||
TEST_SYNC_POINT("DBImpl::FlushJob:MemPurgeSuccessful");
|
||||
} else {
|
||||
TEST_SYNC_POINT("DBImpl::FlushJob:MemPurgeUnsuccessful");
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Status FlushJob::WriteLevel0Table() {
|
||||
AutoThreadOperationStageUpdater stage_updater(
|
||||
ThreadStatus::STAGE_FLUSH_WRITE_L0);
|
||||
@@ -362,7 +645,7 @@ Status FlushJob::WriteLevel0Table() {
|
||||
|
||||
{
|
||||
ScopedArenaIterator iter(
|
||||
NewMergingIterator(&cfd_->internal_comparator(), &memtables[0],
|
||||
NewMergingIterator(&cfd_->internal_comparator(), memtables.data(),
|
||||
static_cast<int>(memtables.size()), &arena));
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
"[%s] [JOB %d] Level-0 flush table #%" PRIu64 ": started",
|
||||
@@ -440,6 +723,7 @@ Status FlushJob::WriteLevel0Table() {
|
||||
}
|
||||
}
|
||||
if (tboptions.reason == TableFileCreationReason::kFlush) {
|
||||
TEST_SYNC_POINT("DBImpl::FlushJob:Flush");
|
||||
RecordTick(stats_, MEMTABLE_PAYLOAD_BYTES_AT_FLUSH,
|
||||
memtable_payload_bytes);
|
||||
RecordTick(stats_, MEMTABLE_GARBAGE_BYTES_AT_FLUSH,
|
||||
@@ -469,6 +753,7 @@ Status FlushJob::WriteLevel0Table() {
|
||||
const bool has_output = meta_.fd.GetFileSize() > 0;
|
||||
|
||||
if (s.ok() && has_output) {
|
||||
TEST_SYNC_POINT("DBImpl::FlushJob:SSTFileCreated");
|
||||
// if we have more than 1 background thread, then we cannot
|
||||
// insert files directly into higher levels because some other
|
||||
// threads could be concurrently producing compacted files for
|
||||
|
||||
@@ -101,6 +101,29 @@ class FlushJob {
|
||||
void ReportFlushInputSize(const autovector<MemTable*>& mems);
|
||||
void RecordFlushIOStats();
|
||||
Status WriteLevel0Table();
|
||||
|
||||
// Memtable Garbage Collection algorithm: a MemPurge takes the list
|
||||
// of immutable memtables and filters out (or "purge") the outdated bytes
|
||||
// out of it. The output (the filtered bytes, or "useful payload") is
|
||||
// then transfered into a new memtable. If this memtable is filled, then
|
||||
// the mempurge is aborted and rerouted to a regular flush process. Else,
|
||||
// depending on the heuristics, placed onto the immutable memtable list.
|
||||
// The addition to the imm list will not trigger a flush operation. The
|
||||
// flush of the imm list will instead be triggered once the mutable memtable
|
||||
// is added to the imm list.
|
||||
// This process is typically intended for workloads with heavy overwrites
|
||||
// when we want to avoid SSD writes (and reads) as much as possible.
|
||||
// "MemPurge" is an experimental feature still at a very early stage
|
||||
// of development. At the moment it is only compatible with the Get, Put,
|
||||
// Delete operations as well as Iterators and CompactionFilters.
|
||||
// For this early version, "MemPurge" is called by setting the
|
||||
// options.experimental_allow_mempurge flag as "true". When this is
|
||||
// the case, ALL automatic flush operations (kWRiteBufferManagerFull) will
|
||||
// first go through the MemPurge process. herefore, we strongly
|
||||
// recommend all users not to set this flag as true given that the MemPurge
|
||||
// process has not matured yet.
|
||||
Status MemPurge();
|
||||
uint64_t ExtractEarliestLogFileNumber();
|
||||
#ifndef ROCKSDB_LITE
|
||||
std::unique_ptr<FlushJobInfo> GetFlushJobInfo() const;
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
@@ -418,12 +418,19 @@ TEST_F(FlushJobTest, FlushMemtablesMultipleColumnFamilies) {
|
||||
for (auto cfd : all_cfds) {
|
||||
mutable_cf_options_list.push_back(cfd->GetLatestMutableCFOptions());
|
||||
}
|
||||
autovector<std::list<std::unique_ptr<FlushJobInfo>>*>
|
||||
committed_flush_jobs_info;
|
||||
#ifndef ROCKSDB_LITE
|
||||
for (auto& job : flush_jobs) {
|
||||
committed_flush_jobs_info.push_back(job->GetCommittedFlushJobsInfo());
|
||||
}
|
||||
#endif //! ROCKSDB_LITE
|
||||
|
||||
Status s = InstallMemtableAtomicFlushResults(
|
||||
nullptr /* imm_lists */, all_cfds, mutable_cf_options_list, mems_list,
|
||||
versions_.get(), nullptr /* prep_tracker */, &mutex_, file_meta_ptrs,
|
||||
&job_context.memtables_to_free, nullptr /* db_directory */,
|
||||
nullptr /* log_buffer */);
|
||||
committed_flush_jobs_info, &job_context.memtables_to_free,
|
||||
nullptr /* db_directory */, nullptr /* log_buffer */);
|
||||
ASSERT_OK(s);
|
||||
|
||||
mutex_.Unlock();
|
||||
|
||||
+66
-44
@@ -394,7 +394,7 @@ const std::unordered_map<std::string, DBPropertyInfo>
|
||||
{DB::Properties::kDBStats,
|
||||
{false, &InternalStats::HandleDBStats, nullptr, nullptr, nullptr}},
|
||||
{DB::Properties::kBlockCacheEntryStats,
|
||||
{false, &InternalStats::HandleBlockCacheEntryStats, nullptr,
|
||||
{true, &InternalStats::HandleBlockCacheEntryStats, nullptr,
|
||||
&InternalStats::HandleBlockCacheEntryStatsMap, nullptr}},
|
||||
{DB::Properties::kSSTables,
|
||||
{false, &InternalStats::HandleSsTables, nullptr, nullptr, nullptr}},
|
||||
@@ -510,7 +510,7 @@ const std::unordered_map<std::string, DBPropertyInfo>
|
||||
{false, nullptr, &InternalStats::HandleBlockCachePinnedUsage, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kOptionsStatistics,
|
||||
{false, nullptr, nullptr, nullptr,
|
||||
{true, nullptr, nullptr, nullptr,
|
||||
&DBImpl::GetPropertyHandleOptionsStatistics}},
|
||||
};
|
||||
|
||||
@@ -526,29 +526,41 @@ InternalStats::InternalStats(int num_levels, SystemClock* clock,
|
||||
number_levels_(num_levels),
|
||||
clock_(clock),
|
||||
cfd_(cfd),
|
||||
started_at_(clock->NowMicros()) {}
|
||||
|
||||
Status InternalStats::CollectCacheEntryStats(bool foreground) {
|
||||
// Lazy initialize/reference the collector. It is pinned in cache (through
|
||||
// a shared_ptr) so that it does not get immediately ejected from a full
|
||||
// cache, which would force a re-scan on the next GetStats.
|
||||
if (!cache_entry_stats_collector_) {
|
||||
Cache* block_cache;
|
||||
bool ok = HandleBlockCacheStat(&block_cache);
|
||||
if (ok) {
|
||||
// Extract or create stats collector.
|
||||
Status s = CacheEntryStatsCollector<CacheEntryRoleStats>::GetShared(
|
||||
block_cache, clock_, &cache_entry_stats_collector_);
|
||||
if (!s.ok()) {
|
||||
// Block cache likely under pressure. Scanning could make it worse,
|
||||
// so skip.
|
||||
return s;
|
||||
}
|
||||
started_at_(clock->NowMicros()) {
|
||||
Cache* block_cache = nullptr;
|
||||
bool ok = GetBlockCacheForStats(&block_cache);
|
||||
if (ok) {
|
||||
assert(block_cache);
|
||||
// Extract or create stats collector. Could fail in rare cases.
|
||||
Status s = CacheEntryStatsCollector<CacheEntryRoleStats>::GetShared(
|
||||
block_cache, clock_, &cache_entry_stats_collector_);
|
||||
if (s.ok()) {
|
||||
assert(cache_entry_stats_collector_);
|
||||
} else {
|
||||
return Status::NotFound("block cache not configured");
|
||||
assert(!cache_entry_stats_collector_);
|
||||
}
|
||||
} else {
|
||||
assert(!block_cache);
|
||||
}
|
||||
}
|
||||
|
||||
void InternalStats::TEST_GetCacheEntryRoleStats(CacheEntryRoleStats* stats,
|
||||
bool foreground) {
|
||||
CollectCacheEntryStats(foreground);
|
||||
if (cache_entry_stats_collector_) {
|
||||
cache_entry_stats_collector_->GetStats(stats);
|
||||
}
|
||||
}
|
||||
|
||||
void InternalStats::CollectCacheEntryStats(bool foreground) {
|
||||
// This function is safe to call from any thread because
|
||||
// cache_entry_stats_collector_ field is const after constructor
|
||||
// and ->GetStats does its own synchronization, which also suffices for
|
||||
// cache_entry_stats_.
|
||||
|
||||
if (!cache_entry_stats_collector_) {
|
||||
return; // nothing to do (e.g. no block cache)
|
||||
}
|
||||
assert(cache_entry_stats_collector_);
|
||||
|
||||
// For "background" collections, strictly cap the collection time by
|
||||
// expanding effective cache TTL. For foreground, be more aggressive about
|
||||
@@ -556,9 +568,8 @@ Status InternalStats::CollectCacheEntryStats(bool foreground) {
|
||||
int min_interval_seconds = foreground ? 10 : 180;
|
||||
// 1/500 = max of 0.2% of one CPU thread
|
||||
int min_interval_factor = foreground ? 10 : 500;
|
||||
cache_entry_stats_collector_->GetStats(
|
||||
&cache_entry_stats_, min_interval_seconds, min_interval_factor);
|
||||
return Status::OK();
|
||||
cache_entry_stats_collector_->CollectStats(min_interval_seconds,
|
||||
min_interval_factor);
|
||||
}
|
||||
|
||||
std::function<void(const Slice&, void*, size_t, Cache::DeleterFn)>
|
||||
@@ -649,21 +660,25 @@ void InternalStats::CacheEntryRoleStats::ToMap(
|
||||
|
||||
bool InternalStats::HandleBlockCacheEntryStats(std::string* value,
|
||||
Slice /*suffix*/) {
|
||||
Status s = CollectCacheEntryStats(/*foreground*/ true);
|
||||
if (!s.ok()) {
|
||||
if (!cache_entry_stats_collector_) {
|
||||
return false;
|
||||
}
|
||||
*value = cache_entry_stats_.ToString(clock_);
|
||||
CollectCacheEntryStats(/*foreground*/ true);
|
||||
CacheEntryRoleStats stats;
|
||||
cache_entry_stats_collector_->GetStats(&stats);
|
||||
*value = stats.ToString(clock_);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InternalStats::HandleBlockCacheEntryStatsMap(
|
||||
std::map<std::string, std::string>* values, Slice /*suffix*/) {
|
||||
Status s = CollectCacheEntryStats(/*foreground*/ true);
|
||||
if (!s.ok()) {
|
||||
if (!cache_entry_stats_collector_) {
|
||||
return false;
|
||||
}
|
||||
cache_entry_stats_.ToMap(values, clock_);
|
||||
CollectCacheEntryStats(/*foreground*/ true);
|
||||
CacheEntryRoleStats stats;
|
||||
cache_entry_stats_collector_->GetStats(&stats);
|
||||
stats.ToMap(values, clock_);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1123,7 +1138,7 @@ bool InternalStats::HandleEstimateOldestKeyTime(uint64_t* value, DBImpl* /*db*/,
|
||||
return *value > 0 && *value < std::numeric_limits<uint64_t>::max();
|
||||
}
|
||||
|
||||
bool InternalStats::HandleBlockCacheStat(Cache** block_cache) {
|
||||
bool InternalStats::GetBlockCacheForStats(Cache** block_cache) {
|
||||
assert(block_cache != nullptr);
|
||||
auto* table_factory = cfd_->ioptions()->table_factory.get();
|
||||
assert(table_factory != nullptr);
|
||||
@@ -1135,7 +1150,7 @@ bool InternalStats::HandleBlockCacheStat(Cache** block_cache) {
|
||||
bool InternalStats::HandleBlockCacheCapacity(uint64_t* value, DBImpl* /*db*/,
|
||||
Version* /*version*/) {
|
||||
Cache* block_cache;
|
||||
bool ok = HandleBlockCacheStat(&block_cache);
|
||||
bool ok = GetBlockCacheForStats(&block_cache);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
@@ -1146,7 +1161,7 @@ bool InternalStats::HandleBlockCacheCapacity(uint64_t* value, DBImpl* /*db*/,
|
||||
bool InternalStats::HandleBlockCacheUsage(uint64_t* value, DBImpl* /*db*/,
|
||||
Version* /*version*/) {
|
||||
Cache* block_cache;
|
||||
bool ok = HandleBlockCacheStat(&block_cache);
|
||||
bool ok = GetBlockCacheForStats(&block_cache);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
@@ -1157,7 +1172,7 @@ bool InternalStats::HandleBlockCacheUsage(uint64_t* value, DBImpl* /*db*/,
|
||||
bool InternalStats::HandleBlockCachePinnedUsage(uint64_t* value, DBImpl* /*db*/,
|
||||
Version* /*version*/) {
|
||||
Cache* block_cache;
|
||||
bool ok = HandleBlockCacheStat(&block_cache);
|
||||
bool ok = GetBlockCacheForStats(&block_cache);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
@@ -1504,7 +1519,8 @@ void InternalStats::DumpCFStatsNoFileHistogram(std::string* value) {
|
||||
vstorage->GetTotalBlobFileSize() / kGB);
|
||||
value->append(buf);
|
||||
|
||||
double seconds_up = (clock_->NowMicros() - started_at_ + 1) / kMicrosInSec;
|
||||
uint64_t now_micros = clock_->NowMicros();
|
||||
double seconds_up = (now_micros - started_at_ + 1) / kMicrosInSec;
|
||||
double interval_seconds_up = seconds_up - cf_stats_snapshot_.seconds_up;
|
||||
snprintf(buf, sizeof(buf), "Uptime(secs): %.1f total, %.1f interval\n",
|
||||
seconds_up, interval_seconds_up);
|
||||
@@ -1619,14 +1635,20 @@ void InternalStats::DumpCFStatsNoFileHistogram(std::string* value) {
|
||||
cf_stats_snapshot_.comp_stats = compaction_stats_sum;
|
||||
cf_stats_snapshot_.stall_count = total_stall_count;
|
||||
|
||||
// Always treat CFStats context as "background"
|
||||
Status s = CollectCacheEntryStats(/*foreground=*/false);
|
||||
if (s.ok()) {
|
||||
value->append(cache_entry_stats_.ToString(clock_));
|
||||
} else {
|
||||
value->append("Block cache: ");
|
||||
value->append(s.ToString());
|
||||
value->append("\n");
|
||||
// Do not gather cache entry stats during CFStats because DB
|
||||
// mutex is held. Only dump last cached collection (rely on DB
|
||||
// periodic stats dump to update)
|
||||
if (cache_entry_stats_collector_) {
|
||||
CacheEntryRoleStats stats;
|
||||
// thread safe
|
||||
cache_entry_stats_collector_->GetStats(&stats);
|
||||
|
||||
constexpr uint64_t kDayInMicros = uint64_t{86400} * 1000000U;
|
||||
|
||||
// Skip if stats are extremely old (> 1 day, incl not yet populated)
|
||||
if (now_micros - stats.last_end_time_micros_ < kDayInMicros) {
|
||||
value->append(stats.ToString(clock_));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
-13
@@ -392,7 +392,6 @@ class InternalStats {
|
||||
cf_stats_count_[i] = 0;
|
||||
cf_stats_value_[i] = 0;
|
||||
}
|
||||
cache_entry_stats_.Clear();
|
||||
for (auto& comp_stat : comp_stats_) {
|
||||
comp_stat.Clear();
|
||||
}
|
||||
@@ -459,20 +458,20 @@ class InternalStats {
|
||||
bool GetIntPropertyOutOfMutex(const DBPropertyInfo& property_info,
|
||||
Version* version, uint64_t* value);
|
||||
|
||||
// Unless there is a recent enough collection of the stats, collect and
|
||||
// saved new cache entry stats. If `foreground`, require data to be more
|
||||
// recent to skip re-collection.
|
||||
//
|
||||
// This should only be called while NOT holding the DB mutex.
|
||||
void CollectCacheEntryStats(bool foreground);
|
||||
|
||||
const uint64_t* TEST_GetCFStatsValue() const { return cf_stats_value_; }
|
||||
|
||||
const std::vector<CompactionStats>& TEST_GetCompactionStats() const {
|
||||
return comp_stats_;
|
||||
}
|
||||
|
||||
const CacheEntryRoleStats& TEST_GetCacheEntryRoleStats(bool foreground) {
|
||||
Status s = CollectCacheEntryStats(foreground);
|
||||
if (!s.ok()) {
|
||||
assert(false);
|
||||
cache_entry_stats_.Clear();
|
||||
}
|
||||
return cache_entry_stats_;
|
||||
}
|
||||
void TEST_GetCacheEntryRoleStats(CacheEntryRoleStats* stats, bool foreground);
|
||||
|
||||
// Store a mapping from the user-facing DB::Properties string to our
|
||||
// DBPropertyInfo struct used internally for retrieving properties.
|
||||
@@ -492,16 +491,18 @@ class InternalStats {
|
||||
void DumpCFStatsNoFileHistogram(std::string* value);
|
||||
void DumpCFFileHistogram(std::string* value);
|
||||
|
||||
bool HandleBlockCacheStat(Cache** block_cache);
|
||||
|
||||
Status CollectCacheEntryStats(bool foreground);
|
||||
bool GetBlockCacheForStats(Cache** block_cache);
|
||||
|
||||
// Per-DB stats
|
||||
std::atomic<uint64_t> db_stats_[kIntStatsNumMax];
|
||||
// Per-ColumnFamily stats
|
||||
uint64_t cf_stats_value_[INTERNAL_CF_STATS_ENUM_MAX];
|
||||
uint64_t cf_stats_count_[INTERNAL_CF_STATS_ENUM_MAX];
|
||||
CacheEntryRoleStats cache_entry_stats_;
|
||||
// Initialize/reference the collector in constructor so that we don't need
|
||||
// additional synchronization in InternalStats, relying on synchronization
|
||||
// in CacheEntryStatsCollector::GetStats. This collector is pinned in cache
|
||||
// (through a shared_ptr) so that it does not get immediately ejected from
|
||||
// a full cache, which would force a re-scan on the next GetStats.
|
||||
std::shared_ptr<CacheEntryStatsCollector<CacheEntryRoleStats>>
|
||||
cache_entry_stats_collector_;
|
||||
// Per-ColumnFamily/level compaction stats
|
||||
|
||||
+30
-24
@@ -356,32 +356,38 @@ TEST_F(EventListenerTest, MultiCF) {
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
options.enable_thread_tracking = true;
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
TestFlushListener* listener = new TestFlushListener(options.env, this);
|
||||
options.listeners.emplace_back(listener);
|
||||
options.table_properties_collector_factories.push_back(
|
||||
std::make_shared<TestPropertiesCollectorFactory>());
|
||||
std::vector<std::string> cf_names = {
|
||||
"pikachu", "ilya", "muromec", "dobrynia",
|
||||
"nikitich", "alyosha", "popovich"};
|
||||
CreateAndReopenWithCF(cf_names, options);
|
||||
for (auto atomic_flush : {false, true}) {
|
||||
options.atomic_flush = atomic_flush;
|
||||
options.create_if_missing = true;
|
||||
DestroyAndReopen(options);
|
||||
TestFlushListener* listener = new TestFlushListener(options.env, this);
|
||||
options.listeners.emplace_back(listener);
|
||||
options.table_properties_collector_factories.push_back(
|
||||
std::make_shared<TestPropertiesCollectorFactory>());
|
||||
std::vector<std::string> cf_names = {"pikachu", "ilya", "muromec",
|
||||
"dobrynia", "nikitich", "alyosha",
|
||||
"popovich"};
|
||||
CreateAndReopenWithCF(cf_names, options);
|
||||
|
||||
ASSERT_OK(Put(1, "pikachu", std::string(90000, 'p')));
|
||||
ASSERT_OK(Put(2, "ilya", std::string(90000, 'i')));
|
||||
ASSERT_OK(Put(3, "muromec", std::string(90000, 'm')));
|
||||
ASSERT_OK(Put(4, "dobrynia", std::string(90000, 'd')));
|
||||
ASSERT_OK(Put(5, "nikitich", std::string(90000, 'n')));
|
||||
ASSERT_OK(Put(6, "alyosha", std::string(90000, 'a')));
|
||||
ASSERT_OK(Put(7, "popovich", std::string(90000, 'p')));
|
||||
for (int i = 1; i < 8; ++i) {
|
||||
ASSERT_OK(Flush(i));
|
||||
ASSERT_EQ(listener->flushed_dbs_.size(), i);
|
||||
ASSERT_EQ(listener->flushed_column_family_names_.size(), i);
|
||||
}
|
||||
ASSERT_OK(Put(1, "pikachu", std::string(90000, 'p')));
|
||||
ASSERT_OK(Put(2, "ilya", std::string(90000, 'i')));
|
||||
ASSERT_OK(Put(3, "muromec", std::string(90000, 'm')));
|
||||
ASSERT_OK(Put(4, "dobrynia", std::string(90000, 'd')));
|
||||
ASSERT_OK(Put(5, "nikitich", std::string(90000, 'n')));
|
||||
ASSERT_OK(Put(6, "alyosha", std::string(90000, 'a')));
|
||||
ASSERT_OK(Put(7, "popovich", std::string(90000, 'p')));
|
||||
for (int i = 1; i < 8; ++i) {
|
||||
ASSERT_OK(Flush(i));
|
||||
ASSERT_EQ(listener->flushed_dbs_.size(), i);
|
||||
ASSERT_EQ(listener->flushed_column_family_names_.size(), i);
|
||||
}
|
||||
|
||||
// make sure callback functions are called in the right order
|
||||
for (size_t i = 0; i < cf_names.size(); i++) {
|
||||
ASSERT_EQ(listener->flushed_dbs_[i], db_);
|
||||
ASSERT_EQ(listener->flushed_column_family_names_[i], cf_names[i]);
|
||||
// make sure callback functions are called in the right order
|
||||
for (size_t i = 0; i < cf_names.size(); i++) {
|
||||
ASSERT_EQ(listener->flushed_dbs_[i], db_);
|
||||
ASSERT_EQ(listener->flushed_column_family_names_[i], cf_names[i]);
|
||||
}
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -145,7 +145,8 @@ IOStatus Writer::EmitPhysicalRecord(RecordType t, const char* ptr, size_t n) {
|
||||
}
|
||||
|
||||
// Compute the crc of the record type and the payload.
|
||||
crc = crc32c::Extend(crc, ptr, n);
|
||||
uint32_t payload_crc = crc32c::Value(ptr, n);
|
||||
crc = crc32c::Crc32cCombine(crc, payload_crc, n);
|
||||
crc = crc32c::Mask(crc); // Adjust for storage
|
||||
TEST_SYNC_POINT_CALLBACK("LogWriter::EmitPhysicalRecord:BeforeEncodeChecksum",
|
||||
&crc);
|
||||
@@ -154,7 +155,7 @@ IOStatus Writer::EmitPhysicalRecord(RecordType t, const char* ptr, size_t n) {
|
||||
// Write the header and the payload
|
||||
IOStatus s = dest_->Append(Slice(buf, header_size));
|
||||
if (s.ok()) {
|
||||
s = dest_->Append(Slice(ptr, n));
|
||||
s = dest_->Append(Slice(ptr, n), payload_crc);
|
||||
}
|
||||
block_offset_ += header_size + n;
|
||||
return s;
|
||||
|
||||
+3
-1
@@ -67,7 +67,8 @@ MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
const ImmutableOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
SequenceNumber latest_seq, uint32_t column_family_id)
|
||||
SequenceNumber latest_seq, uint32_t column_family_id,
|
||||
uint64_t current_logfile_number)
|
||||
: comparator_(cmp),
|
||||
moptions_(ioptions, mutable_cf_options),
|
||||
refs_(0),
|
||||
@@ -98,6 +99,7 @@ MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
earliest_seqno_(latest_seq),
|
||||
creation_seq_(latest_seq),
|
||||
mem_next_logfile_number_(0),
|
||||
mem_min_logfile_number_(current_logfile_number),
|
||||
min_prep_log_referenced_(0),
|
||||
locks_(moptions_.inplace_update_support
|
||||
? moptions_.inplace_update_num_locks
|
||||
|
||||
+33
-1
@@ -106,7 +106,8 @@ class MemTable {
|
||||
const ImmutableOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
SequenceNumber earliest_seq, uint32_t column_family_id);
|
||||
SequenceNumber earliest_seq, uint32_t column_family_id,
|
||||
uint64_t current_logfile_number = 0);
|
||||
// No copying allowed
|
||||
MemTable(const MemTable&) = delete;
|
||||
MemTable& operator=(const MemTable&) = delete;
|
||||
@@ -341,6 +342,14 @@ class MemTable {
|
||||
return first_seqno_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Returns the sequence number of the first element that was inserted
|
||||
// into the memtable.
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable (unless this Memtable is immutable).
|
||||
void SetFirstSequenceNumber(SequenceNumber first_seqno) {
|
||||
return first_seqno_.store(first_seqno, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Returns the sequence number that is guaranteed to be smaller than or equal
|
||||
// to the sequence number of any key that could be inserted into this
|
||||
// memtable. It can then be assumed that any write with a larger(or equal)
|
||||
@@ -352,6 +361,15 @@ class MemTable {
|
||||
return earliest_seqno_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Sets the sequence number that is guaranteed to be smaller than or equal
|
||||
// to the sequence number of any key that could be inserted into this
|
||||
// memtable. It can then be assumed that any write with a larger(or equal)
|
||||
// sequence number will be present in this memtable or a later memtable.
|
||||
// Used only for MemPurge operation
|
||||
void SetEarliestSequenceNumber(SequenceNumber earliest_seqno) {
|
||||
return earliest_seqno_.store(earliest_seqno, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// DB's latest sequence ID when the memtable is created. This number
|
||||
// may be updated to a more recent one before any key is inserted.
|
||||
SequenceNumber GetCreationSeq() const { return creation_seq_; }
|
||||
@@ -370,6 +388,16 @@ class MemTable {
|
||||
// operations on the same MemTable.
|
||||
void SetNextLogNumber(uint64_t num) { mem_next_logfile_number_ = num; }
|
||||
|
||||
// Set the earliest log file number that (possibly)
|
||||
// contains entries from this memtable.
|
||||
void SetEarliestLogFileNumber(uint64_t logno) {
|
||||
mem_min_logfile_number_ = logno;
|
||||
}
|
||||
|
||||
// Return the earliest log file number that (possibly)
|
||||
// contains entries from this memtable.
|
||||
uint64_t GetEarliestLogFileNumber() { return mem_min_logfile_number_; }
|
||||
|
||||
// if this memtable contains data from a committed
|
||||
// two phase transaction we must take note of the
|
||||
// log which contains that data so we can know
|
||||
@@ -500,6 +528,10 @@ class MemTable {
|
||||
// The log files earlier than this number can be deleted.
|
||||
uint64_t mem_next_logfile_number_;
|
||||
|
||||
// The earliest log containing entries inserted into
|
||||
// this memtable.
|
||||
uint64_t mem_min_logfile_number_;
|
||||
|
||||
// the earliest log containing a prepared section
|
||||
// which has been inserted into this memtable.
|
||||
std::atomic<uint64_t> min_prep_log_referenced_;
|
||||
|
||||
+58
-10
@@ -392,7 +392,7 @@ Status MemTableList::TryInstallMemtableFlushResults(
|
||||
autovector<MemTable*>* to_delete, FSDirectory* db_directory,
|
||||
LogBuffer* log_buffer,
|
||||
std::list<std::unique_ptr<FlushJobInfo>>* committed_flush_jobs_info,
|
||||
IOStatus* io_s) {
|
||||
IOStatus* io_s, bool write_edits) {
|
||||
AutoThreadOperationStageUpdater stage_updater(
|
||||
ThreadStatus::STAGE_MEMTABLE_INSTALL_FLUSH_RESULTS);
|
||||
mu->AssertHeld();
|
||||
@@ -476,8 +476,14 @@ Status MemTableList::TryInstallMemtableFlushResults(
|
||||
uint64_t min_wal_number_to_keep = 0;
|
||||
if (vset->db_options()->allow_2pc) {
|
||||
assert(edit_list.size() > 0);
|
||||
// Note that if mempurge is successful, the edit_list will
|
||||
// not be applicable (contains info of new min_log number to keep,
|
||||
// and level 0 file path of SST file created during normal flush,
|
||||
// so both pieces of information are irrelevant after a successful
|
||||
// mempurge operation).
|
||||
min_wal_number_to_keep = PrecomputeMinLogNumberToKeep2PC(
|
||||
vset, *cfd, edit_list, memtables_to_flush, prep_tracker);
|
||||
|
||||
// We piggyback the information of earliest log file to keep in the
|
||||
// manifest entry for the last file flushed.
|
||||
edit_list.back()->SetMinLogNumberToKeep(min_wal_number_to_keep);
|
||||
@@ -502,13 +508,25 @@ Status MemTableList::TryInstallMemtableFlushResults(
|
||||
RemoveMemTablesOrRestoreFlags(status, cfd, batch_count, log_buffer,
|
||||
to_delete, mu);
|
||||
};
|
||||
|
||||
// this can release and reacquire the mutex.
|
||||
s = vset->LogAndApply(cfd, mutable_cf_options, edit_list, mu,
|
||||
db_directory, /*new_descriptor_log=*/false,
|
||||
/*column_family_options=*/nullptr,
|
||||
manifest_write_cb);
|
||||
*io_s = vset->io_status();
|
||||
if (write_edits) {
|
||||
// this can release and reacquire the mutex.
|
||||
s = vset->LogAndApply(cfd, mutable_cf_options, edit_list, mu,
|
||||
db_directory, /*new_descriptor_log=*/false,
|
||||
/*column_family_options=*/nullptr,
|
||||
manifest_write_cb);
|
||||
*io_s = vset->io_status();
|
||||
} else {
|
||||
// If write_edit is false (e.g: successful mempurge),
|
||||
// then remove old memtables, wake up manifest write queue threads,
|
||||
// and don't commit anything to the manifest file.
|
||||
RemoveMemTablesOrRestoreFlags(s, cfd, batch_count, log_buffer,
|
||||
to_delete, mu);
|
||||
// Notify new head of manifest write queue.
|
||||
// wake up all the waiting writers
|
||||
// TODO(bjlemaire): explain full reason needed or investigate more.
|
||||
vset->WakeUpWaitingManifestWriters();
|
||||
*io_s = IOStatus::OK();
|
||||
}
|
||||
}
|
||||
}
|
||||
commit_in_progress_ = false;
|
||||
@@ -516,7 +534,8 @@ Status MemTableList::TryInstallMemtableFlushResults(
|
||||
}
|
||||
|
||||
// New memtables are inserted at the front of the list.
|
||||
void MemTableList::Add(MemTable* m, autovector<MemTable*>* to_delete) {
|
||||
void MemTableList::Add(MemTable* m, autovector<MemTable*>* to_delete,
|
||||
bool trigger_flush) {
|
||||
assert(static_cast<int>(current_->memlist_.size()) >= num_flush_not_started_);
|
||||
InstallNewVersion();
|
||||
// this method is used to move mutable memtable into an immutable list.
|
||||
@@ -527,7 +546,8 @@ void MemTableList::Add(MemTable* m, autovector<MemTable*>* to_delete) {
|
||||
current_->Add(m, to_delete);
|
||||
m->MarkImmutable();
|
||||
num_flush_not_started_++;
|
||||
if (num_flush_not_started_ == 1) {
|
||||
|
||||
if (num_flush_not_started_ > 0 && trigger_flush) {
|
||||
imm_flush_needed.store(true, std::memory_order_release);
|
||||
}
|
||||
UpdateCachedValuesFromMemTableListVersion();
|
||||
@@ -674,6 +694,21 @@ void MemTableList::RemoveMemTablesOrRestoreFlags(
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the earliest log that possibly contain entries
|
||||
// from one of the memtables of this memtable_list.
|
||||
uint64_t MemTableList::EarliestLogContainingData() {
|
||||
uint64_t min_log = 0;
|
||||
|
||||
for (auto& m : current_->memlist_) {
|
||||
uint64_t log = m->GetEarliestLogFileNumber();
|
||||
if (log > 0 && (min_log == 0 || log < min_log)) {
|
||||
min_log = log;
|
||||
}
|
||||
}
|
||||
|
||||
return min_log;
|
||||
}
|
||||
|
||||
uint64_t MemTableList::PrecomputeMinLogContainingPrepSection(
|
||||
const std::unordered_set<MemTable*>* memtables_to_flush) {
|
||||
uint64_t min_log = 0;
|
||||
@@ -701,6 +736,8 @@ Status InstallMemtableAtomicFlushResults(
|
||||
const autovector<const autovector<MemTable*>*>& mems_list, VersionSet* vset,
|
||||
LogsWithPrepTracker* prep_tracker, InstrumentedMutex* mu,
|
||||
const autovector<FileMetaData*>& file_metas,
|
||||
const autovector<std::list<std::unique_ptr<FlushJobInfo>>*>&
|
||||
committed_flush_jobs_info,
|
||||
autovector<MemTable*>* to_delete, FSDirectory* db_directory,
|
||||
LogBuffer* log_buffer) {
|
||||
AutoThreadOperationStageUpdater stage_updater(
|
||||
@@ -730,6 +767,17 @@ Status InstallMemtableAtomicFlushResults(
|
||||
(*mems_list[k])[i]->SetFlushCompleted(true);
|
||||
(*mems_list[k])[i]->SetFileNumber(file_metas[k]->fd.GetNumber());
|
||||
}
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (committed_flush_jobs_info[k]) {
|
||||
assert(!mems_list[k]->empty());
|
||||
assert((*mems_list[k])[0]);
|
||||
std::unique_ptr<FlushJobInfo> flush_job_info =
|
||||
(*mems_list[k])[0]->ReleaseFlushJobInfo();
|
||||
committed_flush_jobs_info[k]->push_back(std::move(flush_job_info));
|
||||
}
|
||||
#else //! ROCKSDB_LITE
|
||||
(void)committed_flush_jobs_info;
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
|
||||
Status s;
|
||||
|
||||
+28
-3
@@ -140,6 +140,8 @@ class MemTableListVersion {
|
||||
const autovector<const autovector<MemTable*>*>& mems_list,
|
||||
VersionSet* vset, LogsWithPrepTracker* prep_tracker,
|
||||
InstrumentedMutex* mu, const autovector<FileMetaData*>& file_meta,
|
||||
const autovector<std::list<std::unique_ptr<FlushJobInfo>>*>&
|
||||
committed_flush_jobs_info,
|
||||
autovector<MemTable*>* to_delete, FSDirectory* db_directory,
|
||||
LogBuffer* log_buffer);
|
||||
|
||||
@@ -268,11 +270,15 @@ class MemTableList {
|
||||
autovector<MemTable*>* to_delete, FSDirectory* db_directory,
|
||||
LogBuffer* log_buffer,
|
||||
std::list<std::unique_ptr<FlushJobInfo>>* committed_flush_jobs_info,
|
||||
IOStatus* io_s);
|
||||
IOStatus* io_s, bool write_edits = true);
|
||||
|
||||
// New memtables are inserted at the front of the list.
|
||||
// Takes ownership of the referenced held on *m by the caller of Add().
|
||||
void Add(MemTable* m, autovector<MemTable*>* to_delete);
|
||||
// By default, adding memtables will flag that the memtable list needs to be
|
||||
// flushed, but in certain situations, like after a mempurge, we may want to
|
||||
// avoid flushing the memtable list upon addition of a memtable.
|
||||
void Add(MemTable* m, autovector<MemTable*>* to_delete,
|
||||
bool trigger_flush = true);
|
||||
|
||||
// Returns an estimate of the number of bytes of data in use.
|
||||
size_t ApproximateMemoryUsage();
|
||||
@@ -308,7 +314,18 @@ class MemTableList {
|
||||
// non-empty (regardless of the min_write_buffer_number_to_merge
|
||||
// parameter). This flush request will persist until the next time
|
||||
// PickMemtablesToFlush() is called.
|
||||
void FlushRequested() { flush_requested_ = true; }
|
||||
void FlushRequested() {
|
||||
flush_requested_ = true;
|
||||
// If there are some memtables stored in imm() that dont trigger
|
||||
// flush (eg: mempurge output memtable), then update imm_flush_needed.
|
||||
// Note: if race condition and imm_flush_needed is set to true
|
||||
// when there is num_flush_not_started_==0, then there is no
|
||||
// impact whatsoever. Imm_flush_needed is only used in an assert
|
||||
// in IsFlushPending().
|
||||
if (num_flush_not_started_ > 0) {
|
||||
imm_flush_needed.store(true, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
bool HasFlushRequested() { return flush_requested_; }
|
||||
|
||||
@@ -332,6 +349,10 @@ class MemTableList {
|
||||
|
||||
size_t* current_memory_usage() { return ¤t_memory_usage_; }
|
||||
|
||||
// Returns the earliest log that possibly contain entries
|
||||
// from one of the memtables of this memtable_list.
|
||||
uint64_t EarliestLogContainingData();
|
||||
|
||||
// Returns the min log containing the prep section after memtables listsed in
|
||||
// `memtables_to_flush` are flushed and their status is persisted in manifest.
|
||||
uint64_t PrecomputeMinLogContainingPrepSection(
|
||||
@@ -383,6 +404,8 @@ class MemTableList {
|
||||
const autovector<const autovector<MemTable*>*>& mems_list,
|
||||
VersionSet* vset, LogsWithPrepTracker* prep_tracker,
|
||||
InstrumentedMutex* mu, const autovector<FileMetaData*>& file_meta,
|
||||
const autovector<std::list<std::unique_ptr<FlushJobInfo>>*>&
|
||||
committed_flush_jobs_info,
|
||||
autovector<MemTable*>* to_delete, FSDirectory* db_directory,
|
||||
LogBuffer* log_buffer);
|
||||
|
||||
@@ -433,6 +456,8 @@ extern Status InstallMemtableAtomicFlushResults(
|
||||
const autovector<const autovector<MemTable*>*>& mems_list, VersionSet* vset,
|
||||
LogsWithPrepTracker* prep_tracker, InstrumentedMutex* mu,
|
||||
const autovector<FileMetaData*>& file_meta,
|
||||
const autovector<std::list<std::unique_ptr<FlushJobInfo>>*>&
|
||||
committed_flush_jobs_info,
|
||||
autovector<MemTable*>* to_delete, FSDirectory* db_directory,
|
||||
LogBuffer* log_buffer);
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -182,12 +182,21 @@ class MemTableListTest : public testing::Test {
|
||||
for (auto& meta : file_metas) {
|
||||
file_meta_ptrs.push_back(&meta);
|
||||
}
|
||||
std::vector<std::list<std::unique_ptr<FlushJobInfo>>>
|
||||
committed_flush_jobs_info_storage(cf_ids.size());
|
||||
autovector<std::list<std::unique_ptr<FlushJobInfo>>*>
|
||||
committed_flush_jobs_info;
|
||||
for (int i = 0; i < static_cast<int>(cf_ids.size()); ++i) {
|
||||
committed_flush_jobs_info.push_back(
|
||||
&committed_flush_jobs_info_storage[i]);
|
||||
}
|
||||
|
||||
InstrumentedMutex mutex;
|
||||
InstrumentedMutexLock l(&mutex);
|
||||
return InstallMemtableAtomicFlushResults(
|
||||
&lists, cfds, mutable_cf_options_list, mems_list, &versions,
|
||||
nullptr /* prep_tracker */, &mutex, file_meta_ptrs, to_delete, nullptr,
|
||||
&log_buffer);
|
||||
nullptr /* prep_tracker */, &mutex, file_meta_ptrs,
|
||||
committed_flush_jobs_info, to_delete, nullptr, &log_buffer);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+16
-3
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "db/blob/blob_fetcher.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "monitoring/statistics.h"
|
||||
@@ -119,7 +120,8 @@ Status MergeHelper::MergeUntil(InternalIterator* iter,
|
||||
CompactionRangeDelAggregator* range_del_agg,
|
||||
const SequenceNumber stop_before,
|
||||
const bool at_bottom,
|
||||
const bool allow_data_in_errors) {
|
||||
const bool allow_data_in_errors,
|
||||
Version* version) {
|
||||
// Get a copy of the internal key, before it's invalidated by iter->Next()
|
||||
// Also maintain the list of merge operands seen.
|
||||
assert(HasOperator());
|
||||
@@ -203,12 +205,23 @@ Status MergeHelper::MergeUntil(InternalIterator* iter,
|
||||
// want. Also if we're in compaction and it's a put, it would be nice to
|
||||
// run compaction filter on it.
|
||||
const Slice val = iter->value();
|
||||
PinnableSlice blob_value;
|
||||
const Slice* val_ptr;
|
||||
if (kTypeValue == ikey.type &&
|
||||
if ((kTypeValue == ikey.type || kTypeBlobIndex == ikey.type) &&
|
||||
(range_del_agg == nullptr ||
|
||||
!range_del_agg->ShouldDelete(
|
||||
ikey, RangeDelPositioningMode::kForwardTraversal))) {
|
||||
val_ptr = &val;
|
||||
if (ikey.type == kTypeBlobIndex) {
|
||||
assert(version);
|
||||
BlobFetcher blob_fetcher(version, ReadOptions());
|
||||
s = blob_fetcher.FetchBlob(ikey.user_key, val, &blob_value);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
val_ptr = &blob_value;
|
||||
} else {
|
||||
val_ptr = &val;
|
||||
}
|
||||
} else {
|
||||
val_ptr = nullptr;
|
||||
}
|
||||
|
||||
+2
-1
@@ -84,7 +84,8 @@ class MergeHelper {
|
||||
CompactionRangeDelAggregator* range_del_agg = nullptr,
|
||||
const SequenceNumber stop_before = 0,
|
||||
const bool at_bottom = false,
|
||||
const bool allow_data_in_errors = false);
|
||||
const bool allow_data_in_errors = false,
|
||||
Version* version = nullptr);
|
||||
|
||||
// Filters a merge operand using the compaction filter specified
|
||||
// in the constructor. Returns the decision that the filter made.
|
||||
|
||||
@@ -228,7 +228,7 @@ TEST_F(ObsoleteFilesTest, BlobFiles) {
|
||||
constexpr uint64_t second_total_blob_count = 100;
|
||||
constexpr uint64_t second_total_blob_bytes = 2000000;
|
||||
constexpr char second_checksum_method[] = "CRC32B";
|
||||
constexpr char second_checksum_value[] = "6dbdf23a";
|
||||
constexpr char second_checksum_value[] = "\x6d\xbd\xf2\x3a";
|
||||
|
||||
auto shared_meta = SharedBlobFileMetaData::Create(
|
||||
second_blob_file_number, second_total_blob_count, second_total_blob_bytes,
|
||||
|
||||
+1
-1
@@ -387,7 +387,7 @@ class Repairer {
|
||||
// Initialize per-column family memtables
|
||||
for (auto* cfd : *vset_.GetColumnFamilySet()) {
|
||||
cfd->CreateNewMemtable(*cfd->GetLatestMutableCFOptions(),
|
||||
kMaxSequenceNumber);
|
||||
kMaxSequenceNumber, cfd->GetLogNumber());
|
||||
}
|
||||
auto cf_mems = new ColumnFamilyMemTablesImpl(vset_.GetColumnFamilySet());
|
||||
|
||||
|
||||
@@ -50,10 +50,6 @@ class IntTblPropCollectorFactory {
|
||||
|
||||
using IntTblPropCollectorFactories =
|
||||
std::vector<std::unique_ptr<IntTblPropCollectorFactory>>;
|
||||
using IntTblPropCollectorFactoryIter =
|
||||
IntTblPropCollectorFactories::const_iterator;
|
||||
using IntTblPropCollectorFactoryRange =
|
||||
std::pair<IntTblPropCollectorFactoryIter, IntTblPropCollectorFactoryIter>;
|
||||
|
||||
// When rocksdb creates a new table, it will encode all "user keys" into
|
||||
// "internal keys", which contains meta information of a given entry.
|
||||
|
||||
@@ -659,7 +659,9 @@ TEST_F(VersionBuilderTest, ApplyBlobFileAddition) {
|
||||
constexpr uint64_t total_blob_count = 5678;
|
||||
constexpr uint64_t total_blob_bytes = 999999;
|
||||
constexpr char checksum_method[] = "SHA1";
|
||||
constexpr char checksum_value[] = "bdb7f34a59dfa1592ce7f52e99f98c570c525cbd";
|
||||
constexpr char checksum_value[] =
|
||||
"\xbd\xb7\xf3\x4a\x59\xdf\xa1\x59\x2c\xe7\xf5\x2e\x99\xf9\x8c\x57\x0c\x52"
|
||||
"\x5c\xbd";
|
||||
|
||||
edit.AddBlobFile(blob_file_number, total_blob_count, total_blob_bytes,
|
||||
checksum_method, checksum_value);
|
||||
@@ -703,7 +705,9 @@ TEST_F(VersionBuilderTest, ApplyBlobFileAdditionAlreadyInBase) {
|
||||
constexpr uint64_t total_blob_count = 5678;
|
||||
constexpr uint64_t total_blob_bytes = 999999;
|
||||
constexpr char checksum_method[] = "SHA1";
|
||||
constexpr char checksum_value[] = "bdb7f34a59dfa1592ce7f52e99f98c570c525cbd";
|
||||
constexpr char checksum_value[] =
|
||||
"\xbd\xb7\xf3\x4a\x59\xdf\xa1\x59\x2c\xe7\xf5\x2e\x99\xf9\x8c\x57\x0c\x52"
|
||||
"\x5c\xbd";
|
||||
constexpr uint64_t garbage_blob_count = 123;
|
||||
constexpr uint64_t garbage_blob_bytes = 456789;
|
||||
|
||||
@@ -744,7 +748,9 @@ TEST_F(VersionBuilderTest, ApplyBlobFileAdditionAlreadyApplied) {
|
||||
constexpr uint64_t total_blob_count = 5678;
|
||||
constexpr uint64_t total_blob_bytes = 999999;
|
||||
constexpr char checksum_method[] = "SHA1";
|
||||
constexpr char checksum_value[] = "bdb7f34a59dfa1592ce7f52e99f98c570c525cbd";
|
||||
constexpr char checksum_value[] =
|
||||
"\xbd\xb7\xf3\x4a\x59\xdf\xa1\x59\x2c\xe7\xf5\x2e\x99\xf9\x8c\x57\x0c\x52"
|
||||
"\x5c\xbd";
|
||||
|
||||
edit.AddBlobFile(blob_file_number, total_blob_count, total_blob_bytes,
|
||||
checksum_method, checksum_value);
|
||||
@@ -764,7 +770,9 @@ TEST_F(VersionBuilderTest, ApplyBlobFileGarbageFileInBase) {
|
||||
constexpr uint64_t total_blob_count = 5678;
|
||||
constexpr uint64_t total_blob_bytes = 999999;
|
||||
constexpr char checksum_method[] = "SHA1";
|
||||
constexpr char checksum_value[] = "bdb7f34a59dfa1592ce7f52e99f98c570c525cbd";
|
||||
constexpr char checksum_value[] =
|
||||
"\xbd\xb7\xf3\x4a\x59\xdf\xa1\x59\x2c\xe7\xf5\x2e\x99\xf9\x8c\x57\x0c\x52"
|
||||
"\x5c\xbd";
|
||||
constexpr uint64_t garbage_blob_count = 123;
|
||||
constexpr uint64_t garbage_blob_bytes = 456789;
|
||||
|
||||
@@ -841,7 +849,9 @@ TEST_F(VersionBuilderTest, ApplyBlobFileGarbageFileAdditionApplied) {
|
||||
constexpr uint64_t total_blob_count = 5678;
|
||||
constexpr uint64_t total_blob_bytes = 999999;
|
||||
constexpr char checksum_method[] = "SHA1";
|
||||
constexpr char checksum_value[] = "bdb7f34a59dfa1592ce7f52e99f98c570c525cbd";
|
||||
constexpr char checksum_value[] =
|
||||
"\xbd\xb7\xf3\x4a\x59\xdf\xa1\x59\x2c\xe7\xf5\x2e\x99\xf9\x8c\x57\x0c\x52"
|
||||
"\x5c\xbd";
|
||||
|
||||
addition.AddBlobFile(blob_file_number, total_blob_count, total_blob_bytes,
|
||||
checksum_method, checksum_value);
|
||||
|
||||
@@ -548,6 +548,11 @@ Status VersionEditHandler::ExtractInfoFromVersionEdit(ColumnFamilyData* cfd,
|
||||
"records NOT monotonically increasing");
|
||||
} else {
|
||||
cfd->SetLogNumber(edit.log_number_);
|
||||
if (version_set_->db_options()->experimental_allow_mempurge &&
|
||||
edit.log_number_ > 0 &&
|
||||
(cfd->mem()->GetEarliestLogFileNumber() == 0)) {
|
||||
cfd->mem()->SetEarliestLogFileNumber(edit.log_number_);
|
||||
}
|
||||
version_edit_params_.SetLogNumber(edit.log_number_);
|
||||
}
|
||||
}
|
||||
|
||||
+34
-42
@@ -1469,6 +1469,10 @@ void Version::GetColumnFamilyMetaData(ColumnFamilyMetaData* cf_meta) {
|
||||
cf_meta->file_count = 0;
|
||||
cf_meta->levels.clear();
|
||||
|
||||
cf_meta->blob_file_size = 0;
|
||||
cf_meta->blob_file_count = 0;
|
||||
cf_meta->blob_files.clear();
|
||||
|
||||
auto* ioptions = cfd_->ioptions();
|
||||
auto* vstorage = storage_info();
|
||||
|
||||
@@ -1504,6 +1508,17 @@ void Version::GetColumnFamilyMetaData(ColumnFamilyMetaData* cf_meta) {
|
||||
level, level_size, std::move(files));
|
||||
cf_meta->size += level_size;
|
||||
}
|
||||
for (const auto& iter : vstorage->GetBlobFiles()) {
|
||||
const auto meta = iter.second.get();
|
||||
cf_meta->blob_files.emplace_back(
|
||||
meta->GetBlobFileNumber(), BlobFileName("", meta->GetBlobFileNumber()),
|
||||
ioptions->cf_paths.front().path, meta->GetBlobFileSize(),
|
||||
meta->GetTotalBlobCount(), meta->GetTotalBlobBytes(),
|
||||
meta->GetGarbageBlobCount(), meta->GetGarbageBlobBytes(),
|
||||
meta->GetChecksumMethod(), meta->GetChecksumValue());
|
||||
cf_meta->blob_file_count++;
|
||||
cf_meta->blob_file_size += meta->GetBlobFileSize();
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t Version::GetSstFilesSize() {
|
||||
@@ -1924,6 +1939,9 @@ void Version::Get(const ReadOptions& read_options, const LookupKey& k,
|
||||
fp.GetHitFileLevel());
|
||||
}
|
||||
if (!status->ok()) {
|
||||
if (db_statistics_ != nullptr) {
|
||||
get_context.ReportCounters();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3049,8 +3067,7 @@ void VersionStorageInfo::ComputeBottommostFilesMarkedForCompaction() {
|
||||
bottommost_files_mark_threshold_ = kMaxSequenceNumber;
|
||||
for (auto& level_and_file : bottommost_files_) {
|
||||
if (!level_and_file.second->being_compacted &&
|
||||
level_and_file.second->fd.largest_seqno != 0 &&
|
||||
level_and_file.second->num_deletions > 1) {
|
||||
level_and_file.second->fd.largest_seqno != 0) {
|
||||
// largest_seqno might be nonzero due to containing the final key in an
|
||||
// earlier compaction, whose seqnum we didn't zero out. Multiple deletions
|
||||
// ensures the file really contains deleted or overwritten keys.
|
||||
@@ -3376,7 +3393,7 @@ const char* VersionStorageInfo::LevelFileSummary(FileSummaryStorage* scratch,
|
||||
return scratch->buffer;
|
||||
}
|
||||
|
||||
int64_t VersionStorageInfo::MaxNextLevelOverlappingBytes() {
|
||||
uint64_t VersionStorageInfo::MaxNextLevelOverlappingBytes() {
|
||||
uint64_t result = 0;
|
||||
std::vector<FileMetaData*> overlaps;
|
||||
for (int level = 1; level < num_levels() - 1; level++) {
|
||||
@@ -4092,7 +4109,7 @@ Status VersionSet::ProcessManifestWrites(
|
||||
{
|
||||
FileOptions opt_file_opts = fs_->OptimizeForManifestWrite(file_options_);
|
||||
mu->Unlock();
|
||||
|
||||
TEST_SYNC_POINT("VersionSet::LogAndApply:WriteManifestStart");
|
||||
TEST_SYNC_POINT_CALLBACK("VersionSet::LogAndApply:WriteManifest", nullptr);
|
||||
if (!first_writer.edit_list.front()->IsColumnFamilyManipulation()) {
|
||||
for (int i = 0; i < static_cast<int>(versions.size()); ++i) {
|
||||
@@ -4134,6 +4151,7 @@ Status VersionSet::ProcessManifestWrites(
|
||||
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
|
||||
std::move(descriptor_file), descriptor_fname, opt_file_opts, clock_,
|
||||
io_tracer_, nullptr, db_options_->listeners, nullptr,
|
||||
tmp_set.Contains(FileType::kDescriptorFile),
|
||||
tmp_set.Contains(FileType::kDescriptorFile)));
|
||||
descriptor_log_.reset(
|
||||
new log::Writer(std::move(file_writer), 0, false));
|
||||
@@ -4396,6 +4414,14 @@ Status VersionSet::ProcessManifestWrites(
|
||||
return s;
|
||||
}
|
||||
|
||||
void VersionSet::WakeUpWaitingManifestWriters() {
|
||||
// wake up all the waiting writers
|
||||
// Notify new head of manifest write queue.
|
||||
if (!manifest_writers_.empty()) {
|
||||
manifest_writers_.front()->cv.Signal();
|
||||
}
|
||||
}
|
||||
|
||||
// 'datas' is grammatically incorrect. We still use this notation to indicate
|
||||
// that this variable represents a collection of column_family_data.
|
||||
Status VersionSet::LogAndApply(
|
||||
@@ -5496,43 +5522,6 @@ InternalIterator* VersionSet::MakeInputIterator(
|
||||
return result;
|
||||
}
|
||||
|
||||
// verify that the files listed in this compaction are present
|
||||
// in the current version
|
||||
bool VersionSet::VerifyCompactionFileConsistency(Compaction* c) {
|
||||
#ifndef NDEBUG
|
||||
Version* version = c->column_family_data()->current();
|
||||
const VersionStorageInfo* vstorage = version->storage_info();
|
||||
if (c->input_version() != version) {
|
||||
ROCKS_LOG_INFO(
|
||||
db_options_->info_log,
|
||||
"[%s] compaction output being applied to a different base version from"
|
||||
" input version",
|
||||
c->column_family_data()->GetName().c_str());
|
||||
}
|
||||
|
||||
for (size_t input = 0; input < c->num_input_levels(); ++input) {
|
||||
int level = c->level(input);
|
||||
for (size_t i = 0; i < c->num_input_files(input); ++i) {
|
||||
uint64_t number = c->input(input, i)->fd.GetNumber();
|
||||
bool found = false;
|
||||
for (size_t j = 0; j < vstorage->files_[level].size(); j++) {
|
||||
FileMetaData* f = vstorage->files_[level][j];
|
||||
if (f->fd.GetNumber() == number) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return false; // input files non existent in current version
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)c;
|
||||
#endif
|
||||
return true; // everything good
|
||||
}
|
||||
|
||||
Status VersionSet::GetMetadataForFile(uint64_t number, int* filelevel,
|
||||
FileMetaData** meta,
|
||||
ColumnFamilyData** cfd) {
|
||||
@@ -5590,6 +5579,9 @@ void VersionSet::GetLiveFilesMetaData(std::vector<LiveFileMetaData>* metadata) {
|
||||
filemetadata.oldest_blob_file_number = file->oldest_blob_file_number;
|
||||
filemetadata.file_checksum = file->file_checksum;
|
||||
filemetadata.file_checksum_func_name = file->file_checksum_func_name;
|
||||
filemetadata.temperature = file->temperature;
|
||||
filemetadata.oldest_ancester_time = file->TryGetOldestAncesterTime();
|
||||
filemetadata.file_creation_time = file->TryGetFileCreationTime();
|
||||
metadata->push_back(filemetadata);
|
||||
}
|
||||
}
|
||||
@@ -5655,7 +5647,7 @@ ColumnFamilyData* VersionSet::CreateColumnFamily(
|
||||
// GetLatestMutableCFOptions() is safe here without mutex since the
|
||||
// cfd is not available to client
|
||||
new_cfd->CreateNewMemtable(*new_cfd->GetLatestMutableCFOptions(),
|
||||
LastSequence());
|
||||
LastSequence(), edit->log_number_);
|
||||
new_cfd->SetLogNumber(edit->log_number_);
|
||||
return new_cfd;
|
||||
}
|
||||
|
||||
+20
-7
@@ -447,7 +447,7 @@ class VersionStorageInfo {
|
||||
|
||||
// Return the maximum overlapping data (in bytes) at next level for any
|
||||
// file at a level >= 1.
|
||||
int64_t MaxNextLevelOverlappingBytes();
|
||||
uint64_t MaxNextLevelOverlappingBytes();
|
||||
|
||||
// Return a human readable string that describes this version's contents.
|
||||
std::string DebugString(bool hex = false) const;
|
||||
@@ -1014,6 +1014,7 @@ class VersionSet {
|
||||
FileSystem* fs,
|
||||
std::string* manifest_filename,
|
||||
uint64_t* manifest_file_number);
|
||||
void WakeUpWaitingManifestWriters();
|
||||
|
||||
// Recover the last saved descriptor from persistent storage.
|
||||
// If read_only == true, Recover() will not complain if some column families
|
||||
@@ -1160,6 +1161,15 @@ class VersionSet {
|
||||
if (min_log_num > num && !cfd->IsDropped()) {
|
||||
min_log_num = num;
|
||||
}
|
||||
// If mempurge is activated, there may be an immutable memtable
|
||||
// that has data not flushed to any SST file.
|
||||
if (db_options_->experimental_allow_mempurge && !(cfd->IsEmpty()) &&
|
||||
!(cfd->IsDropped())) {
|
||||
num = cfd->imm()->EarliestLogContainingData();
|
||||
if ((num > 0) && (min_log_num > num)) {
|
||||
min_log_num = num;
|
||||
}
|
||||
}
|
||||
}
|
||||
return min_log_num;
|
||||
}
|
||||
@@ -1177,6 +1187,15 @@ class VersionSet {
|
||||
if (min_log_num > cfd->GetLogNumber() && !cfd->IsDropped()) {
|
||||
min_log_num = cfd->GetLogNumber();
|
||||
}
|
||||
// If mempurge is activated, there may be an immutable memtable
|
||||
// that has data not flushed to any SST file.
|
||||
if (db_options_->experimental_allow_mempurge && !(cfd->IsEmpty()) &&
|
||||
!(cfd->IsDropped())) {
|
||||
uint64_t num = cfd->imm()->EarliestLogContainingData();
|
||||
if ((num > 0) && (min_log_num > num)) {
|
||||
min_log_num = num;
|
||||
}
|
||||
}
|
||||
}
|
||||
return min_log_num;
|
||||
}
|
||||
@@ -1222,12 +1241,6 @@ class VersionSet {
|
||||
// Return the size of the current manifest file
|
||||
uint64_t manifest_file_size() const { return manifest_file_size_; }
|
||||
|
||||
// verify that the files that we started with for a compaction
|
||||
// still exist in the current version and in the same original level.
|
||||
// This ensures that a concurrent compaction did not erroneously
|
||||
// pick the same files to compact.
|
||||
bool VerifyCompactionFileConsistency(Compaction* c);
|
||||
|
||||
Status GetMetadataForFile(uint64_t number, int* filelevel,
|
||||
FileMetaData** metadata, ColumnFamilyData** cfd);
|
||||
|
||||
|
||||
@@ -989,7 +989,8 @@ TEST_F(VersionSetTest, PersistBlobFileStateInNewManifest) {
|
||||
constexpr uint64_t total_blob_bytes = 77777777;
|
||||
constexpr char checksum_method[] = "SHA1";
|
||||
constexpr char checksum_value[] =
|
||||
"bdb7f34a59dfa1592ce7f52e99f98c570c525cbd";
|
||||
"\xbd\xb7\xf3\x4a\x59\xdf\xa1\x59\x2c\xe7\xf5\x2e\x99\xf9\x8c\x57\x0c"
|
||||
"\x52\x5c\xbd";
|
||||
|
||||
auto shared_meta = SharedBlobFileMetaData::Create(
|
||||
blob_file_number, total_blob_count, total_blob_bytes, checksum_method,
|
||||
@@ -1010,7 +1011,7 @@ TEST_F(VersionSetTest, PersistBlobFileStateInNewManifest) {
|
||||
constexpr uint64_t total_blob_count = 555;
|
||||
constexpr uint64_t total_blob_bytes = 66666;
|
||||
constexpr char checksum_method[] = "CRC32";
|
||||
constexpr char checksum_value[] = "3d87ff57";
|
||||
constexpr char checksum_value[] = "\x3d\x87\xff\x57";
|
||||
|
||||
auto shared_meta = SharedBlobFileMetaData::Create(
|
||||
blob_file_number, total_blob_count, total_blob_bytes, checksum_method,
|
||||
@@ -1068,7 +1069,7 @@ TEST_F(VersionSetTest, AddLiveBlobFiles) {
|
||||
constexpr uint64_t first_total_blob_count = 555;
|
||||
constexpr uint64_t first_total_blob_bytes = 66666;
|
||||
constexpr char first_checksum_method[] = "CRC32";
|
||||
constexpr char first_checksum_value[] = "3d87ff57";
|
||||
constexpr char first_checksum_value[] = "\x3d\x87\xff\x57";
|
||||
|
||||
auto first_shared_meta = SharedBlobFileMetaData::Create(
|
||||
first_blob_file_number, first_total_blob_count, first_total_blob_bytes,
|
||||
@@ -1111,7 +1112,7 @@ TEST_F(VersionSetTest, AddLiveBlobFiles) {
|
||||
constexpr uint64_t second_total_blob_count = 100;
|
||||
constexpr uint64_t second_total_blob_bytes = 2000000;
|
||||
constexpr char second_checksum_method[] = "CRC32B";
|
||||
constexpr char second_checksum_value[] = "6dbdf23a";
|
||||
constexpr char second_checksum_value[] = "\x6d\xbd\xf2\x3a";
|
||||
|
||||
auto second_shared_meta = SharedBlobFileMetaData::Create(
|
||||
second_blob_file_number, second_total_blob_count, second_total_blob_bytes,
|
||||
@@ -1151,7 +1152,7 @@ TEST_F(VersionSetTest, ObsoleteBlobFile) {
|
||||
constexpr uint64_t total_blob_count = 555;
|
||||
constexpr uint64_t total_blob_bytes = 66666;
|
||||
constexpr char checksum_method[] = "CRC32";
|
||||
constexpr char checksum_value[] = "3d87ff57";
|
||||
constexpr char checksum_value[] = "\x3d\x87\xff\x57";
|
||||
|
||||
edit.AddBlobFile(blob_file_number, total_blob_count, total_blob_bytes,
|
||||
checksum_method, checksum_value);
|
||||
|
||||
@@ -84,6 +84,7 @@ class MockWriteCallback : public WriteCallback {
|
||||
bool AllowWriteBatching() override { return allow_batching_; }
|
||||
};
|
||||
|
||||
#if !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
class WriteCallbackPTest
|
||||
: public WriteCallbackTest,
|
||||
public ::testing::WithParamInterface<
|
||||
@@ -376,6 +377,7 @@ INSTANTIATE_TEST_CASE_P(WriteCallbackPTest, WriteCallbackPTest,
|
||||
::testing::Bool(), ::testing::Bool(),
|
||||
::testing::Bool(), ::testing::Bool(),
|
||||
::testing::Bool()));
|
||||
#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
|
||||
TEST_F(WriteCallbackTest, WriteCallBackTest) {
|
||||
Options options;
|
||||
|
||||
@@ -131,6 +131,7 @@ DECLARE_int32(get_current_wal_file_one_in);
|
||||
DECLARE_int32(set_options_one_in);
|
||||
DECLARE_int32(set_in_place_one_in);
|
||||
DECLARE_int64(cache_size);
|
||||
DECLARE_int32(cache_numshardbits);
|
||||
DECLARE_bool(cache_index_and_filter_blocks);
|
||||
DECLARE_int32(top_level_index_pinning);
|
||||
DECLARE_int32(partition_pinning);
|
||||
@@ -140,6 +141,7 @@ DECLARE_uint64(subcompactions);
|
||||
DECLARE_uint64(periodic_compaction_seconds);
|
||||
DECLARE_uint64(compaction_ttl);
|
||||
DECLARE_bool(allow_concurrent_memtable_write);
|
||||
DECLARE_bool(experimental_allow_mempurge);
|
||||
DECLARE_bool(enable_write_thread_adaptive_yield);
|
||||
DECLARE_int32(reopen);
|
||||
DECLARE_double(bloom_bits);
|
||||
@@ -262,6 +264,7 @@ DECLARE_bool(fail_if_options_file_error);
|
||||
DECLARE_uint64(batch_protection_bytes_per_key);
|
||||
|
||||
DECLARE_uint64(user_timestamp_size);
|
||||
DECLARE_string(secondary_cache_uri);
|
||||
|
||||
constexpr long KB = 1024;
|
||||
constexpr int kRandomValueMaxFactor = 3;
|
||||
|
||||
@@ -284,6 +284,11 @@ DEFINE_int32(set_in_place_one_in, 0,
|
||||
DEFINE_int64(cache_size, 2LL * KB * KB * KB,
|
||||
"Number of bytes to use as a cache of uncompressed data.");
|
||||
|
||||
DEFINE_int32(cache_numshardbits, 6,
|
||||
"Number of shards for the block cache"
|
||||
" is 2 ** cache_numshardbits. Negative means use default settings."
|
||||
" This is applied only if FLAGS_cache_size is non-negative.");
|
||||
|
||||
DEFINE_bool(cache_index_and_filter_blocks, false,
|
||||
"True if indexes/filters should be cached in block cache.");
|
||||
|
||||
@@ -321,6 +326,9 @@ DEFINE_uint64(compaction_ttl, 1000,
|
||||
DEFINE_bool(allow_concurrent_memtable_write, false,
|
||||
"Allow multi-writers to update mem tables in parallel.");
|
||||
|
||||
DEFINE_bool(experimental_allow_mempurge, false,
|
||||
"Allow mempurge process to collect memtable garbage bytes.");
|
||||
|
||||
DEFINE_bool(enable_write_thread_adaptive_yield, true,
|
||||
"Use a yielding spin loop for brief writer thread waits.");
|
||||
|
||||
@@ -815,4 +823,19 @@ DEFINE_int32(open_metadata_write_fault_one_in, 0,
|
||||
"On non-zero, enables fault injection on file metadata write "
|
||||
"during DB reopen.");
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
DEFINE_string(secondary_cache_uri, "",
|
||||
"Full URI for creating a customized secondary cache object");
|
||||
#endif // ROCKSDB_LITE
|
||||
DEFINE_int32(open_write_fault_one_in, 0,
|
||||
"On non-zero, enables fault injection on file writes "
|
||||
"during DB reopen.");
|
||||
DEFINE_int32(open_read_fault_one_in, 0,
|
||||
"On non-zero, enables fault injection on file reads "
|
||||
"during DB reopen.");
|
||||
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.");
|
||||
|
||||
#endif // GFLAGS
|
||||
@@ -31,6 +31,10 @@ DECLARE_int32(continuous_verification_interval);
|
||||
DECLARE_int32(read_fault_one_in);
|
||||
DECLARE_int32(write_fault_one_in);
|
||||
DECLARE_int32(open_metadata_write_fault_one_in);
|
||||
DECLARE_int32(open_write_fault_one_in);
|
||||
DECLARE_int32(open_read_fault_one_in);
|
||||
|
||||
DECLARE_int32(injest_error_severity);
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
class StressTest;
|
||||
@@ -58,7 +62,7 @@ class SharedState {
|
||||
static bool ignore_read_error;
|
||||
#endif // ROCKSDB_SUPPORT_THREAD_LOCAL
|
||||
|
||||
SharedState(Env* env, StressTest* stress_test)
|
||||
SharedState(Env* /*env*/, StressTest* stress_test)
|
||||
: cv_(&mu_),
|
||||
seed_(static_cast<uint32_t>(FLAGS_seed)),
|
||||
max_key_(FLAGS_max_key),
|
||||
@@ -122,14 +126,15 @@ class SharedState {
|
||||
"--clear_column_family_one_in is greater than zero.");
|
||||
}
|
||||
uint64_t size = 0;
|
||||
Env* default_env = Env::Default();
|
||||
if (status.ok()) {
|
||||
status = env->GetFileSize(FLAGS_expected_values_path, &size);
|
||||
status = default_env->GetFileSize(FLAGS_expected_values_path, &size);
|
||||
}
|
||||
std::unique_ptr<WritableFile> wfile;
|
||||
if (status.ok() && size == 0) {
|
||||
const EnvOptions soptions;
|
||||
status =
|
||||
env->NewWritableFile(FLAGS_expected_values_path, &wfile, soptions);
|
||||
status = default_env->NewWritableFile(FLAGS_expected_values_path,
|
||||
&wfile, soptions);
|
||||
}
|
||||
if (status.ok() && size == 0) {
|
||||
std::string buf(expected_values_size, '\0');
|
||||
@@ -137,8 +142,8 @@ class SharedState {
|
||||
values_init_needed = true;
|
||||
}
|
||||
if (status.ok()) {
|
||||
status = env->NewMemoryMappedFileBuffer(FLAGS_expected_values_path,
|
||||
&expected_mmap_buffer_);
|
||||
status = default_env->NewMemoryMappedFileBuffer(
|
||||
FLAGS_expected_values_path, &expected_mmap_buffer_);
|
||||
}
|
||||
if (status.ok()) {
|
||||
assert(expected_mmap_buffer_->GetLen() == expected_values_size);
|
||||
|
||||
@@ -14,8 +14,10 @@
|
||||
#include "db_stress_tool/db_stress_driver.h"
|
||||
#include "db_stress_tool/db_stress_table_properties_collector.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
#include "rocksdb/sst_file_manager.h"
|
||||
#include "rocksdb/types.h"
|
||||
#include "rocksdb/utilities/object_registry.h"
|
||||
#include "util/cast_util.h"
|
||||
#include "utilities/backupable/backupable_db_impl.h"
|
||||
#include "utilities/fault_injection_fs.h"
|
||||
@@ -49,7 +51,7 @@ std::shared_ptr<const FilterPolicy> CreateFilterPolicy() {
|
||||
} // namespace
|
||||
|
||||
StressTest::StressTest()
|
||||
: cache_(NewCache(FLAGS_cache_size)),
|
||||
: cache_(NewCache(FLAGS_cache_size, FLAGS_cache_numshardbits)),
|
||||
compressed_cache_(NewLRUCache(FLAGS_compressed_cache_size)),
|
||||
filter_policy_(CreateFilterPolicy()),
|
||||
db_(nullptr),
|
||||
@@ -60,7 +62,8 @@ StressTest::StressTest()
|
||||
new_column_family_name_(1),
|
||||
num_times_reopened_(0),
|
||||
db_preload_finished_(false),
|
||||
cmp_db_(nullptr) {
|
||||
cmp_db_(nullptr),
|
||||
is_db_stopped_(false) {
|
||||
if (FLAGS_destroy_db_initially) {
|
||||
std::vector<std::string> files;
|
||||
db_stress_env->GetChildren(FLAGS_db, &files);
|
||||
@@ -114,7 +117,9 @@ StressTest::~StressTest() {
|
||||
delete cmp_db_;
|
||||
}
|
||||
|
||||
std::shared_ptr<Cache> StressTest::NewCache(size_t capacity) {
|
||||
std::shared_ptr<Cache> StressTest::NewCache(size_t capacity,
|
||||
int32_t num_shard_bits) {
|
||||
ConfigOptions config_options;
|
||||
if (capacity <= 0) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -126,7 +131,24 @@ std::shared_ptr<Cache> StressTest::NewCache(size_t capacity) {
|
||||
}
|
||||
return cache;
|
||||
} else {
|
||||
return NewLRUCache((size_t)capacity);
|
||||
LRUCacheOptions opts;
|
||||
opts.capacity = capacity;
|
||||
opts.num_shard_bits = num_shard_bits;
|
||||
#ifndef ROCKSDB_LITE
|
||||
std::shared_ptr<SecondaryCache> secondary_cache;
|
||||
if (!FLAGS_secondary_cache_uri.empty()) {
|
||||
Status s = SecondaryCache::CreateFromString(
|
||||
config_options, FLAGS_secondary_cache_uri, &secondary_cache);
|
||||
if (secondary_cache == nullptr) {
|
||||
fprintf(stderr,
|
||||
"No secondary cache registered matching string: %s status=%s\n",
|
||||
FLAGS_secondary_cache_uri.c_str(), s.ToString().c_str());
|
||||
exit(1);
|
||||
}
|
||||
opts.secondary_cache = secondary_cache;
|
||||
}
|
||||
#endif
|
||||
return NewLRUCache(opts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -594,13 +616,21 @@ void StressTest::OperateDb(ThreadState* thread) {
|
||||
FLAGS_read_fault_one_in);
|
||||
}
|
||||
if (FLAGS_write_fault_one_in) {
|
||||
IOStatus error_msg = IOStatus::IOError("Retryable IO Error");
|
||||
error_msg.SetRetryable(true);
|
||||
IOStatus error_msg;
|
||||
if (FLAGS_injest_error_severity <= 1 || FLAGS_injest_error_severity > 2) {
|
||||
error_msg = IOStatus::IOError("Retryable IO Error");
|
||||
error_msg.SetRetryable(true);
|
||||
} else if (FLAGS_injest_error_severity == 2) {
|
||||
// Ingest the fatal error
|
||||
error_msg = IOStatus::IOError("Fatal IO Error");
|
||||
error_msg.SetDataLoss(true);
|
||||
}
|
||||
std::vector<FileType> types = {FileType::kTableFile,
|
||||
FileType::kDescriptorFile,
|
||||
FileType::kCurrentFile};
|
||||
fault_fs_guard->SetRandomWriteError(
|
||||
thread->shared->GetSeed(), FLAGS_write_fault_one_in, error_msg, types);
|
||||
thread->shared->GetSeed(), FLAGS_write_fault_one_in, error_msg,
|
||||
/*inject_for_all_file_types=*/false, types);
|
||||
}
|
||||
#endif // NDEBUG
|
||||
thread->stats.Start();
|
||||
@@ -2229,6 +2259,7 @@ void StressTest::Open() {
|
||||
options_.max_subcompactions = static_cast<uint32_t>(FLAGS_subcompactions);
|
||||
options_.allow_concurrent_memtable_write =
|
||||
FLAGS_allow_concurrent_memtable_write;
|
||||
options_.experimental_allow_mempurge = FLAGS_experimental_allow_mempurge;
|
||||
options_.periodic_compaction_seconds = FLAGS_periodic_compaction_seconds;
|
||||
options_.ttl = FLAGS_compaction_ttl;
|
||||
options_.enable_pipelined_write = FLAGS_enable_pipelined_write;
|
||||
@@ -2357,10 +2388,10 @@ void StressTest::Open() {
|
||||
|
||||
if ((options_.enable_blob_files || options_.enable_blob_garbage_collection ||
|
||||
FLAGS_allow_setting_blob_options_dynamically) &&
|
||||
(FLAGS_use_merge || FLAGS_best_efforts_recovery)) {
|
||||
FLAGS_best_efforts_recovery) {
|
||||
fprintf(stderr,
|
||||
"Integrated BlobDB is currently incompatible with Merge, "
|
||||
"and best-effort recovery\n");
|
||||
"Integrated BlobDB is currently incompatible with best-effort "
|
||||
"recovery\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -2444,15 +2475,40 @@ void StressTest::Open() {
|
||||
// Only ingest metadata error if it is reopening, as initial open
|
||||
// failure doesn't need to be handled.
|
||||
// TODO cover transaction DB is not covered in this fault test too.
|
||||
bool ingest_meta_error =
|
||||
FLAGS_open_metadata_write_fault_one_in &&
|
||||
bool ingest_meta_error = false;
|
||||
bool ingest_write_error = false;
|
||||
bool ingest_read_error = false;
|
||||
if ((FLAGS_open_metadata_write_fault_one_in ||
|
||||
FLAGS_open_write_fault_one_in || FLAGS_open_read_fault_one_in) &&
|
||||
fault_fs_guard
|
||||
->FileExists(FLAGS_db + "/CURRENT", IOOptions(), nullptr)
|
||||
.ok();
|
||||
if (ingest_meta_error) {
|
||||
fault_fs_guard->EnableMetadataWriteErrorInjection();
|
||||
fault_fs_guard->SetRandomMetadataWriteError(
|
||||
FLAGS_open_metadata_write_fault_one_in);
|
||||
.ok()) {
|
||||
ingest_meta_error = FLAGS_open_metadata_write_fault_one_in;
|
||||
ingest_write_error = FLAGS_open_write_fault_one_in;
|
||||
ingest_read_error = FLAGS_open_read_fault_one_in;
|
||||
if (ingest_meta_error) {
|
||||
fault_fs_guard->EnableMetadataWriteErrorInjection();
|
||||
fault_fs_guard->SetRandomMetadataWriteError(
|
||||
FLAGS_open_metadata_write_fault_one_in);
|
||||
}
|
||||
if (ingest_write_error) {
|
||||
if (!FLAGS_sync) {
|
||||
// When DB Stress is not sync mode, we expect all WAL writes to
|
||||
// WAL is durable. Buffering unsynced writes will cause false
|
||||
// positive in crash tests. Before we figure out a way to
|
||||
// solve it, skip WAL from failure injection.
|
||||
fault_fs_guard->SetSkipDirectWritableTypes({kWalFile});
|
||||
}
|
||||
fault_fs_guard->SetFilesystemDirectWritable(false);
|
||||
fault_fs_guard->EnableWriteErrorInjection();
|
||||
fault_fs_guard->SetRandomWriteError(
|
||||
static_cast<uint32_t>(FLAGS_seed), FLAGS_open_write_fault_one_in,
|
||||
IOStatus::IOError("Injected Open Error"),
|
||||
/*inject_for_all_file_types=*/true, /*types=*/{});
|
||||
}
|
||||
if (ingest_read_error) {
|
||||
fault_fs_guard->SetRandomReadError(FLAGS_open_read_fault_one_in);
|
||||
}
|
||||
}
|
||||
while (true) {
|
||||
#endif // NDEBUG
|
||||
@@ -2486,8 +2542,12 @@ void StressTest::Open() {
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (ingest_meta_error) {
|
||||
if (ingest_meta_error || ingest_write_error || ingest_read_error) {
|
||||
fault_fs_guard->SetFilesystemDirectWritable(true);
|
||||
fault_fs_guard->DisableMetadataWriteErrorInjection();
|
||||
fault_fs_guard->DisableWriteErrorInjection();
|
||||
fault_fs_guard->SetSkipDirectWritableTypes({});
|
||||
fault_fs_guard->SetRandomReadError(0);
|
||||
if (s.ok()) {
|
||||
// Ingested errors might happen in background compactions. We
|
||||
// wait for all compactions to finish to make sure DB is in
|
||||
@@ -2503,6 +2563,8 @@ void StressTest::Open() {
|
||||
// successfully open the DB with correct data if no IO error shows
|
||||
// up.
|
||||
ingest_meta_error = false;
|
||||
ingest_write_error = false;
|
||||
ingest_read_error = false;
|
||||
|
||||
Random rand(static_cast<uint32_t>(FLAGS_seed));
|
||||
if (rand.OneIn(2)) {
|
||||
|
||||
@@ -23,7 +23,7 @@ class StressTest {
|
||||
|
||||
virtual ~StressTest();
|
||||
|
||||
std::shared_ptr<Cache> NewCache(size_t capacity);
|
||||
std::shared_ptr<Cache> NewCache(size_t capacity, int32_t num_shard_bits);
|
||||
|
||||
static std::vector<std::string> GetBlobCompressionTags();
|
||||
|
||||
@@ -237,6 +237,7 @@ class StressTest {
|
||||
// Fields used for continuous verification from another thread
|
||||
DB* cmp_db_;
|
||||
std::vector<ColumnFamilyHandle*> cmp_cfhs_;
|
||||
bool is_db_stopped_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -86,7 +86,8 @@ int db_stress_tool(int argc, char** argv) {
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (FLAGS_read_fault_one_in || FLAGS_sync_fault_injection ||
|
||||
FLAGS_write_fault_one_in || FLAGS_open_metadata_write_fault_one_in) {
|
||||
FLAGS_write_fault_one_in || FLAGS_open_metadata_write_fault_one_in ||
|
||||
FLAGS_open_write_fault_one_in || FLAGS_open_read_fault_one_in) {
|
||||
FaultInjectionTestFS* fs =
|
||||
new FaultInjectionTestFS(raw_env->GetFileSystem());
|
||||
fault_fs_guard.reset(fs);
|
||||
|
||||
@@ -552,8 +552,18 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
}
|
||||
shared->Put(rand_column_family, rand_key, value_base, false /* pending */);
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "put or merge error: %s\n", s.ToString().c_str());
|
||||
std::terminate();
|
||||
if (FLAGS_injest_error_severity >= 2) {
|
||||
if (!is_db_stopped_ && s.severity() >= Status::Severity::kFatalError) {
|
||||
is_db_stopped_ = true;
|
||||
} else if (!is_db_stopped_ ||
|
||||
s.severity() < Status::Severity::kFatalError) {
|
||||
fprintf(stderr, "put or merge error: %s\n", s.ToString().c_str());
|
||||
std::terminate();
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "put or merge error: %s\n", s.ToString().c_str());
|
||||
std::terminate();
|
||||
}
|
||||
}
|
||||
thread->stats.AddBytesForWrites(1, sz);
|
||||
PrintKeyValue(rand_column_family, static_cast<uint32_t>(rand_key), value,
|
||||
@@ -615,8 +625,19 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
shared->Delete(rand_column_family, rand_key, false /* pending */);
|
||||
thread->stats.AddDeletes(1);
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "delete error: %s\n", s.ToString().c_str());
|
||||
std::terminate();
|
||||
if (FLAGS_injest_error_severity >= 2) {
|
||||
if (!is_db_stopped_ &&
|
||||
s.severity() >= Status::Severity::kFatalError) {
|
||||
is_db_stopped_ = true;
|
||||
} else if (!is_db_stopped_ ||
|
||||
s.severity() < Status::Severity::kFatalError) {
|
||||
fprintf(stderr, "delete error: %s\n", s.ToString().c_str());
|
||||
std::terminate();
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "delete error: %s\n", s.ToString().c_str());
|
||||
std::terminate();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
shared->SingleDelete(rand_column_family, rand_key, true /* pending */);
|
||||
@@ -637,8 +658,19 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
shared->SingleDelete(rand_column_family, rand_key, false /* pending */);
|
||||
thread->stats.AddSingleDeletes(1);
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "single delete error: %s\n", s.ToString().c_str());
|
||||
std::terminate();
|
||||
if (FLAGS_injest_error_severity >= 2) {
|
||||
if (!is_db_stopped_ &&
|
||||
s.severity() >= Status::Severity::kFatalError) {
|
||||
is_db_stopped_ = true;
|
||||
} else if (!is_db_stopped_ ||
|
||||
s.severity() < Status::Severity::kFatalError) {
|
||||
fprintf(stderr, "single delete error: %s\n", s.ToString().c_str());
|
||||
std::terminate();
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "single delete error: %s\n", s.ToString().c_str());
|
||||
std::terminate();
|
||||
}
|
||||
}
|
||||
}
|
||||
return s;
|
||||
@@ -684,8 +716,18 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
Slice end_key = end_keystr;
|
||||
Status s = db_->DeleteRange(write_opts, cfh, key, end_key);
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "delete range error: %s\n", s.ToString().c_str());
|
||||
std::terminate();
|
||||
if (FLAGS_injest_error_severity >= 2) {
|
||||
if (!is_db_stopped_ && s.severity() >= Status::Severity::kFatalError) {
|
||||
is_db_stopped_ = true;
|
||||
} else if (!is_db_stopped_ ||
|
||||
s.severity() < Status::Severity::kFatalError) {
|
||||
fprintf(stderr, "delete range error: %s\n", s.ToString().c_str());
|
||||
std::terminate();
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "delete range error: %s\n", s.ToString().c_str());
|
||||
std::terminate();
|
||||
}
|
||||
}
|
||||
int covered = shared->DeleteRange(rand_column_family, rand_key,
|
||||
rand_key + FLAGS_range_deletion_width,
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ GEM
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
zeitwerk (~> 2.2, >= 2.2.2)
|
||||
addressable (2.7.0)
|
||||
addressable (2.8.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
coffee-script (2.4.1)
|
||||
coffee-script-source
|
||||
|
||||
Vendored
+84
-48
@@ -17,15 +17,81 @@
|
||||
#include "test_util/testharness.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
typedef Env* CreateEnvFunc();
|
||||
namespace {
|
||||
// These functions are used to create the various environments under which this
|
||||
// test can execute. These functions are used to allow the test cases to be
|
||||
// created without the Env being initialized, thereby eliminating a potential
|
||||
// static initialization fiasco/race condition when attempting to get a
|
||||
// custom/configured env prior to main being invoked.
|
||||
|
||||
class EnvBasicTestWithParam : public testing::Test,
|
||||
public ::testing::WithParamInterface<Env*> {
|
||||
static Env* GetDefaultEnv() { return Env::Default(); }
|
||||
|
||||
static Env* GetMockEnv() {
|
||||
static std::unique_ptr<Env> mock_env(new MockEnv(Env::Default()));
|
||||
return mock_env.get();
|
||||
}
|
||||
#ifndef ROCKSDB_LITE
|
||||
static Env* NewTestEncryptedEnv(Env* base, const std::string& provider_id) {
|
||||
ConfigOptions config_opts;
|
||||
config_opts.invoke_prepare_options = false;
|
||||
|
||||
std::shared_ptr<EncryptionProvider> provider;
|
||||
EXPECT_OK(EncryptionProvider::CreateFromString(config_opts, provider_id,
|
||||
&provider));
|
||||
return NewEncryptedEnv(base, provider);
|
||||
}
|
||||
|
||||
static Env* GetCtrEncryptedEnv() {
|
||||
static std::unique_ptr<Env> ctr_encrypt_env(
|
||||
NewTestEncryptedEnv(Env::Default(), "CTR://test"));
|
||||
return ctr_encrypt_env.get();
|
||||
}
|
||||
|
||||
static Env* GetMemoryEnv() {
|
||||
static std::unique_ptr<Env> mem_env(NewMemEnv(Env::Default()));
|
||||
return mem_env.get();
|
||||
}
|
||||
|
||||
static Env* GetTestEnv() {
|
||||
static std::shared_ptr<Env> env_guard;
|
||||
static Env* custom_env = nullptr;
|
||||
if (custom_env == nullptr) {
|
||||
const char* uri = getenv("TEST_ENV_URI");
|
||||
if (uri != nullptr) {
|
||||
EXPECT_OK(Env::CreateFromUri(ConfigOptions(), uri, "", &custom_env,
|
||||
&env_guard));
|
||||
}
|
||||
}
|
||||
EXPECT_NE(custom_env, nullptr);
|
||||
return custom_env;
|
||||
}
|
||||
|
||||
static Env* GetTestFS() {
|
||||
static std::shared_ptr<Env> fs_env_guard;
|
||||
static Env* fs_env = nullptr;
|
||||
if (fs_env == nullptr) {
|
||||
const char* uri = getenv("TEST_FS_URI");
|
||||
if (uri != nullptr) {
|
||||
EXPECT_OK(
|
||||
Env::CreateFromUri(ConfigOptions(), uri, "", &fs_env, &fs_env_guard));
|
||||
}
|
||||
}
|
||||
EXPECT_NE(fs_env, nullptr);
|
||||
return fs_env;
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace
|
||||
class EnvBasicTestWithParam
|
||||
: public testing::Test,
|
||||
public ::testing::WithParamInterface<CreateEnvFunc*> {
|
||||
public:
|
||||
Env* env_;
|
||||
const EnvOptions soptions_;
|
||||
std::string test_dir_;
|
||||
|
||||
EnvBasicTestWithParam() : env_(GetParam()) {
|
||||
EnvBasicTestWithParam() : env_(GetParam()()) {
|
||||
test_dir_ = test::PerThreadDBPath(env_, "env_basic_test");
|
||||
}
|
||||
|
||||
@@ -37,35 +103,22 @@ class EnvBasicTestWithParam : public testing::Test,
|
||||
class EnvMoreTestWithParam : public EnvBasicTestWithParam {};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(EnvDefault, EnvBasicTestWithParam,
|
||||
::testing::Values(Env::Default()));
|
||||
::testing::Values(&GetDefaultEnv));
|
||||
INSTANTIATE_TEST_CASE_P(EnvDefault, EnvMoreTestWithParam,
|
||||
::testing::Values(Env::Default()));
|
||||
::testing::Values(&GetDefaultEnv));
|
||||
|
||||
static std::unique_ptr<Env> mock_env(new MockEnv(Env::Default()));
|
||||
INSTANTIATE_TEST_CASE_P(MockEnv, EnvBasicTestWithParam,
|
||||
::testing::Values(mock_env.get()));
|
||||
::testing::Values(&GetMockEnv));
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
static Env* NewTestEncryptedEnv(Env* base, const std::string& provider_id) {
|
||||
std::shared_ptr<EncryptionProvider> provider;
|
||||
EXPECT_OK(EncryptionProvider::CreateFromString(ConfigOptions(), provider_id,
|
||||
&provider));
|
||||
return NewEncryptedEnv(base, provider);
|
||||
}
|
||||
|
||||
// next statements run env test against default encryption code.
|
||||
static std::unique_ptr<Env> ctr_encrypt_env(NewTestEncryptedEnv(Env::Default(),
|
||||
"test://CTR"));
|
||||
INSTANTIATE_TEST_CASE_P(EncryptedEnv, EnvBasicTestWithParam,
|
||||
::testing::Values(ctr_encrypt_env.get()));
|
||||
::testing::Values(&GetCtrEncryptedEnv));
|
||||
INSTANTIATE_TEST_CASE_P(EncryptedEnv, EnvMoreTestWithParam,
|
||||
::testing::Values(ctr_encrypt_env.get()));
|
||||
#endif // ROCKSDB_LITE
|
||||
::testing::Values(&GetCtrEncryptedEnv));
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
static std::unique_ptr<Env> mem_env(NewMemEnv(Env::Default()));
|
||||
INSTANTIATE_TEST_CASE_P(MemEnv, EnvBasicTestWithParam,
|
||||
::testing::Values(mem_env.get()));
|
||||
::testing::Values(&GetMemoryEnv));
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -74,31 +127,15 @@ namespace {
|
||||
//
|
||||
// The purpose of returning an empty vector (instead of nullptr) is that gtest
|
||||
// ValuesIn() will skip running tests when given an empty collection.
|
||||
std::vector<Env*> GetCustomEnvs() {
|
||||
static bool init = false;
|
||||
static std::vector<Env*> res;
|
||||
if (!init) {
|
||||
init = true;
|
||||
const char* uri = getenv("TEST_ENV_URI");
|
||||
if (uri != nullptr) {
|
||||
static std::shared_ptr<Env> env_guard;
|
||||
static Env* custom_env;
|
||||
Status s =
|
||||
Env::CreateFromUri(ConfigOptions(), uri, "", &custom_env, &env_guard);
|
||||
if (s.ok()) {
|
||||
res.emplace_back(custom_env);
|
||||
}
|
||||
}
|
||||
uri = getenv("TEST_FS_URI");
|
||||
if (uri != nullptr) {
|
||||
static std::shared_ptr<Env> fs_env_guard;
|
||||
static Env* fs_env;
|
||||
Status s =
|
||||
Env::CreateFromUri(ConfigOptions(), "", uri, &fs_env, &fs_env_guard);
|
||||
if (s.ok()) {
|
||||
res.emplace_back(fs_env);
|
||||
}
|
||||
}
|
||||
std::vector<CreateEnvFunc*> GetCustomEnvs() {
|
||||
std::vector<CreateEnvFunc*> res;
|
||||
const char* uri = getenv("TEST_ENV_URI");
|
||||
if (uri != nullptr) {
|
||||
res.push_back(&GetTestEnv);
|
||||
}
|
||||
uri = getenv("TEST_FS_URI");
|
||||
if (uri != nullptr) {
|
||||
res.push_back(&GetTestFS);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@@ -110,7 +147,6 @@ INSTANTIATE_TEST_CASE_P(CustomEnv, EnvBasicTestWithParam,
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(CustomEnv, EnvMoreTestWithParam,
|
||||
::testing::ValuesIn(GetCustomEnvs()));
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_P(EnvBasicTestWithParam, Basics) {
|
||||
|
||||
Vendored
+116
-69
@@ -18,62 +18,16 @@
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/io_status.h"
|
||||
#include "rocksdb/system_clock.h"
|
||||
#include "rocksdb/utilities/customizable_util.h"
|
||||
#include "rocksdb/utilities/options_type.h"
|
||||
#include "util/aligned_buffer.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/random.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
#endif
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
static constexpr char kROT13CipherName[] = "ROT13";
|
||||
static constexpr char kCTRProviderName[] = "CTR";
|
||||
|
||||
Status BlockCipher::CreateFromString(const ConfigOptions& /*config_options*/,
|
||||
const std::string& value,
|
||||
std::shared_ptr<BlockCipher>* result) {
|
||||
std::string id = value;
|
||||
size_t colon = value.find(':');
|
||||
if (colon != std::string::npos) {
|
||||
id = value.substr(0, colon);
|
||||
}
|
||||
if (id == kROT13CipherName) {
|
||||
if (colon != std::string::npos) {
|
||||
size_t block_size = ParseSizeT(value.substr(colon + 1));
|
||||
result->reset(new ROT13BlockCipher(block_size));
|
||||
} else {
|
||||
result->reset(new ROT13BlockCipher(32));
|
||||
}
|
||||
return Status::OK();
|
||||
} else {
|
||||
return Status::NotSupported("Could not find cipher ", value);
|
||||
}
|
||||
}
|
||||
|
||||
Status EncryptionProvider::CreateFromString(
|
||||
const ConfigOptions& /*config_options*/, const std::string& value,
|
||||
std::shared_ptr<EncryptionProvider>* result) {
|
||||
std::string id = value;
|
||||
bool is_test = StartsWith(value, "test://");
|
||||
Status status = Status::OK();
|
||||
if (is_test) {
|
||||
id = value.substr(strlen("test://"));
|
||||
}
|
||||
if (id == kCTRProviderName) {
|
||||
result->reset(new CTREncryptionProvider());
|
||||
} else if (is_test) {
|
||||
result->reset(new CTREncryptionProvider());
|
||||
} else {
|
||||
return Status::NotSupported("Could not find provider ", value);
|
||||
}
|
||||
if (status.ok() && is_test) {
|
||||
status = result->get()->TEST_Initialize();
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
std::shared_ptr<EncryptionProvider> EncryptionProvider::NewCTRProvider(
|
||||
const std::shared_ptr<BlockCipher>& cipher) {
|
||||
return std::make_shared<CTREncryptionProvider>(cipher);
|
||||
@@ -1061,20 +1015,53 @@ Status BlockAccessCipherStream::Decrypt(uint64_t fileOffset, char *data, size_t
|
||||
}
|
||||
}
|
||||
|
||||
const char* ROT13BlockCipher::Name() const { return kROT13CipherName; }
|
||||
namespace {
|
||||
static std::unordered_map<std::string, OptionTypeInfo>
|
||||
rot13_block_cipher_type_info = {
|
||||
{"block_size",
|
||||
{0 /* No offset, whole struct*/, OptionType::kInt,
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone}},
|
||||
};
|
||||
// Implements a BlockCipher using ROT13.
|
||||
//
|
||||
// Note: This is a sample implementation of BlockCipher,
|
||||
// it is NOT considered safe and should NOT be used in production.
|
||||
class ROT13BlockCipher : public BlockCipher {
|
||||
private:
|
||||
size_t blockSize_;
|
||||
|
||||
// Encrypt a block of data.
|
||||
// Length of data is equal to BlockSize().
|
||||
Status ROT13BlockCipher::Encrypt(char* data) {
|
||||
for (size_t i = 0; i < blockSize_; ++i) {
|
||||
data[i] += 13;
|
||||
public:
|
||||
explicit ROT13BlockCipher(size_t blockSize) : blockSize_(blockSize) {
|
||||
RegisterOptions("ROT13BlockCipherOptions", &blockSize_,
|
||||
&rot13_block_cipher_type_info);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Decrypt a block of data.
|
||||
// Length of data is equal to BlockSize().
|
||||
Status ROT13BlockCipher::Decrypt(char* data) { return Encrypt(data); }
|
||||
static const char* kClassName() { return "ROT13"; }
|
||||
const char* Name() const override { return kClassName(); }
|
||||
// BlockSize returns the size of each block supported by this cipher stream.
|
||||
size_t BlockSize() override { return blockSize_; }
|
||||
|
||||
// Encrypt a block of data.
|
||||
// Length of data is equal to BlockSize().
|
||||
Status Encrypt(char* data) override {
|
||||
for (size_t i = 0; i < blockSize_; ++i) {
|
||||
data[i] += 13;
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Decrypt a block of data.
|
||||
// Length of data is equal to BlockSize().
|
||||
Status Decrypt(char* data) override { return Encrypt(data); }
|
||||
};
|
||||
static const std::unordered_map<std::string, OptionTypeInfo>
|
||||
ctr_encryption_provider_type_info = {
|
||||
{"cipher",
|
||||
OptionTypeInfo::AsCustomSharedPtr<BlockCipher>(
|
||||
0 /* No offset, whole struct*/, OptionVerificationType::kByName,
|
||||
OptionTypeFlags::kNone)},
|
||||
};
|
||||
} // anonymous namespace
|
||||
|
||||
// Allocate scratch space which is passed to EncryptBlock/DecryptBlock.
|
||||
void CTRCipherStream::AllocateScratch(std::string& scratch) {
|
||||
@@ -1112,7 +1099,11 @@ Status CTRCipherStream::DecryptBlock(uint64_t blockIndex, char* data,
|
||||
return EncryptBlock(blockIndex, data, scratch);
|
||||
}
|
||||
|
||||
const char* CTREncryptionProvider::Name() const { return kCTRProviderName; }
|
||||
CTREncryptionProvider::CTREncryptionProvider(
|
||||
const std::shared_ptr<BlockCipher>& c)
|
||||
: cipher_(c) {
|
||||
RegisterOptions("Cipher", &cipher_, &ctr_encryption_provider_type_info);
|
||||
}
|
||||
|
||||
// GetPrefixLength returns the length of the prefix that is added to every file
|
||||
// and used for storing encryption options.
|
||||
@@ -1122,20 +1113,12 @@ size_t CTREncryptionProvider::GetPrefixLength() const {
|
||||
return defaultPrefixLength;
|
||||
}
|
||||
|
||||
Status CTREncryptionProvider::TEST_Initialize() {
|
||||
if (!cipher_) {
|
||||
return BlockCipher::CreateFromString(
|
||||
ConfigOptions(), std::string(kROT13CipherName) + ":32", &cipher_);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status CTREncryptionProvider::AddCipher(const std::string& /*descriptor*/,
|
||||
const char* cipher, size_t len,
|
||||
bool /*for_write*/) {
|
||||
if (cipher_) {
|
||||
return Status::NotSupported("Cannot add keys to CTREncryptionProvider");
|
||||
} else if (strcmp(kROT13CipherName, cipher) == 0) {
|
||||
} else if (strcmp(ROT13BlockCipher::kClassName(), cipher) == 0) {
|
||||
cipher_.reset(new ROT13BlockCipher(len));
|
||||
return Status::OK();
|
||||
} else {
|
||||
@@ -1252,6 +1235,70 @@ Status CTREncryptionProvider::CreateCipherStreamFromPrefix(
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
namespace {
|
||||
static void RegisterEncryptionBuiltins() {
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [&]() {
|
||||
auto lib = ObjectRegistry::Default()->AddLibrary("encryption");
|
||||
std::string ctr =
|
||||
std::string(CTREncryptionProvider::kClassName()) + "?(://test)";
|
||||
lib->Register<EncryptionProvider>(
|
||||
std::string(CTREncryptionProvider::kClassName()) + "(://test)?",
|
||||
[](const std::string& uri, std::unique_ptr<EncryptionProvider>* guard,
|
||||
std::string* /*errmsg*/) {
|
||||
if (EndsWith(uri, "://test")) {
|
||||
std::shared_ptr<BlockCipher> cipher =
|
||||
std::make_shared<ROT13BlockCipher>(32);
|
||||
guard->reset(new CTREncryptionProvider(cipher));
|
||||
} else {
|
||||
guard->reset(new CTREncryptionProvider());
|
||||
}
|
||||
return guard->get();
|
||||
});
|
||||
|
||||
lib->Register<EncryptionProvider>(
|
||||
"1://test", [](const std::string& /*uri*/,
|
||||
std::unique_ptr<EncryptionProvider>* guard,
|
||||
std::string* /*errmsg*/) {
|
||||
std::shared_ptr<BlockCipher> cipher =
|
||||
std::make_shared<ROT13BlockCipher>(32);
|
||||
guard->reset(new CTREncryptionProvider(cipher));
|
||||
return guard->get();
|
||||
});
|
||||
|
||||
lib->Register<BlockCipher>(
|
||||
std::string(ROT13BlockCipher::kClassName()) + "(:.*)?",
|
||||
[](const std::string& uri, std::unique_ptr<BlockCipher>* guard,
|
||||
std::string* /* errmsg */) {
|
||||
size_t colon = uri.find(':');
|
||||
if (colon != std::string::npos) {
|
||||
size_t block_size = ParseSizeT(uri.substr(colon + 1));
|
||||
guard->reset(new ROT13BlockCipher(block_size));
|
||||
} else {
|
||||
guard->reset(new ROT13BlockCipher(32));
|
||||
}
|
||||
|
||||
return guard->get();
|
||||
});
|
||||
});
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Status BlockCipher::CreateFromString(const ConfigOptions& config_options,
|
||||
const std::string& value,
|
||||
std::shared_ptr<BlockCipher>* result) {
|
||||
RegisterEncryptionBuiltins();
|
||||
return LoadSharedObject<BlockCipher>(config_options, value, nullptr, result);
|
||||
}
|
||||
|
||||
Status EncryptionProvider::CreateFromString(
|
||||
const ConfigOptions& config_options, const std::string& value,
|
||||
std::shared_ptr<EncryptionProvider>* result) {
|
||||
RegisterEncryptionBuiltins();
|
||||
return LoadSharedObject<EncryptionProvider>(config_options, value, nullptr,
|
||||
result);
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+3
-30
@@ -10,31 +10,6 @@
|
||||
#include "rocksdb/env_encryption.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// Implements a BlockCipher using ROT13.
|
||||
//
|
||||
// Note: This is a sample implementation of BlockCipher,
|
||||
// it is NOT considered safe and should NOT be used in production.
|
||||
class ROT13BlockCipher : public BlockCipher {
|
||||
private:
|
||||
size_t blockSize_;
|
||||
|
||||
public:
|
||||
ROT13BlockCipher(size_t blockSize) : blockSize_(blockSize) {}
|
||||
virtual ~ROT13BlockCipher(){};
|
||||
const char* Name() const override;
|
||||
// BlockSize returns the size of each block supported by this cipher stream.
|
||||
size_t BlockSize() override { return blockSize_; }
|
||||
|
||||
// Encrypt a block of data.
|
||||
// Length of data is equal to BlockSize().
|
||||
Status Encrypt(char* data) override;
|
||||
|
||||
// Decrypt a block of data.
|
||||
// Length of data is equal to BlockSize().
|
||||
Status Decrypt(char* data) override;
|
||||
};
|
||||
|
||||
// CTRCipherStream implements BlockAccessCipherStream using an
|
||||
// Counter operations mode.
|
||||
// See https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
|
||||
@@ -86,11 +61,11 @@ class CTREncryptionProvider : public EncryptionProvider {
|
||||
|
||||
public:
|
||||
explicit CTREncryptionProvider(
|
||||
const std::shared_ptr<BlockCipher>& c = nullptr)
|
||||
: cipher_(c){};
|
||||
const std::shared_ptr<BlockCipher>& c = nullptr);
|
||||
virtual ~CTREncryptionProvider() {}
|
||||
|
||||
const char* Name() const override;
|
||||
static const char* kClassName() { return "CTR"; }
|
||||
const char* Name() const override { return kClassName(); }
|
||||
|
||||
// GetPrefixLength returns the length of the prefix that is added to every
|
||||
// file
|
||||
@@ -112,9 +87,7 @@ class CTREncryptionProvider : public EncryptionProvider {
|
||||
|
||||
Status AddCipher(const std::string& descriptor, const char* /*cipher*/,
|
||||
size_t /*len*/, bool /*for_write*/) override;
|
||||
|
||||
protected:
|
||||
Status TEST_Initialize() override;
|
||||
|
||||
// PopulateSecretPrefixPart initializes the data into a new prefix block
|
||||
// that will be encrypted. This function will store the data in plain text.
|
||||
|
||||
Vendored
+2
@@ -7,6 +7,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors
|
||||
|
||||
#include "port/lang.h"
|
||||
#if !defined(OS_WIN)
|
||||
|
||||
#include <dirent.h>
|
||||
@@ -511,6 +512,7 @@ Env* Env::Default() {
|
||||
ThreadLocalPtr::InitSingletons();
|
||||
CompressionContextCache::InitSingleton();
|
||||
INIT_SYNC_POINT_SINGLETONS();
|
||||
// ~PosixEnv must be called on exit
|
||||
static PosixEnv default_env;
|
||||
return &default_env;
|
||||
}
|
||||
|
||||
Vendored
+71
@@ -36,10 +36,13 @@
|
||||
#endif
|
||||
|
||||
#include "env/env_chroot.h"
|
||||
#include "env/env_encryption_ctr.h"
|
||||
#include "logging/log_buffer.h"
|
||||
#include "port/malloc.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/env_encryption.h"
|
||||
#include "rocksdb/system_clock.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "test_util/testharness.h"
|
||||
@@ -2387,6 +2390,74 @@ TEST_F(EnvTest, EnvWriteVerificationTest) {
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
class EncryptionProviderTest : public testing::Test {
|
||||
public:
|
||||
};
|
||||
|
||||
TEST_F(EncryptionProviderTest, LoadCTRProvider) {
|
||||
ConfigOptions config_options;
|
||||
config_options.invoke_prepare_options = false;
|
||||
std::string CTR = CTREncryptionProvider::kClassName();
|
||||
std::shared_ptr<EncryptionProvider> provider;
|
||||
// Test a provider with no cipher
|
||||
ASSERT_OK(
|
||||
EncryptionProvider::CreateFromString(config_options, CTR, &provider));
|
||||
ASSERT_NE(provider, nullptr);
|
||||
ASSERT_EQ(provider->Name(), CTR);
|
||||
ASSERT_NOK(provider->PrepareOptions(config_options));
|
||||
ASSERT_NOK(provider->ValidateOptions(DBOptions(), ColumnFamilyOptions()));
|
||||
auto cipher = provider->GetOptions<std::shared_ptr<BlockCipher>>("Cipher");
|
||||
ASSERT_NE(cipher, nullptr);
|
||||
ASSERT_EQ(cipher->get(), nullptr);
|
||||
provider.reset();
|
||||
|
||||
ASSERT_OK(EncryptionProvider::CreateFromString(config_options,
|
||||
CTR + "://test", &provider));
|
||||
ASSERT_NE(provider, nullptr);
|
||||
ASSERT_EQ(provider->Name(), CTR);
|
||||
ASSERT_OK(provider->PrepareOptions(config_options));
|
||||
ASSERT_OK(provider->ValidateOptions(DBOptions(), ColumnFamilyOptions()));
|
||||
cipher = provider->GetOptions<std::shared_ptr<BlockCipher>>("Cipher");
|
||||
ASSERT_NE(cipher, nullptr);
|
||||
ASSERT_NE(cipher->get(), nullptr);
|
||||
ASSERT_STREQ(cipher->get()->Name(), "ROT13");
|
||||
provider.reset();
|
||||
|
||||
ASSERT_OK(EncryptionProvider::CreateFromString(config_options, "1://test",
|
||||
&provider));
|
||||
ASSERT_NE(provider, nullptr);
|
||||
ASSERT_EQ(provider->Name(), CTR);
|
||||
ASSERT_OK(provider->PrepareOptions(config_options));
|
||||
ASSERT_OK(provider->ValidateOptions(DBOptions(), ColumnFamilyOptions()));
|
||||
cipher = provider->GetOptions<std::shared_ptr<BlockCipher>>("Cipher");
|
||||
ASSERT_NE(cipher, nullptr);
|
||||
ASSERT_NE(cipher->get(), nullptr);
|
||||
ASSERT_STREQ(cipher->get()->Name(), "ROT13");
|
||||
provider.reset();
|
||||
|
||||
ASSERT_OK(EncryptionProvider::CreateFromString(
|
||||
config_options, "id=" + CTR + "; cipher=ROT13", &provider));
|
||||
ASSERT_NE(provider, nullptr);
|
||||
ASSERT_EQ(provider->Name(), CTR);
|
||||
cipher = provider->GetOptions<std::shared_ptr<BlockCipher>>("Cipher");
|
||||
ASSERT_NE(cipher, nullptr);
|
||||
ASSERT_NE(cipher->get(), nullptr);
|
||||
ASSERT_STREQ(cipher->get()->Name(), "ROT13");
|
||||
provider.reset();
|
||||
}
|
||||
|
||||
TEST_F(EncryptionProviderTest, LoadROT13Cipher) {
|
||||
ConfigOptions config_options;
|
||||
std::shared_ptr<BlockCipher> cipher;
|
||||
// Test a provider with no cipher
|
||||
ASSERT_OK(BlockCipher::CreateFromString(config_options, "ROT13", &cipher));
|
||||
ASSERT_NE(cipher, nullptr);
|
||||
ASSERT_STREQ(cipher->Name(), "ROT13");
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+245
-18
@@ -36,7 +36,8 @@ Status WritableFileWriter::Create(const std::shared_ptr<FileSystem>& fs,
|
||||
return s;
|
||||
}
|
||||
|
||||
IOStatus WritableFileWriter::Append(const Slice& data) {
|
||||
IOStatus WritableFileWriter::Append(const Slice& data,
|
||||
uint32_t crc32c_checksum) {
|
||||
const char* src = data.data();
|
||||
size_t left = data.size();
|
||||
IOStatus s;
|
||||
@@ -81,26 +82,74 @@ IOStatus WritableFileWriter::Append(const Slice& data) {
|
||||
assert(buf_.CurrentSize() == 0);
|
||||
}
|
||||
|
||||
// We never write directly to disk with direct I/O on.
|
||||
// or we simply use it for its original purpose to accumulate many small
|
||||
// chunks
|
||||
if (use_direct_io() || (buf_.Capacity() >= left)) {
|
||||
while (left > 0) {
|
||||
size_t appended = buf_.Append(src, left);
|
||||
left -= appended;
|
||||
src += appended;
|
||||
if (perform_data_verification_ && buffered_data_with_checksum_ &&
|
||||
crc32c_checksum != 0) {
|
||||
// Since we want to use the checksum of the input data, we cannot break it
|
||||
// into several pieces. We will only write them in the buffer when buffer
|
||||
// size is enough. Otherwise, we will directly write it down.
|
||||
if (use_direct_io() || (buf_.Capacity() - buf_.CurrentSize()) >= left) {
|
||||
if ((buf_.Capacity() - buf_.CurrentSize()) >= left) {
|
||||
size_t appended = buf_.Append(src, left);
|
||||
if (appended != left) {
|
||||
s = IOStatus::Corruption("Write buffer append failure");
|
||||
}
|
||||
buffered_data_crc32c_checksum_ = crc32c::Crc32cCombine(
|
||||
buffered_data_crc32c_checksum_, crc32c_checksum, appended);
|
||||
} else {
|
||||
while (left > 0) {
|
||||
size_t appended = buf_.Append(src, left);
|
||||
buffered_data_crc32c_checksum_ =
|
||||
crc32c::Extend(buffered_data_crc32c_checksum_, src, appended);
|
||||
left -= appended;
|
||||
src += appended;
|
||||
|
||||
if (left > 0) {
|
||||
s = Flush();
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
if (left > 0) {
|
||||
s = Flush();
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assert(buf_.CurrentSize() == 0);
|
||||
buffered_data_crc32c_checksum_ = crc32c_checksum;
|
||||
s = WriteBufferedWithChecksum(src, left);
|
||||
}
|
||||
} else {
|
||||
// Writing directly to file bypassing the buffer
|
||||
assert(buf_.CurrentSize() == 0);
|
||||
s = WriteBuffered(src, left);
|
||||
// In this case, either we do not need to do the data verification or
|
||||
// caller does not provide the checksum of the data (crc32c_checksum = 0).
|
||||
//
|
||||
// We never write directly to disk with direct I/O on.
|
||||
// or we simply use it for its original purpose to accumulate many small
|
||||
// chunks
|
||||
if (use_direct_io() || (buf_.Capacity() >= left)) {
|
||||
while (left > 0) {
|
||||
size_t appended = buf_.Append(src, left);
|
||||
if (perform_data_verification_ && buffered_data_with_checksum_) {
|
||||
buffered_data_crc32c_checksum_ =
|
||||
crc32c::Extend(buffered_data_crc32c_checksum_, src, appended);
|
||||
}
|
||||
left -= appended;
|
||||
src += appended;
|
||||
|
||||
if (left > 0) {
|
||||
s = Flush();
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Writing directly to file bypassing the buffer
|
||||
assert(buf_.CurrentSize() == 0);
|
||||
if (perform_data_verification_ && buffered_data_with_checksum_) {
|
||||
buffered_data_crc32c_checksum_ = crc32c::Value(src, left);
|
||||
s = WriteBufferedWithChecksum(src, left);
|
||||
} else {
|
||||
s = WriteBuffered(src, left);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_KILL_RANDOM("WritableFileWriter::Append:1");
|
||||
@@ -114,6 +163,7 @@ IOStatus WritableFileWriter::Pad(const size_t pad_bytes) {
|
||||
assert(pad_bytes < kDefaultPageSize);
|
||||
size_t left = pad_bytes;
|
||||
size_t cap = buf_.Capacity() - buf_.CurrentSize();
|
||||
size_t pad_start = buf_.CurrentSize();
|
||||
|
||||
// Assume pad_bytes is small compared to buf_ capacity. So we always
|
||||
// use buf_ rather than write directly to file in certain cases like
|
||||
@@ -132,6 +182,11 @@ IOStatus WritableFileWriter::Pad(const size_t pad_bytes) {
|
||||
}
|
||||
pending_sync_ = true;
|
||||
filesize_ += pad_bytes;
|
||||
if (perform_data_verification_) {
|
||||
buffered_data_crc32c_checksum_ =
|
||||
crc32c::Extend(buffered_data_crc32c_checksum_,
|
||||
buf_.BufferStart() + pad_start, pad_bytes);
|
||||
}
|
||||
return IOStatus::OK();
|
||||
}
|
||||
|
||||
@@ -232,11 +287,19 @@ IOStatus WritableFileWriter::Flush() {
|
||||
if (use_direct_io()) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (pending_sync_) {
|
||||
s = WriteDirect();
|
||||
if (perform_data_verification_ && buffered_data_with_checksum_) {
|
||||
s = WriteDirectWithChecksum();
|
||||
} else {
|
||||
s = WriteDirect();
|
||||
}
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
} else {
|
||||
s = WriteBuffered(buf_.BufferStart(), buf_.CurrentSize());
|
||||
if (perform_data_verification_ && buffered_data_with_checksum_) {
|
||||
s = WriteBufferedWithChecksum(buf_.BufferStart(), buf_.CurrentSize());
|
||||
} else {
|
||||
s = WriteBuffered(buf_.BufferStart(), buf_.CurrentSize());
|
||||
}
|
||||
}
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -451,6 +514,76 @@ IOStatus WritableFileWriter::WriteBuffered(const char* data, size_t size) {
|
||||
src += allowed;
|
||||
}
|
||||
buf_.Size(0);
|
||||
buffered_data_crc32c_checksum_ = 0;
|
||||
return s;
|
||||
}
|
||||
|
||||
IOStatus WritableFileWriter::WriteBufferedWithChecksum(const char* data,
|
||||
size_t size) {
|
||||
IOStatus s;
|
||||
assert(!use_direct_io());
|
||||
assert(perform_data_verification_ && buffered_data_with_checksum_);
|
||||
const char* src = data;
|
||||
size_t left = size;
|
||||
DataVerificationInfo v_info;
|
||||
char checksum_buf[sizeof(uint32_t)];
|
||||
|
||||
// Check how much is allowed. Here, we loop until the rate limiter allows to
|
||||
// write the entire buffer.
|
||||
// TODO: need to be improved since it sort of defeats the purpose of the rate
|
||||
// limiter
|
||||
size_t data_size = left;
|
||||
if (rate_limiter_ != nullptr) {
|
||||
while (data_size > 0) {
|
||||
size_t tmp_size;
|
||||
tmp_size = rate_limiter_->RequestToken(
|
||||
data_size, buf_.Alignment(), writable_file_->GetIOPriority(), stats_,
|
||||
RateLimiter::OpType::kWrite);
|
||||
data_size -= tmp_size;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
IOSTATS_TIMER_GUARD(write_nanos);
|
||||
TEST_SYNC_POINT("WritableFileWriter::Flush:BeforeAppend");
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
FileOperationInfo::StartTimePoint start_ts;
|
||||
uint64_t old_size = writable_file_->GetFileSize(IOOptions(), nullptr);
|
||||
if (ShouldNotifyListeners()) {
|
||||
start_ts = FileOperationInfo::StartNow();
|
||||
old_size = next_write_offset_;
|
||||
}
|
||||
#endif
|
||||
{
|
||||
auto prev_perf_level = GetPerfLevel();
|
||||
|
||||
IOSTATS_CPU_TIMER_GUARD(cpu_write_nanos, clock_);
|
||||
|
||||
EncodeFixed32(checksum_buf, buffered_data_crc32c_checksum_);
|
||||
v_info.checksum = Slice(checksum_buf, sizeof(uint32_t));
|
||||
s = writable_file_->Append(Slice(src, left), IOOptions(), v_info,
|
||||
nullptr);
|
||||
SetPerfLevel(prev_perf_level);
|
||||
}
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (ShouldNotifyListeners()) {
|
||||
auto finish_ts = std::chrono::steady_clock::now();
|
||||
NotifyOnFileWriteFinish(old_size, left, start_ts, finish_ts, s);
|
||||
}
|
||||
#endif
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
IOSTATS_ADD(bytes_written, left);
|
||||
TEST_KILL_RANDOM("WritableFileWriter::WriteBuffered:0");
|
||||
|
||||
// Buffer write is successful, reset the buffer current size to 0 and reset
|
||||
// the corresponding checksum value
|
||||
buf_.Size(0);
|
||||
buffered_data_crc32c_checksum_ = 0;
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -565,5 +698,99 @@ IOStatus WritableFileWriter::WriteDirect() {
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
IOStatus WritableFileWriter::WriteDirectWithChecksum() {
|
||||
assert(use_direct_io());
|
||||
assert(perform_data_verification_ && buffered_data_with_checksum_);
|
||||
IOStatus s;
|
||||
const size_t alignment = buf_.Alignment();
|
||||
assert((next_write_offset_ % alignment) == 0);
|
||||
|
||||
// Calculate whole page final file advance if all writes succeed
|
||||
size_t file_advance = TruncateToPageBoundary(alignment, buf_.CurrentSize());
|
||||
|
||||
// Calculate the leftover tail, we write it here padded with zeros BUT we
|
||||
// will write
|
||||
// it again in the future either on Close() OR when the current whole page
|
||||
// fills out
|
||||
size_t leftover_tail = buf_.CurrentSize() - file_advance;
|
||||
|
||||
// Round up, pad, and combine the checksum.
|
||||
size_t last_cur_size = buf_.CurrentSize();
|
||||
buf_.PadToAlignmentWith(0);
|
||||
size_t padded_size = buf_.CurrentSize() - last_cur_size;
|
||||
const char* padded_start = buf_.BufferStart() + last_cur_size;
|
||||
uint32_t padded_checksum = crc32c::Value(padded_start, padded_size);
|
||||
buffered_data_crc32c_checksum_ = crc32c::Crc32cCombine(
|
||||
buffered_data_crc32c_checksum_, padded_checksum, padded_size);
|
||||
|
||||
const char* src = buf_.BufferStart();
|
||||
uint64_t write_offset = next_write_offset_;
|
||||
size_t left = buf_.CurrentSize();
|
||||
DataVerificationInfo v_info;
|
||||
char checksum_buf[sizeof(uint32_t)];
|
||||
|
||||
// Check how much is allowed. Here, we loop until the rate limiter allows to
|
||||
// write the entire buffer.
|
||||
// TODO: need to be improved since it sort of defeats the purpose of the rate
|
||||
// limiter
|
||||
size_t data_size = left;
|
||||
if (rate_limiter_ != nullptr) {
|
||||
while (data_size > 0) {
|
||||
size_t size;
|
||||
size = rate_limiter_->RequestToken(data_size, buf_.Alignment(),
|
||||
writable_file_->GetIOPriority(),
|
||||
stats_, RateLimiter::OpType::kWrite);
|
||||
data_size -= size;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
IOSTATS_TIMER_GUARD(write_nanos);
|
||||
TEST_SYNC_POINT("WritableFileWriter::Flush:BeforeAppend");
|
||||
FileOperationInfo::StartTimePoint start_ts;
|
||||
if (ShouldNotifyListeners()) {
|
||||
start_ts = FileOperationInfo::StartNow();
|
||||
}
|
||||
// direct writes must be positional
|
||||
EncodeFixed32(checksum_buf, buffered_data_crc32c_checksum_);
|
||||
v_info.checksum = Slice(checksum_buf, sizeof(uint32_t));
|
||||
s = writable_file_->PositionedAppend(Slice(src, left), write_offset,
|
||||
IOOptions(), v_info, nullptr);
|
||||
|
||||
if (ShouldNotifyListeners()) {
|
||||
auto finish_ts = std::chrono::steady_clock::now();
|
||||
NotifyOnFileWriteFinish(write_offset, left, start_ts, finish_ts, s);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
// In this case, we do not change buffered_data_crc32c_checksum_ because
|
||||
// it still aligns with the data in the buffer.
|
||||
buf_.Size(file_advance + leftover_tail);
|
||||
buffered_data_crc32c_checksum_ =
|
||||
crc32c::Value(buf_.BufferStart(), buf_.CurrentSize());
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
IOSTATS_ADD(bytes_written, left);
|
||||
assert((next_write_offset_ % alignment) == 0);
|
||||
|
||||
if (s.ok()) {
|
||||
// Move the tail to the beginning of the buffer
|
||||
// This never happens during normal Append but rather during
|
||||
// explicit call to Flush()/Sync() or Close(). Also the buffer checksum will
|
||||
// recalculated accordingly.
|
||||
buf_.RefitTail(file_advance, leftover_tail);
|
||||
// Adjust the checksum value to align with the data in the buffer
|
||||
buffered_data_crc32c_checksum_ =
|
||||
crc32c::Value(buf_.BufferStart(), buf_.CurrentSize());
|
||||
// This is where we start writing next time which may or not be
|
||||
// the actual file size on disk. They match if the buffer size
|
||||
// is a multiple of whole pages otherwise filesize_ is leftover_tail
|
||||
// behind
|
||||
next_write_offset_ += file_advance;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -144,6 +144,8 @@ class WritableFileWriter {
|
||||
std::unique_ptr<FileChecksumGenerator> checksum_generator_;
|
||||
bool checksum_finalized_;
|
||||
bool perform_data_verification_;
|
||||
uint32_t buffered_data_crc32c_checksum_;
|
||||
bool buffered_data_with_checksum_;
|
||||
|
||||
public:
|
||||
WritableFileWriter(
|
||||
@@ -153,7 +155,8 @@ class WritableFileWriter {
|
||||
Statistics* stats = nullptr,
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners = {},
|
||||
FileChecksumGenFactory* file_checksum_gen_factory = nullptr,
|
||||
bool perform_data_verification = false)
|
||||
bool perform_data_verification = false,
|
||||
bool buffered_data_with_checksum = false)
|
||||
: file_name_(_file_name),
|
||||
writable_file_(std::move(file), io_tracer, _file_name),
|
||||
clock_(clock),
|
||||
@@ -171,7 +174,9 @@ class WritableFileWriter {
|
||||
listeners_(),
|
||||
checksum_generator_(nullptr),
|
||||
checksum_finalized_(false),
|
||||
perform_data_verification_(perform_data_verification) {
|
||||
perform_data_verification_(perform_data_verification),
|
||||
buffered_data_crc32c_checksum_(0),
|
||||
buffered_data_with_checksum_(buffered_data_with_checksum) {
|
||||
TEST_SYNC_POINT_CALLBACK("WritableFileWriter::WritableFileWriter:0",
|
||||
reinterpret_cast<void*>(max_buffer_size_));
|
||||
buf_.Alignment(writable_file_->GetRequiredBufferAlignment());
|
||||
@@ -210,7 +215,9 @@ class WritableFileWriter {
|
||||
|
||||
std::string file_name() const { return file_name_; }
|
||||
|
||||
IOStatus Append(const Slice& data);
|
||||
// When this Append API is called, if the crc32c_checksum is not provided, we
|
||||
// will calculate the checksum internally.
|
||||
IOStatus Append(const Slice& data, uint32_t crc32c_checksum = 0);
|
||||
|
||||
IOStatus Pad(const size_t pad_bytes);
|
||||
|
||||
@@ -251,9 +258,11 @@ class WritableFileWriter {
|
||||
// DMA such as in Direct I/O mode
|
||||
#ifndef ROCKSDB_LITE
|
||||
IOStatus WriteDirect();
|
||||
IOStatus WriteDirectWithChecksum();
|
||||
#endif // !ROCKSDB_LITE
|
||||
// Normal write
|
||||
IOStatus WriteBuffered(const char* data, size_t size);
|
||||
IOStatus WriteBufferedWithChecksum(const char* data, size_t size);
|
||||
IOStatus RangeSync(uint64_t offset, uint64_t nbytes);
|
||||
IOStatus SyncInternal(bool use_fsync);
|
||||
};
|
||||
|
||||
+31
-31
@@ -25,10 +25,10 @@ constexpr char db_path[] = "/tmp/testdb";
|
||||
// enum. The goal is to capture sanitizer bugs, so the code should be
|
||||
// compiled with a given sanitizer (ASan, UBSan, MSan).
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
rocksdb::DB* db;
|
||||
rocksdb::Options options;
|
||||
ROCKSDB_NAMESPACE::DB* db;
|
||||
ROCKSDB_NAMESPACE::Options options;
|
||||
options.create_if_missing = true;
|
||||
rocksdb::Status status = rocksdb::DB::Open(options, db_path, &db);
|
||||
ROCKSDB_NAMESPACE::Status status = ROCKSDB_NAMESPACE::DB::Open(options, db_path, &db);
|
||||
if (!status.ok()) {
|
||||
return 0;
|
||||
}
|
||||
@@ -43,18 +43,18 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
case kPut: {
|
||||
std::string key = fuzzed_data.ConsumeRandomLengthString();
|
||||
std::string val = fuzzed_data.ConsumeRandomLengthString();
|
||||
db->Put(rocksdb::WriteOptions(), key, val);
|
||||
db->Put(ROCKSDB_NAMESPACE::WriteOptions(), key, val);
|
||||
break;
|
||||
}
|
||||
case kGet: {
|
||||
std::string key = fuzzed_data.ConsumeRandomLengthString();
|
||||
std::string value;
|
||||
db->Get(rocksdb::ReadOptions(), key, &value);
|
||||
db->Get(ROCKSDB_NAMESPACE::ReadOptions(), key, &value);
|
||||
break;
|
||||
}
|
||||
case kDelete: {
|
||||
std::string key = fuzzed_data.ConsumeRandomLengthString();
|
||||
db->Delete(rocksdb::WriteOptions(), key);
|
||||
db->Delete(ROCKSDB_NAMESPACE::WriteOptions(), key);
|
||||
break;
|
||||
}
|
||||
case kGetProperty: {
|
||||
@@ -64,16 +64,16 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
break;
|
||||
}
|
||||
case kIterator: {
|
||||
rocksdb::Iterator* it = db->NewIterator(rocksdb::ReadOptions());
|
||||
ROCKSDB_NAMESPACE::Iterator* it = db->NewIterator(ROCKSDB_NAMESPACE::ReadOptions());
|
||||
for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
||||
}
|
||||
delete it;
|
||||
break;
|
||||
}
|
||||
case kSnapshot: {
|
||||
rocksdb::ReadOptions snapshot_options;
|
||||
ROCKSDB_NAMESPACE::ReadOptions snapshot_options;
|
||||
snapshot_options.snapshot = db->GetSnapshot();
|
||||
rocksdb::Iterator* it = db->NewIterator(snapshot_options);
|
||||
ROCKSDB_NAMESPACE::Iterator* it = db->NewIterator(snapshot_options);
|
||||
db->ReleaseSnapshot(snapshot_options.snapshot);
|
||||
delete it;
|
||||
break;
|
||||
@@ -81,51 +81,51 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
case kOpenClose: {
|
||||
db->Close();
|
||||
delete db;
|
||||
status = rocksdb::DB::Open(options, db_path, &db);
|
||||
status = ROCKSDB_NAMESPACE::DB::Open(options, db_path, &db);
|
||||
if (!status.ok()) {
|
||||
rocksdb::DestroyDB(db_path, options);
|
||||
ROCKSDB_NAMESPACE::DestroyDB(db_path, options);
|
||||
return 0;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case kColumn: {
|
||||
rocksdb::ColumnFamilyHandle* cf;
|
||||
rocksdb::Status s;
|
||||
s = db->CreateColumnFamily(rocksdb::ColumnFamilyOptions(), "new_cf",
|
||||
ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf;
|
||||
ROCKSDB_NAMESPACE::Status s;
|
||||
s = db->CreateColumnFamily(ROCKSDB_NAMESPACE::ColumnFamilyOptions(), "new_cf",
|
||||
&cf);
|
||||
s = db->DestroyColumnFamilyHandle(cf);
|
||||
db->Close();
|
||||
delete db;
|
||||
|
||||
// open DB with two column families
|
||||
std::vector<rocksdb::ColumnFamilyDescriptor> column_families;
|
||||
std::vector<ROCKSDB_NAMESPACE::ColumnFamilyDescriptor> column_families;
|
||||
// have to open default column family
|
||||
column_families.push_back(rocksdb::ColumnFamilyDescriptor(
|
||||
rocksdb::kDefaultColumnFamilyName, rocksdb::ColumnFamilyOptions()));
|
||||
column_families.push_back(ROCKSDB_NAMESPACE::ColumnFamilyDescriptor(
|
||||
ROCKSDB_NAMESPACE::kDefaultColumnFamilyName, ROCKSDB_NAMESPACE::ColumnFamilyOptions()));
|
||||
// open the new one, too
|
||||
column_families.push_back(rocksdb::ColumnFamilyDescriptor(
|
||||
"new_cf", rocksdb::ColumnFamilyOptions()));
|
||||
std::vector<rocksdb::ColumnFamilyHandle*> handles;
|
||||
s = rocksdb::DB::Open(rocksdb::DBOptions(), db_path, column_families,
|
||||
column_families.push_back(ROCKSDB_NAMESPACE::ColumnFamilyDescriptor(
|
||||
"new_cf", ROCKSDB_NAMESPACE::ColumnFamilyOptions()));
|
||||
std::vector<ROCKSDB_NAMESPACE::ColumnFamilyHandle*> handles;
|
||||
s = ROCKSDB_NAMESPACE::DB::Open(ROCKSDB_NAMESPACE::DBOptions(), db_path, column_families,
|
||||
&handles, &db);
|
||||
|
||||
if (s.ok()) {
|
||||
std::string key1 = fuzzed_data.ConsumeRandomLengthString();
|
||||
std::string val1 = fuzzed_data.ConsumeRandomLengthString();
|
||||
std::string key2 = fuzzed_data.ConsumeRandomLengthString();
|
||||
s = db->Put(rocksdb::WriteOptions(), handles[1], key1, val1);
|
||||
s = db->Put(ROCKSDB_NAMESPACE::WriteOptions(), handles[1], key1, val1);
|
||||
std::string value;
|
||||
s = db->Get(rocksdb::ReadOptions(), handles[1], key2, &value);
|
||||
s = db->Get(ROCKSDB_NAMESPACE::ReadOptions(), handles[1], key2, &value);
|
||||
s = db->DropColumnFamily(handles[1]);
|
||||
for (auto handle : handles) {
|
||||
s = db->DestroyColumnFamilyHandle(handle);
|
||||
}
|
||||
} else {
|
||||
status = rocksdb::DB::Open(options, db_path, &db);
|
||||
status = ROCKSDB_NAMESPACE::DB::Open(options, db_path, &db);
|
||||
if (!status.ok()) {
|
||||
// At this point there is no saving to do. So we exit
|
||||
rocksdb::DestroyDB(db_path, rocksdb::Options());
|
||||
ROCKSDB_NAMESPACE::DestroyDB(db_path, ROCKSDB_NAMESPACE::Options());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -135,15 +135,15 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
std::string slice_start = fuzzed_data.ConsumeRandomLengthString();
|
||||
std::string slice_end = fuzzed_data.ConsumeRandomLengthString();
|
||||
|
||||
rocksdb::Slice begin(slice_start);
|
||||
rocksdb::Slice end(slice_end);
|
||||
rocksdb::CompactRangeOptions options;
|
||||
rocksdb::Status s = db->CompactRange(options, &begin, &end);
|
||||
ROCKSDB_NAMESPACE::Slice begin(slice_start);
|
||||
ROCKSDB_NAMESPACE::Slice end(slice_end);
|
||||
ROCKSDB_NAMESPACE::CompactRangeOptions options;
|
||||
ROCKSDB_NAMESPACE::Status s = db->CompactRange(options, &begin, &end);
|
||||
break;
|
||||
}
|
||||
case kSeekForPrev: {
|
||||
std::string key = fuzzed_data.ConsumeRandomLengthString();
|
||||
auto iter = db->NewIterator(rocksdb::ReadOptions());
|
||||
auto iter = db->NewIterator(ROCKSDB_NAMESPACE::ReadOptions());
|
||||
iter->SeekForPrev(key);
|
||||
delete iter;
|
||||
break;
|
||||
@@ -154,6 +154,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
// Cleanup DB
|
||||
db->Close();
|
||||
delete db;
|
||||
rocksdb::DestroyDB(db_path, options);
|
||||
ROCKSDB_NAMESPACE::DestroyDB(db_path, options);
|
||||
return 0;
|
||||
}
|
||||
+12
-12
@@ -16,7 +16,7 @@
|
||||
|
||||
protobuf_mutator::libfuzzer::PostProcessorRegistration<DBOperations> reg = {
|
||||
[](DBOperations* input, unsigned int /* seed */) {
|
||||
const rocksdb::Comparator* comparator = rocksdb::BytewiseComparator();
|
||||
const ROCKSDB_NAMESPACE::Comparator* comparator = ROCKSDB_NAMESPACE::BytewiseComparator();
|
||||
auto ops = input->mutable_operations();
|
||||
// Make sure begin <= end for DELETE_RANGE.
|
||||
for (DBOperation& op : *ops) {
|
||||
@@ -41,22 +41,22 @@ DEFINE_PROTO_FUZZER(DBOperations& input) {
|
||||
}
|
||||
|
||||
const std::string kDbPath = "/tmp/db_map_fuzzer_test";
|
||||
auto fs = rocksdb::FileSystem::Default();
|
||||
if (fs->FileExists(kDbPath, rocksdb::IOOptions(), /*dbg=*/nullptr).ok()) {
|
||||
auto fs = ROCKSDB_NAMESPACE::FileSystem::Default();
|
||||
if (fs->FileExists(kDbPath, ROCKSDB_NAMESPACE::IOOptions(), /*dbg=*/nullptr).ok()) {
|
||||
std::cerr << "db path " << kDbPath << " already exists" << std::endl;
|
||||
abort();
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> kv;
|
||||
rocksdb::DB* db = nullptr;
|
||||
rocksdb::Options options;
|
||||
ROCKSDB_NAMESPACE::DB* db = nullptr;
|
||||
ROCKSDB_NAMESPACE::Options options;
|
||||
options.create_if_missing = true;
|
||||
CHECK_OK(rocksdb::DB::Open(options, kDbPath, &db));
|
||||
CHECK_OK(ROCKSDB_NAMESPACE::DB::Open(options, kDbPath, &db));
|
||||
|
||||
for (const DBOperation& op : input.operations()) {
|
||||
switch (op.type()) {
|
||||
case OpType::PUT: {
|
||||
CHECK_OK(db->Put(rocksdb::WriteOptions(), op.key(), op.value()));
|
||||
CHECK_OK(db->Put(ROCKSDB_NAMESPACE::WriteOptions(), op.key(), op.value()));
|
||||
kv[op.key()] = op.value();
|
||||
break;
|
||||
}
|
||||
@@ -64,13 +64,13 @@ DEFINE_PROTO_FUZZER(DBOperations& input) {
|
||||
break;
|
||||
}
|
||||
case OpType::DELETE: {
|
||||
CHECK_OK(db->Delete(rocksdb::WriteOptions(), op.key()));
|
||||
CHECK_OK(db->Delete(ROCKSDB_NAMESPACE::WriteOptions(), op.key()));
|
||||
kv.erase(op.key());
|
||||
break;
|
||||
}
|
||||
case OpType::DELETE_RANGE: {
|
||||
// [op.key(), op.value()) corresponds to [begin, end).
|
||||
CHECK_OK(db->DeleteRange(rocksdb::WriteOptions(),
|
||||
CHECK_OK(db->DeleteRange(ROCKSDB_NAMESPACE::WriteOptions(),
|
||||
db->DefaultColumnFamily(), op.key(),
|
||||
op.value()));
|
||||
kv.erase(kv.lower_bound(op.key()), kv.lower_bound(op.value()));
|
||||
@@ -86,9 +86,9 @@ DEFINE_PROTO_FUZZER(DBOperations& input) {
|
||||
delete db;
|
||||
db = nullptr;
|
||||
|
||||
CHECK_OK(rocksdb::DB::Open(options, kDbPath, &db));
|
||||
CHECK_OK(ROCKSDB_NAMESPACE::DB::Open(options, kDbPath, &db));
|
||||
auto kv_it = kv.begin();
|
||||
rocksdb::Iterator* it = db->NewIterator(rocksdb::ReadOptions());
|
||||
ROCKSDB_NAMESPACE::Iterator* it = db->NewIterator(ROCKSDB_NAMESPACE::ReadOptions());
|
||||
for (it->SeekToFirst(); it->Valid(); it->Next(), kv_it++) {
|
||||
CHECK_TRUE(kv_it != kv.end());
|
||||
CHECK_EQ(it->key().ToString(), kv_it->first);
|
||||
@@ -99,5 +99,5 @@ DEFINE_PROTO_FUZZER(DBOperations& input) {
|
||||
|
||||
CHECK_OK(db->Close());
|
||||
delete db;
|
||||
CHECK_OK(rocksdb::DestroyDB(kDbPath, options));
|
||||
CHECK_OK(ROCKSDB_NAMESPACE::DestroyDB(kDbPath, options));
|
||||
}
|
||||
@@ -173,9 +173,10 @@ struct CompressionOptions {
|
||||
parallel_threads(1),
|
||||
enabled(false),
|
||||
max_dict_buffer_bytes(0) {}
|
||||
CompressionOptions(int wbits, int _lev, int _strategy, int _max_dict_bytes,
|
||||
int _zstd_max_train_bytes, int _parallel_threads,
|
||||
bool _enabled, uint64_t _max_dict_buffer_bytes)
|
||||
CompressionOptions(int wbits, int _lev, int _strategy,
|
||||
uint32_t _max_dict_bytes, uint32_t _zstd_max_train_bytes,
|
||||
uint32_t _parallel_threads, bool _enabled,
|
||||
uint64_t _max_dict_buffer_bytes)
|
||||
: window_bits(wbits),
|
||||
level(_lev),
|
||||
strategy(_strategy),
|
||||
|
||||
Loaded 100 of 178 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user