mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 02:52:35 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e072c129d5 | ||
|
|
ed05774868 | ||
|
|
e656fa3d19 | ||
|
|
66f369f91e | ||
|
|
050027c35d | ||
|
|
41be9ec937 | ||
|
|
4fd1285584 | ||
|
|
d74333c500 | ||
|
|
57adbf0e91 | ||
|
|
0b8c8cf13a | ||
|
|
1bee5e0254 | ||
|
|
c79e43d0dc | ||
|
|
c26abd4c25 | ||
|
|
fac7a23685 | ||
|
|
0e0a19832e | ||
|
|
d5d8920f2c | ||
|
|
a5d773e077 | ||
|
|
c965c9ef65 | ||
|
|
1aac814578 | ||
|
|
f87adcfb3f | ||
|
|
5d6005c780 | ||
|
|
f62c1e1e56 | ||
|
|
8cf86258b8 | ||
|
|
fff302d989 | ||
|
|
126c223714 | ||
|
|
a6691d0f65 | ||
|
|
4d31d3c2ed | ||
|
|
8353ae8b27 | ||
|
|
3d358a7e25 | ||
|
|
94329ae4ec | ||
|
|
9882652b0e | ||
|
|
2e5a323dbd | ||
|
|
69a32eecab | ||
|
|
ce419c0f10 | ||
|
|
19345de60d | ||
|
|
40dfa26049 | ||
|
|
77f4799515 | ||
|
|
b550fc0b09 | ||
|
|
751d1a3e48 | ||
|
|
cba398df8a | ||
|
|
1d2950b8dd | ||
|
|
d665afdbf3 | ||
|
|
40d19bc12c | ||
|
|
f105e1a501 | ||
|
|
bfaf8291c5 | ||
|
|
c0e0f30667 | ||
|
|
04bd347995 | ||
|
|
ad135f3ffd | ||
|
|
8273435c22 | ||
|
|
415200d792 | ||
|
|
80afa77660 | ||
|
|
1777e5f7e9 | ||
|
|
f4052d13b7 | ||
|
|
d3a3b02134 | ||
|
|
693dffd8e8 | ||
|
|
5fa6ef7f18 | ||
|
|
1a3e23a251 | ||
|
|
ecfd4aef0c | ||
|
|
f85b31a2e9 | ||
|
|
9efae14428 | ||
|
|
f890527b16 | ||
|
|
ff32346415 | ||
|
|
5cbee1f609 | ||
|
|
b6de139df5 | ||
|
|
3ee6c9baec | ||
|
|
a101c9de60 | ||
|
|
9f244b2119 | ||
|
|
2af132c341 | ||
|
|
4f78f9699b | ||
|
|
e36008d863 | ||
|
|
2f4a0ffef8 | ||
|
|
560906ab33 | ||
|
|
e9c74bc474 | ||
|
|
3e02c6e05a | ||
|
|
4bdcc80192 | ||
|
|
c1018b7516 | ||
|
|
7d36bc4273 | ||
|
|
d739de63e5 | ||
|
|
eb99e08076 | ||
|
|
21906d66f6 | ||
|
|
cf85607795 | ||
|
|
2b3c50c429 | ||
|
|
5506954b1f | ||
|
|
7b2c0140ba | ||
|
|
b8fe7df2e5 | ||
|
|
e88d8935ae | ||
|
|
ed50ccd19a | ||
|
|
e6432dfd4c | ||
|
|
a020031552 | ||
|
|
3dc6ebaf74 | ||
|
|
65893ad959 | ||
|
|
b4d0e041d0 | ||
|
|
45b1c788c4 | ||
|
|
5864900cf4 | ||
|
|
463873f1bb | ||
|
|
a00cffaf69 | ||
|
|
d04df2752a | ||
|
|
7c8c803938 | ||
|
|
151dc0038a | ||
|
|
5ab5537d79 | ||
|
|
9baeef712f | ||
|
|
a0f391cafc | ||
|
|
0adac6f88e | ||
|
|
460b44c07f | ||
|
|
4eb7b35f6d | ||
|
|
514f0b0937 | ||
|
|
e228515740 | ||
|
|
8c4ea7b851 | ||
|
|
f6e45382e9 | ||
|
|
6c50082654 | ||
|
|
91ba7837b7 | ||
|
|
c9c58a320f | ||
|
|
4cf2f6723a | ||
|
|
28ea1fb44a | ||
|
|
5490da20a5 | ||
|
|
bd170dda03 | ||
|
|
356f8c5d81 | ||
|
|
b0e190604b | ||
|
|
b71466e982 | ||
|
|
9901e7f681 | ||
|
|
af7ae912e2 | ||
|
|
a96a4a2f7b | ||
|
|
23f34c7ae5 | ||
|
|
c78a87cd71 | ||
|
|
d4081bf0be | ||
|
|
8515bd50c9 | ||
|
|
fd24e4479b | ||
|
|
700d597bd8 | ||
|
|
8e9d9156b0 | ||
|
|
253ae017fa |
No files matched your search
+73
-3
@@ -147,6 +147,13 @@ commands:
|
||||
command: |
|
||||
sudo apt-get update -y && sudo apt-get install -y libsnappy-dev zlib1g-dev libbz2-dev liblz4-dev libzstd-dev
|
||||
|
||||
install-streaming-compress-libs:
|
||||
steps:
|
||||
- run:
|
||||
name: Install streaming compression libs
|
||||
command: |
|
||||
sudo apt-get update -y && sudo apt-get install -y libzstd-dev
|
||||
|
||||
install-libprotobuf-mutator:
|
||||
steps:
|
||||
- run:
|
||||
@@ -161,6 +168,45 @@ commands:
|
||||
command: |
|
||||
echo "export PKG_CONFIG_PATH=/usr/local/OFF/:~/libprotobuf-mutator/build/external.protobuf/lib/pkgconfig/" >> $BASH_ENV
|
||||
echo "export PROTOC_BIN=~/libprotobuf-mutator/build/external.protobuf/bin/protoc" >> $BASH_ENV
|
||||
setup-folly:
|
||||
steps:
|
||||
- run:
|
||||
name: Install folly dependencies
|
||||
command: |
|
||||
sudo apt-get install libgoogle-glog-dev
|
||||
- run:
|
||||
name: Checkout folly sources
|
||||
command: |
|
||||
make checkout_folly
|
||||
|
||||
build-for-benchmarks:
|
||||
steps:
|
||||
- pre-steps
|
||||
- run:
|
||||
name: "Linux build for benchmarks"
|
||||
command: #sized for the resource-class rocksdb-benchmark-sys1
|
||||
make V=1 J=8 -j8 release
|
||||
|
||||
perform-benchmarks:
|
||||
steps:
|
||||
- run:
|
||||
name: "Run basic benchmark, 5 min"
|
||||
command: ./tools/benchmark.sh fillseq_enable_wal
|
||||
environment:
|
||||
LD_LIBRARY_PATH: /usr/local/lib
|
||||
DB_DIR: /tmp/rocksdb-benchmark-datadir
|
||||
WAL_DIR: /tmp/rocksdb-benchmark-waldir
|
||||
OUTPUT_DIR: /tmp/benchmark-results
|
||||
NUM_KEYS: 10000000
|
||||
|
||||
post-benchmarks:
|
||||
steps:
|
||||
- store_artifacts: # store the benchmark output
|
||||
path: /tmp/benchmark-results
|
||||
destination: test_logs
|
||||
- run:
|
||||
name: Send benchmark report to visualisation
|
||||
command: ./build_tools/benchmark_log_tool.py --tsvfile /tmp/benchmark-results/report.tsv --esdocument https://search-rocksdb-bench-k2izhptfeap2hjfxteolsgsynm.us-west-2.es.amazonaws.com/bench_test3/_doc
|
||||
|
||||
executors:
|
||||
windows-2xlarge:
|
||||
@@ -225,6 +271,7 @@ jobs:
|
||||
steps:
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- install-streaming-compress-libs
|
||||
- run: make V=1 J=32 -j32 check
|
||||
- post-steps
|
||||
|
||||
@@ -385,6 +432,18 @@ jobs:
|
||||
- run: CC=clang-10 CXX=clang++-10 ROCKSDB_DISABLE_ALIGNED_NEW=1 CLANG_ANALYZER="/usr/bin/clang++-10" CLANG_SCAN_BUILD=scan-build-10 USE_CLANG=1 make V=1 -j32 analyze # aligned new doesn't work for reason we haven't figured out. For unknown, reason passing "clang++-10" as CLANG_ANALYZER doesn't work, and we need a full path.
|
||||
- post-steps
|
||||
|
||||
build-linux-runner:
|
||||
machine: true
|
||||
resource_class: facebook/rocksdb-benchmark-sys1
|
||||
steps:
|
||||
- pre-steps
|
||||
- run:
|
||||
name: "Checked Linux build (Runner)"
|
||||
command: make V=1 J=8 -j8 check
|
||||
environment:
|
||||
LD_LIBRARY_PATH: /usr/local/lib
|
||||
- post-steps
|
||||
|
||||
build-linux-cmake-with-folly:
|
||||
machine:
|
||||
image: ubuntu-2004:202111-02
|
||||
@@ -393,7 +452,7 @@ jobs:
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- upgrade-cmake
|
||||
- run: make checkout_folly
|
||||
- setup-folly
|
||||
- run: (mkdir build && cd build && cmake -DUSE_FOLLY=1 -DWITH_GFLAGS=1 .. && make V=1 -j20 && ctest -j20)
|
||||
- post-steps
|
||||
|
||||
@@ -428,7 +487,7 @@ jobs:
|
||||
steps:
|
||||
- pre-steps
|
||||
- run: sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test && sudo apt-get update -y && sudo apt-get install gcc-7 g++-7 libgflags-dev
|
||||
- run: make checkout_folly
|
||||
- setup-folly
|
||||
- run: USE_FOLLY=1 CC=gcc-7 CXX=g++-7 V=1 make -j32 check
|
||||
- post-steps
|
||||
|
||||
@@ -483,7 +542,7 @@ jobs:
|
||||
- pre-steps
|
||||
- install-clang-13
|
||||
- install-gflags
|
||||
- run: make checkout_folly
|
||||
- setup-folly
|
||||
- run: CC=clang-13 CXX=clang++-13 USE_CLANG=1 USE_FOLLY=1 COMPILE_WITH_UBSAN=1 COMPILE_WITH_ASAN=1 make -j32 check
|
||||
- post-steps
|
||||
|
||||
@@ -826,6 +885,14 @@ jobs:
|
||||
command: cd fuzz && make sst_file_writer_fuzzer db_fuzzer db_map_fuzzer
|
||||
- post-steps
|
||||
|
||||
benchmark-linux: #use a private Circle CI runner (resource_class) to run the job
|
||||
machine: true
|
||||
resource_class: facebook/rocksdb-benchmark-sys1
|
||||
steps:
|
||||
- build-for-benchmarks
|
||||
- perform-benchmarks
|
||||
- post-benchmarks
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
jobs-linux-run-tests:
|
||||
@@ -894,6 +961,9 @@ workflows:
|
||||
jobs-linux-arm:
|
||||
jobs:
|
||||
- build-linux-arm
|
||||
build-fuzzers:
|
||||
jobs:
|
||||
- build-fuzzers
|
||||
nightly:
|
||||
triggers:
|
||||
- schedule:
|
||||
|
||||
@@ -36,8 +36,6 @@ manifest_dump
|
||||
sst_dump
|
||||
blob_dump
|
||||
block_cache_trace_analyzer
|
||||
db_readonly_with_timestamp_test
|
||||
db_with_timestamp_basic_test
|
||||
tools/block_cache_analyzer/*.pyc
|
||||
column_aware_encoding_exp
|
||||
util/build_version.cc
|
||||
@@ -53,7 +51,6 @@ rocksdb_dump
|
||||
rocksdb_undump
|
||||
db_test2
|
||||
trace_analyzer
|
||||
trace_analyzer_test
|
||||
block_cache_trace_analyzer
|
||||
io_tracer_parser
|
||||
.DS_Store
|
||||
|
||||
+19
-7
@@ -89,7 +89,11 @@ include(CMakeDependentOption)
|
||||
if(MSVC)
|
||||
option(WITH_GFLAGS "build with GFlags" OFF)
|
||||
option(WITH_XPRESS "build with windows built in compression" OFF)
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/thirdparty.inc)
|
||||
option(ROCKSDB_SKIP_THIRDPARTY "skip thirdparty.inc" OFF)
|
||||
|
||||
if(NOT ROCKSDB_SKIP_THIRDPARTY)
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/thirdparty.inc)
|
||||
endif()
|
||||
else()
|
||||
if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD" AND NOT CMAKE_SYSTEM_NAME MATCHES "kFreeBSD")
|
||||
# FreeBSD has jemalloc as default malloc
|
||||
@@ -190,7 +194,7 @@ else()
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wstrict-prototypes")
|
||||
endif()
|
||||
if(MINGW)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-format -fno-asynchronous-unwind-tables")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-format")
|
||||
add_definitions(-D_POSIX_C_SOURCE=1)
|
||||
endif()
|
||||
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
@@ -583,6 +587,7 @@ include_directories(${PROJECT_SOURCE_DIR}/include)
|
||||
if(USE_FOLLY)
|
||||
include_directories(${PROJECT_SOURCE_DIR}/third-party/folly)
|
||||
add_definitions(-DUSE_FOLLY -DFOLLY_NO_CONFIG)
|
||||
list(APPEND THIRDPARTY_LIBS glog)
|
||||
endif()
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
@@ -610,6 +615,7 @@ set(SOURCES
|
||||
db/blob/blob_log_format.cc
|
||||
db/blob/blob_log_sequential_reader.cc
|
||||
db/blob/blob_log_writer.cc
|
||||
db/blob/blob_source.cc
|
||||
db/blob/prefetch_buffer_collection.cc
|
||||
db/builder.cc
|
||||
db/c.cc
|
||||
@@ -671,6 +677,7 @@ set(SOURCES
|
||||
db/version_set.cc
|
||||
db/wal_edit.cc
|
||||
db/wal_manager.cc
|
||||
db/wide/wide_column_serialization.cc
|
||||
db/write_batch.cc
|
||||
db/write_batch_base.cc
|
||||
db/write_controller.cc
|
||||
@@ -733,7 +740,6 @@ set(SOURCES
|
||||
table/adaptive/adaptive_table_factory.cc
|
||||
table/block_based/binary_search_index_reader.cc
|
||||
table/block_based/block.cc
|
||||
table/block_based/block_based_filter_block.cc
|
||||
table/block_based/block_based_table_builder.cc
|
||||
table/block_based/block_based_table_factory.cc
|
||||
table/block_based/block_based_table_iterator.cc
|
||||
@@ -971,9 +977,13 @@ endif()
|
||||
if(USE_FOLLY)
|
||||
list(APPEND SOURCES
|
||||
third-party/folly/folly/container/detail/F14Table.cpp
|
||||
third-party/folly/folly/detail/Futex.cpp
|
||||
third-party/folly/folly/lang/SafeAssert.cpp
|
||||
third-party/folly/folly/lang/ToAscii.cpp
|
||||
third-party/folly/folly/ScopeGuard.cpp)
|
||||
third-party/folly/folly/ScopeGuard.cpp
|
||||
third-party/folly/folly/synchronization/AtomicNotification.cpp
|
||||
third-party/folly/folly/synchronization/DistributedMutex.cpp
|
||||
third-party/folly/folly/synchronization/ParkingLot.cpp)
|
||||
endif()
|
||||
|
||||
set(ROCKSDB_STATIC_LIB rocksdb${ARTIFACT_SUFFIX})
|
||||
@@ -1122,8 +1132,8 @@ if(NOT WIN32 OR ROCKSDB_INSTALL_ON_WINDOWS)
|
||||
)
|
||||
|
||||
configure_file(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}.pc.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc
|
||||
${PROJECT_NAME}.pc.in
|
||||
${PROJECT_NAME}.pc
|
||||
@ONLY
|
||||
)
|
||||
|
||||
@@ -1208,6 +1218,7 @@ if(WITH_TESTS)
|
||||
db/blob/blob_file_garbage_test.cc
|
||||
db/blob/blob_file_reader_test.cc
|
||||
db/blob/blob_garbage_meter_test.cc
|
||||
db/blob/blob_source_test.cc
|
||||
db/blob/db_blob_basic_test.cc
|
||||
db/blob/db_blob_compaction_test.cc
|
||||
db/blob/db_blob_corruption_test.cc
|
||||
@@ -1290,6 +1301,7 @@ if(WITH_TESTS)
|
||||
db/version_set_test.cc
|
||||
db/wal_manager_test.cc
|
||||
db/wal_edit_test.cc
|
||||
db/wide/wide_column_serialization_test.cc
|
||||
db/write_batch_test.cc
|
||||
db/write_callback_test.cc
|
||||
db/write_controller_test.cc
|
||||
@@ -1315,7 +1327,6 @@ if(WITH_TESTS)
|
||||
options/customizable_test.cc
|
||||
options/options_settable_test.cc
|
||||
options/options_test.cc
|
||||
table/block_based/block_based_filter_block_test.cc
|
||||
table/block_based/block_based_table_reader_test.cc
|
||||
table/block_based/block_test.cc
|
||||
table/block_based/data_block_hash_index_test.cc
|
||||
@@ -1384,6 +1395,7 @@ if(WITH_TESTS)
|
||||
utilities/transactions/write_prepared_transaction_test.cc
|
||||
utilities/transactions/write_unprepared_transaction_test.cc
|
||||
utilities/transactions/lock/range/range_locking_test.cc
|
||||
utilities/transactions/timestamped_snapshot_test.cc
|
||||
utilities/ttl/ttl_test.cc
|
||||
utilities/util_merge_operators_test.cc
|
||||
utilities/write_batch_with_index/write_batch_with_index_test.cc
|
||||
|
||||
+80
@@ -1,4 +1,78 @@
|
||||
# Rocksdb Change Log
|
||||
## 7.4.5 (08/02/2022)
|
||||
### Bug Fixes
|
||||
* Fix a bug starting in 7.4.0 in which some fsync operations might be skipped in a DB after any DropColumnFamily on that DB, until it is re-opened. This can lead to data loss on power loss. (For custom FileSystem implementations, this could lead to `FSDirectory::Fsync` or `FSDirectory::Close` after the first `FSDirectory::Close`; Also, valgrind could report call to `close()` with `fd=-1`.)
|
||||
|
||||
## 7.4.4 (07/19/2022)
|
||||
### Public API changes
|
||||
* Removed Customizable support for RateLimiter and removed its CreateFromString() and Type() functions.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a bug where `GenericRateLimiter` could revert the bandwidth set dynamically using `SetBytesPerSecond()` when a user configures a structure enclosing it, e.g., using `GetOptionsFromString()` to configure an `Options` that references an existing `RateLimiter` object.
|
||||
|
||||
## 7.4.3 (07/13/2022)
|
||||
### Behavior Changes
|
||||
* For track_and_verify_wals_in_manifest, revert to the original behavior before #10087: syncing of live WAL file is not tracked, and we track only the synced sizes of **closed** WALs. (PR #10330).
|
||||
|
||||
## 7.4.2 (06/30/2022)
|
||||
### Bug Fixes
|
||||
* Fix a bug in Logger where if dbname and db_log_dir are on different filesystems, dbname creation would fail wrt to db_log_dir path returning an error and fails to open the DB.
|
||||
|
||||
## 7.4.1 (06/28/2022)
|
||||
### Bug Fixes
|
||||
* Pass `rate_limiter_priority` through filter block reader functions to `FileSystem`.
|
||||
|
||||
## 7.4.0 (06/19/2022)
|
||||
### Bug Fixes
|
||||
* Fixed a bug in calculating key-value integrity protection for users of in-place memtable updates. In particular, the affected users would be those who configure `protection_bytes_per_key > 0` on `WriteBatch` or `WriteOptions`, and configure `inplace_callback != nullptr`.
|
||||
* Fixed a bug where a snapshot taken during SST file ingestion would be unstable.
|
||||
* Fixed a bug for non-TransactionDB with avoid_flush_during_recovery = true and TransactionDB where in case of crash, min_log_number_to_keep may not change on recovery and persisting a new MANIFEST with advanced log_numbers for some column families, results in "column family inconsistency" error on second recovery. As a solution, RocksDB will persist the new MANIFEST after successfully syncing the new WAL. If a future recovery starts from the new MANIFEST, then it means the new WAL is successfully synced. Due to the sentinel empty write batch at the beginning, kPointInTimeRecovery of WAL is guaranteed to go after this point. If future recovery starts from the old MANIFEST, it means the writing the new MANIFEST failed. We won't have the "SST ahead of WAL" error.
|
||||
* Fixed a bug where RocksDB DB::Open() may creates and writes to two new MANIFEST files even before recovery succeeds. Now writes to MANIFEST are persisted only after recovery is successful.
|
||||
* Fix a race condition in WAL size tracking which is caused by an unsafe iterator access after container is changed.
|
||||
* Fix unprotected concurrent accesses to `WritableFileWriter::filesize_` by `DB::SyncWAL()` and `DB::Put()` in two write queue mode.
|
||||
* Fix a bug in WAL tracking. Before this PR (#10087), calling `SyncWAL()` on the only WAL file of the db will not log the event in MANIFEST, thus allowing a subsequent `DB::Open` even if the WAL file is missing or corrupted.
|
||||
* Fix a bug that could return wrong results with `index_type=kHashSearch` and using `SetOptions` to change the `prefix_extractor`.
|
||||
* Fixed a bug in WAL tracking with wal_compression. WAL compression writes a kSetCompressionType record which is not associated with any sequence number. As result, WalManager::GetSortedWalsOfType() will skip these WALs and not return them to caller, e.g. Checkpoint, Backup, causing the operations to fail.
|
||||
* Avoid a crash if the IDENTITY file is accidentally truncated to empty. A new DB ID will be written and generated on Open.
|
||||
* Fixed a possible corruption for users of `manual_wal_flush` and/or `FlushWAL(true /* sync */)`, together with `track_and_verify_wals_in_manifest == true`. For those users, losing unsynced data (e.g., due to power loss) could make future DB opens fail with a `Status::Corruption` complaining about missing WAL data.
|
||||
* Fixed a bug in `WriteBatchInternal::Append()` where WAL termination point in write batch was not considered and the function appends an incorrect number of checksums.
|
||||
* Fixed a crash bug introduced in 7.3.0 affecting users of MultiGet with `kDataBlockBinaryAndHash`.
|
||||
* Add some fixes in async_io which was doing extra prefetching in shorter scans.
|
||||
|
||||
### Public API changes
|
||||
* Add new API GetUnixTime in Snapshot class which returns the unix time at which Snapshot is taken.
|
||||
* Add transaction `get_pinned` and `multi_get` to C API.
|
||||
* Add two-phase commit support to C API.
|
||||
* Add `rocksdb_transaction_get_writebatch_wi` and `rocksdb_transaction_rebuild_from_writebatch` to C API.
|
||||
* Add `rocksdb_options_get_blob_file_starting_level` and `rocksdb_options_set_blob_file_starting_level` to C API.
|
||||
* Add `blobFileStartingLevel` and `setBlobFileStartingLevel` to Java API.
|
||||
* Add SingleDelete for DB in C API
|
||||
* Add User Defined Timestamp in C API.
|
||||
* `rocksdb_comparator_with_ts_create` to create timestamp aware comparator
|
||||
* Put, Get, Delete, SingleDelete, MultiGet APIs has corresponding timestamp aware APIs with suffix `with_ts`
|
||||
* And Add C API's for Transaction, SstFileWriter, Compaction as mentioned [here](https://github.com/facebook/rocksdb/wiki/User-defined-Timestamp-(Experimental))
|
||||
* The contract for implementations of Comparator::IsSameLengthImmediateSuccessor has been updated to work around a design bug in `auto_prefix_mode`.
|
||||
* The API documentation for `auto_prefix_mode` now notes some corner cases in which it returns different results than `total_order_seek`, due to design bugs that are not easily fixed. Users using built-in comparators and keys at least the size of a fixed prefix length are not affected.
|
||||
* Obsoleted the NUM_DATA_BLOCKS_READ_PER_LEVEL stat and introduced the NUM_LEVEL_READ_PER_MULTIGET and MULTIGET_COROUTINE_COUNT stats
|
||||
* Introduced `WriteOptions::protection_bytes_per_key`, which can be used to enable key-value integrity protection for live updates.
|
||||
|
||||
### New Features
|
||||
* Add FileSystem::ReadAsync API in io_tracing
|
||||
* Add blob garbage collection parameters `blob_garbage_collection_policy` and `blob_garbage_collection_age_cutoff` to both force-enable and force-disable GC, as well as selectively override age cutoff when using CompactRange.
|
||||
* Add an extra sanity check in `GetSortedWalFiles()` (also used by `GetLiveFilesStorageInfo()`, `BackupEngine`, and `Checkpoint`) to reduce risk of successfully created backup or checkpoint failing to open because of missing WAL file.
|
||||
* Add a new column family option `blob_file_starting_level` to enable writing blob files during flushes and compactions starting from the specified LSM tree level.
|
||||
* Add support for timestamped snapshots (#9879)
|
||||
* Provide support for AbortIO in posix to cancel submitted asynchronous requests using io_uring.
|
||||
* Add support for rate-limiting batched `MultiGet()` APIs
|
||||
|
||||
### Behavior changes
|
||||
* DB::Open(), DB::OpenAsSecondary() will fail if a Logger cannot be created (#9984)
|
||||
* Removed support for reading Bloom filters using obsolete block-based filter format. (Support for writing such filters was dropped in 7.0.) For good read performance on old DBs using these filters, a full compaction is required.
|
||||
* Per KV checksum in write batch is verified before a write batch is written to WAL to detect any corruption to the write batch (#10114).
|
||||
|
||||
### Performance Improvements
|
||||
* When compiled with folly (Meta-internal integration; experimental in open source build), improve the locking performance (CPU efficiency) of LRUCache by using folly DistributedMutex in place of standard mutex.
|
||||
|
||||
## 7.3.0 (05/20/2022)
|
||||
### Bug Fixes
|
||||
* Fixed a bug where manual flush would block forever even though flush options had wait=false.
|
||||
@@ -14,6 +88,8 @@
|
||||
* RemoteCompaction supports table_properties_collector_factories override on compaction worker.
|
||||
* Start tracking SST unique id in MANIFEST, which will be used to verify with SST properties during DB open to make sure the SST file is not overwritten or misplaced. A db option `verify_sst_unique_id_in_manifest` is introduced to enable/disable the verification, if enabled all SST files will be opened during DB-open to verify the unique id (default is false), so it's recommended to use it with `max_open_files = -1` to pre-open the files.
|
||||
* Added the ability to concurrently read data blocks from multiple files in a level in batched MultiGet. This can be enabled by setting the async_io option in ReadOptions. Using this feature requires a FileSystem that supports ReadAsync (PosixFileSystem is not supported yet for this), and for RocksDB to be compiled with folly and c++20.
|
||||
* Charge memory usage of file metadata. RocksDB holds one file metadata structure in-memory per on-disk table file. If an operation reserving memory for file metadata exceeds the avaible space left in the block
|
||||
cache at some point (i.e, causing a cache full under `LRUCacheOptions::strict_capacity_limit` = true), creation will fail with `Status::MemoryLimit()`. To opt in this feature, enable charging `CacheEntryRole::kFileMetadata` in `BlockBasedTableOptions::cache_usage_options`.
|
||||
|
||||
### Public API changes
|
||||
* Add rollback_deletion_type_callback to TransactionDBOptions so that write-prepared transactions know whether to issue a Delete or SingleDelete to cancel a previous key written during prior prepare phase. The PR aims to prevent mixing SingleDeletes and Deletes for the same key that can lead to undefined behaviors for write-prepared transactions.
|
||||
@@ -29,6 +105,7 @@
|
||||
* Add an option, `CompressionOptions::use_zstd_dict_trainer`, to indicate whether zstd dictionary trainer should be used for generating zstd compression dictionaries. The default value of this option is true for backward compatibility. When this option is set to false, zstd API `ZDICT_finalizeDictionary` is used to generate compression dictionaries.
|
||||
* Seek API which positions itself every LevelIterator on the correct data block in the correct SST file which can be parallelized if ReadOptions.async_io option is enabled.
|
||||
* Add new stat number_async_seek in PerfContext that indicates number of async calls made by seek to prefetch data.
|
||||
* Add support for user-defined timestamps to read only DB.
|
||||
|
||||
### Bug Fixes
|
||||
* RocksDB calls FileSystem::Poll API during FilePrefetchBuffer destruction which impacts performance as it waits for read requets completion which is not needed anymore. Calling FileSystem::AbortIO to abort those requests instead fixes that performance issue.
|
||||
@@ -39,6 +116,9 @@
|
||||
* ldb `--try_load_options` default to true if `--db` is specified and not creating a new DB, the user can still explicitly disable that by `--try_load_options=false` (or explicitly enable that by `--try_load_options`).
|
||||
* During Flush write or Compaction write/read, the WriteController is used to determine whether DB writes are stalled or slowed down. The priority (Env::IOPriority) can then be determined accordingly and be passed in IOOptions to the file system.
|
||||
|
||||
### Performance Improvements
|
||||
* Avoid calling malloc_usable_size() in LRU Cache's mutex.
|
||||
* Reduce DB mutex holding time when finding obsolete files to delete. When a file is trivial moved to another level, the internal files will be referenced twice internally and sometimes opened twice too. If a deletion candidate file is not the last reference, we need to destroy the reference and close the file but not deleting the file. Right now we determine it by building a set of all live files. With the improvement, we check the file against all live LSM-tree versions instead.
|
||||
|
||||
## 7.2.0 (04/15/2022)
|
||||
### Bug Fixes
|
||||
|
||||
@@ -461,6 +461,8 @@ ifeq ($(USE_FOLLY),1)
|
||||
endif
|
||||
PLATFORM_CCFLAGS += -DUSE_FOLLY -DFOLLY_NO_CONFIG
|
||||
PLATFORM_CXXFLAGS += -DUSE_FOLLY -DFOLLY_NO_CONFIG
|
||||
# TODO: fix linking with fbcode compiler config
|
||||
PLATFORM_LDFLAGS += -lglog
|
||||
endif
|
||||
|
||||
ifdef TEST_CACHE_LINE_SIZE
|
||||
@@ -1594,9 +1596,6 @@ random_access_file_reader_test: $(OBJ_DIR)/file/random_access_file_reader_test.o
|
||||
file_reader_writer_test: $(OBJ_DIR)/util/file_reader_writer_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
block_based_filter_block_test: $(OBJ_DIR)/table/block_based/block_based_filter_block_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
block_based_table_reader_test: table/block_based/block_based_table_reader_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1777,6 +1776,9 @@ write_prepared_transaction_test: $(OBJ_DIR)/utilities/transactions/write_prepare
|
||||
write_unprepared_transaction_test: $(OBJ_DIR)/utilities/transactions/write_unprepared_transaction_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
timestamped_snapshot_test: $(OBJ_DIR)/utilities/transactions/timestamped_snapshot_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
sst_dump: $(OBJ_DIR)/tools/sst_dump.o $(TOOLS_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1861,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_source_test: $(OBJ_DIR)/db/blob/blob_source_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)
|
||||
|
||||
@@ -1876,7 +1881,7 @@ testutil_test: $(OBJ_DIR)/test_util/testutil_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
io_tracer_test: $(OBJ_DIR)/trace_replay/io_tracer_test.o $(OBJ_DIR)/trace_replay/io_tracer.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
prefetch_test: $(OBJ_DIR)/file/prefetch_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
prefetch_test: $(OBJ_DIR)/file/prefetch_test.o $(OBJ_DIR)/tools/io_tracer_parser_tool.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
io_tracer_parser_test: $(OBJ_DIR)/tools/io_tracer_parser_test.o $(OBJ_DIR)/tools/io_tracer_parser_tool.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
@@ -1902,6 +1907,10 @@ db_basic_bench: $(OBJ_DIR)/microbench/db_basic_bench.o $(LIBRARY)
|
||||
|
||||
cache_reservation_manager_test: $(OBJ_DIR)/cache/cache_reservation_manager_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
wide_column_serialization_test: $(OBJ_DIR)/db/wide/wide_column_serialization_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
#-------------------------------------------------
|
||||
# make install related stuff
|
||||
PREFIX ?= /usr/local
|
||||
@@ -2350,10 +2359,14 @@ checkout_folly:
|
||||
fi
|
||||
@# Pin to a particular version for public CI, so that PR authors don't
|
||||
@# need to worry about folly breaking our integration. Update periodically
|
||||
cd third-party/folly && git reset --hard 98b9b2c1124e99f50f9085ddee74ce32afffc665
|
||||
cd third-party/folly && git reset --hard beacd86d63cd71c904632262e6c36f60874d78ba
|
||||
@# A hack to remove boost dependency.
|
||||
@# NOTE: this hack is not needed if using FBCODE compiler config
|
||||
perl -pi -e 's/^(#include <boost)/\/\/$$1/' third-party/folly/folly/functional/Invoke.h
|
||||
@# NOTE: this hack is required for clang in some cases
|
||||
perl -pi -e 's/int rv = syscall/int rv = (int)syscall/' third-party/folly/folly/detail/Futex.cpp
|
||||
@# NOTE: this hack is required for gcc in some cases
|
||||
perl -pi -e 's/(__has_include.<experimental.memory_resource>.)/__cpp_rtti && $$1/' third-party/folly/folly/memory/MemoryResource.h
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Build size testing
|
||||
|
||||
@@ -30,6 +30,7 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"db/blob/blob_log_format.cc",
|
||||
"db/blob/blob_log_sequential_reader.cc",
|
||||
"db/blob/blob_log_writer.cc",
|
||||
"db/blob/blob_source.cc",
|
||||
"db/blob/prefetch_buffer_collection.cc",
|
||||
"db/builder.cc",
|
||||
"db/c.cc",
|
||||
@@ -91,6 +92,7 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"db/version_set.cc",
|
||||
"db/wal_edit.cc",
|
||||
"db/wal_manager.cc",
|
||||
"db/wide/wide_column_serialization.cc",
|
||||
"db/write_batch.cc",
|
||||
"db/write_batch_base.cc",
|
||||
"db/write_controller.cc",
|
||||
@@ -164,7 +166,6 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"table/adaptive/adaptive_table_factory.cc",
|
||||
"table/block_based/binary_search_index_reader.cc",
|
||||
"table/block_based/block.cc",
|
||||
"table/block_based/block_based_filter_block.cc",
|
||||
"table/block_based/block_based_table_builder.cc",
|
||||
"table/block_based/block_based_table_factory.cc",
|
||||
"table/block_based/block_based_table_iterator.cc",
|
||||
@@ -334,6 +335,7 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"//folly/experimental/coro:collect",
|
||||
"//folly/experimental/coro:coroutine",
|
||||
"//folly/experimental/coro:task",
|
||||
"//folly/synchronization:distributed_mutex",
|
||||
], headers=None, link_whole=False, extra_test_libs=False)
|
||||
|
||||
cpp_library_wrapper(name="rocksdb_whole_archive_lib", srcs=[
|
||||
@@ -358,6 +360,7 @@ cpp_library_wrapper(name="rocksdb_whole_archive_lib", srcs=[
|
||||
"db/blob/blob_log_format.cc",
|
||||
"db/blob/blob_log_sequential_reader.cc",
|
||||
"db/blob/blob_log_writer.cc",
|
||||
"db/blob/blob_source.cc",
|
||||
"db/blob/prefetch_buffer_collection.cc",
|
||||
"db/builder.cc",
|
||||
"db/c.cc",
|
||||
@@ -419,6 +422,7 @@ cpp_library_wrapper(name="rocksdb_whole_archive_lib", srcs=[
|
||||
"db/version_set.cc",
|
||||
"db/wal_edit.cc",
|
||||
"db/wal_manager.cc",
|
||||
"db/wide/wide_column_serialization.cc",
|
||||
"db/write_batch.cc",
|
||||
"db/write_batch_base.cc",
|
||||
"db/write_controller.cc",
|
||||
@@ -492,7 +496,6 @@ cpp_library_wrapper(name="rocksdb_whole_archive_lib", srcs=[
|
||||
"table/adaptive/adaptive_table_factory.cc",
|
||||
"table/block_based/binary_search_index_reader.cc",
|
||||
"table/block_based/block.cc",
|
||||
"table/block_based/block_based_filter_block.cc",
|
||||
"table/block_based/block_based_table_builder.cc",
|
||||
"table/block_based/block_based_table_factory.cc",
|
||||
"table/block_based/block_based_table_iterator.cc",
|
||||
@@ -662,6 +665,7 @@ cpp_library_wrapper(name="rocksdb_whole_archive_lib", srcs=[
|
||||
"//folly/experimental/coro:collect",
|
||||
"//folly/experimental/coro:coroutine",
|
||||
"//folly/experimental/coro:task",
|
||||
"//folly/synchronization:distributed_mutex",
|
||||
], headers=None, link_whole=True, extra_test_libs=False)
|
||||
|
||||
cpp_library_wrapper(name="rocksdb_test_lib", srcs=[
|
||||
@@ -4798,8 +4802,8 @@ cpp_unittest_wrapper(name="blob_garbage_meter_test",
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="block_based_filter_block_test",
|
||||
srcs=["table/block_based/block_based_filter_block_test.cc"],
|
||||
cpp_unittest_wrapper(name="blob_source_test",
|
||||
srcs=["db/blob/blob_source_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
extra_compiler_flags=[])
|
||||
|
||||
@@ -5766,6 +5770,12 @@ cpp_unittest_wrapper(name="timer_test",
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="timestamped_snapshot_test",
|
||||
srcs=["utilities/transactions/timestamped_snapshot_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="trace_analyzer_test",
|
||||
srcs=["tools/trace_analyzer_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
@@ -5814,6 +5824,12 @@ cpp_unittest_wrapper(name="wal_manager_test",
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="wide_column_serialization_test",
|
||||
srcs=["db/wide/wide_column_serialization_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="work_queue_test",
|
||||
srcs=["util/work_queue_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
|
||||
@@ -150,7 +150,8 @@ def generate_targets(repo_path, deps_map):
|
||||
"//folly/experimental/coro:blocking_wait",
|
||||
"//folly/experimental/coro:collect",
|
||||
"//folly/experimental/coro:coroutine",
|
||||
"//folly/experimental/coro:task"])
|
||||
"//folly/experimental/coro:task",
|
||||
"//folly/synchronization:distributed_mutex"])
|
||||
# rocksdb_whole_archive_lib
|
||||
TARGETS.add_library(
|
||||
"rocksdb_whole_archive_lib",
|
||||
@@ -163,7 +164,8 @@ def generate_targets(repo_path, deps_map):
|
||||
"//folly/experimental/coro:blocking_wait",
|
||||
"//folly/experimental/coro:collect",
|
||||
"//folly/experimental/coro:coroutine",
|
||||
"//folly/experimental/coro:task"],
|
||||
"//folly/experimental/coro:task",
|
||||
"//folly/synchronization:distributed_mutex"],
|
||||
headers=None,
|
||||
extra_external_deps="",
|
||||
link_whole=True)
|
||||
|
||||
Executable
+161
@@ -0,0 +1,161 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
# This source code is licensed under both the GPLv2 (found in the
|
||||
# COPYING file in the root directory) and Apache 2.0 License
|
||||
# (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
'''Access the results of benchmark runs
|
||||
Send these results on to OpenSearch graphing service
|
||||
'''
|
||||
|
||||
import argparse
|
||||
import itertools
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import requests
|
||||
from dateutil import parser
|
||||
import logging
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
class Configuration:
|
||||
opensearch_user = os.environ['ES_USER']
|
||||
opensearch_pass = os.environ['ES_PASS']
|
||||
|
||||
class BenchmarkResultException(Exception):
|
||||
def __init__(self, message, content):
|
||||
super().__init__(self, message)
|
||||
self.content = content
|
||||
|
||||
|
||||
class BenchmarkUtils:
|
||||
|
||||
expected_keys = ['ops_sec', 'mb_sec', 'total_size_gb', 'level0_size_gb', 'sum_gb', 'write_amplification',
|
||||
'write_mbps', 'usec_op', 'percentile_50', 'percentile_75',
|
||||
'percentile_99', 'percentile_99.9', 'percentile_99.99', 'uptime',
|
||||
'stall_time', 'stall_percent', 'test_name', 'test_date', 'rocksdb_version',
|
||||
'job_id', 'timestamp']
|
||||
|
||||
metric_keys = ['ops_sec', 'mb_sec', 'total_size_gb', 'level0_size_gb', 'sum_gb', 'write_amplification',
|
||||
'write_mbps', 'usec_op', 'percentile_50', 'percentile_75',
|
||||
'percentile_99', 'percentile_99.9', 'percentile_99.99', 'uptime',
|
||||
'stall_time', 'stall_percent']
|
||||
|
||||
def sanity_check(row):
|
||||
if not 'test_name' in row:
|
||||
return False
|
||||
if row['test_name'] == '':
|
||||
return False
|
||||
if not 'test_date' in row:
|
||||
return False
|
||||
if not 'ops_sec' in row:
|
||||
return False
|
||||
try:
|
||||
v = int(row['ops_sec'])
|
||||
except (ValueError, TypeError):
|
||||
return False
|
||||
return True
|
||||
|
||||
def conform_opensearch(row):
|
||||
(dt, _) = parser.parse(row['test_date'], fuzzy_with_tokens=True)
|
||||
row['test_date'] = dt.isoformat()
|
||||
return dict((key.replace('.', '_'), value)
|
||||
for (key, value) in row.items())
|
||||
|
||||
|
||||
class ResultParser:
|
||||
def __init__(self, field="(\w|[+-:.])+", intrafield="(\s)+", separator="\t"):
|
||||
self.field = re.compile(field)
|
||||
self.intra = re.compile(intrafield)
|
||||
self.sep = re.compile(separator)
|
||||
|
||||
def line(self, l_in: str):
|
||||
'''Parse a line into items
|
||||
Being clever about separators
|
||||
'''
|
||||
l = l_in
|
||||
row = []
|
||||
while l != '':
|
||||
match_item = self.field.match(l)
|
||||
if match_item:
|
||||
item = match_item.group(0)
|
||||
row.append(item)
|
||||
l = l[len(item):]
|
||||
else:
|
||||
match_intra = self.intra.match(l)
|
||||
if match_intra:
|
||||
intra = match_intra.group(0)
|
||||
# Count the separators
|
||||
# If there are >1 then generate extra blank fields
|
||||
# White space with no true separators fakes up a single separator
|
||||
tabbed = self.sep.split(intra)
|
||||
sep_count = len(tabbed) - 1
|
||||
if sep_count == 0:
|
||||
sep_count = 1
|
||||
for i in range(sep_count-1):
|
||||
row.append('')
|
||||
l = l[len(intra):]
|
||||
else:
|
||||
raise BenchmarkResultException(
|
||||
'Invalid TSV line', f"{l_in} at {l}")
|
||||
return row
|
||||
|
||||
def parse(self, lines):
|
||||
'''Parse something that iterates lines'''
|
||||
rows = [self.line(line) for line in lines]
|
||||
header = rows[0]
|
||||
width = len(header)
|
||||
records = [{k: v for (k, v) in itertools.zip_longest(
|
||||
header, row[:width])} for row in rows[1:]]
|
||||
return records
|
||||
|
||||
|
||||
def load_report_from_tsv(filename: str):
|
||||
file = open(filename, 'r')
|
||||
contents = file.readlines()
|
||||
file.close()
|
||||
parser = ResultParser()
|
||||
report = parser.parse(contents)
|
||||
logging.debug(f"Loaded TSV Report: {report}")
|
||||
return report
|
||||
|
||||
|
||||
def push_report_to_opensearch(report, esdocument):
|
||||
sanitized = [BenchmarkUtils.conform_opensearch(row)
|
||||
for row in report if BenchmarkUtils.sanity_check(row)]
|
||||
logging.debug(f"upload {len(sanitized)} benchmarks to opensearch")
|
||||
for single_benchmark in sanitized:
|
||||
logging.debug(f"upload benchmark: {single_benchmark}")
|
||||
response = requests.post(
|
||||
esdocument,
|
||||
json=single_benchmark, auth=(os.environ['ES_USER'], os.environ['ES_PASS']))
|
||||
logging.debug(
|
||||
f"Sent to OpenSearch, status: {response.status_code}, result: {response.text}")
|
||||
response.raise_for_status()
|
||||
|
||||
|
||||
def main():
|
||||
'''Tool for fetching, parsing and uploading benchmark results to OpenSearch / ElasticSearch
|
||||
This tool will
|
||||
|
||||
(1) Open a local tsv benchmark report file
|
||||
(2) Upload to OpenSearch document, via https/JSON
|
||||
'''
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description='CircleCI benchmark scraper.')
|
||||
|
||||
# --tsvfile is the name of the file to read results from
|
||||
# --esdocument is the ElasticSearch document to push these results into
|
||||
#
|
||||
parser.add_argument('--tsvfile', default='build_tools/circle_api_scraper_input.txt',
|
||||
help='File from which to read tsv report')
|
||||
parser.add_argument('--esdocument', help='ElasticSearch/OpenSearch document URL to upload report into')
|
||||
|
||||
args = parser.parse_args()
|
||||
logging.debug(f"Arguments: {args}")
|
||||
reports = load_report_from_tsv(args.tsvfile)
|
||||
push_report_to_opensearch(reports, args.esdocument)
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -5,25 +5,31 @@
|
||||
|
||||
BAD=""
|
||||
|
||||
git grep 'namespace rocksdb' -- '*.[ch]*'
|
||||
git grep -n 'namespace rocksdb' -- '*.[ch]*'
|
||||
if [ "$?" != "1" ]; then
|
||||
echo "^^^^^ Do not hardcode namespace rocksdb. Use ROCKSDB_NAMESPACE"
|
||||
BAD=1
|
||||
fi
|
||||
|
||||
git grep -i 'nocommit' -- ':!build_tools/check-sources.sh'
|
||||
git grep -n -i 'nocommit' -- ':!build_tools/check-sources.sh'
|
||||
if [ "$?" != "1" ]; then
|
||||
echo "^^^^^ Code was not intended to be committed"
|
||||
BAD=1
|
||||
fi
|
||||
|
||||
git grep '<rocksdb/' -- ':!build_tools/check-sources.sh'
|
||||
git grep -n 'include <rocksdb/' -- ':!build_tools/check-sources.sh'
|
||||
if [ "$?" != "1" ]; then
|
||||
echo '^^^^^ Use double-quotes as in #include "rocksdb/something.h"'
|
||||
BAD=1
|
||||
fi
|
||||
|
||||
git grep 'using namespace' -- ':!build_tools' ':!docs' \
|
||||
git grep -n 'include "include/rocksdb/' -- ':!build_tools/check-sources.sh'
|
||||
if [ "$?" != "1" ]; then
|
||||
echo '^^^^^ Use #include "rocksdb/something.h" instead of #include "include/rocksdb/something.h"'
|
||||
BAD=1
|
||||
fi
|
||||
|
||||
git grep -n 'using namespace' -- ':!build_tools' ':!docs' \
|
||||
':!third-party/folly/folly/lang/Align.h' \
|
||||
':!third-party/gtest-1.8.1/fused-src/gtest/gtest.h'
|
||||
if [ "$?" != "1" ]; then
|
||||
@@ -31,6 +37,12 @@ if [ "$?" != "1" ]; then
|
||||
BAD=1
|
||||
fi
|
||||
|
||||
git grep -n -P "[\x80-\xFF]" -- ':!docs' ':!*.md'
|
||||
if [ "$?" != "1" ]; then
|
||||
echo '^^^^ Use only ASCII characters in source files'
|
||||
BAD=1
|
||||
fi
|
||||
|
||||
if [ "$BAD" ]; then
|
||||
exit 1
|
||||
fi
|
||||
@@ -9,7 +9,7 @@ BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/73a237ac5bc0a5f5d67b39b8d253cfebaab88684
|
||||
LZ4_BASE=/mnt/gvfs/third-party2/lz4/ec6573523b0ce55ef6373a4801189027cf07bb2c/1.9.1/platform009/7f3b187
|
||||
ZSTD_BASE=/mnt/gvfs/third-party2/zstd/64c58a207d2495e83abc57a500a956df09b79a7c/1.4.x/platform009/ba86d1f
|
||||
GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/824d0a8a5abb5b121afd1b35fc3896407ea50092/2.2.0/platform009/7f3b187
|
||||
JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/d9aef9feb850b168a68736420f217b01cce11a89/master/platform009/c305944
|
||||
JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/b62912d333ef33f9760efa6219dbe3fe6abb3b0e/master/platform009/c305944
|
||||
NUMA_BASE=/mnt/gvfs/third-party2/numa/0af65f71e23a67bf65dc91b11f95caa39325c432/2.0.11/platform009/7f3b187
|
||||
LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/02486dac347645d31dce116f44e1de3177315be2/1.4/platform009/5191652
|
||||
TBB_BASE=/mnt/gvfs/third-party2/tbb/2e0ec671e550bfca347300bf3f789d9c0fff24ad/2018_U5/platform009/7f3b187
|
||||
|
||||
Vendored
+50
-2
@@ -34,13 +34,61 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
OptionType::kDouble, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kMutable}},
|
||||
};
|
||||
|
||||
static std::unordered_map<std::string, OptionTypeInfo>
|
||||
comp_sec_cache_options_type_info = {
|
||||
{"capacity",
|
||||
{offsetof(struct CompressedSecondaryCacheOptions, capacity),
|
||||
OptionType::kSizeT, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kMutable}},
|
||||
{"num_shard_bits",
|
||||
{offsetof(struct CompressedSecondaryCacheOptions, num_shard_bits),
|
||||
OptionType::kInt, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kMutable}},
|
||||
{"compression_type",
|
||||
{offsetof(struct CompressedSecondaryCacheOptions, compression_type),
|
||||
OptionType::kCompressionType, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kMutable}},
|
||||
{"compress_format_version",
|
||||
{offsetof(struct CompressedSecondaryCacheOptions,
|
||||
compress_format_version),
|
||||
OptionType::kUInt32T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kMutable}},
|
||||
};
|
||||
#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);
|
||||
if (value.find("compressed_secondary_cache://") == 0) {
|
||||
std::string args = value;
|
||||
args.erase(0, std::strlen("compressed_secondary_cache://"));
|
||||
Status status;
|
||||
std::shared_ptr<SecondaryCache> sec_cache;
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
CompressedSecondaryCacheOptions sec_cache_opts;
|
||||
status = OptionTypeInfo::ParseStruct(config_options, "",
|
||||
&comp_sec_cache_options_type_info, "",
|
||||
args, &sec_cache_opts);
|
||||
if (status.ok()) {
|
||||
sec_cache = NewCompressedSecondaryCache(sec_cache_opts);
|
||||
}
|
||||
|
||||
#else
|
||||
(void)config_options;
|
||||
status = Status::NotSupported(
|
||||
"Cannot load compressed secondary cache in LITE mode ", args);
|
||||
#endif //! ROCKSDB_LITE
|
||||
|
||||
if (status.ok()) {
|
||||
result->swap(sec_cache);
|
||||
}
|
||||
return status;
|
||||
} else {
|
||||
return LoadSharedObject<SecondaryCache>(config_options, value, nullptr,
|
||||
result);
|
||||
}
|
||||
}
|
||||
|
||||
Status Cache::CreateFromString(const ConfigOptions& config_options,
|
||||
|
||||
Vendored
+20
-3
@@ -12,6 +12,7 @@
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
|
||||
#include "cache/fast_lru_cache.h"
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "monitoring/histogram.h"
|
||||
#include "port/port.h"
|
||||
@@ -25,6 +26,7 @@
|
||||
#include "table/block_based/block_based_table_reader.h"
|
||||
#include "table/block_based/cachable_entry.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/distributed_mutex.h"
|
||||
#include "util/gflags_compat.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/mutexlock.h"
|
||||
@@ -76,7 +78,7 @@ DEFINE_string(secondary_cache_uri, "",
|
||||
static class std::shared_ptr<ROCKSDB_NAMESPACE::SecondaryCache> secondary_cache;
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
DEFINE_bool(use_clock_cache, false, "");
|
||||
DEFINE_string(cache_type, "lru_cache", "Type of block cache.");
|
||||
|
||||
// ## BEGIN stress_cache_key sub-tool options ##
|
||||
// See class StressCacheKey below.
|
||||
@@ -279,13 +281,17 @@ class CacheBench {
|
||||
if (max_key > (static_cast<uint64_t>(1) << max_log_)) max_log_++;
|
||||
}
|
||||
|
||||
if (FLAGS_use_clock_cache) {
|
||||
if (FLAGS_cache_type == "clock_cache") {
|
||||
cache_ = NewClockCache(FLAGS_cache_size, FLAGS_num_shard_bits);
|
||||
if (!cache_) {
|
||||
fprintf(stderr, "Clock cache not supported.\n");
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
} else if (FLAGS_cache_type == "fast_lru_cache") {
|
||||
cache_ = NewFastLRUCache(
|
||||
FLAGS_cache_size, FLAGS_value_bytes, FLAGS_num_shard_bits,
|
||||
false /*strict_capacity_limit*/, kDefaultCacheMetadataChargePolicy);
|
||||
} else if (FLAGS_cache_type == "lru_cache") {
|
||||
LRUCacheOptions opts(FLAGS_cache_size, FLAGS_num_shard_bits, false, 0.5);
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (!FLAGS_secondary_cache_uri.empty()) {
|
||||
@@ -303,6 +309,9 @@ class CacheBench {
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
cache_ = NewLRUCache(opts);
|
||||
} else {
|
||||
fprintf(stderr, "Cache type not supported.");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -579,7 +588,15 @@ class CacheBench {
|
||||
}
|
||||
|
||||
void PrintEnv() const {
|
||||
#if defined(__GNUC__) && !defined(__OPTIMIZE__)
|
||||
printf(
|
||||
"WARNING: Optimization is disabled: benchmarks unnecessarily slow\n");
|
||||
#endif
|
||||
#ifndef NDEBUG
|
||||
printf("WARNING: Assertions are enabled; benchmarks unnecessarily slow\n");
|
||||
#endif
|
||||
printf("RocksDB version : %d.%d\n", kMajorVersion, kMinorVersion);
|
||||
printf("DMutex impl name : %s\n", DMutex::kName());
|
||||
printf("Number of threads : %u\n", FLAGS_threads);
|
||||
printf("Ops per thread : %" PRIu64 "\n", FLAGS_ops_per_thread);
|
||||
printf("Cache size : %s\n",
|
||||
|
||||
Vendored
+2
@@ -22,6 +22,7 @@ std::array<std::string, kNumCacheEntryRoles> kCacheEntryRoleToCamelString{{
|
||||
"CompressionDictionaryBuildingBuffer",
|
||||
"FilterConstruction",
|
||||
"BlockBasedTableReader",
|
||||
"FileMetadata",
|
||||
"Misc",
|
||||
}};
|
||||
|
||||
@@ -36,6 +37,7 @@ std::array<std::string, kNumCacheEntryRoles> kCacheEntryRoleToHyphenString{{
|
||||
"compression-dictionary-building-buffer",
|
||||
"filter-construction",
|
||||
"block-based-table-reader",
|
||||
"file-metadata",
|
||||
"misc",
|
||||
}};
|
||||
|
||||
|
||||
Vendored
+1
@@ -180,4 +180,5 @@ template class CacheReservationManagerImpl<
|
||||
template class CacheReservationManagerImpl<CacheEntryRole::kFilterConstruction>;
|
||||
template class CacheReservationManagerImpl<CacheEntryRole::kMisc>;
|
||||
template class CacheReservationManagerImpl<CacheEntryRole::kWriteBuffer>;
|
||||
template class CacheReservationManagerImpl<CacheEntryRole::kFileMetadata>;
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+28
@@ -36,6 +36,12 @@ class CacheReservationManager {
|
||||
};
|
||||
virtual ~CacheReservationManager() {}
|
||||
virtual Status UpdateCacheReservation(std::size_t new_memory_used) = 0;
|
||||
// TODO(hx235): replace the usage of
|
||||
// `UpdateCacheReservation(memory_used_delta, increase)` with
|
||||
// `UpdateCacheReservation(new_memory_used)` so that we only have one
|
||||
// `UpdateCacheReservation` function
|
||||
virtual Status UpdateCacheReservation(std::size_t memory_used_delta,
|
||||
bool increase) = 0;
|
||||
virtual Status MakeCacheReservation(
|
||||
std::size_t incremental_memory_used,
|
||||
std::unique_ptr<CacheReservationManager::CacheReservationHandle>
|
||||
@@ -128,6 +134,11 @@ class CacheReservationManagerImpl
|
||||
// On keeping dummy entries the same, it always returns Status::OK().
|
||||
Status UpdateCacheReservation(std::size_t new_memory_used) override;
|
||||
|
||||
Status UpdateCacheReservation(std::size_t /* memory_used_delta */,
|
||||
bool /* increase */) override {
|
||||
return Status::NotSupported();
|
||||
}
|
||||
|
||||
// One of the two ways of reserving cache space and releasing is done through
|
||||
// destruction of CacheReservationHandle.
|
||||
// See UpdateCacheReservation() for the other way.
|
||||
@@ -254,6 +265,23 @@ class ConcurrentCacheReservationManager
|
||||
std::lock_guard<std::mutex> lock(cache_res_mgr_mu_);
|
||||
return cache_res_mgr_->UpdateCacheReservation(new_memory_used);
|
||||
}
|
||||
|
||||
inline Status UpdateCacheReservation(std::size_t memory_used_delta,
|
||||
bool increase) override {
|
||||
std::lock_guard<std::mutex> lock(cache_res_mgr_mu_);
|
||||
std::size_t total_mem_used = cache_res_mgr_->GetTotalMemoryUsed();
|
||||
Status s;
|
||||
if (!increase) {
|
||||
assert(total_mem_used >= memory_used_delta);
|
||||
s = cache_res_mgr_->UpdateCacheReservation(total_mem_used -
|
||||
memory_used_delta);
|
||||
} else {
|
||||
s = cache_res_mgr_->UpdateCacheReservation(total_mem_used +
|
||||
memory_used_delta);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
inline Status MakeCacheReservation(
|
||||
std::size_t incremental_memory_used,
|
||||
std::unique_ptr<CacheReservationManager::CacheReservationHandle> *handle)
|
||||
|
||||
Vendored
+116
-35
@@ -18,24 +18,43 @@
|
||||
#include "cache/clock_cache.h"
|
||||
#include "cache/fast_lru_cache.h"
|
||||
#include "cache/lru_cache.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
namespace {
|
||||
|
||||
// Conversions between numeric keys/values and the types expected by Cache.
|
||||
static std::string EncodeKey(int k) {
|
||||
std::string EncodeKey16Bytes(int k) {
|
||||
std::string result;
|
||||
PutFixed32(&result, k);
|
||||
result.append(std::string(12, 'a')); // Because we need a 16B output, we
|
||||
// add a 12-byte padding.
|
||||
return result;
|
||||
}
|
||||
|
||||
int DecodeKey16Bytes(const Slice& k) {
|
||||
assert(k.size() == 16);
|
||||
return DecodeFixed32(k.data()); // Decodes only the first 4 bytes of k.
|
||||
}
|
||||
|
||||
std::string EncodeKey32Bits(int k) {
|
||||
std::string result;
|
||||
PutFixed32(&result, k);
|
||||
return result;
|
||||
}
|
||||
static int DecodeKey(const Slice& k) {
|
||||
|
||||
int DecodeKey32Bits(const Slice& k) {
|
||||
assert(k.size() == 4);
|
||||
return DecodeFixed32(k.data());
|
||||
}
|
||||
static void* EncodeValue(uintptr_t v) { return reinterpret_cast<void*>(v); }
|
||||
static int DecodeValue(void* v) {
|
||||
|
||||
void* EncodeValue(uintptr_t v) { return reinterpret_cast<void*>(v); }
|
||||
|
||||
int DecodeValue(void* v) {
|
||||
return static_cast<int>(reinterpret_cast<uintptr_t>(v));
|
||||
}
|
||||
|
||||
@@ -50,12 +69,19 @@ void eraseDeleter(const Slice& /*key*/, void* value) {
|
||||
cache->Erase("foo");
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
class CacheTest : public testing::TestWithParam<std::string> {
|
||||
public:
|
||||
static CacheTest* current_;
|
||||
static std::string type_;
|
||||
|
||||
static void Deleter(const Slice& key, void* v) {
|
||||
current_->deleted_keys_.push_back(DecodeKey(key));
|
||||
if (type_ == kFast) {
|
||||
current_->deleted_keys_.push_back(DecodeKey16Bytes(key));
|
||||
} else {
|
||||
current_->deleted_keys_.push_back(DecodeKey32Bits(key));
|
||||
}
|
||||
current_->deleted_values_.push_back(DecodeValue(v));
|
||||
}
|
||||
|
||||
@@ -74,6 +100,7 @@ class CacheTest : public testing::TestWithParam<std::string> {
|
||||
: cache_(NewCache(kCacheSize, kNumShardBits, false)),
|
||||
cache2_(NewCache(kCacheSize2, kNumShardBits2, false)) {
|
||||
current_ = this;
|
||||
type_ = GetParam();
|
||||
}
|
||||
|
||||
~CacheTest() override {}
|
||||
@@ -87,7 +114,9 @@ class CacheTest : public testing::TestWithParam<std::string> {
|
||||
return NewClockCache(capacity);
|
||||
}
|
||||
if (type == kFast) {
|
||||
return NewFastLRUCache(capacity);
|
||||
return NewFastLRUCache(
|
||||
capacity, 1 /*estimated_value_size*/, -1 /*num_shard_bits*/,
|
||||
false /*strict_capacity_limit*/, kDefaultCacheMetadataChargePolicy);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -110,12 +139,34 @@ class CacheTest : public testing::TestWithParam<std::string> {
|
||||
charge_policy);
|
||||
}
|
||||
if (type == kFast) {
|
||||
return NewFastLRUCache(capacity, num_shard_bits, strict_capacity_limit,
|
||||
return NewFastLRUCache(capacity, 1 /*estimated_value_size*/,
|
||||
num_shard_bits, strict_capacity_limit,
|
||||
charge_policy);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// These functions encode/decode keys in tests cases that use
|
||||
// int keys.
|
||||
// Currently, FastLRUCache requires keys to be 16B long, whereas
|
||||
// LRUCache and ClockCache don't, so the encoding depends on
|
||||
// the cache type.
|
||||
std::string EncodeKey(int k) {
|
||||
if (GetParam() == kFast) {
|
||||
return EncodeKey16Bytes(k);
|
||||
} else {
|
||||
return EncodeKey32Bits(k);
|
||||
}
|
||||
}
|
||||
|
||||
int DecodeKey(const Slice& k) {
|
||||
if (GetParam() == kFast) {
|
||||
return DecodeKey16Bytes(k);
|
||||
} else {
|
||||
return DecodeKey32Bits(k);
|
||||
}
|
||||
}
|
||||
|
||||
int Lookup(std::shared_ptr<Cache> cache, int key) {
|
||||
Cache::Handle* handle = cache->Lookup(EncodeKey(key));
|
||||
const int r = (handle == nullptr) ? -1 : DecodeValue(cache->Value(handle));
|
||||
@@ -159,11 +210,18 @@ class CacheTest : public testing::TestWithParam<std::string> {
|
||||
Erase(cache2_, key);
|
||||
}
|
||||
};
|
||||
|
||||
CacheTest* CacheTest::current_;
|
||||
std::string CacheTest::type_;
|
||||
|
||||
class LRUCacheTest : public CacheTest {};
|
||||
|
||||
TEST_P(CacheTest, UsageTest) {
|
||||
if (GetParam() == kFast) {
|
||||
ROCKSDB_GTEST_BYPASS("FastLRUCache requires 16 byte keys.");
|
||||
return;
|
||||
}
|
||||
|
||||
// cache is std::shared_ptr and will be automatically cleaned up.
|
||||
const uint64_t kCapacity = 100000;
|
||||
auto cache = NewCache(kCapacity, 8, false, kDontChargeCacheMetadata);
|
||||
@@ -208,6 +266,11 @@ TEST_P(CacheTest, UsageTest) {
|
||||
}
|
||||
|
||||
TEST_P(CacheTest, PinnedUsageTest) {
|
||||
if (GetParam() == kFast) {
|
||||
ROCKSDB_GTEST_BYPASS("FastLRUCache requires 16 byte keys.");
|
||||
return;
|
||||
}
|
||||
|
||||
// cache is std::shared_ptr and will be automatically cleaned up.
|
||||
const uint64_t kCapacity = 200000;
|
||||
auto cache = NewCache(kCapacity, 8, false, kDontChargeCacheMetadata);
|
||||
@@ -460,12 +523,22 @@ TEST_P(CacheTest, EvictionPolicyRef) {
|
||||
}
|
||||
|
||||
TEST_P(CacheTest, EvictEmptyCache) {
|
||||
if (GetParam() == kFast) {
|
||||
ROCKSDB_GTEST_BYPASS("FastLRUCache requires 16 byte keys.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Insert item large than capacity to trigger eviction on empty cache.
|
||||
auto cache = NewCache(1, 0, false);
|
||||
ASSERT_OK(cache->Insert("foo", nullptr, 10, dumbDeleter));
|
||||
}
|
||||
|
||||
TEST_P(CacheTest, EraseFromDeleter) {
|
||||
if (GetParam() == kFast) {
|
||||
ROCKSDB_GTEST_BYPASS("FastLRUCache requires 16 byte keys.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Have deleter which will erase item from cache, which will re-enter
|
||||
// the cache at that point.
|
||||
std::shared_ptr<Cache> cache = NewCache(10, 0, false);
|
||||
@@ -534,9 +607,9 @@ TEST_P(CacheTest, NewId) {
|
||||
|
||||
class Value {
|
||||
public:
|
||||
explicit Value(size_t v) : v_(v) { }
|
||||
explicit Value(int v) : v_(v) {}
|
||||
|
||||
size_t v_;
|
||||
int v_;
|
||||
};
|
||||
|
||||
namespace {
|
||||
@@ -584,8 +657,8 @@ TEST_P(CacheTest, SetCapacity) {
|
||||
std::shared_ptr<Cache> cache = NewCache(5, 0, false);
|
||||
std::vector<Cache::Handle*> handles(10);
|
||||
// Insert 5 entries, but not releasing.
|
||||
for (size_t i = 0; i < 5; i++) {
|
||||
std::string key = std::to_string(i + 1);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
std::string key = EncodeKey(i + 1);
|
||||
Status s = cache->Insert(key, new Value(i + 1), 1, &deleter, &handles[i]);
|
||||
ASSERT_TRUE(s.ok());
|
||||
}
|
||||
@@ -599,14 +672,14 @@ TEST_P(CacheTest, SetCapacity) {
|
||||
// insert 5 more elements to cache, then release 5,
|
||||
// then decrease capacity to 7, final capacity should be 7
|
||||
// and usage should be 7
|
||||
for (size_t i = 5; i < 10; i++) {
|
||||
std::string key = std::to_string(i + 1);
|
||||
for (int i = 5; i < 10; i++) {
|
||||
std::string key = EncodeKey(i + 1);
|
||||
Status s = cache->Insert(key, new Value(i + 1), 1, &deleter, &handles[i]);
|
||||
ASSERT_TRUE(s.ok());
|
||||
}
|
||||
ASSERT_EQ(10U, cache->GetCapacity());
|
||||
ASSERT_EQ(10U, cache->GetUsage());
|
||||
for (size_t i = 0; i < 5; i++) {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
cache->Release(handles[i]);
|
||||
}
|
||||
ASSERT_EQ(10U, cache->GetCapacity());
|
||||
@@ -616,7 +689,7 @@ TEST_P(CacheTest, SetCapacity) {
|
||||
ASSERT_EQ(7, cache->GetUsage());
|
||||
|
||||
// release remaining 5 to keep valgrind happy
|
||||
for (size_t i = 5; i < 10; i++) {
|
||||
for (int i = 5; i < 10; i++) {
|
||||
cache->Release(handles[i]);
|
||||
}
|
||||
|
||||
@@ -630,8 +703,8 @@ TEST_P(LRUCacheTest, SetStrictCapacityLimit) {
|
||||
std::shared_ptr<Cache> cache = NewCache(5, 0, false);
|
||||
std::vector<Cache::Handle*> handles(10);
|
||||
Status s;
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
std::string key = std::to_string(i + 1);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
std::string key = EncodeKey(i + 1);
|
||||
s = cache->Insert(key, new Value(i + 1), 1, &deleter, &handles[i]);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_NE(nullptr, handles[i]);
|
||||
@@ -639,7 +712,7 @@ TEST_P(LRUCacheTest, SetStrictCapacityLimit) {
|
||||
ASSERT_EQ(10, cache->GetUsage());
|
||||
|
||||
// test2: set the flag to true. Insert and check if it fails.
|
||||
std::string extra_key = "extra";
|
||||
std::string extra_key = EncodeKey(100);
|
||||
Value* extra_value = new Value(0);
|
||||
cache->SetStrictCapacityLimit(true);
|
||||
Cache::Handle* handle;
|
||||
@@ -648,14 +721,14 @@ TEST_P(LRUCacheTest, SetStrictCapacityLimit) {
|
||||
ASSERT_EQ(nullptr, handle);
|
||||
ASSERT_EQ(10, cache->GetUsage());
|
||||
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
cache->Release(handles[i]);
|
||||
}
|
||||
|
||||
// test3: init with flag being true.
|
||||
std::shared_ptr<Cache> cache2 = NewCache(5, 0, true);
|
||||
for (size_t i = 0; i < 5; i++) {
|
||||
std::string key = std::to_string(i + 1);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
std::string key = EncodeKey(i + 1);
|
||||
s = cache2->Insert(key, new Value(i + 1), 1, &deleter, &handles[i]);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_NE(nullptr, handles[i]);
|
||||
@@ -670,7 +743,7 @@ TEST_P(LRUCacheTest, SetStrictCapacityLimit) {
|
||||
ASSERT_EQ(5, cache2->GetUsage());
|
||||
ASSERT_EQ(nullptr, cache2->Lookup(extra_key));
|
||||
|
||||
for (size_t i = 0; i < 5; i++) {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
cache2->Release(handles[i]);
|
||||
}
|
||||
}
|
||||
@@ -684,15 +757,15 @@ TEST_P(CacheTest, OverCapacity) {
|
||||
std::vector<Cache::Handle*> handles(n+1);
|
||||
|
||||
// Insert n+1 entries, but not releasing.
|
||||
for (size_t i = 0; i < n + 1; i++) {
|
||||
std::string key = std::to_string(i + 1);
|
||||
for (int i = 0; i < static_cast<int>(n + 1); i++) {
|
||||
std::string key = EncodeKey(i + 1);
|
||||
Status s = cache->Insert(key, new Value(i + 1), 1, &deleter, &handles[i]);
|
||||
ASSERT_TRUE(s.ok());
|
||||
}
|
||||
|
||||
// Guess what's in the cache now?
|
||||
for (size_t i = 0; i < n + 1; i++) {
|
||||
std::string key = std::to_string(i + 1);
|
||||
for (int i = 0; i < static_cast<int>(n + 1); i++) {
|
||||
std::string key = EncodeKey(i + 1);
|
||||
auto h = cache->Lookup(key);
|
||||
ASSERT_TRUE(h != nullptr);
|
||||
if (h) cache->Release(h);
|
||||
@@ -700,7 +773,7 @@ TEST_P(CacheTest, OverCapacity) {
|
||||
|
||||
// the cache is over capacity since nothing could be evicted
|
||||
ASSERT_EQ(n + 1U, cache->GetUsage());
|
||||
for (size_t i = 0; i < n + 1; i++) {
|
||||
for (int i = 0; i < static_cast<int>(n + 1); i++) {
|
||||
cache->Release(handles[i]);
|
||||
}
|
||||
// Make sure eviction is triggered.
|
||||
@@ -712,14 +785,14 @@ TEST_P(CacheTest, OverCapacity) {
|
||||
// element 0 is evicted and the rest is there
|
||||
// This is consistent with the LRU policy since the element 0
|
||||
// was released first
|
||||
for (size_t i = 0; i < n + 1; i++) {
|
||||
std::string key = std::to_string(i + 1);
|
||||
for (int i = 0; i < static_cast<int>(n + 1); i++) {
|
||||
std::string key = EncodeKey(i + 1);
|
||||
auto h = cache->Lookup(key);
|
||||
if (h) {
|
||||
ASSERT_NE(i, 0U);
|
||||
ASSERT_NE(static_cast<size_t>(i), 0U);
|
||||
cache->Release(h);
|
||||
} else {
|
||||
ASSERT_EQ(i, 0U);
|
||||
ASSERT_EQ(static_cast<size_t>(i), 0U);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -745,7 +818,7 @@ TEST_P(CacheTest, ApplyToAllCacheEntriesTest) {
|
||||
std::sort(inserted.begin(), inserted.end());
|
||||
std::sort(legacy_callback_state.begin(), legacy_callback_state.end());
|
||||
ASSERT_EQ(inserted.size(), legacy_callback_state.size());
|
||||
for (size_t i = 0; i < inserted.size(); ++i) {
|
||||
for (int i = 0; i < static_cast<int>(inserted.size()); ++i) {
|
||||
EXPECT_EQ(inserted[i], legacy_callback_state[i]);
|
||||
}
|
||||
}
|
||||
@@ -773,7 +846,7 @@ TEST_P(CacheTest, ApplyToAllEntriesTest) {
|
||||
std::sort(inserted.begin(), inserted.end());
|
||||
std::sort(callback_state.begin(), callback_state.end());
|
||||
ASSERT_EQ(inserted.size(), callback_state.size());
|
||||
for (size_t i = 0; i < inserted.size(); ++i) {
|
||||
for (int i = 0; i < static_cast<int>(inserted.size()); ++i) {
|
||||
EXPECT_EQ(inserted[i], callback_state[i]);
|
||||
}
|
||||
}
|
||||
@@ -788,8 +861,10 @@ TEST_P(CacheTest, ApplyToAllEntriesDuringResize) {
|
||||
constexpr int kSpecialCharge = 2;
|
||||
constexpr int kNotSpecialCharge = 1;
|
||||
constexpr int kSpecialCount = 100;
|
||||
size_t expected_usage = 0;
|
||||
for (int i = 0; i < kSpecialCount; ++i) {
|
||||
Insert(i, i * 2, kSpecialCharge);
|
||||
expected_usage += kSpecialCharge;
|
||||
}
|
||||
|
||||
// For callback
|
||||
@@ -810,12 +885,17 @@ TEST_P(CacheTest, ApplyToAllEntriesDuringResize) {
|
||||
});
|
||||
|
||||
// In parallel, add more entries, enough to cause resize but not enough
|
||||
// to cause ejections
|
||||
for (int i = kSpecialCount * 1; i < kSpecialCount * 6; ++i) {
|
||||
// to cause ejections. (Note: if any cache shard is over capacity, there
|
||||
// will be ejections)
|
||||
for (int i = kSpecialCount * 1; i < kSpecialCount * 5; ++i) {
|
||||
Insert(i, i * 2, kNotSpecialCharge);
|
||||
expected_usage += kNotSpecialCharge;
|
||||
}
|
||||
|
||||
apply_thread.join();
|
||||
// verify no evictions
|
||||
ASSERT_EQ(cache_->GetUsage(), expected_usage);
|
||||
// verify everything seen in ApplyToAllEntries
|
||||
ASSERT_EQ(special_count, kSpecialCount);
|
||||
}
|
||||
|
||||
@@ -859,6 +939,7 @@ INSTANTIATE_TEST_CASE_P(CacheTestInstance, LRUCacheTest,
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
Vendored
+8
-8
@@ -39,7 +39,7 @@ std::shared_ptr<Cache> NewClockCache(
|
||||
#include "port/port.h"
|
||||
#include "tbb/concurrent_hash_map.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/distributed_mutex.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -368,7 +368,7 @@ class ClockCacheShard final : public CacheShard {
|
||||
|
||||
// Guards list_, head_, and recycle_. In addition, updating table_ also has
|
||||
// to hold the mutex, to avoid the cache being in inconsistent state.
|
||||
mutable port::Mutex mutex_;
|
||||
mutable DMutex mutex_;
|
||||
|
||||
// The circular list of cache handles. Initially the list is empty. Once a
|
||||
// handle is needed by insertion, and no more handles are available in
|
||||
@@ -431,7 +431,7 @@ void ClockCacheShard::ApplyToSomeEntries(
|
||||
DeleterFn deleter)>& callback,
|
||||
uint32_t average_entries_per_lock, uint32_t* state) {
|
||||
assert(average_entries_per_lock > 0);
|
||||
MutexLock lock(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
|
||||
// Figure out the range to iterate, update `state`
|
||||
size_t list_size = list_.size();
|
||||
@@ -532,7 +532,7 @@ bool ClockCacheShard::Unref(CacheHandle* handle, bool set_usage,
|
||||
pinned_usage_.fetch_sub(total_charge, std::memory_order_relaxed);
|
||||
// Cleanup if it is the last reference.
|
||||
if (!InCache(flags)) {
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
RecycleHandle(handle, context);
|
||||
}
|
||||
}
|
||||
@@ -598,7 +598,7 @@ bool ClockCacheShard::EvictFromCache(size_t charge, CleanupContext* context) {
|
||||
void ClockCacheShard::SetCapacity(size_t capacity) {
|
||||
CleanupContext context;
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
capacity_.store(capacity, std::memory_order_relaxed);
|
||||
EvictFromCache(0, &context);
|
||||
}
|
||||
@@ -618,7 +618,7 @@ CacheHandle* ClockCacheShard::Insert(
|
||||
uint32_t meta_charge =
|
||||
CacheHandle::CalcMetadataCharge(key, metadata_charge_policy_);
|
||||
size_t total_charge = charge + meta_charge;
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
bool success = EvictFromCache(total_charge, context);
|
||||
bool strict = strict_capacity_limit_.load(std::memory_order_relaxed);
|
||||
if (!success && (strict || !hold_reference)) {
|
||||
@@ -744,7 +744,7 @@ void ClockCacheShard::Erase(const Slice& key, uint32_t hash) {
|
||||
|
||||
bool ClockCacheShard::EraseAndConfirm(const Slice& key, uint32_t hash,
|
||||
CleanupContext* context) {
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
HashTable::accessor accessor;
|
||||
bool erased = false;
|
||||
if (table_.find(accessor, ClockCacheKey(key, hash))) {
|
||||
@@ -758,7 +758,7 @@ bool ClockCacheShard::EraseAndConfirm(const Slice& key, uint32_t hash,
|
||||
void ClockCacheShard::EraseUnRefEntries() {
|
||||
CleanupContext context;
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
table_.clear();
|
||||
for (auto& handle : list_) {
|
||||
UnsetInCache(&handle, &context);
|
||||
|
||||
+71
-31
@@ -10,6 +10,8 @@
|
||||
|
||||
#include "memory/jemalloc_nodump_allocator.h"
|
||||
#include "memory/memory_allocator.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/compression.h"
|
||||
@@ -79,37 +81,7 @@ class CompressedSecondaryCacheTest : public testing::Test {
|
||||
|
||||
void SetFailCreate(bool fail) { fail_create_ = fail; }
|
||||
|
||||
void BasicTest(bool sec_cache_is_compressed, bool use_jemalloc) {
|
||||
CompressedSecondaryCacheOptions opts;
|
||||
opts.capacity = 2048;
|
||||
opts.num_shard_bits = 0;
|
||||
opts.metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
|
||||
if (sec_cache_is_compressed) {
|
||||
if (!LZ4_Supported()) {
|
||||
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
||||
opts.compression_type = CompressionType::kNoCompression;
|
||||
}
|
||||
} else {
|
||||
opts.compression_type = CompressionType::kNoCompression;
|
||||
}
|
||||
|
||||
if (use_jemalloc) {
|
||||
JemallocAllocatorOptions jopts;
|
||||
std::shared_ptr<MemoryAllocator> allocator;
|
||||
std::string msg;
|
||||
if (JemallocNodumpAllocator::IsSupported(&msg)) {
|
||||
Status s = NewJemallocNodumpAllocator(jopts, &allocator);
|
||||
if (s.ok()) {
|
||||
opts.memory_allocator = allocator;
|
||||
}
|
||||
} else {
|
||||
ROCKSDB_GTEST_BYPASS("JEMALLOC not supported");
|
||||
}
|
||||
}
|
||||
std::shared_ptr<SecondaryCache> sec_cache =
|
||||
NewCompressedSecondaryCache(opts);
|
||||
|
||||
void BasicTestHelper(std::shared_ptr<SecondaryCache> sec_cache) {
|
||||
bool is_in_sec_cache{true};
|
||||
// Lookup an non-existent key.
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle0 =
|
||||
@@ -160,6 +132,38 @@ class CompressedSecondaryCacheTest : public testing::Test {
|
||||
sec_cache.reset();
|
||||
}
|
||||
|
||||
void BasicTest(bool sec_cache_is_compressed, bool use_jemalloc) {
|
||||
CompressedSecondaryCacheOptions opts;
|
||||
opts.capacity = 2048;
|
||||
opts.num_shard_bits = 0;
|
||||
opts.metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
|
||||
if (sec_cache_is_compressed) {
|
||||
if (!LZ4_Supported()) {
|
||||
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
||||
opts.compression_type = CompressionType::kNoCompression;
|
||||
}
|
||||
} else {
|
||||
opts.compression_type = CompressionType::kNoCompression;
|
||||
}
|
||||
|
||||
if (use_jemalloc) {
|
||||
JemallocAllocatorOptions jopts;
|
||||
std::shared_ptr<MemoryAllocator> allocator;
|
||||
std::string msg;
|
||||
if (JemallocNodumpAllocator::IsSupported(&msg)) {
|
||||
Status s = NewJemallocNodumpAllocator(jopts, &allocator);
|
||||
if (s.ok()) {
|
||||
opts.memory_allocator = allocator;
|
||||
}
|
||||
} else {
|
||||
ROCKSDB_GTEST_BYPASS("JEMALLOC not supported");
|
||||
}
|
||||
}
|
||||
std::shared_ptr<SecondaryCache> sec_cache =
|
||||
NewCompressedSecondaryCache(opts);
|
||||
}
|
||||
|
||||
void FailsTest(bool sec_cache_is_compressed) {
|
||||
CompressedSecondaryCacheOptions secondary_cache_opts;
|
||||
if (sec_cache_is_compressed) {
|
||||
@@ -547,6 +551,42 @@ TEST_F(CompressedSecondaryCacheTest,
|
||||
BasicTest(true, true);
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
TEST_F(CompressedSecondaryCacheTest, BasicTestFromStringWithNoCompression) {
|
||||
std::string sec_cache_uri =
|
||||
"compressed_secondary_cache://"
|
||||
"capacity=2048;num_shard_bits=0;compression_type=kNoCompression";
|
||||
std::shared_ptr<SecondaryCache> sec_cache;
|
||||
Status s = SecondaryCache::CreateFromString(ConfigOptions(), sec_cache_uri,
|
||||
&sec_cache);
|
||||
EXPECT_OK(s);
|
||||
BasicTestHelper(sec_cache);
|
||||
}
|
||||
|
||||
TEST_F(CompressedSecondaryCacheTest, BasicTestFromStringWithCompression) {
|
||||
std::string sec_cache_uri;
|
||||
if (LZ4_Supported()) {
|
||||
sec_cache_uri =
|
||||
"compressed_secondary_cache://"
|
||||
"capacity=2048;num_shard_bits=0;compression_type=kLZ4Compression;"
|
||||
"compress_format_version=2";
|
||||
} else {
|
||||
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
||||
sec_cache_uri =
|
||||
"compressed_secondary_cache://"
|
||||
"capacity=2048;num_shard_bits=0;compression_type=kNoCompression";
|
||||
}
|
||||
|
||||
std::shared_ptr<SecondaryCache> sec_cache;
|
||||
Status s = SecondaryCache::CreateFromString(ConfigOptions(), sec_cache_uri,
|
||||
&sec_cache);
|
||||
EXPECT_OK(s);
|
||||
BasicTestHelper(sec_cache);
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(CompressedSecondaryCacheTest, FailsTestWithNoCompression) {
|
||||
FailsTest(false);
|
||||
}
|
||||
|
||||
Vendored
+89
-100
@@ -16,17 +16,19 @@
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "port/lang.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/distributed_mutex.h"
|
||||
|
||||
#define KEY_LENGTH \
|
||||
16 // TODO(guido) Make use of this symbol in other parts of the source code
|
||||
// (e.g., cache_key.h, cache_test.cc, etc.)
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
namespace fast_lru_cache {
|
||||
|
||||
LRUHandleTable::LRUHandleTable(int max_upper_hash_bits)
|
||||
: length_bits_(/* historical starting size*/ 4),
|
||||
list_(new LRUHandle* [size_t{1} << length_bits_] {}),
|
||||
elems_(0),
|
||||
max_length_bits_(max_upper_hash_bits) {}
|
||||
LRUHandleTable::LRUHandleTable(int hash_bits)
|
||||
: length_bits_(hash_bits),
|
||||
list_(new LRUHandle* [size_t{1} << length_bits_] {}) {}
|
||||
|
||||
LRUHandleTable::~LRUHandleTable() {
|
||||
ApplyToEntriesRange(
|
||||
@@ -42,19 +44,15 @@ LRUHandle* LRUHandleTable::Lookup(const Slice& key, uint32_t hash) {
|
||||
return *FindPointer(key, hash);
|
||||
}
|
||||
|
||||
inline LRUHandle** LRUHandleTable::Head(uint32_t hash) {
|
||||
return &list_[hash >> (32 - length_bits_)];
|
||||
}
|
||||
|
||||
LRUHandle* LRUHandleTable::Insert(LRUHandle* h) {
|
||||
LRUHandle** ptr = FindPointer(h->key(), h->hash);
|
||||
LRUHandle* old = *ptr;
|
||||
h->next_hash = (old == nullptr ? nullptr : old->next_hash);
|
||||
*ptr = h;
|
||||
if (old == nullptr) {
|
||||
++elems_;
|
||||
if ((elems_ >> length_bits_) > 0) { // elems_ >= length
|
||||
// Since each cache entry is fairly large, we aim for a small
|
||||
// average linked list length (<= 1).
|
||||
Resize();
|
||||
}
|
||||
}
|
||||
return old;
|
||||
}
|
||||
|
||||
@@ -63,7 +61,6 @@ LRUHandle* LRUHandleTable::Remove(const Slice& key, uint32_t hash) {
|
||||
LRUHandle* result = *ptr;
|
||||
if (result != nullptr) {
|
||||
*ptr = result->next_hash;
|
||||
--elems_;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -76,46 +73,13 @@ LRUHandle** LRUHandleTable::FindPointer(const Slice& key, uint32_t hash) {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void LRUHandleTable::Resize() {
|
||||
if (length_bits_ >= max_length_bits_) {
|
||||
// Due to reaching limit of hash information, if we made the table bigger,
|
||||
// we would allocate more addresses but only the same number would be used.
|
||||
return;
|
||||
}
|
||||
if (length_bits_ >= 31) {
|
||||
// Avoid undefined behavior shifting uint32_t by 32.
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t old_length = uint32_t{1} << length_bits_;
|
||||
int new_length_bits = length_bits_ + 1;
|
||||
std::unique_ptr<LRUHandle* []> new_list {
|
||||
new LRUHandle* [size_t{1} << new_length_bits] {}
|
||||
};
|
||||
uint32_t count = 0;
|
||||
for (uint32_t i = 0; i < old_length; i++) {
|
||||
LRUHandle* h = list_[i];
|
||||
while (h != nullptr) {
|
||||
LRUHandle* next = h->next_hash;
|
||||
uint32_t hash = h->hash;
|
||||
LRUHandle** ptr = &new_list[hash >> (32 - new_length_bits)];
|
||||
h->next_hash = *ptr;
|
||||
*ptr = h;
|
||||
h = next;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
assert(elems_ == count);
|
||||
list_ = std::move(new_list);
|
||||
length_bits_ = new_length_bits;
|
||||
}
|
||||
|
||||
LRUCacheShard::LRUCacheShard(size_t capacity, bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
int max_upper_hash_bits)
|
||||
LRUCacheShard::LRUCacheShard(size_t capacity, size_t estimated_value_size,
|
||||
bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy)
|
||||
: capacity_(0),
|
||||
strict_capacity_limit_(strict_capacity_limit),
|
||||
table_(max_upper_hash_bits),
|
||||
table_(
|
||||
GetHashBits(capacity, estimated_value_size, metadata_charge_policy)),
|
||||
usage_(0),
|
||||
lru_usage_(0) {
|
||||
set_metadata_charge_policy(metadata_charge_policy);
|
||||
@@ -129,7 +93,7 @@ LRUCacheShard::LRUCacheShard(size_t capacity, bool strict_capacity_limit,
|
||||
void LRUCacheShard::EraseUnRefEntries() {
|
||||
autovector<LRUHandle*> last_reference_list;
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
while (lru_.next != &lru_) {
|
||||
LRUHandle* old = lru_.next;
|
||||
// LRU list contains only elements which can be evicted.
|
||||
@@ -137,9 +101,8 @@ void LRUCacheShard::EraseUnRefEntries() {
|
||||
LRU_Remove(old);
|
||||
table_.Remove(old->key(), old->hash);
|
||||
old->SetInCache(false);
|
||||
size_t total_charge = old->CalcTotalCharge(metadata_charge_policy_);
|
||||
assert(usage_ >= total_charge);
|
||||
usage_ -= total_charge;
|
||||
assert(usage_ >= old->total_charge);
|
||||
usage_ -= old->total_charge;
|
||||
last_reference_list.push_back(old);
|
||||
}
|
||||
}
|
||||
@@ -157,7 +120,7 @@ void LRUCacheShard::ApplyToSomeEntries(
|
||||
// The state is essentially going to be the starting hash, which works
|
||||
// nicely even if we resize between calls because we use upper-most
|
||||
// hash bits for table indexes.
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
uint32_t length_bits = table_.GetLengthBits();
|
||||
uint32_t length = uint32_t{1} << length_bits;
|
||||
|
||||
@@ -177,8 +140,10 @@ void LRUCacheShard::ApplyToSomeEntries(
|
||||
}
|
||||
|
||||
table_.ApplyToEntriesRange(
|
||||
[callback](LRUHandle* h) {
|
||||
callback(h->key(), h->value, h->charge, h->deleter);
|
||||
[callback,
|
||||
metadata_charge_policy = metadata_charge_policy_](LRUHandle* h) {
|
||||
callback(h->key(), h->value, h->GetCharge(metadata_charge_policy),
|
||||
h->deleter);
|
||||
},
|
||||
index_begin, index_end);
|
||||
}
|
||||
@@ -189,21 +154,19 @@ void LRUCacheShard::LRU_Remove(LRUHandle* e) {
|
||||
e->next->prev = e->prev;
|
||||
e->prev->next = e->next;
|
||||
e->prev = e->next = nullptr;
|
||||
size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
|
||||
assert(lru_usage_ >= total_charge);
|
||||
lru_usage_ -= total_charge;
|
||||
assert(lru_usage_ >= e->total_charge);
|
||||
lru_usage_ -= e->total_charge;
|
||||
}
|
||||
|
||||
void LRUCacheShard::LRU_Insert(LRUHandle* e) {
|
||||
assert(e->next == nullptr);
|
||||
assert(e->prev == nullptr);
|
||||
size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
|
||||
// Inset "e" to head of LRU list.
|
||||
e->next = &lru_;
|
||||
e->prev = lru_.prev;
|
||||
e->prev->next = e;
|
||||
e->next->prev = e;
|
||||
lru_usage_ += total_charge;
|
||||
lru_usage_ += e->total_charge;
|
||||
}
|
||||
|
||||
void LRUCacheShard::EvictFromLRU(size_t charge,
|
||||
@@ -215,17 +178,37 @@ void LRUCacheShard::EvictFromLRU(size_t charge,
|
||||
LRU_Remove(old);
|
||||
table_.Remove(old->key(), old->hash);
|
||||
old->SetInCache(false);
|
||||
size_t old_total_charge = old->CalcTotalCharge(metadata_charge_policy_);
|
||||
assert(usage_ >= old_total_charge);
|
||||
usage_ -= old_total_charge;
|
||||
assert(usage_ >= old->total_charge);
|
||||
usage_ -= old->total_charge;
|
||||
deleted->push_back(old);
|
||||
}
|
||||
}
|
||||
|
||||
int LRUCacheShard::GetHashBits(
|
||||
size_t capacity, size_t estimated_value_size,
|
||||
CacheMetadataChargePolicy metadata_charge_policy) {
|
||||
LRUHandle* e = reinterpret_cast<LRUHandle*>(
|
||||
new char[sizeof(LRUHandle) - 1 + KEY_LENGTH]);
|
||||
e->key_length = KEY_LENGTH;
|
||||
e->deleter = nullptr;
|
||||
e->refs = 0;
|
||||
e->flags = 0;
|
||||
e->refs = 0;
|
||||
|
||||
e->CalcTotalCharge(estimated_value_size, metadata_charge_policy);
|
||||
size_t num_entries = capacity / e->total_charge;
|
||||
e->Free();
|
||||
int num_hash_bits = 0;
|
||||
while (num_entries >>= 1) {
|
||||
++num_hash_bits;
|
||||
}
|
||||
return num_hash_bits;
|
||||
}
|
||||
|
||||
void LRUCacheShard::SetCapacity(size_t capacity) {
|
||||
autovector<LRUHandle*> last_reference_list;
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
capacity_ = capacity;
|
||||
EvictFromLRU(0, &last_reference_list);
|
||||
}
|
||||
@@ -237,7 +220,7 @@ void LRUCacheShard::SetCapacity(size_t capacity) {
|
||||
}
|
||||
|
||||
void LRUCacheShard::SetStrictCapacityLimit(bool strict_capacity_limit) {
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
strict_capacity_limit_ = strict_capacity_limit;
|
||||
}
|
||||
|
||||
@@ -245,16 +228,14 @@ Status LRUCacheShard::InsertItem(LRUHandle* e, Cache::Handle** handle,
|
||||
bool free_handle_on_fail) {
|
||||
Status s = Status::OK();
|
||||
autovector<LRUHandle*> last_reference_list;
|
||||
size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
|
||||
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
|
||||
// Free the space following strict LRU policy until enough space
|
||||
// is freed or the lru list is empty.
|
||||
EvictFromLRU(total_charge, &last_reference_list);
|
||||
EvictFromLRU(e->total_charge, &last_reference_list);
|
||||
|
||||
if ((usage_ + total_charge) > capacity_ &&
|
||||
if ((usage_ + e->total_charge) > capacity_ &&
|
||||
(strict_capacity_limit_ || handle == nullptr)) {
|
||||
e->SetInCache(false);
|
||||
if (handle == nullptr) {
|
||||
@@ -272,7 +253,7 @@ Status LRUCacheShard::InsertItem(LRUHandle* e, Cache::Handle** handle,
|
||||
// Insert into the cache. Note that the cache might get larger than its
|
||||
// capacity if not enough space was freed up.
|
||||
LRUHandle* old = table_.Insert(e);
|
||||
usage_ += total_charge;
|
||||
usage_ += e->total_charge;
|
||||
if (old != nullptr) {
|
||||
s = Status::OkOverwritten();
|
||||
assert(old->InCache());
|
||||
@@ -280,10 +261,8 @@ Status LRUCacheShard::InsertItem(LRUHandle* e, Cache::Handle** handle,
|
||||
if (!old->HasRefs()) {
|
||||
// old is on LRU because it's in cache and its reference count is 0.
|
||||
LRU_Remove(old);
|
||||
size_t old_total_charge =
|
||||
old->CalcTotalCharge(metadata_charge_policy_);
|
||||
assert(usage_ >= old_total_charge);
|
||||
usage_ -= old_total_charge;
|
||||
assert(usage_ >= old->total_charge);
|
||||
usage_ -= old->total_charge;
|
||||
last_reference_list.push_back(old);
|
||||
}
|
||||
}
|
||||
@@ -310,7 +289,7 @@ Status LRUCacheShard::InsertItem(LRUHandle* e, Cache::Handle** handle,
|
||||
Cache::Handle* LRUCacheShard::Lookup(const Slice& key, uint32_t hash) {
|
||||
LRUHandle* e = nullptr;
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
e = table_.Lookup(key, hash);
|
||||
if (e != nullptr) {
|
||||
assert(e->InCache());
|
||||
@@ -326,7 +305,7 @@ Cache::Handle* LRUCacheShard::Lookup(const Slice& key, uint32_t hash) {
|
||||
|
||||
bool LRUCacheShard::Ref(Cache::Handle* h) {
|
||||
LRUHandle* e = reinterpret_cast<LRUHandle*>(h);
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
// To create another reference - entry must be already externally referenced.
|
||||
assert(e->HasRefs());
|
||||
e->Ref();
|
||||
@@ -340,7 +319,7 @@ bool LRUCacheShard::Release(Cache::Handle* handle, bool erase_if_last_ref) {
|
||||
LRUHandle* e = reinterpret_cast<LRUHandle*>(handle);
|
||||
bool last_reference = false;
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
last_reference = e->Unref();
|
||||
if (last_reference && e->InCache()) {
|
||||
// The item is still in cache, and nobody else holds a reference to it.
|
||||
@@ -358,9 +337,8 @@ bool LRUCacheShard::Release(Cache::Handle* handle, bool erase_if_last_ref) {
|
||||
}
|
||||
// If it was the last reference, then decrement the cache usage.
|
||||
if (last_reference) {
|
||||
size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
|
||||
assert(usage_ >= total_charge);
|
||||
usage_ -= total_charge;
|
||||
assert(usage_ >= e->total_charge);
|
||||
usage_ -= e->total_charge;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,6 +353,11 @@ Status LRUCacheShard::Insert(const Slice& key, uint32_t hash, void* value,
|
||||
size_t charge, Cache::DeleterFn deleter,
|
||||
Cache::Handle** handle,
|
||||
Cache::Priority /*priority*/) {
|
||||
if (key.size() != KEY_LENGTH) {
|
||||
return Status::NotSupported("FastLRUCache only supports key size " +
|
||||
std::to_string(KEY_LENGTH) + "B");
|
||||
}
|
||||
|
||||
// Allocate the memory here outside of the mutex.
|
||||
// If the cache is full, we'll have to release it.
|
||||
// It shouldn't happen very often though.
|
||||
@@ -384,12 +367,12 @@ Status LRUCacheShard::Insert(const Slice& key, uint32_t hash, void* value,
|
||||
e->value = value;
|
||||
e->flags = 0;
|
||||
e->deleter = deleter;
|
||||
e->charge = charge;
|
||||
e->key_length = key.size();
|
||||
e->hash = hash;
|
||||
e->refs = 0;
|
||||
e->next = e->prev = nullptr;
|
||||
e->SetInCache(true);
|
||||
e->CalcTotalCharge(charge, metadata_charge_policy_);
|
||||
memcpy(e->key_data, key.data(), key.size());
|
||||
|
||||
return InsertItem(e, handle, /* free_handle_on_fail */ true);
|
||||
@@ -399,7 +382,7 @@ void LRUCacheShard::Erase(const Slice& key, uint32_t hash) {
|
||||
LRUHandle* e;
|
||||
bool last_reference = false;
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
e = table_.Remove(key, hash);
|
||||
if (e != nullptr) {
|
||||
assert(e->InCache());
|
||||
@@ -407,9 +390,8 @@ void LRUCacheShard::Erase(const Slice& key, uint32_t hash) {
|
||||
if (!e->HasRefs()) {
|
||||
// The entry is in LRU since it's in hash and has no external references
|
||||
LRU_Remove(e);
|
||||
size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
|
||||
assert(usage_ >= total_charge);
|
||||
usage_ -= total_charge;
|
||||
assert(usage_ >= e->total_charge);
|
||||
usage_ -= e->total_charge;
|
||||
last_reference = true;
|
||||
}
|
||||
}
|
||||
@@ -423,20 +405,20 @@ void LRUCacheShard::Erase(const Slice& key, uint32_t hash) {
|
||||
}
|
||||
|
||||
size_t LRUCacheShard::GetUsage() const {
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
return usage_;
|
||||
}
|
||||
|
||||
size_t LRUCacheShard::GetPinnedUsage() const {
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
assert(usage_ >= lru_usage_);
|
||||
return usage_ - lru_usage_;
|
||||
}
|
||||
|
||||
std::string LRUCacheShard::GetPrintableOptions() const { return std::string{}; }
|
||||
|
||||
LRUCache::LRUCache(size_t capacity, int num_shard_bits,
|
||||
bool strict_capacity_limit,
|
||||
LRUCache::LRUCache(size_t capacity, size_t estimated_value_size,
|
||||
int num_shard_bits, bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy)
|
||||
: ShardedCache(capacity, num_shard_bits, strict_capacity_limit) {
|
||||
num_shards_ = 1 << num_shard_bits;
|
||||
@@ -445,8 +427,8 @@ LRUCache::LRUCache(size_t capacity, int num_shard_bits,
|
||||
size_t per_shard = (capacity + (num_shards_ - 1)) / num_shards_;
|
||||
for (int i = 0; i < num_shards_; i++) {
|
||||
new (&shards_[i])
|
||||
LRUCacheShard(per_shard, strict_capacity_limit, metadata_charge_policy,
|
||||
/* max_upper_hash_bits */ 32 - num_shard_bits);
|
||||
LRUCacheShard(per_shard, estimated_value_size, strict_capacity_limit,
|
||||
metadata_charge_policy);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,7 +455,12 @@ void* LRUCache::Value(Handle* handle) {
|
||||
}
|
||||
|
||||
size_t LRUCache::GetCharge(Handle* handle) const {
|
||||
return reinterpret_cast<const LRUHandle*>(handle)->charge;
|
||||
CacheMetadataChargePolicy metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
if (num_shards_ > 0) {
|
||||
metadata_charge_policy = shards_[0].metadata_charge_policy_;
|
||||
}
|
||||
return reinterpret_cast<const LRUHandle*>(handle)->GetCharge(
|
||||
metadata_charge_policy);
|
||||
}
|
||||
|
||||
Cache::DeleterFn LRUCache::GetDeleter(Handle* handle) const {
|
||||
@@ -496,7 +483,8 @@ void LRUCache::DisownData() {
|
||||
} // namespace fast_lru_cache
|
||||
|
||||
std::shared_ptr<Cache> NewFastLRUCache(
|
||||
size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
||||
size_t capacity, size_t estimated_value_size, int num_shard_bits,
|
||||
bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy) {
|
||||
if (num_shard_bits >= 20) {
|
||||
return nullptr; // The cache cannot be sharded into too many fine pieces.
|
||||
@@ -505,7 +493,8 @@ std::shared_ptr<Cache> NewFastLRUCache(
|
||||
num_shard_bits = GetDefaultCacheShardBits(capacity);
|
||||
}
|
||||
return std::make_shared<fast_lru_cache::LRUCache>(
|
||||
capacity, num_shard_bits, strict_capacity_limit, metadata_charge_policy);
|
||||
capacity, estimated_value_size, num_shard_bits, strict_capacity_limit,
|
||||
metadata_charge_policy);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+41
-29
@@ -17,6 +17,7 @@
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/distributed_mutex.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
namespace fast_lru_cache {
|
||||
@@ -29,7 +30,7 @@ struct LRUHandle {
|
||||
LRUHandle* next_hash;
|
||||
LRUHandle* next;
|
||||
LRUHandle* prev;
|
||||
size_t charge; // TODO(opt): Only allow uint32_t?
|
||||
size_t total_charge; // TODO(opt): Only allow uint32_t?
|
||||
size_t key_length;
|
||||
// The hash of key(). Used for fast sharding and comparisons.
|
||||
uint32_t hash;
|
||||
@@ -79,18 +80,31 @@ struct LRUHandle {
|
||||
}
|
||||
|
||||
// Calculate the memory usage by metadata.
|
||||
inline size_t CalcTotalCharge(
|
||||
CacheMetadataChargePolicy metadata_charge_policy) {
|
||||
size_t meta_charge = 0;
|
||||
if (metadata_charge_policy == kFullChargeCacheMetadata) {
|
||||
inline size_t CalcMetaCharge(
|
||||
CacheMetadataChargePolicy metadata_charge_policy) const {
|
||||
if (metadata_charge_policy != kFullChargeCacheMetadata) {
|
||||
return 0;
|
||||
} else {
|
||||
#ifdef ROCKSDB_MALLOC_USABLE_SIZE
|
||||
meta_charge += malloc_usable_size(static_cast<void*>(this));
|
||||
return malloc_usable_size(
|
||||
const_cast<void*>(static_cast<const void*>(this)));
|
||||
#else
|
||||
// This is the size that is used when a new handle is created.
|
||||
meta_charge += sizeof(LRUHandle) - 1 + key_length;
|
||||
return sizeof(LRUHandle) - 1 + key_length;
|
||||
#endif
|
||||
}
|
||||
return charge + meta_charge;
|
||||
}
|
||||
|
||||
inline void CalcTotalCharge(
|
||||
size_t charge, CacheMetadataChargePolicy metadata_charge_policy) {
|
||||
total_charge = charge + CalcMetaCharge(metadata_charge_policy);
|
||||
}
|
||||
|
||||
inline size_t GetCharge(
|
||||
CacheMetadataChargePolicy metadata_charge_policy) const {
|
||||
size_t meta_charge = CalcMetaCharge(metadata_charge_policy);
|
||||
assert(total_charge >= meta_charge);
|
||||
return total_charge - meta_charge;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -101,10 +115,7 @@ struct LRUHandle {
|
||||
// 4.4.3's builtin hashtable.
|
||||
class LRUHandleTable {
|
||||
public:
|
||||
// If the table uses more hash bits than `max_upper_hash_bits`,
|
||||
// it will eat into the bits used for sharding, which are constant
|
||||
// for a given LRUHandleTable.
|
||||
explicit LRUHandleTable(int max_upper_hash_bits);
|
||||
explicit LRUHandleTable(int hash_bits);
|
||||
~LRUHandleTable();
|
||||
|
||||
LRUHandle* Lookup(const Slice& key, uint32_t hash);
|
||||
@@ -126,14 +137,16 @@ class LRUHandleTable {
|
||||
|
||||
int GetLengthBits() const { return length_bits_; }
|
||||
|
||||
// Return the address of the head of the chain in the bucket given
|
||||
// by the hash.
|
||||
inline LRUHandle** Head(uint32_t hash);
|
||||
|
||||
private:
|
||||
// Return a pointer to slot that points to a cache entry that
|
||||
// matches key/hash. If there is no such cache entry, return a
|
||||
// pointer to the trailing slot in the corresponding linked list.
|
||||
LRUHandle** FindPointer(const Slice& key, uint32_t hash);
|
||||
|
||||
void Resize();
|
||||
|
||||
// Number of hash bits (upper because lower bits used for sharding)
|
||||
// used for table index. Length == 1 << length_bits_
|
||||
int length_bits_;
|
||||
@@ -141,20 +154,14 @@ class LRUHandleTable {
|
||||
// The table consists of an array of buckets where each bucket is
|
||||
// a linked list of cache entries that hash into the bucket.
|
||||
std::unique_ptr<LRUHandle*[]> list_;
|
||||
|
||||
// Number of elements currently in the table.
|
||||
uint32_t elems_;
|
||||
|
||||
// Set from max_upper_hash_bits (see constructor).
|
||||
const int max_length_bits_;
|
||||
};
|
||||
|
||||
// A single shard of sharded cache.
|
||||
class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
public:
|
||||
LRUCacheShard(size_t capacity, bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
int max_upper_hash_bits);
|
||||
LRUCacheShard(size_t capacity, size_t estimated_value_size,
|
||||
bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy);
|
||||
~LRUCacheShard() override = default;
|
||||
|
||||
// Separate from constructor so caller can easily make an array of LRUCache
|
||||
@@ -226,6 +233,11 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
// holding the mutex_.
|
||||
void EvictFromLRU(size_t charge, autovector<LRUHandle*>* deleted);
|
||||
|
||||
// Returns the number of bits used to hash an element in the per-shard
|
||||
// table.
|
||||
static int GetHashBits(size_t capacity, size_t estimated_value_size,
|
||||
CacheMetadataChargePolicy metadata_charge_policy);
|
||||
|
||||
// Initialized before use.
|
||||
size_t capacity_;
|
||||
|
||||
@@ -262,7 +274,7 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
// mutex_ protects the following state.
|
||||
// We don't count mutex_ as the cache's internal state so semantically we
|
||||
// don't mind mutex_ invoking the non-const actions.
|
||||
mutable port::Mutex mutex_;
|
||||
mutable DMutex mutex_;
|
||||
};
|
||||
|
||||
class LRUCache
|
||||
@@ -271,7 +283,8 @@ class LRUCache
|
||||
#endif
|
||||
: public ShardedCache {
|
||||
public:
|
||||
LRUCache(size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
||||
LRUCache(size_t capacity, size_t estimated_value_size, int num_shard_bits,
|
||||
bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy =
|
||||
kDontChargeCacheMetadata);
|
||||
~LRUCache() override;
|
||||
@@ -291,9 +304,8 @@ class LRUCache
|
||||
} // namespace fast_lru_cache
|
||||
|
||||
std::shared_ptr<Cache> NewFastLRUCache(
|
||||
size_t capacity, int num_shard_bits = -1,
|
||||
bool strict_capacity_limit = false,
|
||||
CacheMetadataChargePolicy metadata_charge_policy =
|
||||
kDefaultCacheMetadataChargePolicy);
|
||||
size_t capacity, size_t estimated_value_size, int num_shard_bits,
|
||||
bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy);
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+54
-57
@@ -16,7 +16,7 @@
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "port/lang.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/distributed_mutex.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
namespace lru_cache {
|
||||
@@ -135,7 +135,7 @@ LRUCacheShard::LRUCacheShard(
|
||||
void LRUCacheShard::EraseUnRefEntries() {
|
||||
autovector<LRUHandle*> last_reference_list;
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
while (lru_.next != &lru_) {
|
||||
LRUHandle* old = lru_.next;
|
||||
// LRU list contains only elements which can be evicted.
|
||||
@@ -143,9 +143,8 @@ void LRUCacheShard::EraseUnRefEntries() {
|
||||
LRU_Remove(old);
|
||||
table_.Remove(old->key(), old->hash);
|
||||
old->SetInCache(false);
|
||||
size_t total_charge = old->CalcTotalCharge(metadata_charge_policy_);
|
||||
assert(usage_ >= total_charge);
|
||||
usage_ -= total_charge;
|
||||
assert(usage_ >= old->total_charge);
|
||||
usage_ -= old->total_charge;
|
||||
last_reference_list.push_back(old);
|
||||
}
|
||||
}
|
||||
@@ -162,7 +161,7 @@ void LRUCacheShard::ApplyToSomeEntries(
|
||||
// The state is essentially going to be the starting hash, which works
|
||||
// nicely even if we resize between calls because we use upper-most
|
||||
// hash bits for table indexes.
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
uint32_t length_bits = table_.GetLengthBits();
|
||||
uint32_t length = uint32_t{1} << length_bits;
|
||||
|
||||
@@ -182,23 +181,25 @@ void LRUCacheShard::ApplyToSomeEntries(
|
||||
}
|
||||
|
||||
table_.ApplyToEntriesRange(
|
||||
[callback](LRUHandle* h) {
|
||||
[callback,
|
||||
metadata_charge_policy = metadata_charge_policy_](LRUHandle* h) {
|
||||
DeleterFn deleter = h->IsSecondaryCacheCompatible()
|
||||
? h->info_.helper->del_cb
|
||||
: h->info_.deleter;
|
||||
callback(h->key(), h->value, h->charge, deleter);
|
||||
callback(h->key(), h->value, h->GetCharge(metadata_charge_policy),
|
||||
deleter);
|
||||
},
|
||||
index_begin, index_end);
|
||||
}
|
||||
|
||||
void LRUCacheShard::TEST_GetLRUList(LRUHandle** lru, LRUHandle** lru_low_pri) {
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
*lru = &lru_;
|
||||
*lru_low_pri = lru_low_pri_;
|
||||
}
|
||||
|
||||
size_t LRUCacheShard::TEST_GetLRUSize() {
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
LRUHandle* lru_handle = lru_.next;
|
||||
size_t lru_size = 0;
|
||||
while (lru_handle != &lru_) {
|
||||
@@ -209,7 +210,7 @@ size_t LRUCacheShard::TEST_GetLRUSize() {
|
||||
}
|
||||
|
||||
double LRUCacheShard::GetHighPriPoolRatio() {
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
return high_pri_pool_ratio_;
|
||||
}
|
||||
|
||||
@@ -222,19 +223,17 @@ void LRUCacheShard::LRU_Remove(LRUHandle* e) {
|
||||
e->next->prev = e->prev;
|
||||
e->prev->next = e->next;
|
||||
e->prev = e->next = nullptr;
|
||||
size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
|
||||
assert(lru_usage_ >= total_charge);
|
||||
lru_usage_ -= total_charge;
|
||||
assert(lru_usage_ >= e->total_charge);
|
||||
lru_usage_ -= e->total_charge;
|
||||
if (e->InHighPriPool()) {
|
||||
assert(high_pri_pool_usage_ >= total_charge);
|
||||
high_pri_pool_usage_ -= total_charge;
|
||||
assert(high_pri_pool_usage_ >= e->total_charge);
|
||||
high_pri_pool_usage_ -= e->total_charge;
|
||||
}
|
||||
}
|
||||
|
||||
void LRUCacheShard::LRU_Insert(LRUHandle* e) {
|
||||
assert(e->next == nullptr);
|
||||
assert(e->prev == nullptr);
|
||||
size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
|
||||
if (high_pri_pool_ratio_ > 0 && (e->IsHighPri() || e->HasHit())) {
|
||||
// Inset "e" to head of LRU list.
|
||||
e->next = &lru_;
|
||||
@@ -242,7 +241,7 @@ void LRUCacheShard::LRU_Insert(LRUHandle* e) {
|
||||
e->prev->next = e;
|
||||
e->next->prev = e;
|
||||
e->SetInHighPriPool(true);
|
||||
high_pri_pool_usage_ += total_charge;
|
||||
high_pri_pool_usage_ += e->total_charge;
|
||||
MaintainPoolSize();
|
||||
} else {
|
||||
// Insert "e" to the head of low-pri pool. Note that when
|
||||
@@ -254,7 +253,7 @@ void LRUCacheShard::LRU_Insert(LRUHandle* e) {
|
||||
e->SetInHighPriPool(false);
|
||||
lru_low_pri_ = e;
|
||||
}
|
||||
lru_usage_ += total_charge;
|
||||
lru_usage_ += e->total_charge;
|
||||
}
|
||||
|
||||
void LRUCacheShard::MaintainPoolSize() {
|
||||
@@ -263,10 +262,8 @@ void LRUCacheShard::MaintainPoolSize() {
|
||||
lru_low_pri_ = lru_low_pri_->next;
|
||||
assert(lru_low_pri_ != &lru_);
|
||||
lru_low_pri_->SetInHighPriPool(false);
|
||||
size_t total_charge =
|
||||
lru_low_pri_->CalcTotalCharge(metadata_charge_policy_);
|
||||
assert(high_pri_pool_usage_ >= total_charge);
|
||||
high_pri_pool_usage_ -= total_charge;
|
||||
assert(high_pri_pool_usage_ >= lru_low_pri_->total_charge);
|
||||
high_pri_pool_usage_ -= lru_low_pri_->total_charge;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,9 +276,8 @@ void LRUCacheShard::EvictFromLRU(size_t charge,
|
||||
LRU_Remove(old);
|
||||
table_.Remove(old->key(), old->hash);
|
||||
old->SetInCache(false);
|
||||
size_t old_total_charge = old->CalcTotalCharge(metadata_charge_policy_);
|
||||
assert(usage_ >= old_total_charge);
|
||||
usage_ -= old_total_charge;
|
||||
assert(usage_ >= old->total_charge);
|
||||
usage_ -= old->total_charge;
|
||||
deleted->push_back(old);
|
||||
}
|
||||
}
|
||||
@@ -289,7 +285,7 @@ void LRUCacheShard::EvictFromLRU(size_t charge,
|
||||
void LRUCacheShard::SetCapacity(size_t capacity) {
|
||||
autovector<LRUHandle*> last_reference_list;
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
capacity_ = capacity;
|
||||
high_pri_pool_capacity_ = capacity_ * high_pri_pool_ratio_;
|
||||
EvictFromLRU(0, &last_reference_list);
|
||||
@@ -308,7 +304,7 @@ void LRUCacheShard::SetCapacity(size_t capacity) {
|
||||
}
|
||||
|
||||
void LRUCacheShard::SetStrictCapacityLimit(bool strict_capacity_limit) {
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
strict_capacity_limit_ = strict_capacity_limit;
|
||||
}
|
||||
|
||||
@@ -316,16 +312,15 @@ Status LRUCacheShard::InsertItem(LRUHandle* e, Cache::Handle** handle,
|
||||
bool free_handle_on_fail) {
|
||||
Status s = Status::OK();
|
||||
autovector<LRUHandle*> last_reference_list;
|
||||
size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
|
||||
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
|
||||
// Free the space following strict LRU policy until enough space
|
||||
// is freed or the lru list is empty.
|
||||
EvictFromLRU(total_charge, &last_reference_list);
|
||||
EvictFromLRU(e->total_charge, &last_reference_list);
|
||||
|
||||
if ((usage_ + total_charge) > capacity_ &&
|
||||
if ((usage_ + e->total_charge) > capacity_ &&
|
||||
(strict_capacity_limit_ || handle == nullptr)) {
|
||||
e->SetInCache(false);
|
||||
if (handle == nullptr) {
|
||||
@@ -343,7 +338,7 @@ Status LRUCacheShard::InsertItem(LRUHandle* e, Cache::Handle** handle,
|
||||
// Insert into the cache. Note that the cache might get larger than its
|
||||
// capacity if not enough space was freed up.
|
||||
LRUHandle* old = table_.Insert(e);
|
||||
usage_ += total_charge;
|
||||
usage_ += e->total_charge;
|
||||
if (old != nullptr) {
|
||||
s = Status::OkOverwritten();
|
||||
assert(old->InCache());
|
||||
@@ -351,10 +346,8 @@ Status LRUCacheShard::InsertItem(LRUHandle* e, Cache::Handle** handle,
|
||||
if (!old->HasRefs()) {
|
||||
// old is on LRU because it's in cache and its reference count is 0.
|
||||
LRU_Remove(old);
|
||||
size_t old_total_charge =
|
||||
old->CalcTotalCharge(metadata_charge_policy_);
|
||||
assert(usage_ >= old_total_charge);
|
||||
usage_ -= old_total_charge;
|
||||
assert(usage_ >= old->total_charge);
|
||||
usage_ -= old->total_charge;
|
||||
last_reference_list.push_back(old);
|
||||
}
|
||||
}
|
||||
@@ -391,7 +384,7 @@ void LRUCacheShard::Promote(LRUHandle* e) {
|
||||
e->SetIncomplete(false);
|
||||
e->SetInCache(true);
|
||||
e->value = secondary_handle->Value();
|
||||
e->charge = secondary_handle->Size();
|
||||
e->CalcTotalCharge(secondary_handle->Size(), metadata_charge_policy_);
|
||||
delete secondary_handle;
|
||||
|
||||
// This call could fail if the cache is over capacity and
|
||||
@@ -409,8 +402,9 @@ void LRUCacheShard::Promote(LRUHandle* e) {
|
||||
} else {
|
||||
// Since the secondary cache lookup failed, mark the item as not in cache
|
||||
// Don't charge the cache as its only metadata that'll shortly be released
|
||||
MutexLock l(&mutex_);
|
||||
e->charge = 0;
|
||||
DMutexLock l(mutex_);
|
||||
// TODO
|
||||
e->CalcTotalCharge(0, metadata_charge_policy_);
|
||||
e->SetInCache(false);
|
||||
}
|
||||
}
|
||||
@@ -422,7 +416,7 @@ Cache::Handle* LRUCacheShard::Lookup(
|
||||
bool wait, Statistics* stats) {
|
||||
LRUHandle* e = nullptr;
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
e = table_.Lookup(key, hash);
|
||||
if (e != nullptr) {
|
||||
assert(e->InCache());
|
||||
@@ -495,7 +489,7 @@ Cache::Handle* LRUCacheShard::Lookup(
|
||||
|
||||
bool LRUCacheShard::Ref(Cache::Handle* h) {
|
||||
LRUHandle* e = reinterpret_cast<LRUHandle*>(h);
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
// To create another reference - entry must be already externally referenced.
|
||||
assert(e->HasRefs());
|
||||
e->Ref();
|
||||
@@ -503,7 +497,7 @@ bool LRUCacheShard::Ref(Cache::Handle* h) {
|
||||
}
|
||||
|
||||
void LRUCacheShard::SetHighPriorityPoolRatio(double high_pri_pool_ratio) {
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
high_pri_pool_ratio_ = high_pri_pool_ratio;
|
||||
high_pri_pool_capacity_ = capacity_ * high_pri_pool_ratio_;
|
||||
MaintainPoolSize();
|
||||
@@ -516,7 +510,7 @@ bool LRUCacheShard::Release(Cache::Handle* handle, bool erase_if_last_ref) {
|
||||
LRUHandle* e = reinterpret_cast<LRUHandle*>(handle);
|
||||
bool last_reference = false;
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
last_reference = e->Unref();
|
||||
if (last_reference && e->InCache()) {
|
||||
// The item is still in cache, and nobody else holds a reference to it.
|
||||
@@ -538,9 +532,8 @@ bool LRUCacheShard::Release(Cache::Handle* handle, bool erase_if_last_ref) {
|
||||
// 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;
|
||||
assert(usage_ >= e->total_charge);
|
||||
usage_ -= e->total_charge;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -573,7 +566,6 @@ Status LRUCacheShard::Insert(const Slice& key, uint32_t hash, void* value,
|
||||
#endif // __SANITIZE_THREAD__
|
||||
e->info_.deleter = deleter;
|
||||
}
|
||||
e->charge = charge;
|
||||
e->key_length = key.size();
|
||||
e->hash = hash;
|
||||
e->refs = 0;
|
||||
@@ -581,6 +573,7 @@ Status LRUCacheShard::Insert(const Slice& key, uint32_t hash, void* value,
|
||||
e->SetInCache(true);
|
||||
e->SetPriority(priority);
|
||||
memcpy(e->key_data, key.data(), key.size());
|
||||
e->CalcTotalCharge(charge, metadata_charge_policy_);
|
||||
|
||||
return InsertItem(e, handle, /* free_handle_on_fail */ true);
|
||||
}
|
||||
@@ -589,7 +582,7 @@ void LRUCacheShard::Erase(const Slice& key, uint32_t hash) {
|
||||
LRUHandle* e;
|
||||
bool last_reference = false;
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
e = table_.Remove(key, hash);
|
||||
if (e != nullptr) {
|
||||
assert(e->InCache());
|
||||
@@ -597,9 +590,8 @@ void LRUCacheShard::Erase(const Slice& key, uint32_t hash) {
|
||||
if (!e->HasRefs()) {
|
||||
// The entry is in LRU since it's in hash and has no external references
|
||||
LRU_Remove(e);
|
||||
size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
|
||||
assert(usage_ >= total_charge);
|
||||
usage_ -= total_charge;
|
||||
assert(usage_ >= e->total_charge);
|
||||
usage_ -= e->total_charge;
|
||||
last_reference = true;
|
||||
}
|
||||
}
|
||||
@@ -614,7 +606,7 @@ void LRUCacheShard::Erase(const Slice& key, uint32_t hash) {
|
||||
|
||||
bool LRUCacheShard::IsReady(Cache::Handle* handle) {
|
||||
LRUHandle* e = reinterpret_cast<LRUHandle*>(handle);
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
bool ready = true;
|
||||
if (e->IsPending()) {
|
||||
assert(secondary_cache_);
|
||||
@@ -625,12 +617,12 @@ bool LRUCacheShard::IsReady(Cache::Handle* handle) {
|
||||
}
|
||||
|
||||
size_t LRUCacheShard::GetUsage() const {
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
return usage_;
|
||||
}
|
||||
|
||||
size_t LRUCacheShard::GetPinnedUsage() const {
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
assert(usage_ >= lru_usage_);
|
||||
return usage_ - lru_usage_;
|
||||
}
|
||||
@@ -639,7 +631,7 @@ std::string LRUCacheShard::GetPrintableOptions() const {
|
||||
const int kBufferSize = 200;
|
||||
char buffer[kBufferSize];
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
DMutexLock l(mutex_);
|
||||
snprintf(buffer, kBufferSize, " high_pri_pool_ratio: %.3lf\n",
|
||||
high_pri_pool_ratio_);
|
||||
}
|
||||
@@ -690,7 +682,12 @@ void* LRUCache::Value(Handle* handle) {
|
||||
}
|
||||
|
||||
size_t LRUCache::GetCharge(Handle* handle) const {
|
||||
return reinterpret_cast<const LRUHandle*>(handle)->charge;
|
||||
CacheMetadataChargePolicy metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
if (num_shards_ > 0) {
|
||||
metadata_charge_policy = shards_[0].metadata_charge_policy_;
|
||||
}
|
||||
return reinterpret_cast<const LRUHandle*>(handle)->GetCharge(
|
||||
metadata_charge_policy);
|
||||
}
|
||||
|
||||
Cache::DeleterFn LRUCache::GetDeleter(Handle* handle) const {
|
||||
|
||||
Vendored
+24
-10
@@ -17,6 +17,7 @@
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/distributed_mutex.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
namespace lru_cache {
|
||||
@@ -66,7 +67,7 @@ struct LRUHandle {
|
||||
};
|
||||
LRUHandle* next;
|
||||
LRUHandle* prev;
|
||||
size_t charge; // TODO(opt): Only allow uint32_t?
|
||||
size_t total_charge; // TODO(opt): Only allow uint32_t?
|
||||
size_t key_length;
|
||||
// The hash of key(). Used for fast sharding and comparisons.
|
||||
uint32_t hash;
|
||||
@@ -209,19 +210,32 @@ struct LRUHandle {
|
||||
delete[] reinterpret_cast<char*>(this);
|
||||
}
|
||||
|
||||
// Calculate the memory usage by metadata.
|
||||
inline size_t CalcTotalCharge(
|
||||
CacheMetadataChargePolicy metadata_charge_policy) {
|
||||
size_t meta_charge = 0;
|
||||
if (metadata_charge_policy == kFullChargeCacheMetadata) {
|
||||
inline size_t CalcuMetaCharge(
|
||||
CacheMetadataChargePolicy metadata_charge_policy) const {
|
||||
if (metadata_charge_policy != kFullChargeCacheMetadata) {
|
||||
return 0;
|
||||
} else {
|
||||
#ifdef ROCKSDB_MALLOC_USABLE_SIZE
|
||||
meta_charge += malloc_usable_size(static_cast<void*>(this));
|
||||
return malloc_usable_size(
|
||||
const_cast<void*>(static_cast<const void*>(this)));
|
||||
#else
|
||||
// This is the size that is used when a new handle is created.
|
||||
meta_charge += sizeof(LRUHandle) - 1 + key_length;
|
||||
return sizeof(LRUHandle) - 1 + key_length;
|
||||
#endif
|
||||
}
|
||||
return charge + meta_charge;
|
||||
}
|
||||
|
||||
// Calculate the memory usage by metadata.
|
||||
inline void CalcTotalCharge(
|
||||
size_t charge, CacheMetadataChargePolicy metadata_charge_policy) {
|
||||
total_charge = charge + CalcuMetaCharge(metadata_charge_policy);
|
||||
}
|
||||
|
||||
inline size_t GetCharge(
|
||||
CacheMetadataChargePolicy metadata_charge_policy) const {
|
||||
size_t meta_charge = CalcuMetaCharge(metadata_charge_policy);
|
||||
assert(total_charge >= meta_charge);
|
||||
return total_charge - meta_charge;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -440,7 +454,7 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
// mutex_ protects the following state.
|
||||
// We don't count mutex_ as the cache's internal state so semantically we
|
||||
// don't mind mutex_ invoking the non-const actions.
|
||||
mutable port::Mutex mutex_;
|
||||
mutable DMutex mutex_;
|
||||
|
||||
std::shared_ptr<SecondaryCache> secondary_cache_;
|
||||
};
|
||||
|
||||
Vendored
+48
@@ -9,6 +9,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "cache/cache_key.h"
|
||||
#include "cache/fast_lru_cache.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "file/sst_file_manager_impl.h"
|
||||
#include "port/port.h"
|
||||
@@ -205,6 +206,53 @@ TEST_F(LRUCacheTest, EntriesWithPriority) {
|
||||
ValidateLRUList({"e", "f", "g", "Z", "d"}, 2);
|
||||
}
|
||||
|
||||
// TODO(guido) Consolidate the following FastLRUCache tests with
|
||||
// that of LRUCache.
|
||||
class FastLRUCacheTest : public testing::Test {
|
||||
public:
|
||||
FastLRUCacheTest() {}
|
||||
~FastLRUCacheTest() override { DeleteCache(); }
|
||||
|
||||
void DeleteCache() {
|
||||
if (cache_ != nullptr) {
|
||||
cache_->~LRUCacheShard();
|
||||
port::cacheline_aligned_free(cache_);
|
||||
cache_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void NewCache(size_t capacity) {
|
||||
DeleteCache();
|
||||
cache_ = reinterpret_cast<fast_lru_cache::LRUCacheShard*>(
|
||||
port::cacheline_aligned_alloc(sizeof(fast_lru_cache::LRUCacheShard)));
|
||||
new (cache_) fast_lru_cache::LRUCacheShard(
|
||||
capacity, 1 /*estimated_value_size*/, false /*strict_capacity_limit*/,
|
||||
kDontChargeCacheMetadata);
|
||||
}
|
||||
|
||||
Status Insert(const std::string& key) {
|
||||
return cache_->Insert(key, 0 /*hash*/, nullptr /*value*/, 1 /*charge*/,
|
||||
nullptr /*deleter*/, nullptr /*handle*/,
|
||||
Cache::Priority::LOW);
|
||||
}
|
||||
|
||||
Status Insert(char key, size_t len) { return Insert(std::string(len, key)); }
|
||||
|
||||
private:
|
||||
fast_lru_cache::LRUCacheShard* cache_ = nullptr;
|
||||
};
|
||||
|
||||
TEST_F(FastLRUCacheTest, ValidateKeySize) {
|
||||
NewCache(3);
|
||||
EXPECT_OK(Insert('a', 16));
|
||||
EXPECT_NOK(Insert('b', 15));
|
||||
EXPECT_OK(Insert('b', 16));
|
||||
EXPECT_NOK(Insert('c', 17));
|
||||
EXPECT_NOK(Insert('d', 1000));
|
||||
EXPECT_NOK(Insert('e', 11));
|
||||
EXPECT_NOK(Insert('f', 0));
|
||||
}
|
||||
|
||||
class TestSecondaryCache : public SecondaryCache {
|
||||
public:
|
||||
// Specifies what action to take on a lookup for a particular key
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/blob/blob_source.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
|
||||
#include "db/blob/blob_file_reader.h"
|
||||
#include "options/cf_options.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
BlobSource::BlobSource(const ImmutableOptions* immutable_options,
|
||||
const std::string& db_id,
|
||||
const std::string& db_session_id,
|
||||
BlobFileCache* blob_file_cache)
|
||||
: db_id_(db_id),
|
||||
db_session_id_(db_session_id),
|
||||
statistics_(immutable_options->statistics.get()),
|
||||
blob_file_cache_(blob_file_cache),
|
||||
blob_cache_(immutable_options->blob_cache) {}
|
||||
|
||||
BlobSource::~BlobSource() = default;
|
||||
|
||||
Status BlobSource::GetBlobFromCache(const Slice& cache_key,
|
||||
CachableEntry<std::string>* blob) const {
|
||||
assert(blob);
|
||||
assert(blob->IsEmpty());
|
||||
assert(blob_cache_);
|
||||
assert(!cache_key.empty());
|
||||
|
||||
Cache::Handle* cache_handle = nullptr;
|
||||
cache_handle = GetEntryFromCache(cache_key);
|
||||
if (cache_handle != nullptr) {
|
||||
blob->SetCachedValue(
|
||||
static_cast<std::string*>(blob_cache_->Value(cache_handle)),
|
||||
blob_cache_.get(), cache_handle);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
assert(blob->IsEmpty());
|
||||
|
||||
return Status::NotFound("Blob not found in cache");
|
||||
}
|
||||
|
||||
Status BlobSource::PutBlobIntoCache(const Slice& cache_key,
|
||||
CachableEntry<std::string>* cached_blob,
|
||||
PinnableSlice* blob) const {
|
||||
assert(blob);
|
||||
assert(!cache_key.empty());
|
||||
assert(blob_cache_);
|
||||
|
||||
Status s;
|
||||
const Cache::Priority priority = Cache::Priority::LOW;
|
||||
|
||||
// Objects to be put into the cache have to be heap-allocated and
|
||||
// self-contained, i.e. own their contents. The Cache has to be able to take
|
||||
// unique ownership of them. Therefore, we copy the blob into a string
|
||||
// directly, and insert that into the cache.
|
||||
std::string* buf = new std::string();
|
||||
buf->assign(blob->data(), blob->size());
|
||||
|
||||
// TODO: support custom allocators and provide a better estimated memory
|
||||
// usage using malloc_usable_size.
|
||||
Cache::Handle* cache_handle = nullptr;
|
||||
s = InsertEntryIntoCache(cache_key, buf, buf->size(), &cache_handle,
|
||||
priority);
|
||||
if (s.ok()) {
|
||||
assert(cache_handle != nullptr);
|
||||
cached_blob->SetCachedValue(buf, blob_cache_.get(), cache_handle);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Status BlobSource::GetBlob(const ReadOptions& read_options,
|
||||
const Slice& user_key, uint64_t file_number,
|
||||
uint64_t offset, uint64_t file_size,
|
||||
uint64_t value_size,
|
||||
CompressionType compression_type,
|
||||
FilePrefetchBuffer* prefetch_buffer,
|
||||
PinnableSlice* value, uint64_t* bytes_read) {
|
||||
assert(value);
|
||||
|
||||
Status s;
|
||||
|
||||
const CacheKey cache_key = GetCacheKey(file_number, file_size, offset);
|
||||
|
||||
CachableEntry<std::string> blob_entry;
|
||||
|
||||
// First, try to get the blob from the cache
|
||||
//
|
||||
// If blob cache is enabled, we'll try to read from it.
|
||||
if (blob_cache_) {
|
||||
Slice key = cache_key.AsSlice();
|
||||
s = GetBlobFromCache(key, &blob_entry);
|
||||
if (s.ok() && blob_entry.GetValue()) {
|
||||
assert(blob_entry.GetValue()->size() == value_size);
|
||||
if (bytes_read) {
|
||||
*bytes_read = value_size;
|
||||
}
|
||||
value->PinSelf(*blob_entry.GetValue());
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
assert(blob_entry.IsEmpty());
|
||||
|
||||
const bool no_io = read_options.read_tier == kBlockCacheTier;
|
||||
if (no_io) {
|
||||
return Status::Incomplete("Cannot read blob(s): no disk I/O allowed");
|
||||
}
|
||||
|
||||
// Can't find the blob from the cache. Since I/O is allowed, read from the
|
||||
// file.
|
||||
{
|
||||
CacheHandleGuard<BlobFileReader> blob_file_reader;
|
||||
s = blob_file_cache_->GetBlobFileReader(file_number, &blob_file_reader);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
assert(blob_file_reader.GetValue());
|
||||
|
||||
if (compression_type != blob_file_reader.GetValue()->GetCompressionType()) {
|
||||
return Status::Corruption("Compression type mismatch when reading blob");
|
||||
}
|
||||
|
||||
s = blob_file_reader.GetValue()->GetBlob(
|
||||
read_options, user_key, offset, value_size, compression_type,
|
||||
prefetch_buffer, value, bytes_read);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
if (blob_cache_ && read_options.fill_cache) {
|
||||
// If filling cache is allowed and a cache is configured, try to put the
|
||||
// blob to the cache.
|
||||
Slice key = cache_key.AsSlice();
|
||||
s = PutBlobIntoCache(key, &blob_entry, value);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
assert(s.ok());
|
||||
return s;
|
||||
}
|
||||
|
||||
bool BlobSource::TEST_BlobInCache(uint64_t file_number, uint64_t file_size,
|
||||
uint64_t offset) const {
|
||||
const CacheKey cache_key = GetCacheKey(file_number, file_size, offset);
|
||||
const Slice key = cache_key.AsSlice();
|
||||
|
||||
CachableEntry<std::string> blob_entry;
|
||||
const Status s = GetBlobFromCache(key, &blob_entry);
|
||||
|
||||
if (s.ok() && blob_entry.GetValue() != nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -0,0 +1,88 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
#include "cache/cache_helpers.h"
|
||||
#include "cache/cache_key.h"
|
||||
#include "db/blob/blob_file_cache.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
#include "table/block_based/cachable_entry.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
struct ImmutableOptions;
|
||||
class Status;
|
||||
class FilePrefetchBuffer;
|
||||
class Slice;
|
||||
|
||||
// BlobSource is a class that provides universal access to blobs, regardless of
|
||||
// whether they are in the blob cache, secondary cache, or (remote) storage.
|
||||
// Depending on user settings, it always fetch blobs from multi-tier cache and
|
||||
// storage with minimal cost.
|
||||
class BlobSource {
|
||||
public:
|
||||
BlobSource(const ImmutableOptions* immutable_options,
|
||||
const std::string& db_id, const std::string& db_session_id,
|
||||
BlobFileCache* blob_file_cache);
|
||||
|
||||
BlobSource(const BlobSource&) = delete;
|
||||
BlobSource& operator=(const BlobSource&) = delete;
|
||||
|
||||
~BlobSource();
|
||||
|
||||
Status GetBlob(const ReadOptions& read_options, const Slice& user_key,
|
||||
uint64_t file_number, uint64_t offset, uint64_t file_size,
|
||||
uint64_t value_size, CompressionType compression_type,
|
||||
FilePrefetchBuffer* prefetch_buffer, PinnableSlice* value,
|
||||
uint64_t* bytes_read);
|
||||
|
||||
bool TEST_BlobInCache(uint64_t file_number, uint64_t file_size,
|
||||
uint64_t offset) const;
|
||||
|
||||
private:
|
||||
Status GetBlobFromCache(const Slice& cache_key,
|
||||
CachableEntry<std::string>* blob) const;
|
||||
|
||||
Status PutBlobIntoCache(const Slice& cache_key,
|
||||
CachableEntry<std::string>* cached_blob,
|
||||
PinnableSlice* blob) const;
|
||||
|
||||
inline CacheKey GetCacheKey(uint64_t file_number, uint64_t file_size,
|
||||
uint64_t offset) const {
|
||||
OffsetableCacheKey base_cache_key(db_id_, db_session_id_, file_number,
|
||||
file_size);
|
||||
return base_cache_key.WithOffset(offset);
|
||||
}
|
||||
|
||||
inline Cache::Handle* GetEntryFromCache(const Slice& key) const {
|
||||
return blob_cache_->Lookup(key, statistics_);
|
||||
}
|
||||
|
||||
inline Status InsertEntryIntoCache(const Slice& key, std::string* value,
|
||||
size_t charge,
|
||||
Cache::Handle** cache_handle,
|
||||
Cache::Priority priority) const {
|
||||
return blob_cache_->Insert(key, value, charge,
|
||||
&DeleteCacheEntry<std::string>, cache_handle,
|
||||
priority);
|
||||
}
|
||||
|
||||
const std::string db_id_;
|
||||
const std::string db_session_id_;
|
||||
|
||||
Statistics* statistics_;
|
||||
|
||||
// A cache to store blob file reader.
|
||||
BlobFileCache* blob_file_cache_;
|
||||
|
||||
// A cache to store uncompressed blobs.
|
||||
std::shared_ptr<Cache> blob_cache_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -0,0 +1,298 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/blob/blob_source.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "db/blob/blob_file_cache.h"
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "db/blob/blob_log_writer.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "file/filename.h"
|
||||
#include "file/read_write_util.h"
|
||||
#include "options/cf_options.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "util/compression.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
namespace {
|
||||
|
||||
// Creates a test blob file with `num` blobs in it.
|
||||
void WriteBlobFile(const ImmutableOptions& immutable_options,
|
||||
uint32_t column_family_id, bool has_ttl,
|
||||
const ExpirationRange& expiration_range_header,
|
||||
const ExpirationRange& expiration_range_footer,
|
||||
uint64_t blob_file_number, const std::vector<Slice>& keys,
|
||||
const std::vector<Slice>& blobs, CompressionType compression,
|
||||
std::vector<uint64_t>& blob_offsets,
|
||||
std::vector<uint64_t>& blob_sizes) {
|
||||
assert(!immutable_options.cf_paths.empty());
|
||||
size_t num = keys.size();
|
||||
assert(num == blobs.size());
|
||||
assert(num == blob_offsets.size());
|
||||
assert(num == blob_sizes.size());
|
||||
|
||||
const std::string blob_file_path =
|
||||
BlobFileName(immutable_options.cf_paths.front().path, blob_file_number);
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
ASSERT_OK(NewWritableFile(immutable_options.fs.get(), blob_file_path, &file,
|
||||
FileOptions()));
|
||||
|
||||
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
|
||||
std::move(file), blob_file_path, FileOptions(), immutable_options.clock));
|
||||
|
||||
constexpr Statistics* statistics = nullptr;
|
||||
constexpr bool use_fsync = false;
|
||||
constexpr bool do_flush = false;
|
||||
|
||||
BlobLogWriter blob_log_writer(std::move(file_writer), immutable_options.clock,
|
||||
statistics, blob_file_number, use_fsync,
|
||||
do_flush);
|
||||
|
||||
BlobLogHeader header(column_family_id, compression, has_ttl,
|
||||
expiration_range_header);
|
||||
|
||||
ASSERT_OK(blob_log_writer.WriteHeader(header));
|
||||
|
||||
std::vector<std::string> compressed_blobs(num);
|
||||
std::vector<Slice> blobs_to_write(num);
|
||||
if (kNoCompression == compression) {
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
blobs_to_write[i] = blobs[i];
|
||||
blob_sizes[i] = blobs[i].size();
|
||||
}
|
||||
} else {
|
||||
CompressionOptions opts;
|
||||
CompressionContext context(compression);
|
||||
constexpr uint64_t sample_for_compression = 0;
|
||||
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
|
||||
compression, sample_for_compression);
|
||||
|
||||
constexpr uint32_t compression_format_version = 2;
|
||||
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
ASSERT_TRUE(CompressData(blobs[i], info, compression_format_version,
|
||||
&compressed_blobs[i]));
|
||||
blobs_to_write[i] = compressed_blobs[i];
|
||||
blob_sizes[i] = compressed_blobs[i].size();
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
uint64_t key_offset = 0;
|
||||
ASSERT_OK(blob_log_writer.AddRecord(keys[i], blobs_to_write[i], &key_offset,
|
||||
&blob_offsets[i]));
|
||||
}
|
||||
|
||||
BlobLogFooter footer;
|
||||
footer.blob_count = num;
|
||||
footer.expiration_range = expiration_range_footer;
|
||||
|
||||
std::string checksum_method;
|
||||
std::string checksum_value;
|
||||
ASSERT_OK(
|
||||
blob_log_writer.AppendFooter(footer, &checksum_method, &checksum_value));
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
class BlobSourceTest : public DBTestBase {
|
||||
protected:
|
||||
public:
|
||||
explicit BlobSourceTest()
|
||||
: DBTestBase("blob_source_test", /*env_do_fsync=*/true) {}
|
||||
};
|
||||
|
||||
TEST_F(BlobSourceTest, GetBlobsFromCache) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(env_, "BlobSourceTest_GetBlobsFromCache"), 0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
LRUCacheOptions co;
|
||||
co.capacity = 2048;
|
||||
co.num_shard_bits = 2;
|
||||
co.metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
options.blob_cache = NewLRUCache(co);
|
||||
options.lowest_used_cache_tier = CacheTier::kVolatileTier;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
std::string db_id;
|
||||
ASSERT_OK(db_->GetDbIdentity(db_id));
|
||||
|
||||
std::string db_session_id;
|
||||
ASSERT_OK(db_->GetDbSessionId(db_session_id));
|
||||
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
constexpr ExpirationRange expiration_range;
|
||||
constexpr uint64_t blob_file_number = 1;
|
||||
constexpr size_t num_blobs = 16;
|
||||
|
||||
std::vector<std::string> key_strs;
|
||||
std::vector<std::string> blob_strs;
|
||||
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
key_strs.push_back("key" + std::to_string(i));
|
||||
blob_strs.push_back("blob" + std::to_string(i));
|
||||
}
|
||||
|
||||
std::vector<Slice> keys;
|
||||
std::vector<Slice> blobs;
|
||||
|
||||
uint64_t file_size = BlobLogHeader::kSize;
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
keys.push_back({key_strs[i]});
|
||||
blobs.push_back({blob_strs[i]});
|
||||
file_size += BlobLogRecord::kHeaderSize + keys[i].size() + blobs[i].size();
|
||||
}
|
||||
file_size += BlobLogFooter::kSize;
|
||||
|
||||
std::vector<uint64_t> blob_offsets(keys.size());
|
||||
std::vector<uint64_t> blob_sizes(keys.size());
|
||||
|
||||
WriteBlobFile(immutable_options, column_family_id, has_ttl, expiration_range,
|
||||
expiration_range, blob_file_number, keys, blobs, kNoCompression,
|
||||
blob_offsets, blob_sizes);
|
||||
|
||||
constexpr size_t capacity = 1024;
|
||||
std::shared_ptr<Cache> backing_cache =
|
||||
NewLRUCache(capacity); // Blob file cache
|
||||
|
||||
FileOptions file_options;
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
std::unique_ptr<BlobFileCache> blob_file_cache(new BlobFileCache(
|
||||
backing_cache.get(), &immutable_options, &file_options, column_family_id,
|
||||
blob_file_read_hist, nullptr /*IOTracer*/));
|
||||
|
||||
BlobSource blob_source(&immutable_options, db_id, db_session_id,
|
||||
blob_file_cache.get());
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = true;
|
||||
|
||||
constexpr FilePrefetchBuffer* prefetch_buffer = nullptr;
|
||||
|
||||
{
|
||||
// GetBlob
|
||||
std::vector<PinnableSlice> values(keys.size());
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
read_options.fill_cache = false;
|
||||
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
ASSERT_FALSE(blob_source.TEST_BlobInCache(blob_file_number, file_size,
|
||||
blob_offsets[i]));
|
||||
|
||||
ASSERT_OK(blob_source.GetBlob(read_options, keys[i], blob_file_number,
|
||||
blob_offsets[i], file_size, blob_sizes[i],
|
||||
kNoCompression, prefetch_buffer, &values[i],
|
||||
&bytes_read));
|
||||
ASSERT_EQ(values[i], blobs[i]);
|
||||
ASSERT_EQ(bytes_read,
|
||||
blob_sizes[i] + keys[i].size() + BlobLogRecord::kHeaderSize);
|
||||
|
||||
ASSERT_FALSE(blob_source.TEST_BlobInCache(blob_file_number, file_size,
|
||||
blob_offsets[i]));
|
||||
}
|
||||
|
||||
read_options.fill_cache = true;
|
||||
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
ASSERT_FALSE(blob_source.TEST_BlobInCache(blob_file_number, file_size,
|
||||
blob_offsets[i]));
|
||||
|
||||
ASSERT_OK(blob_source.GetBlob(read_options, keys[i], blob_file_number,
|
||||
blob_offsets[i], file_size, blob_sizes[i],
|
||||
kNoCompression, prefetch_buffer, &values[i],
|
||||
&bytes_read));
|
||||
ASSERT_EQ(values[i], blobs[i]);
|
||||
ASSERT_EQ(bytes_read,
|
||||
blob_sizes[i] + keys[i].size() + BlobLogRecord::kHeaderSize);
|
||||
|
||||
ASSERT_TRUE(blob_source.TEST_BlobInCache(blob_file_number, file_size,
|
||||
blob_offsets[i]));
|
||||
}
|
||||
|
||||
read_options.fill_cache = true;
|
||||
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
ASSERT_TRUE(blob_source.TEST_BlobInCache(blob_file_number, file_size,
|
||||
blob_offsets[i]));
|
||||
|
||||
ASSERT_OK(blob_source.GetBlob(read_options, keys[i], blob_file_number,
|
||||
blob_offsets[i], file_size, blob_sizes[i],
|
||||
kNoCompression, prefetch_buffer, &values[i],
|
||||
&bytes_read));
|
||||
ASSERT_EQ(values[i], blobs[i]);
|
||||
ASSERT_EQ(bytes_read, blob_sizes[i]);
|
||||
|
||||
ASSERT_TRUE(blob_source.TEST_BlobInCache(blob_file_number, file_size,
|
||||
blob_offsets[i]));
|
||||
}
|
||||
|
||||
// Cache-only GetBlob
|
||||
read_options.read_tier = ReadTier::kBlockCacheTier;
|
||||
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
ASSERT_TRUE(blob_source.TEST_BlobInCache(blob_file_number, file_size,
|
||||
blob_offsets[i]));
|
||||
|
||||
ASSERT_OK(blob_source.GetBlob(read_options, keys[i], blob_file_number,
|
||||
blob_offsets[i], file_size, blob_sizes[i],
|
||||
kNoCompression, prefetch_buffer, &values[i],
|
||||
&bytes_read));
|
||||
ASSERT_EQ(values[i], blobs[i]);
|
||||
ASSERT_EQ(bytes_read, blob_sizes[i]);
|
||||
|
||||
ASSERT_TRUE(blob_source.TEST_BlobInCache(blob_file_number, file_size,
|
||||
blob_offsets[i]));
|
||||
}
|
||||
}
|
||||
|
||||
options.blob_cache->EraseUnRefEntries();
|
||||
|
||||
{
|
||||
// Cache-only GetBlob
|
||||
std::vector<PinnableSlice> values(keys.size());
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
read_options.read_tier = ReadTier::kBlockCacheTier;
|
||||
read_options.fill_cache = true;
|
||||
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
ASSERT_FALSE(blob_source.TEST_BlobInCache(blob_file_number, file_size,
|
||||
blob_offsets[i]));
|
||||
|
||||
ASSERT_NOK(blob_source.GetBlob(read_options, keys[i], blob_file_number,
|
||||
blob_offsets[i], file_size, blob_sizes[i],
|
||||
kNoCompression, prefetch_buffer,
|
||||
&values[i], &bytes_read));
|
||||
ASSERT_TRUE(values[i].empty());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
|
||||
ASSERT_FALSE(blob_source.TEST_BlobInCache(blob_file_number, file_size,
|
||||
blob_offsets[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -243,6 +243,84 @@ TEST_F(DBBlobCompactionTest, FilterByKeyLength) {
|
||||
Close();
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBBlobCompactionTest, BlobCompactWithStartingLevel) {
|
||||
Options options = GetDefaultOptions();
|
||||
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 1000;
|
||||
options.blob_file_starting_level = 5;
|
||||
options.create_if_missing = true;
|
||||
|
||||
// Open DB with fixed-prefix sst-partitioner so that compaction will cut
|
||||
// new table file when encountering a new key whose 1-byte prefix changes.
|
||||
constexpr size_t key_len = 1;
|
||||
options.sst_partitioner_factory =
|
||||
NewSstPartitionerFixedPrefixFactory(key_len);
|
||||
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
constexpr size_t blob_size = 3000;
|
||||
|
||||
constexpr char first_key[] = "a";
|
||||
const std::string first_blob(blob_size, 'a');
|
||||
ASSERT_OK(Put(first_key, first_blob));
|
||||
|
||||
constexpr char second_key[] = "b";
|
||||
const std::string second_blob(2 * blob_size, 'b');
|
||||
ASSERT_OK(Put(second_key, second_blob));
|
||||
|
||||
constexpr char third_key[] = "d";
|
||||
const std::string third_blob(blob_size, 'd');
|
||||
ASSERT_OK(Put(third_key, third_blob));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
constexpr char fourth_key[] = "c";
|
||||
const std::string fourth_blob(blob_size, 'c');
|
||||
ASSERT_OK(Put(fourth_key, fourth_blob));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_EQ(0, GetBlobFileNumbers().size());
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(/*level=*/0));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(/*level=*/1));
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr));
|
||||
|
||||
// No blob file should be created since blob_file_starting_level is 5.
|
||||
ASSERT_EQ(0, GetBlobFileNumbers().size());
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(/*level=*/0));
|
||||
ASSERT_EQ(4, NumTableFilesAtLevel(/*level=*/1));
|
||||
|
||||
{
|
||||
options.blob_file_starting_level = 1;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put(first_key, first_blob));
|
||||
ASSERT_OK(Put(second_key, second_blob));
|
||||
ASSERT_OK(Put(third_key, third_blob));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put(fourth_key, fourth_blob));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_EQ(0, GetBlobFileNumbers().size());
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(/*level=*/0));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(/*level=*/1));
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr));
|
||||
// The compaction's output level equals to blob_file_starting_level.
|
||||
ASSERT_EQ(1, GetBlobFileNumbers().size());
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(/*level=*/0));
|
||||
ASSERT_EQ(4, NumTableFilesAtLevel(/*level=*/1));
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_F(DBBlobCompactionTest, BlindWriteFilter) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
|
||||
+7
-5
@@ -181,7 +181,10 @@ Status BuildTable(
|
||||
snapshots.empty() ? 0 : snapshots.back(), snapshot_checker);
|
||||
|
||||
std::unique_ptr<BlobFileBuilder> blob_file_builder(
|
||||
(mutable_cf_options.enable_blob_files && blob_file_additions)
|
||||
(mutable_cf_options.enable_blob_files &&
|
||||
tboptions.level_at_creation >=
|
||||
mutable_cf_options.blob_file_starting_level &&
|
||||
blob_file_additions)
|
||||
? new BlobFileBuilder(
|
||||
versions, fs, &ioptions, &mutable_cf_options, &file_options,
|
||||
job_id, tboptions.column_family_id,
|
||||
@@ -190,6 +193,7 @@ Status BuildTable(
|
||||
&blob_file_paths, blob_file_additions)
|
||||
: nullptr);
|
||||
|
||||
const std::atomic<bool> kManualCompactionCanceledFalse{false};
|
||||
CompactionIterator c_iter(
|
||||
iter, tboptions.internal_comparator.user_comparator(), &merge,
|
||||
kMaxSequenceNumber, &snapshots, earliest_write_conflict_snapshot,
|
||||
@@ -198,11 +202,9 @@ Status BuildTable(
|
||||
true /* internal key corruption is not ok */, range_del_agg.get(),
|
||||
blob_file_builder.get(), ioptions.allow_data_in_errors,
|
||||
ioptions.enforce_single_del_contracts,
|
||||
/*manual_compaction_canceled=*/kManualCompactionCanceledFalse,
|
||||
/*compaction=*/nullptr, compaction_filter.get(),
|
||||
/*shutting_down=*/nullptr,
|
||||
/*manual_compaction_paused=*/nullptr,
|
||||
/*manual_compaction_canceled=*/nullptr, db_options.info_log,
|
||||
full_history_ts_low);
|
||||
/*shutting_down=*/nullptr, db_options.info_log, full_history_ts_low);
|
||||
|
||||
c_iter.SeekToFirst();
|
||||
for (; c_iter.Valid(); c_iter.Next()) {
|
||||
|
||||
+290
-30
@@ -33,11 +33,6 @@ int geteuid() {
|
||||
return result;
|
||||
}
|
||||
|
||||
// VS < 2015
|
||||
#if defined(_MSC_VER) && (_MSC_VER < 1900)
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
const char* phase = "";
|
||||
@@ -169,6 +164,16 @@ static void CheckPinGetCF(rocksdb_t* db, const rocksdb_readoptions_t* options,
|
||||
rocksdb_pinnableslice_destroy(p);
|
||||
}
|
||||
|
||||
static void CheckMultiGetValues(size_t num_keys, char** values,
|
||||
size_t* values_sizes, char** errs,
|
||||
const char** expected) {
|
||||
for (size_t i = 0; i < num_keys; i++) {
|
||||
CheckNoError(errs[i]);
|
||||
CheckEqual(expected[i], values[i], values_sizes[i]);
|
||||
Free(&values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void CheckIter(rocksdb_iterator_t* iter,
|
||||
const char* key, const char* val) {
|
||||
size_t len;
|
||||
@@ -283,6 +288,16 @@ static rocksdb_t* CheckCompaction(rocksdb_t* db, rocksdb_options_t* options,
|
||||
CheckNoError(err);
|
||||
CheckGet(db, roptions, "baz", "bazvalue");
|
||||
|
||||
// Disable compaction
|
||||
rocksdb_disable_manual_compaction(db);
|
||||
rocksdb_compact_range(db, NULL, 0, NULL, 0);
|
||||
// should not filter anything when disabled
|
||||
CheckGet(db, roptions, "foo", "foovalue");
|
||||
CheckGet(db, roptions, "bar", "barvalue");
|
||||
CheckGet(db, roptions, "baz", "bazvalue");
|
||||
// Reenable compaction
|
||||
rocksdb_enable_manual_compaction(db);
|
||||
|
||||
// Force compaction
|
||||
rocksdb_compact_range(db, NULL, 0, NULL, 0);
|
||||
// should have filtered bar, but not foo
|
||||
@@ -366,18 +381,49 @@ static void CheckTxnGetCF(rocksdb_transaction_t* txn,
|
||||
Free(&val);
|
||||
}
|
||||
|
||||
static void CheckTxnPinGet(rocksdb_transaction_t* txn,
|
||||
const rocksdb_readoptions_t* options,
|
||||
const char* key, const char* expected) {
|
||||
rocksdb_pinnableslice_t* p = NULL;
|
||||
const char* val = NULL;
|
||||
char* err = NULL;
|
||||
size_t val_len;
|
||||
p = rocksdb_transaction_get_pinned(txn, options, key, strlen(key), &err);
|
||||
CheckNoError(err);
|
||||
val = rocksdb_pinnableslice_value(p, &val_len);
|
||||
CheckEqual(expected, val, val_len);
|
||||
rocksdb_pinnableslice_destroy(p);
|
||||
}
|
||||
|
||||
static void CheckTxnPinGetCF(rocksdb_transaction_t* txn,
|
||||
const rocksdb_readoptions_t* options,
|
||||
rocksdb_column_family_handle_t* column_family,
|
||||
const char* key, const char* expected) {
|
||||
rocksdb_pinnableslice_t* p = NULL;
|
||||
const char* val = NULL;
|
||||
char* err = NULL;
|
||||
size_t val_len;
|
||||
p = rocksdb_transaction_get_pinned_cf(txn, options, column_family, key,
|
||||
strlen(key), &err);
|
||||
CheckNoError(err);
|
||||
val = rocksdb_pinnableslice_value(p, &val_len);
|
||||
CheckEqual(expected, val, val_len);
|
||||
rocksdb_pinnableslice_destroy(p);
|
||||
}
|
||||
|
||||
static void CheckTxnDBGet(
|
||||
rocksdb_transactiondb_t* txn_db,
|
||||
const rocksdb_readoptions_t* options,
|
||||
const char* key,
|
||||
const char* expected) {
|
||||
char* err = NULL;
|
||||
size_t val_len;
|
||||
char* val;
|
||||
val = rocksdb_transactiondb_get(txn_db, options, key, strlen(key), &val_len, &err);
|
||||
CheckNoError(err);
|
||||
CheckEqual(expected, val, val_len);
|
||||
Free(&val);
|
||||
char* err = NULL;
|
||||
size_t val_len;
|
||||
char* val;
|
||||
val = rocksdb_transactiondb_get(txn_db, options, key, strlen(key), &val_len,
|
||||
&err);
|
||||
CheckNoError(err);
|
||||
CheckEqual(expected, val, val_len);
|
||||
Free(&val);
|
||||
}
|
||||
|
||||
static void CheckTxnDBGetCF(rocksdb_transactiondb_t* txn_db,
|
||||
@@ -394,6 +440,36 @@ static void CheckTxnDBGetCF(rocksdb_transactiondb_t* txn_db,
|
||||
Free(&val);
|
||||
}
|
||||
|
||||
static void CheckTxnDBPinGet(rocksdb_transactiondb_t* txn_db,
|
||||
const rocksdb_readoptions_t* options,
|
||||
const char* key, const char* expected) {
|
||||
rocksdb_pinnableslice_t* p = NULL;
|
||||
const char* val = NULL;
|
||||
char* err = NULL;
|
||||
size_t val_len;
|
||||
p = rocksdb_transactiondb_get_pinned(txn_db, options, key, strlen(key), &err);
|
||||
CheckNoError(err);
|
||||
val = rocksdb_pinnableslice_value(p, &val_len);
|
||||
CheckEqual(expected, val, val_len);
|
||||
rocksdb_pinnableslice_destroy(p);
|
||||
}
|
||||
|
||||
static void CheckTxnDBPinGetCF(rocksdb_transactiondb_t* txn_db,
|
||||
const rocksdb_readoptions_t* options,
|
||||
rocksdb_column_family_handle_t* column_family,
|
||||
const char* key, const char* expected) {
|
||||
rocksdb_pinnableslice_t* p = NULL;
|
||||
const char* val = NULL;
|
||||
char* err = NULL;
|
||||
size_t val_len;
|
||||
p = rocksdb_transactiondb_get_pinned_cf(txn_db, options, column_family, key,
|
||||
strlen(key), &err);
|
||||
CheckNoError(err);
|
||||
val = rocksdb_pinnableslice_value(p, &val_len);
|
||||
CheckEqual(expected, val, val_len);
|
||||
rocksdb_pinnableslice_destroy(p);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
@@ -914,24 +990,9 @@ int main(int argc, char** argv) {
|
||||
char* vals[3];
|
||||
size_t vals_sizes[3];
|
||||
char* errs[3];
|
||||
const char* expected[3] = {"c", "hello", NULL};
|
||||
rocksdb_multi_get(db, roptions, 3, keys, keys_sizes, vals, vals_sizes, errs);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < 3; i++) {
|
||||
CheckEqual(NULL, errs[i], 0);
|
||||
switch (i) {
|
||||
case 0:
|
||||
CheckEqual("c", vals[i], vals_sizes[i]);
|
||||
break;
|
||||
case 1:
|
||||
CheckEqual("hello", vals[i], vals_sizes[i]);
|
||||
break;
|
||||
case 2:
|
||||
CheckEqual(NULL, vals[i], vals_sizes[i]);
|
||||
break;
|
||||
}
|
||||
Free(&vals[i]);
|
||||
}
|
||||
CheckMultiGetValues(3, vals, vals_sizes, errs, expected);
|
||||
}
|
||||
|
||||
StartPhase("pin_get");
|
||||
@@ -1796,6 +1857,9 @@ int main(int argc, char** argv) {
|
||||
CheckCondition(262144 ==
|
||||
rocksdb_options_get_blob_compaction_readahead_size(o));
|
||||
|
||||
rocksdb_options_set_blob_file_starting_level(o, 5);
|
||||
CheckCondition(5 == rocksdb_options_get_blob_file_starting_level(o));
|
||||
|
||||
// Create a copy that should be equal to the original.
|
||||
rocksdb_options_t* copy;
|
||||
copy = rocksdb_options_create_copy(o);
|
||||
@@ -2623,11 +2687,13 @@ int main(int argc, char** argv) {
|
||||
rocksdb_transactiondb_put(txn_db, woptions, "foo", 3, "hello", 5, &err);
|
||||
CheckNoError(err);
|
||||
CheckTxnDBGet(txn_db, roptions, "foo", "hello");
|
||||
CheckTxnDBPinGet(txn_db, roptions, "foo", "hello");
|
||||
|
||||
// delete from outside transaction
|
||||
rocksdb_transactiondb_delete(txn_db, woptions, "foo", 3, &err);
|
||||
CheckNoError(err);
|
||||
CheckTxnDBGet(txn_db, roptions, "foo", NULL);
|
||||
CheckTxnDBPinGet(txn_db, roptions, "foo", NULL);
|
||||
|
||||
// write batch into TransactionDB
|
||||
rocksdb_writebatch_t* wb = rocksdb_writebatch_create();
|
||||
@@ -2639,24 +2705,63 @@ int main(int argc, char** argv) {
|
||||
rocksdb_transactiondb_write(txn_db, woptions, wb, &err);
|
||||
rocksdb_writebatch_destroy(wb);
|
||||
CheckTxnDBGet(txn_db, roptions, "box", "c");
|
||||
CheckTxnDBPinGet(txn_db, roptions, "box", "c");
|
||||
CheckNoError(err);
|
||||
|
||||
// multi get
|
||||
{
|
||||
const char* keys[3] = {"box", "foo", "notfound"};
|
||||
const size_t keys_sizes[3] = {3, 3, 8};
|
||||
char* vals[3];
|
||||
size_t vals_sizes[3];
|
||||
char* errs[3];
|
||||
const char* expected[3] = {"c", NULL, NULL};
|
||||
rocksdb_transactiondb_multi_get(txn_db, roptions, 3, keys, keys_sizes,
|
||||
vals, vals_sizes, errs);
|
||||
CheckMultiGetValues(3, vals, vals_sizes, errs, expected);
|
||||
}
|
||||
|
||||
// begin a transaction
|
||||
txn = rocksdb_transaction_begin(txn_db, woptions, txn_options, NULL);
|
||||
// put
|
||||
rocksdb_transaction_put(txn, "foo", 3, "hello", 5, &err);
|
||||
CheckNoError(err);
|
||||
CheckTxnGet(txn, roptions, "foo", "hello");
|
||||
CheckTxnPinGet(txn, roptions, "foo", "hello");
|
||||
{
|
||||
const char* keys[3] = {"box", "foo", "notfound"};
|
||||
const size_t keys_sizes[3] = {3, 3, 8};
|
||||
char* vals[3];
|
||||
size_t vals_sizes[3];
|
||||
char* errs[3];
|
||||
const char* expected[3] = {"c", "hello", NULL};
|
||||
rocksdb_transaction_multi_get(txn, roptions, 3, keys, keys_sizes, vals,
|
||||
vals_sizes, errs);
|
||||
CheckMultiGetValues(3, vals, vals_sizes, errs, expected);
|
||||
}
|
||||
// delete
|
||||
rocksdb_transaction_delete(txn, "foo", 3, &err);
|
||||
CheckNoError(err);
|
||||
CheckTxnGet(txn, roptions, "foo", NULL);
|
||||
CheckTxnPinGet(txn, roptions, "foo", NULL);
|
||||
|
||||
rocksdb_transaction_put(txn, "foo", 3, "hello", 5, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
// read from outside transaction, before commit
|
||||
CheckTxnDBGet(txn_db, roptions, "foo", NULL);
|
||||
CheckTxnDBPinGet(txn_db, roptions, "foo", NULL);
|
||||
{
|
||||
const char* keys[3] = {"box", "foo", "notfound"};
|
||||
const size_t keys_sizes[3] = {3, 3, 8};
|
||||
char* vals[3];
|
||||
size_t vals_sizes[3];
|
||||
char* errs[3];
|
||||
const char* expected[3] = {"c", NULL, NULL};
|
||||
rocksdb_transactiondb_multi_get(txn_db, roptions, 3, keys, keys_sizes,
|
||||
vals, vals_sizes, errs);
|
||||
CheckMultiGetValues(3, vals, vals_sizes, errs, expected);
|
||||
}
|
||||
|
||||
// commit
|
||||
rocksdb_transaction_commit(txn, &err);
|
||||
@@ -2664,6 +2769,18 @@ int main(int argc, char** argv) {
|
||||
|
||||
// read from outside transaction, after commit
|
||||
CheckTxnDBGet(txn_db, roptions, "foo", "hello");
|
||||
CheckTxnDBPinGet(txn_db, roptions, "foo", "hello");
|
||||
{
|
||||
const char* keys[3] = {"box", "foo", "notfound"};
|
||||
const size_t keys_sizes[3] = {3, 3, 8};
|
||||
char* vals[3];
|
||||
size_t vals_sizes[3];
|
||||
char* errs[3];
|
||||
const char* expected[3] = {"c", "hello", NULL};
|
||||
rocksdb_transactiondb_multi_get(txn_db, roptions, 3, keys, keys_sizes,
|
||||
vals, vals_sizes, errs);
|
||||
CheckMultiGetValues(3, vals, vals_sizes, errs, expected);
|
||||
}
|
||||
|
||||
// reuse old transaction
|
||||
txn = rocksdb_transaction_begin(txn_db, woptions, txn_options, txn);
|
||||
@@ -2677,9 +2794,11 @@ int main(int argc, char** argv) {
|
||||
CheckNoError(err);
|
||||
|
||||
CheckTxnDBGet(txn_db, roptions, "foo", "hello");
|
||||
CheckTxnDBPinGet(txn_db, roptions, "foo", "hello");
|
||||
rocksdb_readoptions_set_snapshot(roptions, NULL);
|
||||
rocksdb_transactiondb_release_snapshot(txn_db, snapshot);
|
||||
CheckTxnDBGet(txn_db, roptions, "foo", "hey");
|
||||
CheckTxnDBPinGet(txn_db, roptions, "foo", "hey");
|
||||
|
||||
// iterate
|
||||
rocksdb_transaction_put(txn, "bar", 3, "hi", 2, &err);
|
||||
@@ -2696,25 +2815,34 @@ int main(int argc, char** argv) {
|
||||
rocksdb_transaction_rollback(txn, &err);
|
||||
CheckNoError(err);
|
||||
CheckTxnDBGet(txn_db, roptions, "bar", NULL);
|
||||
CheckTxnDBPinGet(txn_db, roptions, "bar", NULL);
|
||||
|
||||
// save point
|
||||
rocksdb_transaction_put(txn, "foo1", 4, "hi1", 3, &err);
|
||||
rocksdb_transaction_set_savepoint(txn);
|
||||
CheckTxnGet(txn, roptions, "foo1", "hi1");
|
||||
CheckTxnPinGet(txn, roptions, "foo1", "hi1");
|
||||
rocksdb_transaction_put(txn, "foo2", 4, "hi2", 3, &err);
|
||||
CheckTxnGet(txn, roptions, "foo2", "hi2");
|
||||
CheckTxnPinGet(txn, roptions, "foo2", "hi2");
|
||||
|
||||
// rollback to savepoint
|
||||
rocksdb_transaction_rollback_to_savepoint(txn, &err);
|
||||
CheckNoError(err);
|
||||
CheckTxnGet(txn, roptions, "foo2", NULL);
|
||||
CheckTxnGet(txn, roptions, "foo1", "hi1");
|
||||
CheckTxnPinGet(txn, roptions, "foo2", NULL);
|
||||
CheckTxnPinGet(txn, roptions, "foo1", "hi1");
|
||||
CheckTxnDBGet(txn_db, roptions, "foo1", NULL);
|
||||
CheckTxnDBGet(txn_db, roptions, "foo2", NULL);
|
||||
CheckTxnDBPinGet(txn_db, roptions, "foo1", NULL);
|
||||
CheckTxnDBPinGet(txn_db, roptions, "foo2", NULL);
|
||||
rocksdb_transaction_commit(txn, &err);
|
||||
CheckNoError(err);
|
||||
CheckTxnDBGet(txn_db, roptions, "foo1", "hi1");
|
||||
CheckTxnDBGet(txn_db, roptions, "foo2", NULL);
|
||||
CheckTxnDBPinGet(txn_db, roptions, "foo1", "hi1");
|
||||
CheckTxnDBPinGet(txn_db, roptions, "foo2", NULL);
|
||||
|
||||
// Column families.
|
||||
rocksdb_column_family_handle_t* cfh;
|
||||
@@ -2726,14 +2854,38 @@ int main(int argc, char** argv) {
|
||||
8, &err);
|
||||
CheckNoError(err);
|
||||
CheckTxnDBGetCF(txn_db, roptions, cfh, "cf_foo", "cf_hello");
|
||||
CheckTxnDBPinGetCF(txn_db, roptions, cfh, "cf_foo", "cf_hello");
|
||||
{
|
||||
const rocksdb_column_family_handle_t* get_handles[2] = {cfh, cfh};
|
||||
const char* keys[2] = {"cf_foo", "notfound"};
|
||||
const size_t keys_sizes[2] = {6, 8};
|
||||
char* vals[2];
|
||||
size_t vals_sizes[2];
|
||||
char* errs[2];
|
||||
const char* expected[2] = {"cf_hello", NULL};
|
||||
rocksdb_transactiondb_multi_get_cf(txn_db, roptions, get_handles, 2, keys,
|
||||
keys_sizes, vals, vals_sizes, errs);
|
||||
CheckMultiGetValues(2, vals, vals_sizes, errs, expected);
|
||||
}
|
||||
|
||||
rocksdb_transactiondb_delete_cf(txn_db, woptions, cfh, "cf_foo", 6, &err);
|
||||
CheckNoError(err);
|
||||
CheckTxnDBGetCF(txn_db, roptions, cfh, "cf_foo", NULL);
|
||||
CheckTxnDBPinGetCF(txn_db, roptions, cfh, "cf_foo", NULL);
|
||||
|
||||
rocksdb_column_family_handle_destroy(cfh);
|
||||
// flush
|
||||
rocksdb_flushoptions_t* flush_options = rocksdb_flushoptions_create();
|
||||
rocksdb_flushoptions_set_wait(flush_options, 1);
|
||||
rocksdb_transactiondb_flush_wal(txn_db, 1, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_transactiondb_flush_cf(txn_db, flush_options, cfh, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_transactiondb_flush(txn_db, flush_options, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_flushoptions_destroy(flush_options);
|
||||
|
||||
// close and destroy
|
||||
rocksdb_column_family_handle_destroy(cfh);
|
||||
rocksdb_transaction_destroy(txn);
|
||||
rocksdb_transactiondb_close(txn_db);
|
||||
rocksdb_destroy_db(options, dbname, &err);
|
||||
@@ -2742,6 +2894,97 @@ int main(int argc, char** argv) {
|
||||
rocksdb_transactiondb_options_destroy(txn_db_options);
|
||||
}
|
||||
|
||||
StartPhase("two-phase commit");
|
||||
{
|
||||
// open a TransactionDB
|
||||
txn_db_options = rocksdb_transactiondb_options_create();
|
||||
txn_options = rocksdb_transaction_options_create();
|
||||
rocksdb_options_set_create_if_missing(options, 1);
|
||||
txn_db = rocksdb_transactiondb_open(options, txn_db_options, dbname, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_transaction_options_set_skip_prepare(txn_options, 0);
|
||||
txn = rocksdb_transaction_begin(txn_db, woptions, txn_options, NULL);
|
||||
rocksdb_transaction_commit(txn, &err);
|
||||
CheckCondition(err != NULL);
|
||||
Free(&err);
|
||||
err = NULL;
|
||||
rocksdb_transaction_prepare(txn, &err);
|
||||
CheckCondition(err != NULL);
|
||||
Free(&err);
|
||||
err = NULL;
|
||||
rocksdb_transaction_set_name(txn, "txn1", 4, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_transaction_prepare(txn, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_transaction_commit(txn, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_transaction_destroy(txn);
|
||||
|
||||
// prepare 2 transactions and close db.
|
||||
rocksdb_transaction_t* txn1 =
|
||||
rocksdb_transaction_begin(txn_db, woptions, txn_options, NULL);
|
||||
rocksdb_transaction_put(txn1, "bar1", 4, "1", 1, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_transaction_set_name(txn1, "txn1", 4, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_transaction_prepare(txn1, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_transaction_t* txn2 =
|
||||
rocksdb_transaction_begin(txn_db, woptions, txn_options, NULL);
|
||||
rocksdb_transaction_put(txn2, "bar2", 4, "2", 1, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_transaction_set_name(txn2, "txn2", 4, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_transaction_prepare(txn2, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_transaction_destroy(txn1);
|
||||
rocksdb_transaction_destroy(txn2);
|
||||
rocksdb_transactiondb_close(txn_db);
|
||||
rocksdb_transaction_options_destroy(txn_options);
|
||||
rocksdb_transactiondb_options_destroy(txn_db_options);
|
||||
|
||||
// reopen db and get all prepared.
|
||||
txn_db_options = rocksdb_transactiondb_options_create();
|
||||
txn_options = rocksdb_transaction_options_create();
|
||||
rocksdb_options_set_error_if_exists(options, 0);
|
||||
txn_db = rocksdb_transactiondb_open(options, txn_db_options, dbname, &err);
|
||||
CheckNoError(err);
|
||||
CheckTxnDBPinGet(txn_db, roptions, "bar1", NULL);
|
||||
CheckTxnDBPinGet(txn_db, roptions, "bar2", NULL);
|
||||
size_t cnt;
|
||||
rocksdb_transaction_t** txns =
|
||||
rocksdb_transactiondb_get_prepared_transactions(txn_db, &cnt);
|
||||
CheckCondition(cnt == 2);
|
||||
size_t i;
|
||||
for (i = 0; i < cnt; i++) {
|
||||
txn = txns[i];
|
||||
size_t name_len = 0;
|
||||
char* name = rocksdb_transaction_get_name(txn, &name_len);
|
||||
CheckCondition(name_len == 4);
|
||||
if (strncmp(name, "txn1", name_len) == 0) {
|
||||
rocksdb_transaction_commit(txn, &err);
|
||||
} else if (strncmp(name, "txn2", name_len) == 0) {
|
||||
rocksdb_transaction_rollback(txn, &err);
|
||||
}
|
||||
rocksdb_free(name);
|
||||
CheckNoError(err);
|
||||
rocksdb_transaction_destroy(txn);
|
||||
}
|
||||
rocksdb_free(txns);
|
||||
CheckTxnDBGet(txn_db, roptions, "bar1", "1");
|
||||
CheckTxnDBGet(txn_db, roptions, "bar2", NULL);
|
||||
rocksdb_transactiondb_put(txn_db, woptions, "bar2", 4, "2", 1, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
// close and destroy
|
||||
rocksdb_transactiondb_close(txn_db);
|
||||
rocksdb_destroy_db(options, dbname, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_transaction_options_destroy(txn_options);
|
||||
rocksdb_transactiondb_options_destroy(txn_db_options);
|
||||
}
|
||||
|
||||
StartPhase("optimistic_transactions");
|
||||
{
|
||||
rocksdb_options_t* db_options = rocksdb_options_create();
|
||||
@@ -2758,6 +3001,7 @@ int main(int argc, char** argv) {
|
||||
rocksdb_transaction_put(txn2, "key1", 4, "value1", 6, &err);
|
||||
CheckNoError(err);
|
||||
CheckTxnGet(txn1, roptions, "key", "value");
|
||||
CheckTxnPinGet(txn1, roptions, "key", "value");
|
||||
rocksdb_transaction_commit(txn1, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_transaction_commit(txn2, &err);
|
||||
@@ -2784,6 +3028,19 @@ int main(int argc, char** argv) {
|
||||
txn);
|
||||
CheckGetCF(db, roptions, cfh1, "key_cf1", "val_cf1");
|
||||
CheckTxnGetCF(txn, roptions, cfh1, "key_cf1", "val_cf1");
|
||||
CheckTxnPinGetCF(txn, roptions, cfh1, "key_cf1", "val_cf1");
|
||||
{
|
||||
const rocksdb_column_family_handle_t* get_handles[3] = {cfh1, cfh2, cfh2};
|
||||
const char* keys[3] = {"key_cf1", "key_cf2", "notfound"};
|
||||
const size_t keys_sizes[3] = {7, 7, 8};
|
||||
char* vals[3];
|
||||
size_t vals_sizes[3];
|
||||
char* errs[3];
|
||||
const char* expected[3] = {"val_cf1", "val_cf2", NULL};
|
||||
rocksdb_transaction_multi_get_cf(txn, roptions, get_handles, 3, keys,
|
||||
keys_sizes, vals, vals_sizes, errs);
|
||||
CheckMultiGetValues(3, vals, vals_sizes, errs, expected);
|
||||
}
|
||||
|
||||
// Check iterator with column family
|
||||
rocksdb_transaction_put_cf(txn, cfh1, "key1_cf", 7, "val1_cf", 7, &err);
|
||||
@@ -2829,6 +3086,9 @@ int main(int argc, char** argv) {
|
||||
CheckTxnGetCF(txn_cf, roptions, cf_handles[0], "key", "value");
|
||||
CheckTxnGetCF(txn_cf, roptions, cf_handles[1], "key_cf1", "val_cf1");
|
||||
CheckTxnGetCF(txn_cf, roptions, cf_handles[2], "key_cf2", "val_cf2");
|
||||
CheckTxnPinGetCF(txn_cf, roptions, cf_handles[0], "key", "value");
|
||||
CheckTxnPinGetCF(txn_cf, roptions, cf_handles[1], "key_cf1", "val_cf1");
|
||||
CheckTxnPinGetCF(txn_cf, roptions, cf_handles[2], "key_cf2", "val_cf2");
|
||||
rocksdb_transaction_destroy(txn_cf);
|
||||
rocksdb_options_destroy(cf_options);
|
||||
rocksdb_column_family_handle_destroy(cf_handles[0]);
|
||||
|
||||
@@ -619,6 +619,26 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
}
|
||||
|
||||
RecalculateWriteStallConditions(mutable_cf_options_);
|
||||
|
||||
if (cf_options.table_factory->IsInstanceOf(
|
||||
TableFactory::kBlockBasedTableName()) &&
|
||||
cf_options.table_factory->GetOptions<BlockBasedTableOptions>()) {
|
||||
const BlockBasedTableOptions* bbto =
|
||||
cf_options.table_factory->GetOptions<BlockBasedTableOptions>();
|
||||
const auto& options_overrides = bbto->cache_usage_options.options_overrides;
|
||||
const auto file_metadata_charged =
|
||||
options_overrides.at(CacheEntryRole::kFileMetadata).charged;
|
||||
if (bbto->block_cache &&
|
||||
file_metadata_charged == CacheEntryRoleOptions::Decision::kEnabled) {
|
||||
// TODO(hx235): Add a `ConcurrentCacheReservationManager` at DB scope
|
||||
// responsible for reservation of `ObsoleteFileInfo` so that we can keep
|
||||
// this `file_metadata_cache_res_mgr_` nonconcurrent
|
||||
file_metadata_cache_res_mgr_.reset(new ConcurrentCacheReservationManager(
|
||||
std::make_shared<
|
||||
CacheReservationManagerImpl<CacheEntryRole::kFileMetadata>>(
|
||||
bbto->block_cache)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DB mutex held
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "cache/cache_reservation_manager.h"
|
||||
#include "db/memtable_list.h"
|
||||
#include "db/table_cache.h"
|
||||
#include "db/table_properties_collector.h"
|
||||
@@ -520,6 +521,10 @@ class ColumnFamilyData {
|
||||
|
||||
ThreadLocalPtr* TEST_GetLocalSV() { return local_sv_.get(); }
|
||||
WriteBufferManager* write_buffer_mgr() { return write_buffer_manager_; }
|
||||
std::shared_ptr<CacheReservationManager>
|
||||
GetFileMetadataCacheReservationManager() {
|
||||
return file_metadata_cache_res_mgr_;
|
||||
}
|
||||
|
||||
static const uint32_t kDummyColumnFamilyDataId;
|
||||
|
||||
@@ -618,6 +623,10 @@ class ColumnFamilyData {
|
||||
bool db_paths_registered_;
|
||||
|
||||
std::string full_history_ts_low_;
|
||||
|
||||
// For charging memory usage of file metadata created for newly added files to
|
||||
// a Version associated with this CFD
|
||||
std::shared_ptr<CacheReservationManager> file_metadata_cache_res_mgr_;
|
||||
};
|
||||
|
||||
// ColumnFamilySet has interesting thread-safety requirements
|
||||
|
||||
+24
-36
@@ -1419,16 +1419,15 @@ TEST_P(ColumnFamilyTest, MultipleManualCompactions) {
|
||||
WaitForFlush(1);
|
||||
AssertFilesPerLevel(std::to_string(i + 1), 1);
|
||||
}
|
||||
bool cf_1_1 = true;
|
||||
std::atomic_bool cf_1_1{true};
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"ColumnFamilyTest::MultiManual:4", "ColumnFamilyTest::MultiManual:1"},
|
||||
{"ColumnFamilyTest::MultiManual:2", "ColumnFamilyTest::MultiManual:5"},
|
||||
{"ColumnFamilyTest::MultiManual:2", "ColumnFamilyTest::MultiManual:3"}});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* /*arg*/) {
|
||||
if (cf_1_1) {
|
||||
if (cf_1_1.exchange(false)) {
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::MultiManual:4");
|
||||
cf_1_1 = false;
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::MultiManual:3");
|
||||
}
|
||||
});
|
||||
@@ -1515,15 +1514,14 @@ TEST_P(ColumnFamilyTest, AutomaticAndManualCompactions) {
|
||||
auto stop_token =
|
||||
dbfull()->TEST_write_controler().GetCompactionPressureToken();
|
||||
|
||||
bool cf_1_1 = true;
|
||||
std::atomic_bool cf_1_1{true};
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"ColumnFamilyTest::AutoManual:4", "ColumnFamilyTest::AutoManual:1"},
|
||||
{"ColumnFamilyTest::AutoManual:2", "ColumnFamilyTest::AutoManual:5"},
|
||||
{"ColumnFamilyTest::AutoManual:2", "ColumnFamilyTest::AutoManual:3"}});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* /*arg*/) {
|
||||
if (cf_1_1) {
|
||||
cf_1_1 = false;
|
||||
if (cf_1_1.exchange(false)) {
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::AutoManual:4");
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::AutoManual:3");
|
||||
}
|
||||
@@ -1618,21 +1616,19 @@ TEST_P(ColumnFamilyTest, ManualAndAutomaticCompactions) {
|
||||
WaitForFlush(1);
|
||||
AssertFilesPerLevel(std::to_string(i + 1), 1);
|
||||
}
|
||||
bool cf_1_1 = true;
|
||||
bool cf_1_2 = true;
|
||||
std::atomic_bool cf_1_1{true};
|
||||
std::atomic_bool cf_1_2{true};
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"ColumnFamilyTest::ManualAuto:4", "ColumnFamilyTest::ManualAuto:1"},
|
||||
{"ColumnFamilyTest::ManualAuto:5", "ColumnFamilyTest::ManualAuto:2"},
|
||||
{"ColumnFamilyTest::ManualAuto:2", "ColumnFamilyTest::ManualAuto:3"}});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* /*arg*/) {
|
||||
if (cf_1_1) {
|
||||
if (cf_1_1.exchange(false)) {
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:4");
|
||||
cf_1_1 = false;
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:3");
|
||||
} else if (cf_1_2) {
|
||||
} else if (cf_1_2.exchange(false)) {
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:2");
|
||||
cf_1_2 = false;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1712,8 +1708,8 @@ TEST_P(ColumnFamilyTest, SameCFManualManualCompactions) {
|
||||
WaitForFlush(1);
|
||||
AssertFilesPerLevel(std::to_string(i + 1), 1);
|
||||
}
|
||||
bool cf_1_1 = true;
|
||||
bool cf_1_2 = true;
|
||||
std::atomic_bool cf_1_1{true};
|
||||
std::atomic_bool cf_1_2{true};
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"ColumnFamilyTest::ManualManual:4", "ColumnFamilyTest::ManualManual:2"},
|
||||
{"ColumnFamilyTest::ManualManual:4", "ColumnFamilyTest::ManualManual:5"},
|
||||
@@ -1722,13 +1718,11 @@ TEST_P(ColumnFamilyTest, SameCFManualManualCompactions) {
|
||||
"ColumnFamilyTest::ManualManual:3"}});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* /*arg*/) {
|
||||
if (cf_1_1) {
|
||||
if (cf_1_1.exchange(false)) {
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualManual:4");
|
||||
cf_1_1 = false;
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualManual:3");
|
||||
} else if (cf_1_2) {
|
||||
} else if (cf_1_2.exchange(false)) {
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualManual:2");
|
||||
cf_1_2 = false;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1814,8 +1808,8 @@ TEST_P(ColumnFamilyTest, SameCFManualAutomaticCompactions) {
|
||||
WaitForFlush(1);
|
||||
AssertFilesPerLevel(std::to_string(i + 1), 1);
|
||||
}
|
||||
bool cf_1_1 = true;
|
||||
bool cf_1_2 = true;
|
||||
std::atomic_bool cf_1_1{true};
|
||||
std::atomic_bool cf_1_2{true};
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"ColumnFamilyTest::ManualAuto:4", "ColumnFamilyTest::ManualAuto:2"},
|
||||
{"ColumnFamilyTest::ManualAuto:4", "ColumnFamilyTest::ManualAuto:5"},
|
||||
@@ -1823,13 +1817,11 @@ TEST_P(ColumnFamilyTest, SameCFManualAutomaticCompactions) {
|
||||
{"ColumnFamilyTest::ManualAuto:1", "ColumnFamilyTest::ManualAuto:3"}});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* /*arg*/) {
|
||||
if (cf_1_1) {
|
||||
if (cf_1_1.exchange(false)) {
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:4");
|
||||
cf_1_1 = false;
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:3");
|
||||
} else if (cf_1_2) {
|
||||
} else if (cf_1_2.exchange(false)) {
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:2");
|
||||
cf_1_2 = false;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1907,8 +1899,8 @@ TEST_P(ColumnFamilyTest, SameCFManualAutomaticCompactionsLevel) {
|
||||
WaitForFlush(1);
|
||||
AssertFilesPerLevel(std::to_string(i + 1), 1);
|
||||
}
|
||||
bool cf_1_1 = true;
|
||||
bool cf_1_2 = true;
|
||||
std::atomic_bool cf_1_1{true};
|
||||
std::atomic_bool cf_1_2{true};
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"ColumnFamilyTest::ManualAuto:4", "ColumnFamilyTest::ManualAuto:2"},
|
||||
{"ColumnFamilyTest::ManualAuto:4", "ColumnFamilyTest::ManualAuto:5"},
|
||||
@@ -1918,13 +1910,11 @@ TEST_P(ColumnFamilyTest, SameCFManualAutomaticCompactionsLevel) {
|
||||
{"ColumnFamilyTest::ManualAuto:1", "ColumnFamilyTest::ManualAuto:3"}});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* /*arg*/) {
|
||||
if (cf_1_1) {
|
||||
if (cf_1_1.exchange(false)) {
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:4");
|
||||
cf_1_1 = false;
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:3");
|
||||
} else if (cf_1_2) {
|
||||
} else if (cf_1_2.exchange(false)) {
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:2");
|
||||
cf_1_2 = false;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1999,8 +1989,8 @@ TEST_P(ColumnFamilyTest, SameCFAutomaticManualCompactions) {
|
||||
auto stop_token =
|
||||
dbfull()->TEST_write_controler().GetCompactionPressureToken();
|
||||
|
||||
bool cf_1_1 = true;
|
||||
bool cf_1_2 = true;
|
||||
std::atomic_bool cf_1_1{true};
|
||||
std::atomic_bool cf_1_2{true};
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"ColumnFamilyTest::AutoManual:4", "ColumnFamilyTest::AutoManual:2"},
|
||||
{"ColumnFamilyTest::AutoManual:4", "ColumnFamilyTest::AutoManual:5"},
|
||||
@@ -2008,13 +1998,11 @@ TEST_P(ColumnFamilyTest, SameCFAutomaticManualCompactions) {
|
||||
"ColumnFamilyTest::AutoManual:3"}});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* /*arg*/) {
|
||||
if (cf_1_1) {
|
||||
if (cf_1_1.exchange(false)) {
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::AutoManual:4");
|
||||
cf_1_1 = false;
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::AutoManual:3");
|
||||
} else if (cf_1_2) {
|
||||
} else if (cf_1_2.exchange(false)) {
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::AutoManual:2");
|
||||
cf_1_2 = false;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -214,7 +214,9 @@ Compaction::Compaction(
|
||||
CompressionOptions _compression_opts, Temperature _output_temperature,
|
||||
uint32_t _max_subcompactions, std::vector<FileMetaData*> _grandparents,
|
||||
bool _manual_compaction, const std::string& _trim_ts, double _score,
|
||||
bool _deletion_compaction, CompactionReason _compaction_reason)
|
||||
bool _deletion_compaction, CompactionReason _compaction_reason,
|
||||
BlobGarbageCollectionPolicy _blob_garbage_collection_policy,
|
||||
double _blob_garbage_collection_age_cutoff)
|
||||
: input_vstorage_(vstorage),
|
||||
start_level_(_inputs[0].level),
|
||||
output_level_(_output_level),
|
||||
@@ -240,7 +242,19 @@ Compaction::Compaction(
|
||||
trim_ts_(_trim_ts),
|
||||
is_trivial_move_(false),
|
||||
compaction_reason_(_compaction_reason),
|
||||
notify_on_compaction_completion_(false) {
|
||||
notify_on_compaction_completion_(false),
|
||||
enable_blob_garbage_collection_(
|
||||
_blob_garbage_collection_policy == BlobGarbageCollectionPolicy::kForce
|
||||
? true
|
||||
: (_blob_garbage_collection_policy ==
|
||||
BlobGarbageCollectionPolicy::kDisable
|
||||
? false
|
||||
: mutable_cf_options()->enable_blob_garbage_collection)),
|
||||
blob_garbage_collection_age_cutoff_(
|
||||
_blob_garbage_collection_age_cutoff < 0 ||
|
||||
_blob_garbage_collection_age_cutoff > 1
|
||||
? mutable_cf_options()->blob_garbage_collection_age_cutoff
|
||||
: _blob_garbage_collection_age_cutoff) {
|
||||
MarkFilesBeingCompacted(true);
|
||||
if (is_manual_compaction_) {
|
||||
compaction_reason_ = CompactionReason::kManualCompaction;
|
||||
@@ -325,8 +339,8 @@ bool Compaction::IsTrivialMove() const {
|
||||
}
|
||||
|
||||
if (!(start_level_ != output_level_ && num_input_levels() == 1 &&
|
||||
input(0, 0)->fd.GetPathId() == output_path_id() &&
|
||||
InputCompressionMatchesOutput())) {
|
||||
input(0, 0)->fd.GetPathId() == output_path_id() &&
|
||||
InputCompressionMatchesOutput())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,10 @@ class Compaction {
|
||||
std::vector<FileMetaData*> grandparents,
|
||||
bool manual_compaction = false, const std::string& trim_ts = "",
|
||||
double score = -1, bool deletion_compaction = false,
|
||||
CompactionReason compaction_reason = CompactionReason::kUnknown);
|
||||
CompactionReason compaction_reason = CompactionReason::kUnknown,
|
||||
BlobGarbageCollectionPolicy blob_garbage_collection_policy =
|
||||
BlobGarbageCollectionPolicy::kUseDefault,
|
||||
double blob_garbage_collection_age_cutoff = -1);
|
||||
|
||||
// No copying allowed
|
||||
Compaction(const Compaction&) = delete;
|
||||
@@ -306,6 +309,14 @@ class Compaction {
|
||||
|
||||
uint32_t max_subcompactions() const { return max_subcompactions_; }
|
||||
|
||||
bool enable_blob_garbage_collection() const {
|
||||
return enable_blob_garbage_collection_;
|
||||
}
|
||||
|
||||
double blob_garbage_collection_age_cutoff() const {
|
||||
return blob_garbage_collection_age_cutoff_;
|
||||
}
|
||||
|
||||
// start and end are sub compact range. Null if no boundary.
|
||||
// This is used to filter out some input files' ancester's time range.
|
||||
uint64_t MinInputFileOldestAncesterTime(const InternalKey* start,
|
||||
@@ -332,8 +343,9 @@ class Compaction {
|
||||
|
||||
// Get the atomic file boundaries for all files in the compaction. Necessary
|
||||
// in order to avoid the scenario described in
|
||||
// https://github.com/facebook/rocksdb/pull/4432#discussion_r221072219 and plumb
|
||||
// down appropriate key boundaries to RangeDelAggregator during compaction.
|
||||
// https://github.com/facebook/rocksdb/pull/4432#discussion_r221072219 and
|
||||
// plumb down appropriate key boundaries to RangeDelAggregator during
|
||||
// compaction.
|
||||
static std::vector<CompactionInputFiles> PopulateWithAtomicBoundaries(
|
||||
VersionStorageInfo* vstorage, std::vector<CompactionInputFiles> inputs);
|
||||
|
||||
@@ -348,7 +360,7 @@ class Compaction {
|
||||
|
||||
VersionStorageInfo* input_vstorage_;
|
||||
|
||||
const int start_level_; // the lowest level to be compacted
|
||||
const int start_level_; // the lowest level to be compacted
|
||||
const int output_level_; // levels to which output files are stored
|
||||
uint64_t max_output_file_size_;
|
||||
uint64_t max_compaction_bytes_;
|
||||
@@ -359,7 +371,7 @@ class Compaction {
|
||||
VersionEdit edit_;
|
||||
const int number_levels_;
|
||||
ColumnFamilyData* cfd_;
|
||||
Arena arena_; // Arena used to allocate space for file_levels_
|
||||
Arena arena_; // Arena used to allocate space for file_levels_
|
||||
|
||||
const uint32_t output_path_id_;
|
||||
CompressionType output_compression_;
|
||||
@@ -377,7 +389,7 @@ class Compaction {
|
||||
// State used to check for number of overlapping grandparent files
|
||||
// (grandparent == "output_level_ + 1")
|
||||
std::vector<FileMetaData*> grandparents_;
|
||||
const double score_; // score that was used to pick this compaction.
|
||||
const double score_; // score that was used to pick this compaction.
|
||||
|
||||
// Is this compaction creating a file in the bottom most level?
|
||||
const bool bottommost_level_;
|
||||
@@ -412,6 +424,12 @@ class Compaction {
|
||||
// Notify on compaction completion only if listener was notified on compaction
|
||||
// begin.
|
||||
bool notify_on_compaction_completion_;
|
||||
|
||||
// Enable/disable GC collection for blobs during compaction.
|
||||
bool enable_blob_garbage_collection_;
|
||||
|
||||
// Blob garbage collection age cutoff.
|
||||
double blob_garbage_collection_age_cutoff_;
|
||||
};
|
||||
|
||||
// Return sum of sizes of all files in `files`.
|
||||
|
||||
@@ -28,11 +28,10 @@ CompactionIterator::CompactionIterator(
|
||||
Env* env, bool report_detailed_time, bool expect_valid_internal_key,
|
||||
CompactionRangeDelAggregator* range_del_agg,
|
||||
BlobFileBuilder* blob_file_builder, bool allow_data_in_errors,
|
||||
bool enforce_single_del_contracts, const Compaction* compaction,
|
||||
const CompactionFilter* compaction_filter,
|
||||
bool enforce_single_del_contracts,
|
||||
const std::atomic<bool>& manual_compaction_canceled,
|
||||
const Compaction* compaction, const CompactionFilter* compaction_filter,
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const std::atomic<int>* manual_compaction_paused,
|
||||
const std::atomic<bool>* manual_compaction_canceled,
|
||||
const std::shared_ptr<Logger> info_log,
|
||||
const std::string* full_history_ts_low)
|
||||
: CompactionIterator(
|
||||
@@ -40,10 +39,10 @@ CompactionIterator::CompactionIterator(
|
||||
earliest_write_conflict_snapshot, job_snapshot, snapshot_checker, env,
|
||||
report_detailed_time, expect_valid_internal_key, range_del_agg,
|
||||
blob_file_builder, allow_data_in_errors, enforce_single_del_contracts,
|
||||
manual_compaction_canceled,
|
||||
std::unique_ptr<CompactionProxy>(
|
||||
compaction ? new RealCompaction(compaction) : nullptr),
|
||||
compaction_filter, shutting_down, manual_compaction_paused,
|
||||
manual_compaction_canceled, info_log, full_history_ts_low) {}
|
||||
compaction_filter, shutting_down, info_log, full_history_ts_low) {}
|
||||
|
||||
CompactionIterator::CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
@@ -54,11 +53,10 @@ CompactionIterator::CompactionIterator(
|
||||
CompactionRangeDelAggregator* range_del_agg,
|
||||
BlobFileBuilder* blob_file_builder, bool allow_data_in_errors,
|
||||
bool enforce_single_del_contracts,
|
||||
const std::atomic<bool>& manual_compaction_canceled,
|
||||
std::unique_ptr<CompactionProxy> compaction,
|
||||
const CompactionFilter* compaction_filter,
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const std::atomic<int>* manual_compaction_paused,
|
||||
const std::atomic<bool>* manual_compaction_canceled,
|
||||
const std::shared_ptr<Logger> info_log,
|
||||
const std::string* full_history_ts_low)
|
||||
: input_(input, cmp,
|
||||
@@ -78,7 +76,6 @@ CompactionIterator::CompactionIterator(
|
||||
compaction_(std::move(compaction)),
|
||||
compaction_filter_(compaction_filter),
|
||||
shutting_down_(shutting_down),
|
||||
manual_compaction_paused_(manual_compaction_paused),
|
||||
manual_compaction_canceled_(manual_compaction_canceled),
|
||||
info_log_(info_log),
|
||||
allow_data_in_errors_(allow_data_in_errors),
|
||||
@@ -160,10 +157,12 @@ void CompactionIterator::Next() {
|
||||
Status s = ParseInternalKey(key_, &ikey_, allow_data_in_errors_);
|
||||
// MergeUntil stops when it encounters a corrupt key and does not
|
||||
// include them in the result, so we expect the keys here to be valid.
|
||||
assert(s.ok());
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_FATAL(info_log_, "Invalid key in compaction. %s",
|
||||
s.getState());
|
||||
ROCKS_LOG_FATAL(
|
||||
info_log_, "Invalid ikey %s in compaction. %s",
|
||||
allow_data_in_errors_ ? key_.ToString(true).c_str() : "hidden",
|
||||
s.getState());
|
||||
assert(false);
|
||||
}
|
||||
|
||||
// Keep current_key_ in sync.
|
||||
@@ -520,18 +519,18 @@ void CompactionIterator::NextFromInput() {
|
||||
// In the previous iteration we encountered a single delete that we could
|
||||
// not compact out. We will keep this Put, but can drop it's data.
|
||||
// (See Optimization 3, below.)
|
||||
assert(ikey_.type == kTypeValue || ikey_.type == kTypeBlobIndex);
|
||||
if (ikey_.type != kTypeValue && ikey_.type != kTypeBlobIndex) {
|
||||
ROCKS_LOG_FATAL(info_log_,
|
||||
"Unexpected key type %d for compaction output",
|
||||
ikey_.type);
|
||||
ROCKS_LOG_FATAL(info_log_, "Unexpected key %s for compaction output",
|
||||
ikey_.DebugString(allow_data_in_errors_, true).c_str());
|
||||
assert(false);
|
||||
}
|
||||
assert(current_user_key_snapshot_ >= last_snapshot);
|
||||
if (current_user_key_snapshot_ < last_snapshot) {
|
||||
ROCKS_LOG_FATAL(info_log_,
|
||||
"current_user_key_snapshot_ (%" PRIu64
|
||||
"key %s, current_user_key_snapshot_ (%" PRIu64
|
||||
") < last_snapshot (%" PRIu64 ")",
|
||||
ikey_.DebugString(allow_data_in_errors_, true).c_str(),
|
||||
current_user_key_snapshot_, last_snapshot);
|
||||
assert(false);
|
||||
}
|
||||
|
||||
if (ikey_.type == kTypeBlobIndex) {
|
||||
@@ -770,12 +769,13 @@ void CompactionIterator::NextFromInput() {
|
||||
// Note: Dropping this key will not affect TransactionDB write-conflict
|
||||
// checking since there has already been a record returned for this key
|
||||
// in this snapshot.
|
||||
assert(last_sequence >= current_user_key_sequence_);
|
||||
if (last_sequence < current_user_key_sequence_) {
|
||||
ROCKS_LOG_FATAL(info_log_,
|
||||
"last_sequence (%" PRIu64
|
||||
"key %s, last_sequence (%" PRIu64
|
||||
") < current_user_key_sequence_ (%" PRIu64 ")",
|
||||
ikey_.DebugString(allow_data_in_errors_, true).c_str(),
|
||||
last_sequence, current_user_key_sequence_);
|
||||
assert(false);
|
||||
}
|
||||
|
||||
++iter_stats_.num_record_drop_hidden; // rule (A)
|
||||
@@ -887,10 +887,12 @@ void CompactionIterator::NextFromInput() {
|
||||
pik_status = ParseInternalKey(key_, &ikey_, allow_data_in_errors_);
|
||||
// MergeUntil stops when it encounters a corrupt key and does not
|
||||
// include them in the result, so we expect the keys here to valid.
|
||||
assert(pik_status.ok());
|
||||
if (!pik_status.ok()) {
|
||||
ROCKS_LOG_FATAL(info_log_, "Invalid key in compaction. %s",
|
||||
pik_status.getState());
|
||||
ROCKS_LOG_FATAL(
|
||||
info_log_, "Invalid key %s in compaction. %s",
|
||||
allow_data_in_errors_ ? key_.ToString(true).c_str() : "hidden",
|
||||
pik_status.getState());
|
||||
assert(false);
|
||||
}
|
||||
// Keep current_key_ in sync.
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
|
||||
@@ -1093,15 +1095,22 @@ void CompactionIterator::PrepareOutput() {
|
||||
!compaction_->allow_ingest_behind() && bottommost_level_ &&
|
||||
DefinitelyInSnapshot(ikey_.sequence, earliest_snapshot_) &&
|
||||
ikey_.type != kTypeMerge && current_key_committed_) {
|
||||
assert(ikey_.type != kTypeDeletion);
|
||||
assert(ikey_.type != kTypeSingleDeletion ||
|
||||
(timestamp_size_ || full_history_ts_low_));
|
||||
if (ikey_.type == kTypeDeletion ||
|
||||
(ikey_.type == kTypeSingleDeletion &&
|
||||
(!timestamp_size_ || !full_history_ts_low_))) {
|
||||
ROCKS_LOG_FATAL(info_log_,
|
||||
"Unexpected key type %d for seq-zero optimization",
|
||||
ikey_.type);
|
||||
(ikey_.type == kTypeSingleDeletion && timestamp_size_ == 0)) {
|
||||
ROCKS_LOG_FATAL(
|
||||
info_log_,
|
||||
"Unexpected key %s for seq-zero optimization. "
|
||||
"earliest_snapshot %" PRIu64
|
||||
", earliest_write_conflict_snapshot %" PRIu64
|
||||
" job_snapshot %" PRIu64
|
||||
". timestamp_size: %d full_history_ts_low_ %s",
|
||||
ikey_.DebugString(allow_data_in_errors_, true).c_str(),
|
||||
earliest_snapshot_, earliest_write_conflict_snapshot_,
|
||||
job_snapshot_, static_cast<int>(timestamp_size_),
|
||||
full_history_ts_low_ != nullptr
|
||||
? Slice(*full_history_ts_low_).ToString(true).c_str()
|
||||
: "null");
|
||||
assert(false);
|
||||
}
|
||||
ikey_.sequence = 0;
|
||||
last_key_seq_zeroed_ = true;
|
||||
@@ -1132,14 +1141,17 @@ inline SequenceNumber CompactionIterator::findEarliestVisibleSnapshot(
|
||||
}
|
||||
auto snapshots_iter = std::lower_bound(
|
||||
snapshots_->begin(), snapshots_->end(), in);
|
||||
assert(prev_snapshot != nullptr);
|
||||
if (snapshots_iter == snapshots_->begin()) {
|
||||
*prev_snapshot = 0;
|
||||
} else {
|
||||
*prev_snapshot = *std::prev(snapshots_iter);
|
||||
assert(*prev_snapshot < in);
|
||||
if (*prev_snapshot >= in) {
|
||||
ROCKS_LOG_FATAL(info_log_,
|
||||
"*prev_snapshot >= in in findEarliestVisibleSnapshot");
|
||||
"*prev_snapshot (%" PRIu64 ") >= in (%" PRIu64
|
||||
") in findEarliestVisibleSnapshot",
|
||||
*prev_snapshot, in);
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
if (snapshot_checker_ == nullptr) {
|
||||
@@ -1149,9 +1161,12 @@ inline SequenceNumber CompactionIterator::findEarliestVisibleSnapshot(
|
||||
bool has_released_snapshot = !released_snapshots_.empty();
|
||||
for (; snapshots_iter != snapshots_->end(); ++snapshots_iter) {
|
||||
auto cur = *snapshots_iter;
|
||||
assert(in <= cur);
|
||||
if (in > cur) {
|
||||
ROCKS_LOG_FATAL(info_log_, "in > cur in findEarliestVisibleSnapshot");
|
||||
ROCKS_LOG_FATAL(info_log_,
|
||||
"in (%" PRIu64 ") > cur (%" PRIu64
|
||||
") in findEarliestVisibleSnapshot",
|
||||
in, cur);
|
||||
assert(false);
|
||||
}
|
||||
// Skip if cur is in released_snapshots.
|
||||
if (has_released_snapshot && released_snapshots_.count(cur) > 0) {
|
||||
|
||||
@@ -142,12 +142,11 @@ class CompactionIterator {
|
||||
}
|
||||
|
||||
bool enable_blob_garbage_collection() const override {
|
||||
return compaction_->mutable_cf_options()->enable_blob_garbage_collection;
|
||||
return compaction_->enable_blob_garbage_collection();
|
||||
}
|
||||
|
||||
double blob_garbage_collection_age_cutoff() const override {
|
||||
return compaction_->mutable_cf_options()
|
||||
->blob_garbage_collection_age_cutoff;
|
||||
return compaction_->blob_garbage_collection_age_cutoff();
|
||||
}
|
||||
|
||||
uint64_t blob_compaction_readahead_size() const override {
|
||||
@@ -168,39 +167,42 @@ class CompactionIterator {
|
||||
const Compaction* compaction_;
|
||||
};
|
||||
|
||||
CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
SequenceNumber last_sequence, std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SequenceNumber job_snapshot, const SnapshotChecker* snapshot_checker,
|
||||
Env* env, bool report_detailed_time, bool expect_valid_internal_key,
|
||||
CompactionRangeDelAggregator* range_del_agg,
|
||||
BlobFileBuilder* blob_file_builder, bool allow_data_in_errors,
|
||||
bool enforce_single_del_contracts, const Compaction* compaction = nullptr,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
const std::atomic<bool>* shutting_down = nullptr,
|
||||
const std::atomic<int>* manual_compaction_paused = nullptr,
|
||||
const std::atomic<bool>* manual_compaction_canceled = nullptr,
|
||||
const std::shared_ptr<Logger> info_log = nullptr,
|
||||
const std::string* full_history_ts_low = nullptr);
|
||||
CompactionIterator(InternalIterator* input, const Comparator* cmp,
|
||||
MergeHelper* merge_helper, SequenceNumber last_sequence,
|
||||
std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SequenceNumber job_snapshot,
|
||||
const SnapshotChecker* snapshot_checker, Env* env,
|
||||
bool report_detailed_time, bool expect_valid_internal_key,
|
||||
CompactionRangeDelAggregator* range_del_agg,
|
||||
BlobFileBuilder* blob_file_builder,
|
||||
bool allow_data_in_errors,
|
||||
bool enforce_single_del_contracts,
|
||||
const std::atomic<bool>& manual_compaction_canceled,
|
||||
const Compaction* compaction = nullptr,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
const std::atomic<bool>* shutting_down = nullptr,
|
||||
const std::shared_ptr<Logger> info_log = nullptr,
|
||||
const std::string* full_history_ts_low = nullptr);
|
||||
|
||||
// Constructor with custom CompactionProxy, used for tests.
|
||||
CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
SequenceNumber last_sequence, std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SequenceNumber job_snapshot, const SnapshotChecker* snapshot_checker,
|
||||
Env* env, bool report_detailed_time, bool expect_valid_internal_key,
|
||||
CompactionRangeDelAggregator* range_del_agg,
|
||||
BlobFileBuilder* blob_file_builder, bool allow_data_in_errors,
|
||||
bool enforce_single_del_contracts,
|
||||
std::unique_ptr<CompactionProxy> compaction,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
const std::atomic<bool>* shutting_down = nullptr,
|
||||
const std::atomic<int>* manual_compaction_paused = nullptr,
|
||||
const std::atomic<bool>* manual_compaction_canceled = nullptr,
|
||||
const std::shared_ptr<Logger> info_log = nullptr,
|
||||
const std::string* full_history_ts_low = nullptr);
|
||||
CompactionIterator(InternalIterator* input, const Comparator* cmp,
|
||||
MergeHelper* merge_helper, SequenceNumber last_sequence,
|
||||
std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SequenceNumber job_snapshot,
|
||||
const SnapshotChecker* snapshot_checker, Env* env,
|
||||
bool report_detailed_time, bool expect_valid_internal_key,
|
||||
CompactionRangeDelAggregator* range_del_agg,
|
||||
BlobFileBuilder* blob_file_builder,
|
||||
bool allow_data_in_errors,
|
||||
bool enforce_single_del_contracts,
|
||||
const std::atomic<bool>& manual_compaction_canceled,
|
||||
std::unique_ptr<CompactionProxy> compaction,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
const std::atomic<bool>* shutting_down = nullptr,
|
||||
const std::shared_ptr<Logger> info_log = nullptr,
|
||||
const std::string* full_history_ts_low = nullptr);
|
||||
|
||||
~CompactionIterator();
|
||||
|
||||
@@ -321,8 +323,7 @@ class CompactionIterator {
|
||||
std::unique_ptr<CompactionProxy> compaction_;
|
||||
const CompactionFilter* compaction_filter_;
|
||||
const std::atomic<bool>* shutting_down_;
|
||||
const std::atomic<int>* manual_compaction_paused_;
|
||||
const std::atomic<bool>* manual_compaction_canceled_;
|
||||
const std::atomic<bool>& manual_compaction_canceled_;
|
||||
bool bottommost_level_;
|
||||
bool valid_ = false;
|
||||
bool visible_at_tip_;
|
||||
@@ -427,10 +428,7 @@ class CompactionIterator {
|
||||
|
||||
bool IsPausingManualCompaction() {
|
||||
// This is a best-effort facility, so memory_order_relaxed is sufficient.
|
||||
return (manual_compaction_paused_ &&
|
||||
manual_compaction_paused_->load(std::memory_order_relaxed) > 0) ||
|
||||
(manual_compaction_canceled_ &&
|
||||
manual_compaction_canceled_->load(std::memory_order_relaxed));
|
||||
return manual_compaction_canceled_.load(std::memory_order_relaxed);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -279,10 +279,9 @@ class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
snapshot_checker_.get(), Env::Default(),
|
||||
false /* report_detailed_time */, false, range_del_agg_.get(),
|
||||
nullptr /* blob_file_builder */, true /*allow_data_in_errors*/,
|
||||
true /*enforce_single_del_contracts*/, std::move(compaction), filter,
|
||||
&shutting_down_,
|
||||
/*manual_compaction_paused=*/nullptr,
|
||||
/*manual_compaction_canceled=*/nullptr, /*info_log=*/nullptr,
|
||||
true /*enforce_single_del_contracts*/,
|
||||
/*manual_compaction_canceled=*/kManualCompactionCanceledFalse_,
|
||||
std::move(compaction), filter, &shutting_down_, /*info_log=*/nullptr,
|
||||
full_history_ts_low));
|
||||
}
|
||||
|
||||
@@ -341,6 +340,7 @@ class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
std::unique_ptr<CompactionRangeDelAggregator> range_del_agg_;
|
||||
std::unique_ptr<SnapshotChecker> snapshot_checker_;
|
||||
std::atomic<bool> shutting_down_{false};
|
||||
const std::atomic<bool> kManualCompactionCanceledFalse_{false};
|
||||
FakeCompaction* compaction_proxy_;
|
||||
};
|
||||
|
||||
|
||||
@@ -429,8 +429,7 @@ CompactionJob::CompactionJob(
|
||||
bool paranoid_file_checks, bool measure_io_stats, const std::string& dbname,
|
||||
CompactionJobStats* compaction_job_stats, Env::Priority thread_pri,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::atomic<int>* manual_compaction_paused,
|
||||
const std::atomic<bool>* manual_compaction_canceled,
|
||||
const std::atomic<bool>& manual_compaction_canceled,
|
||||
const std::string& db_id, const std::string& db_session_id,
|
||||
std::string full_history_ts_low, std::string trim_ts,
|
||||
BlobFileCompletionCallback* blob_callback)
|
||||
@@ -456,7 +455,6 @@ CompactionJob::CompactionJob(
|
||||
fs_->OptimizeForCompactionTableRead(file_options, db_options_)),
|
||||
versions_(versions),
|
||||
shutting_down_(shutting_down),
|
||||
manual_compaction_paused_(manual_compaction_paused),
|
||||
manual_compaction_canceled_(manual_compaction_canceled),
|
||||
db_directory_(db_directory),
|
||||
blob_output_directory_(blob_output_directory),
|
||||
@@ -1256,8 +1254,8 @@ void CompactionJob::NotifyOnSubcompactionBegin(
|
||||
if (shutting_down_->load(std::memory_order_acquire)) {
|
||||
return;
|
||||
}
|
||||
if (c->is_manual_compaction() && manual_compaction_paused_ &&
|
||||
manual_compaction_paused_->load(std::memory_order_acquire) > 0) {
|
||||
if (c->is_manual_compaction() &&
|
||||
manual_compaction_canceled_.load(std::memory_order_acquire)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1454,7 +1452,9 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
|
||||
std::unique_ptr<BlobFileBuilder> blob_file_builder(
|
||||
mutable_cf_options->enable_blob_files
|
||||
(mutable_cf_options->enable_blob_files &&
|
||||
sub_compact->compaction->output_level() >=
|
||||
mutable_cf_options->blob_file_starting_level)
|
||||
? new BlobFileBuilder(
|
||||
versions_, fs_.get(),
|
||||
sub_compact->compaction->immutable_options(),
|
||||
@@ -1468,7 +1468,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"CompactionJob::Run():PausingManualCompaction:1",
|
||||
reinterpret_cast<void*>(
|
||||
const_cast<std::atomic<int>*>(manual_compaction_paused_)));
|
||||
const_cast<std::atomic<bool>*>(&manual_compaction_canceled_)));
|
||||
|
||||
Status status;
|
||||
const std::string* const full_history_ts_low =
|
||||
@@ -1482,9 +1482,9 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
snapshot_checker_, env_, ShouldReportDetailedTime(env_, stats_),
|
||||
/*expect_valid_internal_key=*/true, &range_del_agg,
|
||||
blob_file_builder.get(), db_options_.allow_data_in_errors,
|
||||
db_options_.enforce_single_del_contracts, sub_compact->compaction,
|
||||
compaction_filter, shutting_down_, manual_compaction_paused_,
|
||||
manual_compaction_canceled_, db_options_.info_log, full_history_ts_low));
|
||||
db_options_.enforce_single_del_contracts, manual_compaction_canceled_,
|
||||
sub_compact->compaction, compaction_filter, shutting_down_,
|
||||
db_options_.info_log, full_history_ts_low));
|
||||
auto c_iter = sub_compact->c_iter.get();
|
||||
c_iter->SeekToFirst();
|
||||
if (c_iter->Valid() && sub_compact->compaction->output_level() != 0) {
|
||||
@@ -1566,7 +1566,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"CompactionJob::Run():PausingManualCompaction:2",
|
||||
reinterpret_cast<void*>(
|
||||
const_cast<std::atomic<int>*>(manual_compaction_paused_)));
|
||||
const_cast<std::atomic<bool>*>(&manual_compaction_canceled_)));
|
||||
if (partitioner.get()) {
|
||||
last_key_for_partitioner.assign(c_iter->user_key().data_,
|
||||
c_iter->user_key().size_);
|
||||
@@ -1645,10 +1645,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
status = Status::ShutdownInProgress("Database shutdown");
|
||||
}
|
||||
if ((status.ok() || status.IsColumnFamilyDropped()) &&
|
||||
((manual_compaction_paused_ &&
|
||||
manual_compaction_paused_->load(std::memory_order_relaxed) > 0) ||
|
||||
(manual_compaction_canceled_ &&
|
||||
manual_compaction_canceled_->load(std::memory_order_relaxed)))) {
|
||||
(manual_compaction_canceled_.load(std::memory_order_relaxed))) {
|
||||
status = Status::Incomplete(Status::SubCode::kManualCompactionPaused);
|
||||
}
|
||||
if (status.ok()) {
|
||||
@@ -2525,7 +2522,7 @@ CompactionServiceCompactionJob::CompactionServiceCompactionJob(
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
std::shared_ptr<Cache> table_cache, EventLogger* event_logger,
|
||||
const std::string& dbname, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::atomic<bool>* manual_compaction_canceled,
|
||||
const std::atomic<bool>& manual_compaction_canceled,
|
||||
const std::string& db_id, const std::string& db_session_id,
|
||||
const std::string& output_path,
|
||||
const CompactionServiceInput& compaction_service_input,
|
||||
@@ -2538,7 +2535,7 @@ CompactionServiceCompactionJob::CompactionServiceCompactionJob(
|
||||
compaction->mutable_cf_options()->paranoid_file_checks,
|
||||
compaction->mutable_cf_options()->report_bg_io_stats, dbname,
|
||||
&(compaction_service_result->stats), Env::Priority::USER, io_tracer,
|
||||
nullptr, manual_compaction_canceled, db_id, db_session_id,
|
||||
manual_compaction_canceled, db_id, db_session_id,
|
||||
compaction->column_family_data()->GetFullHistoryTsLow()),
|
||||
output_path_(output_path),
|
||||
compaction_input_(compaction_service_input),
|
||||
|
||||
@@ -78,8 +78,7 @@ class CompactionJob {
|
||||
bool paranoid_file_checks, bool measure_io_stats,
|
||||
const std::string& dbname, CompactionJobStats* compaction_job_stats,
|
||||
Env::Priority thread_pri, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::atomic<int>* manual_compaction_paused = nullptr,
|
||||
const std::atomic<bool>* manual_compaction_canceled = nullptr,
|
||||
const std::atomic<bool>& manual_compaction_canceled,
|
||||
const std::string& db_id = "", const std::string& db_session_id = "",
|
||||
std::string full_history_ts_low = "", std::string trim_ts = "",
|
||||
BlobFileCompletionCallback* blob_callback = nullptr);
|
||||
@@ -195,8 +194,7 @@ class CompactionJob {
|
||||
FileOptions file_options_for_read_;
|
||||
VersionSet* versions_;
|
||||
const std::atomic<bool>* shutting_down_;
|
||||
const std::atomic<int>* manual_compaction_paused_;
|
||||
const std::atomic<bool>* manual_compaction_canceled_;
|
||||
const std::atomic<bool>& manual_compaction_canceled_;
|
||||
FSDirectory* db_directory_;
|
||||
FSDirectory* blob_output_directory_;
|
||||
InstrumentedMutex* db_mutex_;
|
||||
@@ -357,7 +355,7 @@ class CompactionServiceCompactionJob : private CompactionJob {
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
std::shared_ptr<Cache> table_cache, EventLogger* event_logger,
|
||||
const std::string& dbname, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::atomic<bool>* manual_compaction_canceled,
|
||||
const std::atomic<bool>& manual_compaction_canceled,
|
||||
const std::string& db_id, const std::string& db_session_id,
|
||||
const std::string& output_path,
|
||||
const CompactionServiceInput& compaction_service_input,
|
||||
|
||||
@@ -69,12 +69,138 @@ void VerifyInitializationOfCompactionJobStats(
|
||||
#endif // !defined(IOS_CROSS_COMPILE)
|
||||
}
|
||||
|
||||
// Mock FSWritableFile for testing io priority.
|
||||
// Only override the essential functions for testing compaction io priority.
|
||||
class MockTestWritableFile : public FSWritableFileOwnerWrapper {
|
||||
public:
|
||||
MockTestWritableFile(std::unique_ptr<FSWritableFile>&& file,
|
||||
Env::IOPriority io_priority)
|
||||
: FSWritableFileOwnerWrapper(std::move(file)),
|
||||
write_io_priority_(io_priority) {}
|
||||
IOStatus Append(const Slice& data, const IOOptions& options,
|
||||
IODebugContext* dbg) override {
|
||||
EXPECT_EQ(options.rate_limiter_priority, write_io_priority_);
|
||||
return target()->Append(data, options, dbg);
|
||||
}
|
||||
IOStatus Append(const Slice& data, const IOOptions& options,
|
||||
const DataVerificationInfo& verification_info,
|
||||
IODebugContext* dbg) override {
|
||||
EXPECT_EQ(options.rate_limiter_priority, write_io_priority_);
|
||||
return target()->Append(data, options, verification_info, dbg);
|
||||
}
|
||||
IOStatus Close(const IOOptions& options, IODebugContext* dbg) override {
|
||||
EXPECT_EQ(options.rate_limiter_priority, write_io_priority_);
|
||||
return target()->Close(options, dbg);
|
||||
}
|
||||
IOStatus Flush(const IOOptions& options, IODebugContext* dbg) override {
|
||||
EXPECT_EQ(options.rate_limiter_priority, write_io_priority_);
|
||||
return target()->Flush(options, dbg);
|
||||
}
|
||||
IOStatus Sync(const IOOptions& options, IODebugContext* dbg) override {
|
||||
EXPECT_EQ(options.rate_limiter_priority, write_io_priority_);
|
||||
return target()->Sync(options, dbg);
|
||||
}
|
||||
IOStatus Fsync(const IOOptions& options, IODebugContext* dbg) override {
|
||||
EXPECT_EQ(options.rate_limiter_priority, write_io_priority_);
|
||||
return target()->Fsync(options, dbg);
|
||||
}
|
||||
uint64_t GetFileSize(const IOOptions& options, IODebugContext* dbg) override {
|
||||
EXPECT_EQ(options.rate_limiter_priority, write_io_priority_);
|
||||
return target()->GetFileSize(options, dbg);
|
||||
}
|
||||
IOStatus RangeSync(uint64_t offset, uint64_t nbytes, const IOOptions& options,
|
||||
IODebugContext* dbg) override {
|
||||
EXPECT_EQ(options.rate_limiter_priority, write_io_priority_);
|
||||
return target()->RangeSync(offset, nbytes, options, dbg);
|
||||
}
|
||||
|
||||
void PrepareWrite(size_t offset, size_t len, const IOOptions& options,
|
||||
IODebugContext* dbg) override {
|
||||
EXPECT_EQ(options.rate_limiter_priority, write_io_priority_);
|
||||
target()->PrepareWrite(offset, len, options, dbg);
|
||||
}
|
||||
|
||||
IOStatus Allocate(uint64_t offset, uint64_t len, const IOOptions& options,
|
||||
IODebugContext* dbg) override {
|
||||
EXPECT_EQ(options.rate_limiter_priority, write_io_priority_);
|
||||
return target()->Allocate(offset, len, options, dbg);
|
||||
}
|
||||
|
||||
private:
|
||||
Env::IOPriority write_io_priority_;
|
||||
};
|
||||
|
||||
// Mock FSRandomAccessFile for testing io priority.
|
||||
// Only override the essential functions for testing compaction io priority.
|
||||
class MockTestRandomAccessFile : public FSRandomAccessFileOwnerWrapper {
|
||||
public:
|
||||
MockTestRandomAccessFile(std::unique_ptr<FSRandomAccessFile>&& file,
|
||||
Env::IOPriority io_priority)
|
||||
: FSRandomAccessFileOwnerWrapper(std::move(file)),
|
||||
read_io_priority_(io_priority) {}
|
||||
|
||||
IOStatus Read(uint64_t offset, size_t n, const IOOptions& options,
|
||||
Slice* result, char* scratch,
|
||||
IODebugContext* dbg) const override {
|
||||
EXPECT_EQ(options.rate_limiter_priority, read_io_priority_);
|
||||
return target()->Read(offset, n, options, result, scratch, dbg);
|
||||
}
|
||||
IOStatus Prefetch(uint64_t offset, size_t n, const IOOptions& options,
|
||||
IODebugContext* dbg) override {
|
||||
EXPECT_EQ(options.rate_limiter_priority, read_io_priority_);
|
||||
return target()->Prefetch(offset, n, options, dbg);
|
||||
}
|
||||
|
||||
private:
|
||||
Env::IOPriority read_io_priority_;
|
||||
};
|
||||
|
||||
// Mock FileSystem for testing io priority.
|
||||
class MockTestFileSystem : public FileSystemWrapper {
|
||||
public:
|
||||
explicit MockTestFileSystem(const std::shared_ptr<FileSystem>& base,
|
||||
Env::IOPriority read_io_priority,
|
||||
Env::IOPriority write_io_priority)
|
||||
: FileSystemWrapper(base),
|
||||
read_io_priority_(read_io_priority),
|
||||
write_io_priority_(write_io_priority) {}
|
||||
|
||||
static const char* kClassName() { return "MockTestFileSystem"; }
|
||||
const char* Name() const override { return kClassName(); }
|
||||
|
||||
IOStatus NewRandomAccessFile(const std::string& fname,
|
||||
const FileOptions& file_opts,
|
||||
std::unique_ptr<FSRandomAccessFile>* result,
|
||||
IODebugContext* dbg) override {
|
||||
IOStatus s = target()->NewRandomAccessFile(fname, file_opts, result, dbg);
|
||||
EXPECT_OK(s);
|
||||
result->reset(
|
||||
new MockTestRandomAccessFile(std::move(*result), read_io_priority_));
|
||||
return s;
|
||||
}
|
||||
IOStatus NewWritableFile(const std::string& fname,
|
||||
const FileOptions& file_opts,
|
||||
std::unique_ptr<FSWritableFile>* result,
|
||||
IODebugContext* dbg) override {
|
||||
IOStatus s = target()->NewWritableFile(fname, file_opts, result, dbg);
|
||||
EXPECT_OK(s);
|
||||
result->reset(
|
||||
new MockTestWritableFile(std::move(*result), write_io_priority_));
|
||||
return s;
|
||||
}
|
||||
|
||||
private:
|
||||
Env::IOPriority read_io_priority_;
|
||||
Env::IOPriority write_io_priority_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
class CompactionJobTestBase : public testing::Test {
|
||||
protected:
|
||||
CompactionJobTestBase(std::string dbname, const Comparator* ucmp,
|
||||
std::function<std::string(uint64_t)> encode_u64_ts)
|
||||
std::function<std::string(uint64_t)> encode_u64_ts,
|
||||
bool test_io_priority)
|
||||
: dbname_(std::move(dbname)),
|
||||
ucmp_(ucmp),
|
||||
db_options_(),
|
||||
@@ -90,7 +216,8 @@ class CompactionJobTestBase : public testing::Test {
|
||||
shutting_down_(false),
|
||||
mock_table_factory_(new mock::MockTableFactory()),
|
||||
error_handler_(nullptr, db_options_, &mutex_),
|
||||
encode_u64_ts_(std::move(encode_u64_ts)) {
|
||||
encode_u64_ts_(std::move(encode_u64_ts)),
|
||||
test_io_priority_(test_io_priority) {
|
||||
Env* base_env = Env::Default();
|
||||
EXPECT_OK(
|
||||
test::CreateEnvFromSystem(ConfigOptions(), &base_env, &env_guard_));
|
||||
@@ -105,7 +232,12 @@ class CompactionJobTestBase : public testing::Test {
|
||||
db_options_.db_paths.emplace_back(dbname_,
|
||||
std::numeric_limits<uint64_t>::max());
|
||||
cf_options_.comparator = ucmp_;
|
||||
cf_options_.table_factory = mock_table_factory_;
|
||||
if (test_io_priority_) {
|
||||
BlockBasedTableOptions table_options;
|
||||
cf_options_.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
} else {
|
||||
cf_options_.table_factory = mock_table_factory_;
|
||||
}
|
||||
}
|
||||
|
||||
std::string GenerateFileName(uint64_t file_number) {
|
||||
@@ -145,6 +277,33 @@ class CompactionJobTestBase : public testing::Test {
|
||||
return blob_index;
|
||||
}
|
||||
|
||||
// Creates a table with the specificied key value pairs.
|
||||
void CreateTable(const std::string& table_name,
|
||||
const mock::KVVector& contents, uint64_t& file_size) {
|
||||
std::unique_ptr<WritableFileWriter> file_writer;
|
||||
Status s = WritableFileWriter::Create(fs_, table_name, FileOptions(),
|
||||
&file_writer, nullptr);
|
||||
ASSERT_OK(s);
|
||||
std::unique_ptr<TableBuilder> table_builder(
|
||||
cf_options_.table_factory->NewTableBuilder(
|
||||
TableBuilderOptions(*cfd_->ioptions(), mutable_cf_options_,
|
||||
cfd_->internal_comparator(),
|
||||
cfd_->int_tbl_prop_collector_factories(),
|
||||
CompressionType::kNoCompression,
|
||||
CompressionOptions(), 0 /* column_family_id */,
|
||||
kDefaultColumnFamilyName, -1 /* level */),
|
||||
file_writer.get()));
|
||||
// Build table.
|
||||
for (auto kv : contents) {
|
||||
std::string key;
|
||||
std::string value;
|
||||
std::tie(key, value) = kv;
|
||||
table_builder->Add(key, value);
|
||||
}
|
||||
ASSERT_OK(table_builder->Finish());
|
||||
file_size = table_builder->FileSize();
|
||||
}
|
||||
|
||||
void AddMockFile(const mock::KVVector& contents, int level = 0) {
|
||||
assert(contents.size() > 0);
|
||||
|
||||
@@ -198,11 +357,18 @@ class CompactionJobTestBase : public testing::Test {
|
||||
}
|
||||
|
||||
uint64_t file_number = versions_->NewFileNumber();
|
||||
EXPECT_OK(mock_table_factory_->CreateMockTable(
|
||||
env_, GenerateFileName(file_number), std::move(contents)));
|
||||
|
||||
uint64_t file_size;
|
||||
if (test_io_priority_) {
|
||||
CreateTable(GenerateFileName(file_number), contents, file_size);
|
||||
} else {
|
||||
file_size = 10;
|
||||
EXPECT_OK(mock_table_factory_->CreateMockTable(
|
||||
env_, GenerateFileName(file_number), std::move(contents)));
|
||||
}
|
||||
|
||||
VersionEdit edit;
|
||||
edit.AddFile(level, file_number, 0, 10, smallest_key, largest_key,
|
||||
edit.AddFile(level, file_number, 0, file_size, smallest_key, largest_key,
|
||||
smallest_seqno, largest_seqno, false, Temperature::kUnknown,
|
||||
oldest_blob_file_number, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
@@ -323,7 +489,15 @@ class CompactionJobTestBase : public testing::Test {
|
||||
SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber,
|
||||
int output_level = 1, bool verify = true,
|
||||
uint64_t expected_oldest_blob_file_number = kInvalidBlobFileNumber,
|
||||
bool check_get_priority = false) {
|
||||
bool check_get_priority = false,
|
||||
Env::IOPriority read_io_priority = Env::IO_TOTAL,
|
||||
Env::IOPriority write_io_priority = Env::IO_TOTAL) {
|
||||
// For compaction, set fs as MockTestFileSystem to check the io_priority.
|
||||
if (test_io_priority_) {
|
||||
db_options_.fs.reset(
|
||||
new MockTestFileSystem(fs_, read_io_priority, write_io_priority));
|
||||
}
|
||||
|
||||
auto cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
|
||||
size_t num_input_files = 0;
|
||||
@@ -353,6 +527,7 @@ class CompactionJobTestBase : public testing::Test {
|
||||
SnapshotChecker* snapshot_checker = nullptr;
|
||||
ASSERT_TRUE(full_history_ts_low_.empty() ||
|
||||
ucmp_->timestamp_size() == full_history_ts_low_.size());
|
||||
const std::atomic<bool> kManualCompactionCanceledFalse{false};
|
||||
CompactionJob compaction_job(
|
||||
0, &compaction, db_options_, mutable_db_options_, env_options_,
|
||||
versions_.get(), &shutting_down_, &log_buffer, nullptr, nullptr,
|
||||
@@ -360,9 +535,9 @@ class CompactionJobTestBase : public testing::Test {
|
||||
earliest_write_conflict_snapshot, snapshot_checker, nullptr,
|
||||
table_cache_, &event_logger, false, false, dbname_,
|
||||
&compaction_job_stats_, Env::Priority::USER, nullptr /* IOTracer */,
|
||||
/*manual_compaction_paused=*/nullptr,
|
||||
/*manual_compaction_canceled=*/nullptr, env_->GenerateUniqueId(),
|
||||
DBImpl::GenerateDbSessionId(nullptr), full_history_ts_low_);
|
||||
/*manual_compaction_canceled=*/kManualCompactionCanceledFalse,
|
||||
env_->GenerateUniqueId(), DBImpl::GenerateDbSessionId(nullptr),
|
||||
full_history_ts_low_);
|
||||
VerifyInitializationOfCompactionJobStats(compaction_job_stats_);
|
||||
|
||||
compaction_job.Prepare();
|
||||
@@ -444,15 +619,16 @@ class CompactionJobTestBase : public testing::Test {
|
||||
ErrorHandler error_handler_;
|
||||
std::string full_history_ts_low_;
|
||||
const std::function<std::string(uint64_t)> encode_u64_ts_;
|
||||
bool test_io_priority_;
|
||||
};
|
||||
|
||||
// TODO(icanadi) Make it simpler once we mock out VersionSet
|
||||
class CompactionJobTest : public CompactionJobTestBase {
|
||||
public:
|
||||
CompactionJobTest()
|
||||
: CompactionJobTestBase(test::PerThreadDBPath("compaction_job_test"),
|
||||
BytewiseComparator(),
|
||||
[](uint64_t /*ts*/) { return ""; }) {}
|
||||
: CompactionJobTestBase(
|
||||
test::PerThreadDBPath("compaction_job_test"), BytewiseComparator(),
|
||||
[](uint64_t /*ts*/) { return ""; }, false) {}
|
||||
};
|
||||
|
||||
TEST_F(CompactionJobTest, Simple) {
|
||||
@@ -1342,23 +1518,13 @@ TEST_F(CompactionJobTest, ResultSerialization) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompactionJobTest, GetRateLimiterPriority) {
|
||||
NewDB();
|
||||
|
||||
auto expected_results = CreateTwoFiles(false);
|
||||
auto cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
auto files = cfd->current()->storage_info()->LevelFiles(0);
|
||||
ASSERT_EQ(2U, files.size());
|
||||
RunCompaction({files}, expected_results, {}, kMaxSequenceNumber, 1, true,
|
||||
kInvalidBlobFileNumber, true);
|
||||
}
|
||||
|
||||
class CompactionJobTimestampTest : public CompactionJobTestBase {
|
||||
public:
|
||||
CompactionJobTimestampTest()
|
||||
: CompactionJobTestBase(test::PerThreadDBPath("compaction_job_ts_test"),
|
||||
test::BytewiseComparatorWithU64TsWrapper(),
|
||||
test::EncodeInt) {}
|
||||
test::EncodeInt, false) {}
|
||||
};
|
||||
|
||||
TEST_F(CompactionJobTimestampTest, GCDisabled) {
|
||||
@@ -1474,6 +1640,69 @@ TEST_F(CompactionJobTimestampTest, SomeKeysExpired) {
|
||||
RunCompaction({files}, expected_results);
|
||||
}
|
||||
|
||||
// The io priority of the compaction reads and writes are different from
|
||||
// other DB reads and writes. To prepare the compaction input files, use the
|
||||
// default filesystem from Env. To test the io priority of the compaction
|
||||
// reads and writes, db_options_.fs is set as MockTestFileSystem.
|
||||
class CompactionJobIOPriorityTest : public CompactionJobTestBase {
|
||||
public:
|
||||
CompactionJobIOPriorityTest()
|
||||
: CompactionJobTestBase(
|
||||
test::PerThreadDBPath("compaction_job_io_priority_test"),
|
||||
BytewiseComparator(), [](uint64_t /*ts*/) { return ""; }, true) {}
|
||||
};
|
||||
|
||||
TEST_F(CompactionJobIOPriorityTest, WriteControllerStateNormal) {
|
||||
// When the state from WriteController is normal.
|
||||
NewDB();
|
||||
mock::KVVector expected_results = CreateTwoFiles(false);
|
||||
auto cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
auto files = cfd->current()->storage_info()->LevelFiles(0);
|
||||
ASSERT_EQ(2U, files.size());
|
||||
RunCompaction({files}, expected_results, {}, kMaxSequenceNumber, 1, false,
|
||||
kInvalidBlobFileNumber, false, Env::IO_LOW, Env::IO_LOW);
|
||||
}
|
||||
|
||||
TEST_F(CompactionJobIOPriorityTest, WriteControllerStateDelayed) {
|
||||
// When the state from WriteController is Delayed.
|
||||
NewDB();
|
||||
mock::KVVector expected_results = CreateTwoFiles(false);
|
||||
auto cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
auto files = cfd->current()->storage_info()->LevelFiles(0);
|
||||
ASSERT_EQ(2U, files.size());
|
||||
{
|
||||
std::unique_ptr<WriteControllerToken> delay_token =
|
||||
write_controller_.GetDelayToken(1000000);
|
||||
RunCompaction({files}, expected_results, {}, kMaxSequenceNumber, 1, false,
|
||||
kInvalidBlobFileNumber, false, Env::IO_USER, Env::IO_USER);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompactionJobIOPriorityTest, WriteControllerStateStalled) {
|
||||
// When the state from WriteController is Stalled.
|
||||
NewDB();
|
||||
mock::KVVector expected_results = CreateTwoFiles(false);
|
||||
auto cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
auto files = cfd->current()->storage_info()->LevelFiles(0);
|
||||
ASSERT_EQ(2U, files.size());
|
||||
{
|
||||
std::unique_ptr<WriteControllerToken> stop_token =
|
||||
write_controller_.GetStopToken();
|
||||
RunCompaction({files}, expected_results, {}, kMaxSequenceNumber, 1, false,
|
||||
kInvalidBlobFileNumber, false, Env::IO_USER, Env::IO_USER);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompactionJobIOPriorityTest, GetRateLimiterPriority) {
|
||||
NewDB();
|
||||
mock::KVVector expected_results = CreateTwoFiles(false);
|
||||
auto cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
auto files = cfd->current()->storage_info()->LevelFiles(0);
|
||||
ASSERT_EQ(2U, files.size());
|
||||
RunCompaction({files}, expected_results, {}, kMaxSequenceNumber, 1, false,
|
||||
kInvalidBlobFileNumber, true, Env::IO_LOW, Env::IO_LOW);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -640,7 +640,11 @@ Compaction* CompactionPicker::CompactRange(
|
||||
GetCompressionType(vstorage, mutable_cf_options, output_level, 1),
|
||||
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
|
||||
Temperature::kUnknown, compact_range_options.max_subcompactions,
|
||||
/* grandparents */ {}, /* is manual */ true, trim_ts);
|
||||
/* grandparents */ {}, /* is manual */ true, trim_ts, /* score */ -1,
|
||||
/* deletion_compaction */ false, CompactionReason::kUnknown,
|
||||
compact_range_options.blob_garbage_collection_policy,
|
||||
compact_range_options.blob_garbage_collection_age_cutoff);
|
||||
|
||||
RegisterCompaction(c);
|
||||
vstorage->ComputeCompactionScore(ioptions_, mutable_cf_options);
|
||||
return c;
|
||||
@@ -818,8 +822,10 @@ Compaction* CompactionPicker::CompactRange(
|
||||
vstorage->base_level()),
|
||||
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
|
||||
Temperature::kUnknown, compact_range_options.max_subcompactions,
|
||||
std::move(grandparents),
|
||||
/* is manual compaction */ true, trim_ts);
|
||||
std::move(grandparents), /* is manual */ true, trim_ts, /* score */ -1,
|
||||
/* deletion_compaction */ false, CompactionReason::kUnknown,
|
||||
compact_range_options.blob_garbage_collection_policy,
|
||||
compact_range_options.blob_garbage_collection_age_cutoff);
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionPicker::CompactRange:Return", compaction);
|
||||
RegisterCompaction(compaction);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "table/unique_id_impl.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -294,8 +295,8 @@ TEST_F(CompactionServiceTest, BasicCompactions) {
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"Version::VerifySstUniqueIds::Passed", [&](void* arg) {
|
||||
// override job status
|
||||
auto id = static_cast<std::string*>(arg);
|
||||
assert(!id->empty());
|
||||
auto id = static_cast<UniqueId64x2*>(arg);
|
||||
assert(*id != kNullUniqueId64x2);
|
||||
verify_passed++;
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
+32
-14
@@ -449,67 +449,85 @@ TEST_P(ComparatorDBTest, TwoStrComparator) {
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
void VerifyNotSuccessor(const Slice& s, const Slice& t) {
|
||||
auto bc = BytewiseComparator();
|
||||
auto rbc = ReverseBytewiseComparator();
|
||||
ASSERT_FALSE(bc->IsSameLengthImmediateSuccessor(s, t));
|
||||
ASSERT_FALSE(rbc->IsSameLengthImmediateSuccessor(s, t));
|
||||
ASSERT_FALSE(bc->IsSameLengthImmediateSuccessor(t, s));
|
||||
ASSERT_FALSE(rbc->IsSameLengthImmediateSuccessor(t, s));
|
||||
}
|
||||
|
||||
void VerifySuccessor(const Slice& s, const Slice& t) {
|
||||
auto bc = BytewiseComparator();
|
||||
auto rbc = ReverseBytewiseComparator();
|
||||
ASSERT_TRUE(bc->IsSameLengthImmediateSuccessor(s, t));
|
||||
ASSERT_FALSE(rbc->IsSameLengthImmediateSuccessor(s, t));
|
||||
ASSERT_FALSE(bc->IsSameLengthImmediateSuccessor(t, s));
|
||||
// Should be true but that increases exposure to a design bug in
|
||||
// auto_prefix_mode, so currently set to FALSE
|
||||
ASSERT_FALSE(rbc->IsSameLengthImmediateSuccessor(t, s));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_P(ComparatorDBTest, IsSameLengthImmediateSuccessor) {
|
||||
{
|
||||
// different length
|
||||
Slice s("abcxy");
|
||||
Slice t("abcxyz");
|
||||
ASSERT_FALSE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t));
|
||||
VerifyNotSuccessor(s, t);
|
||||
}
|
||||
{
|
||||
Slice s("abcxyz");
|
||||
Slice t("abcxy");
|
||||
ASSERT_FALSE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t));
|
||||
VerifyNotSuccessor(s, t);
|
||||
}
|
||||
{
|
||||
// not last byte different
|
||||
Slice s("abc1xyz");
|
||||
Slice t("abc2xyz");
|
||||
ASSERT_FALSE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t));
|
||||
VerifyNotSuccessor(s, t);
|
||||
}
|
||||
{
|
||||
// same string
|
||||
Slice s("abcxyz");
|
||||
Slice t("abcxyz");
|
||||
ASSERT_FALSE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t));
|
||||
VerifyNotSuccessor(s, t);
|
||||
}
|
||||
{
|
||||
Slice s("abcxy");
|
||||
Slice t("abcxz");
|
||||
ASSERT_TRUE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t));
|
||||
}
|
||||
{
|
||||
Slice s("abcxz");
|
||||
Slice t("abcxy");
|
||||
ASSERT_FALSE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t));
|
||||
VerifySuccessor(s, t);
|
||||
}
|
||||
{
|
||||
const char s_array[] = "\x50\x8a\xac";
|
||||
const char t_array[] = "\x50\x8a\xad";
|
||||
Slice s(s_array);
|
||||
Slice t(t_array);
|
||||
ASSERT_TRUE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t));
|
||||
VerifySuccessor(s, t);
|
||||
}
|
||||
{
|
||||
const char s_array[] = "\x50\x8a\xff";
|
||||
const char t_array[] = "\x50\x8b\x00";
|
||||
Slice s(s_array, 3);
|
||||
Slice t(t_array, 3);
|
||||
ASSERT_TRUE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t));
|
||||
VerifySuccessor(s, t);
|
||||
}
|
||||
{
|
||||
const char s_array[] = "\x50\x8a\xff\xff";
|
||||
const char t_array[] = "\x50\x8b\x00\x00";
|
||||
Slice s(s_array, 4);
|
||||
Slice t(t_array, 4);
|
||||
ASSERT_TRUE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t));
|
||||
VerifySuccessor(s, t);
|
||||
}
|
||||
{
|
||||
const char s_array[] = "\x50\x8a\xff\xff";
|
||||
const char t_array[] = "\x50\x8b\x00\x01";
|
||||
Slice s(s_array, 4);
|
||||
Slice t(t_array, 4);
|
||||
ASSERT_FALSE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t));
|
||||
VerifyNotSuccessor(s, t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+67
-10
@@ -1065,7 +1065,7 @@ INSTANTIATE_TEST_CASE_P(CorruptionTest, CrashDuringRecoveryWithCorruptionTest,
|
||||
// The combination of corrupting a WAL and injecting an error during subsequent
|
||||
// re-open exposes the bug of prematurely persisting a new MANIFEST with
|
||||
// advanced ColumnFamilyData::log_number.
|
||||
TEST_P(CrashDuringRecoveryWithCorruptionTest, DISABLED_CrashDuringRecovery) {
|
||||
TEST_P(CrashDuringRecoveryWithCorruptionTest, CrashDuringRecovery) {
|
||||
CloseDb();
|
||||
Options options;
|
||||
options.track_and_verify_wals_in_manifest =
|
||||
@@ -1107,7 +1107,8 @@ TEST_P(CrashDuringRecoveryWithCorruptionTest, DISABLED_CrashDuringRecovery) {
|
||||
// number. TEST_SwitchMemtable makes sure WALs are not synced and test can
|
||||
// corrupt un-sync WAL.
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "key" + std::to_string(i), "value"));
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "key" + std::to_string(i),
|
||||
"value" + std::to_string(i)));
|
||||
ASSERT_OK(dbimpl->TEST_SwitchMemtable());
|
||||
}
|
||||
|
||||
@@ -1188,6 +1189,23 @@ TEST_P(CrashDuringRecoveryWithCorruptionTest, DISABLED_CrashDuringRecovery) {
|
||||
{
|
||||
options.avoid_flush_during_recovery = avoid_flush_during_recovery_;
|
||||
ASSERT_OK(DB::Open(options, dbname_, cf_descs, &handles, &db_));
|
||||
|
||||
// Verify that data is not lost.
|
||||
{
|
||||
std::string v;
|
||||
ASSERT_OK(db_->Get(ReadOptions(), handles[1], "old_key", &v));
|
||||
ASSERT_EQ("dontcare", v);
|
||||
|
||||
v.clear();
|
||||
ASSERT_OK(db_->Get(ReadOptions(), "key" + std::to_string(0), &v));
|
||||
ASSERT_EQ("value" + std::to_string(0), v);
|
||||
|
||||
// Since it's corrupting second last wal, below key is not found.
|
||||
v.clear();
|
||||
ASSERT_EQ(db_->Get(ReadOptions(), "key" + std::to_string(1), &v),
|
||||
Status::NotFound());
|
||||
}
|
||||
|
||||
for (auto* h : handles) {
|
||||
delete h;
|
||||
}
|
||||
@@ -1219,8 +1237,7 @@ TEST_P(CrashDuringRecoveryWithCorruptionTest, DISABLED_CrashDuringRecovery) {
|
||||
// The combination of corrupting a WAL and injecting an error during subsequent
|
||||
// re-open exposes the bug of prematurely persisting a new MANIFEST with
|
||||
// advanced ColumnFamilyData::log_number.
|
||||
TEST_P(CrashDuringRecoveryWithCorruptionTest,
|
||||
DISABLED_TxnDbCrashDuringRecovery) {
|
||||
TEST_P(CrashDuringRecoveryWithCorruptionTest, TxnDbCrashDuringRecovery) {
|
||||
CloseDb();
|
||||
Options options;
|
||||
options.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery;
|
||||
@@ -1271,13 +1288,14 @@ TEST_P(CrashDuringRecoveryWithCorruptionTest,
|
||||
|
||||
// Put and flush cf0
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
ASSERT_OK(txn_db->Put(WriteOptions(), "dontcare", "value"));
|
||||
ASSERT_OK(txn_db->Put(WriteOptions(), "key" + std::to_string(i),
|
||||
"value" + std::to_string(i)));
|
||||
ASSERT_OK(dbimpl->TEST_SwitchMemtable());
|
||||
}
|
||||
|
||||
// Put cf1
|
||||
txn = txn_db->BeginTransaction(WriteOptions(), TransactionOptions());
|
||||
ASSERT_OK(txn->Put(handles[1], "foo1", "value"));
|
||||
ASSERT_OK(txn->Put(handles[1], "foo1", "value1"));
|
||||
ASSERT_OK(txn->Commit());
|
||||
|
||||
delete txn;
|
||||
@@ -1337,7 +1355,6 @@ TEST_P(CrashDuringRecoveryWithCorruptionTest,
|
||||
std::vector<uint64_t> file_nums;
|
||||
GetSortedWalFiles(file_nums);
|
||||
size_t size = file_nums.size();
|
||||
assert(size >= 2);
|
||||
uint64_t log_num = file_nums[size - 1];
|
||||
CorruptFileWithTruncation(FileType::kWalFile, log_num);
|
||||
}
|
||||
@@ -1354,6 +1371,27 @@ TEST_P(CrashDuringRecoveryWithCorruptionTest,
|
||||
{
|
||||
ASSERT_OK(TransactionDB::Open(options, txn_db_opts, dbname_, cf_descs,
|
||||
&handles, &txn_db));
|
||||
|
||||
// Verify that data is not lost.
|
||||
{
|
||||
std::string v;
|
||||
// Key not visible since it's not committed.
|
||||
ASSERT_EQ(txn_db->Get(ReadOptions(), handles[1], "foo", &v),
|
||||
Status::NotFound());
|
||||
|
||||
v.clear();
|
||||
ASSERT_OK(txn_db->Get(ReadOptions(), "key" + std::to_string(0), &v));
|
||||
ASSERT_EQ("value" + std::to_string(0), v);
|
||||
|
||||
// Last WAL is corrupted which contains two keys below.
|
||||
v.clear();
|
||||
ASSERT_EQ(txn_db->Get(ReadOptions(), "key" + std::to_string(1), &v),
|
||||
Status::NotFound());
|
||||
v.clear();
|
||||
ASSERT_EQ(txn_db->Get(ReadOptions(), handles[1], "foo1", &v),
|
||||
Status::NotFound());
|
||||
}
|
||||
|
||||
for (auto* h : handles) {
|
||||
delete h;
|
||||
}
|
||||
@@ -1396,8 +1434,7 @@ TEST_P(CrashDuringRecoveryWithCorruptionTest,
|
||||
// The combination of corrupting a WAL and injecting an error during subsequent
|
||||
// re-open exposes the bug of prematurely persisting a new MANIFEST with
|
||||
// advanced ColumnFamilyData::log_number.
|
||||
TEST_P(CrashDuringRecoveryWithCorruptionTest,
|
||||
DISABLED_CrashDuringRecoveryWithFlush) {
|
||||
TEST_P(CrashDuringRecoveryWithCorruptionTest, CrashDuringRecoveryWithFlush) {
|
||||
CloseDb();
|
||||
Options options;
|
||||
options.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery;
|
||||
@@ -1430,7 +1467,8 @@ TEST_P(CrashDuringRecoveryWithCorruptionTest,
|
||||
// Write to default_cf and flush this cf several times to advance wal
|
||||
// number.
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "key" + std::to_string(i), "value"));
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "key" + std::to_string(i),
|
||||
"value" + std::to_string(i)));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
}
|
||||
|
||||
@@ -1483,6 +1521,25 @@ TEST_P(CrashDuringRecoveryWithCorruptionTest,
|
||||
{
|
||||
options.avoid_flush_during_recovery = avoid_flush_during_recovery_;
|
||||
ASSERT_OK(DB::Open(options, dbname_, cf_descs, &handles, &db_));
|
||||
|
||||
// Verify that data is not lost.
|
||||
{
|
||||
std::string v;
|
||||
ASSERT_OK(db_->Get(ReadOptions(), handles[1], "old_key", &v));
|
||||
ASSERT_EQ("dontcare", v);
|
||||
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
v.clear();
|
||||
ASSERT_OK(db_->Get(ReadOptions(), "key" + std::to_string(i), &v));
|
||||
ASSERT_EQ("value" + std::to_string(i), v);
|
||||
}
|
||||
|
||||
// Since it's corrupting last wal after Flush, below key is not found.
|
||||
v.clear();
|
||||
ASSERT_EQ(db_->Get(ReadOptions(), handles[1], "dontcare", &v),
|
||||
Status::NotFound());
|
||||
}
|
||||
|
||||
for (auto* h : handles) {
|
||||
delete h;
|
||||
}
|
||||
|
||||
+173
-54
@@ -25,6 +25,7 @@
|
||||
#endif
|
||||
#include "util/file_checksum_helper.h"
|
||||
#include "util/random.h"
|
||||
#include "utilities/counted_fs.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
#include "utilities/merge_operators/string_append/stringappend.h"
|
||||
@@ -578,55 +579,84 @@ TEST_F(DBBasicTest, ManifestRollOver) {
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, IdentityAcrossRestarts1) {
|
||||
TEST_F(DBBasicTest, IdentityAcrossRestarts) {
|
||||
constexpr size_t kMinIdSize = 10;
|
||||
do {
|
||||
std::string id1;
|
||||
ASSERT_OK(db_->GetDbIdentity(id1));
|
||||
for (bool with_manifest : {false, true}) {
|
||||
std::string idfilename = IdentityFileName(dbname_);
|
||||
std::string id1, tmp;
|
||||
ASSERT_OK(db_->GetDbIdentity(id1));
|
||||
ASSERT_GE(id1.size(), kMinIdSize);
|
||||
|
||||
Options options = CurrentOptions();
|
||||
Reopen(options);
|
||||
std::string id2;
|
||||
ASSERT_OK(db_->GetDbIdentity(id2));
|
||||
// id1 should match id2 because identity was not regenerated
|
||||
ASSERT_EQ(id1.compare(id2), 0);
|
||||
Options options = CurrentOptions();
|
||||
options.write_dbid_to_manifest = with_manifest;
|
||||
Reopen(options);
|
||||
std::string id2;
|
||||
ASSERT_OK(db_->GetDbIdentity(id2));
|
||||
// id2 should match id1 because identity was not regenerated
|
||||
ASSERT_EQ(id1, id2);
|
||||
ASSERT_OK(ReadFileToString(env_, idfilename, &tmp));
|
||||
ASSERT_EQ(tmp, id2);
|
||||
|
||||
std::string idfilename = IdentityFileName(dbname_);
|
||||
ASSERT_OK(env_->DeleteFile(idfilename));
|
||||
Reopen(options);
|
||||
std::string id3;
|
||||
ASSERT_OK(db_->GetDbIdentity(id3));
|
||||
if (options.write_dbid_to_manifest) {
|
||||
ASSERT_EQ(id1.compare(id3), 0);
|
||||
} else {
|
||||
// id1 should NOT match id3 because identity was regenerated
|
||||
ASSERT_NE(id1.compare(id3), 0);
|
||||
// Recover from deleted/missing IDENTITY
|
||||
ASSERT_OK(env_->DeleteFile(idfilename));
|
||||
Reopen(options);
|
||||
std::string id3;
|
||||
ASSERT_OK(db_->GetDbIdentity(id3));
|
||||
if (with_manifest) {
|
||||
// id3 should match id1 because identity was restored from manifest
|
||||
ASSERT_EQ(id1, id3);
|
||||
} else {
|
||||
// id3 should NOT match id1 because identity was regenerated
|
||||
ASSERT_NE(id1, id3);
|
||||
ASSERT_GE(id3.size(), kMinIdSize);
|
||||
}
|
||||
ASSERT_OK(ReadFileToString(env_, idfilename, &tmp));
|
||||
ASSERT_EQ(tmp, id3);
|
||||
|
||||
// Recover from truncated IDENTITY
|
||||
{
|
||||
std::unique_ptr<WritableFile> w;
|
||||
ASSERT_OK(env_->NewWritableFile(idfilename, &w, EnvOptions()));
|
||||
ASSERT_OK(w->Close());
|
||||
}
|
||||
Reopen(options);
|
||||
std::string id4;
|
||||
ASSERT_OK(db_->GetDbIdentity(id4));
|
||||
if (with_manifest) {
|
||||
// id4 should match id1 because identity was restored from manifest
|
||||
ASSERT_EQ(id1, id4);
|
||||
} else {
|
||||
// id4 should NOT match id1 because identity was regenerated
|
||||
ASSERT_NE(id1, id4);
|
||||
ASSERT_GE(id4.size(), kMinIdSize);
|
||||
}
|
||||
ASSERT_OK(ReadFileToString(env_, idfilename, &tmp));
|
||||
ASSERT_EQ(tmp, id4);
|
||||
|
||||
// Recover from overwritten IDENTITY
|
||||
std::string silly_id = "asdf123456789";
|
||||
{
|
||||
std::unique_ptr<WritableFile> w;
|
||||
ASSERT_OK(env_->NewWritableFile(idfilename, &w, EnvOptions()));
|
||||
ASSERT_OK(w->Append(silly_id));
|
||||
ASSERT_OK(w->Close());
|
||||
}
|
||||
Reopen(options);
|
||||
std::string id5;
|
||||
ASSERT_OK(db_->GetDbIdentity(id5));
|
||||
if (with_manifest) {
|
||||
// id4 should match id1 because identity was restored from manifest
|
||||
ASSERT_EQ(id1, id5);
|
||||
} else {
|
||||
ASSERT_EQ(id5, silly_id);
|
||||
}
|
||||
ASSERT_OK(ReadFileToString(env_, idfilename, &tmp));
|
||||
ASSERT_EQ(tmp, id5);
|
||||
}
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, IdentityAcrossRestarts2) {
|
||||
do {
|
||||
std::string id1;
|
||||
ASSERT_OK(db_->GetDbIdentity(id1));
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.write_dbid_to_manifest = true;
|
||||
Reopen(options);
|
||||
std::string id2;
|
||||
ASSERT_OK(db_->GetDbIdentity(id2));
|
||||
// id1 should match id2 because identity was not regenerated
|
||||
ASSERT_EQ(id1.compare(id2), 0);
|
||||
|
||||
std::string idfilename = IdentityFileName(dbname_);
|
||||
ASSERT_OK(env_->DeleteFile(idfilename));
|
||||
Reopen(options);
|
||||
std::string id3;
|
||||
ASSERT_OK(db_->GetDbIdentity(id3));
|
||||
// id1 should NOT match id3 because identity was regenerated
|
||||
ASSERT_EQ(id1, id3);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBBasicTest, Snapshot) {
|
||||
env_->SetMockSleep();
|
||||
@@ -1153,6 +1183,39 @@ TEST_F(DBBasicTest, DBClose) {
|
||||
ASSERT_EQ(env->GetCloseCount(), 3);
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, DBCloseAllDirectoryFDs) {
|
||||
Options options = GetDefaultOptions();
|
||||
std::string dbname = test::PerThreadDBPath("db_close_all_dir_fds_test");
|
||||
// Configure a specific WAL directory
|
||||
options.wal_dir = dbname + "_wal_dir";
|
||||
// Configure 3 different data directories
|
||||
options.db_paths.emplace_back(dbname + "_1", 512 * 1024);
|
||||
options.db_paths.emplace_back(dbname + "_2", 4 * 1024 * 1024);
|
||||
options.db_paths.emplace_back(dbname + "_3", 1024 * 1024 * 1024);
|
||||
|
||||
ASSERT_OK(DestroyDB(dbname, options));
|
||||
|
||||
DB* db = nullptr;
|
||||
std::unique_ptr<Env> env = NewCompositeEnv(
|
||||
std::make_shared<CountedFileSystem>(FileSystem::Default()));
|
||||
options.create_if_missing = true;
|
||||
options.env = env.get();
|
||||
Status s = DB::Open(options, dbname, &db);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(db != nullptr);
|
||||
|
||||
// Explicitly close the database to ensure the open and close counter for
|
||||
// directories are equivalent
|
||||
s = db->Close();
|
||||
auto* counted_fs =
|
||||
options.env->GetFileSystem()->CheckedCast<CountedFileSystem>();
|
||||
ASSERT_TRUE(counted_fs != nullptr);
|
||||
ASSERT_EQ(counted_fs->counters()->dir_opens,
|
||||
counted_fs->counters()->dir_closes);
|
||||
ASSERT_OK(s);
|
||||
delete db;
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, DBCloseFlushError) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_injection_env(
|
||||
new FaultInjectionTestEnv(env_));
|
||||
@@ -1467,7 +1530,7 @@ TEST_P(DBMultiGetTestWithParam, MultiGetBatchedSimpleUnsorted) {
|
||||
#endif // USE_COROUTINES
|
||||
// Skip for unbatched MultiGet
|
||||
if (!std::get<0>(GetParam())) {
|
||||
ROCKSDB_GTEST_SKIP("This test is only for batched MultiGet");
|
||||
ROCKSDB_GTEST_BYPASS("This test is only for batched MultiGet");
|
||||
return;
|
||||
}
|
||||
do {
|
||||
@@ -1521,7 +1584,7 @@ TEST_P(DBMultiGetTestWithParam, MultiGetBatchedSortedMultiFile) {
|
||||
#endif // USE_COROUTINES
|
||||
// Skip for unbatched MultiGet
|
||||
if (!std::get<0>(GetParam())) {
|
||||
ROCKSDB_GTEST_SKIP("This test is only for batched MultiGet");
|
||||
ROCKSDB_GTEST_BYPASS("This test is only for batched MultiGet");
|
||||
return;
|
||||
}
|
||||
do {
|
||||
@@ -1579,7 +1642,7 @@ TEST_P(DBMultiGetTestWithParam, MultiGetBatchedDuplicateKeys) {
|
||||
#endif // USE_COROUTINES
|
||||
// Skip for unbatched MultiGet
|
||||
if (!std::get<0>(GetParam())) {
|
||||
ROCKSDB_GTEST_SKIP("This test is only for batched MultiGet");
|
||||
ROCKSDB_GTEST_BYPASS("This test is only for batched MultiGet");
|
||||
return;
|
||||
}
|
||||
Options opts = CurrentOptions();
|
||||
@@ -1643,7 +1706,7 @@ TEST_P(DBMultiGetTestWithParam, MultiGetBatchedMultiLevel) {
|
||||
#endif // USE_COROUTINES
|
||||
// Skip for unbatched MultiGet
|
||||
if (!std::get<0>(GetParam())) {
|
||||
ROCKSDB_GTEST_SKIP("This test is only for batched MultiGet");
|
||||
ROCKSDB_GTEST_BYPASS("This test is only for batched MultiGet");
|
||||
return;
|
||||
}
|
||||
Options options = CurrentOptions();
|
||||
@@ -1729,7 +1792,7 @@ TEST_P(DBMultiGetTestWithParam, MultiGetBatchedMultiLevelMerge) {
|
||||
#endif // USE_COROUTINES
|
||||
// Skip for unbatched MultiGet
|
||||
if (!std::get<0>(GetParam())) {
|
||||
ROCKSDB_GTEST_SKIP("This test is only for batched MultiGet");
|
||||
ROCKSDB_GTEST_BYPASS("This test is only for batched MultiGet");
|
||||
return;
|
||||
}
|
||||
Options options = CurrentOptions();
|
||||
@@ -1826,7 +1889,7 @@ TEST_P(DBMultiGetTestWithParam, MultiGetBatchedValueSizeInMemory) {
|
||||
#endif // USE_COROUTINES
|
||||
// Skip for unbatched MultiGet
|
||||
if (!std::get<0>(GetParam())) {
|
||||
ROCKSDB_GTEST_SKIP("This test is only for batched MultiGet");
|
||||
ROCKSDB_GTEST_BYPASS("This test is only for batched MultiGet");
|
||||
return;
|
||||
}
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
@@ -1958,7 +2021,7 @@ TEST_P(DBMultiGetTestWithParam, MultiGetBatchedValueSizeMultiLevelMerge) {
|
||||
#endif // USE_COROUTINES
|
||||
// Skip for unbatched MultiGet
|
||||
if (!std::get<0>(GetParam())) {
|
||||
ROCKSDB_GTEST_SKIP("This test is only for batched MultiGet");
|
||||
ROCKSDB_GTEST_BYPASS("This test is only for batched MultiGet");
|
||||
return;
|
||||
}
|
||||
Options options = CurrentOptions();
|
||||
@@ -2158,6 +2221,7 @@ TEST_F(DBMultiGetAsyncIOTest, GetFromL0) {
|
||||
// No async IO in this case since we don't do parallel lookup in L0
|
||||
ASSERT_EQ(multiget_io_batch_size.count, 0);
|
||||
ASSERT_EQ(multiget_io_batch_size.max, 0);
|
||||
ASSERT_EQ(statistics()->getTickerCount(MULTIGET_COROUTINE_COUNT), 0);
|
||||
}
|
||||
|
||||
TEST_F(DBMultiGetAsyncIOTest, GetFromL1) {
|
||||
@@ -2194,6 +2258,7 @@ TEST_F(DBMultiGetAsyncIOTest, GetFromL1) {
|
||||
// A batch of 3 async IOs is expected, one for each overlapping file in L1
|
||||
ASSERT_EQ(multiget_io_batch_size.count, 1);
|
||||
ASSERT_EQ(multiget_io_batch_size.max, 3);
|
||||
ASSERT_EQ(statistics()->getTickerCount(MULTIGET_COROUTINE_COUNT), 3);
|
||||
}
|
||||
|
||||
TEST_F(DBMultiGetAsyncIOTest, LastKeyInFile) {
|
||||
@@ -2344,27 +2409,37 @@ TEST_F(DBBasicTest, MultiGetStats) {
|
||||
values.data(), s.data(), false);
|
||||
|
||||
ASSERT_EQ(values.size(), kMultiGetBatchSize);
|
||||
HistogramData hist_data_blocks;
|
||||
HistogramData hist_level;
|
||||
HistogramData hist_index_and_filter_blocks;
|
||||
HistogramData hist_sst;
|
||||
|
||||
options.statistics->histogramData(NUM_DATA_BLOCKS_READ_PER_LEVEL,
|
||||
&hist_data_blocks);
|
||||
options.statistics->histogramData(NUM_LEVEL_READ_PER_MULTIGET, &hist_level);
|
||||
options.statistics->histogramData(NUM_INDEX_AND_FILTER_BLOCKS_READ_PER_LEVEL,
|
||||
&hist_index_and_filter_blocks);
|
||||
options.statistics->histogramData(NUM_SST_READ_PER_LEVEL, &hist_sst);
|
||||
|
||||
// Maximum number of blocks read from a file system in a level.
|
||||
ASSERT_EQ(hist_data_blocks.max, 32);
|
||||
ASSERT_EQ(hist_level.max, 1);
|
||||
ASSERT_GT(hist_index_and_filter_blocks.max, 0);
|
||||
// Maximum number of sst files read from file system in a level.
|
||||
ASSERT_EQ(hist_sst.max, 2);
|
||||
|
||||
// Minimun number of blocks read in a level.
|
||||
ASSERT_EQ(hist_data_blocks.min, 4);
|
||||
ASSERT_EQ(hist_level.min, 1);
|
||||
ASSERT_GT(hist_index_and_filter_blocks.min, 0);
|
||||
// Minimun number of sst files read in a level.
|
||||
ASSERT_EQ(hist_sst.min, 1);
|
||||
|
||||
for (PinnableSlice& value : values) {
|
||||
value.Reset();
|
||||
}
|
||||
for (Status& status : s) {
|
||||
status = Status::OK();
|
||||
}
|
||||
db_->MultiGet(read_opts, handles_[1], kMultiGetBatchSize, &keys[950],
|
||||
values.data(), s.data(), false);
|
||||
options.statistics->histogramData(NUM_LEVEL_READ_PER_MULTIGET, &hist_level);
|
||||
ASSERT_EQ(hist_level.max, 2);
|
||||
}
|
||||
|
||||
// Test class for batched MultiGet with prefix extractor
|
||||
@@ -4053,6 +4128,27 @@ TEST_F(DBBasicTest, DestroyDefaultCfHandle) {
|
||||
ASSERT_TRUE(db_->DestroyColumnFamilyHandle(default_cf).IsInvalidArgument());
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, FailOpenIfLoggerCreationFail) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"rocksdb::CreateLoggerFromOptions:AfterGetPath", [&](void* arg) {
|
||||
auto* s = reinterpret_cast<Status*>(arg);
|
||||
assert(s);
|
||||
*s = Status::IOError("Injected");
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Status s = TryReopen(options);
|
||||
ASSERT_EQ(nullptr, options.info_log);
|
||||
ASSERT_TRUE(s.IsIOError());
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBBasicTest, VerifyFileChecksums) {
|
||||
Options options = GetDefaultOptions();
|
||||
@@ -4096,6 +4192,29 @@ TEST_F(DBBasicTest, VerifyFileChecksums) {
|
||||
Reopen(options);
|
||||
ASSERT_TRUE(db_->VerifyFileChecksums(ReadOptions()).IsInvalidArgument());
|
||||
}
|
||||
|
||||
// TODO: re-enable after we provide finer-grained control for WAL tracking to
|
||||
// meet the needs of different use cases, durability levels and recovery modes.
|
||||
TEST_F(DBBasicTest, DISABLED_ManualWalSync) {
|
||||
Options options = CurrentOptions();
|
||||
options.track_and_verify_wals_in_manifest = true;
|
||||
options.wal_recovery_mode = WALRecoveryMode::kAbsoluteConsistency;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put("x", "y"));
|
||||
// This does not create a new WAL.
|
||||
ASSERT_OK(db_->SyncWAL());
|
||||
EXPECT_FALSE(dbfull()->GetVersionSet()->GetWalSet().GetWals().empty());
|
||||
|
||||
std::unique_ptr<LogFile> wal;
|
||||
Status s = db_->GetCurrentWalFile(&wal);
|
||||
ASSERT_OK(s);
|
||||
Close();
|
||||
|
||||
EXPECT_OK(env_->DeleteFile(LogFileName(dbname_, wal->LogNumber())));
|
||||
|
||||
ASSERT_TRUE(TryReopen(options).IsCorruption());
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
// A test class for intercepting random reads and injecting artificial
|
||||
|
||||
+12
-10
@@ -670,7 +670,7 @@ class DBBlockCacheTest1 : public DBTestBase,
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBBlockCacheTest1, DBBlockCacheTest1,
|
||||
::testing::Values(1, 2, 3));
|
||||
::testing::Values(1, 2));
|
||||
|
||||
TEST_P(DBBlockCacheTest1, WarmCacheWithBlocksDuringFlush) {
|
||||
Options options = CurrentOptions();
|
||||
@@ -686,13 +686,10 @@ TEST_P(DBBlockCacheTest1, WarmCacheWithBlocksDuringFlush) {
|
||||
table_options.partition_filters = true;
|
||||
table_options.index_type =
|
||||
BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10));
|
||||
break;
|
||||
case 2: // block-based filter
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10, true));
|
||||
break;
|
||||
case 3: // full filter
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
case 2: // full filter
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10));
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
@@ -936,7 +933,9 @@ TEST_F(DBBlockCacheTest, AddRedundantStats) {
|
||||
for (std::shared_ptr<Cache> base_cache :
|
||||
{NewLRUCache(capacity, num_shard_bits),
|
||||
NewClockCache(capacity, num_shard_bits),
|
||||
NewFastLRUCache(capacity, num_shard_bits)}) {
|
||||
NewFastLRUCache(capacity, 1 /*estimated_value_size*/, num_shard_bits,
|
||||
false /*strict_capacity_limit*/,
|
||||
kDefaultCacheMetadataChargePolicy)}) {
|
||||
if (!base_cache) {
|
||||
// Skip clock cache when not supported
|
||||
continue;
|
||||
@@ -1290,12 +1289,15 @@ TEST_F(DBBlockCacheTest, CacheEntryRoleStats) {
|
||||
int iterations_tested = 0;
|
||||
for (bool partition : {false, true}) {
|
||||
for (std::shared_ptr<Cache> cache :
|
||||
{NewLRUCache(capacity), NewClockCache(capacity),
|
||||
NewFastLRUCache(capacity)}) {
|
||||
{NewLRUCache(capacity), NewClockCache(capacity)}) {
|
||||
// This test doesn't support FastLRUCache because the
|
||||
// keys used are not 16B long.
|
||||
// TODO(guido) Add support for FastLRUCache.
|
||||
if (!cache) {
|
||||
// Skip clock cache when not supported
|
||||
continue;
|
||||
}
|
||||
|
||||
++iterations_tested;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
|
||||
+74
-71
@@ -37,8 +37,6 @@ std::shared_ptr<const FilterPolicy> Create(double bits_per_key,
|
||||
return BloomLikeFilterPolicy::Create(name, bits_per_key);
|
||||
}
|
||||
const std::string kLegacyBloom = test::LegacyBloomFilterPolicy::kClassName();
|
||||
const std::string kDeprecatedBlock =
|
||||
DeprecatedBlockBasedBloomFilterPolicy::kClassName();
|
||||
const std::string kFastLocalBloom =
|
||||
test::FastLocalBloomFilterPolicy::kClassName();
|
||||
const std::string kStandard128Ribbon =
|
||||
@@ -189,7 +187,7 @@ TEST_F(DBBloomFilterTest, GetFilterByPrefixBloomCustomPrefixExtractor) {
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
get_perf_context()->EnablePerLevelPerfContext();
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10));
|
||||
if (partition_filters) {
|
||||
bbto.partition_filters = true;
|
||||
bbto.index_type = BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch;
|
||||
@@ -256,6 +254,15 @@ TEST_F(DBBloomFilterTest, GetFilterByPrefixBloomCustomPrefixExtractor) {
|
||||
(*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful);
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// No bloom on extractor changed, after re-open
|
||||
options.prefix_extractor.reset(NewCappedPrefixTransform(10));
|
||||
Reopen(options);
|
||||
ASSERT_EQ("NOT_FOUND", Get("foobarbar"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 3);
|
||||
ASSERT_EQ(
|
||||
3,
|
||||
(*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful);
|
||||
|
||||
get_perf_context()->Reset();
|
||||
}
|
||||
}
|
||||
@@ -267,7 +274,7 @@ TEST_F(DBBloomFilterTest, GetFilterByPrefixBloom) {
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
get_perf_context()->EnablePerLevelPerfContext();
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10));
|
||||
if (partition_filters) {
|
||||
bbto.partition_filters = true;
|
||||
bbto.index_type = BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch;
|
||||
@@ -331,7 +338,7 @@ TEST_F(DBBloomFilterTest, WholeKeyFilterProp) {
|
||||
get_perf_context()->EnablePerLevelPerfContext();
|
||||
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10));
|
||||
bbto.whole_key_filtering = false;
|
||||
if (partition_filters) {
|
||||
bbto.partition_filters = true;
|
||||
@@ -747,7 +754,6 @@ TEST_P(DBBloomFilterTestWithParam, SkipFilterOnEssentiallyZeroBpk) {
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
FormatDef, DBBloomFilterTestDefFormatVersion,
|
||||
::testing::Values(
|
||||
std::make_tuple(kDeprecatedBlock, false, test::kDefaultFormatVersion),
|
||||
std::make_tuple(kAutoBloom, true, test::kDefaultFormatVersion),
|
||||
std::make_tuple(kAutoBloom, false, test::kDefaultFormatVersion),
|
||||
std::make_tuple(kAutoRibbon, false, test::kDefaultFormatVersion)));
|
||||
@@ -755,7 +761,6 @@ INSTANTIATE_TEST_CASE_P(
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
FormatDef, DBBloomFilterTestWithParam,
|
||||
::testing::Values(
|
||||
std::make_tuple(kDeprecatedBlock, false, test::kDefaultFormatVersion),
|
||||
std::make_tuple(kAutoBloom, true, test::kDefaultFormatVersion),
|
||||
std::make_tuple(kAutoBloom, false, test::kDefaultFormatVersion),
|
||||
std::make_tuple(kAutoRibbon, false, test::kDefaultFormatVersion)));
|
||||
@@ -763,7 +768,6 @@ INSTANTIATE_TEST_CASE_P(
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
FormatLatest, DBBloomFilterTestWithParam,
|
||||
::testing::Values(
|
||||
std::make_tuple(kDeprecatedBlock, false, kLatestFormatVersion),
|
||||
std::make_tuple(kAutoBloom, true, kLatestFormatVersion),
|
||||
std::make_tuple(kAutoBloom, false, kLatestFormatVersion),
|
||||
std::make_tuple(kAutoRibbon, false, kLatestFormatVersion)));
|
||||
@@ -829,8 +833,6 @@ std::shared_ptr<const FilterPolicy> kCompatibilityRibbonPolicy{
|
||||
NewRibbonFilterPolicy(20, -1)};
|
||||
|
||||
std::vector<CompatibilityConfig> kCompatibilityConfigs = {
|
||||
{Create(20, kDeprecatedBlock), false,
|
||||
BlockBasedTableOptions().format_version},
|
||||
{kCompatibilityBloomPolicy, false, BlockBasedTableOptions().format_version},
|
||||
{kCompatibilityBloomPolicy, true, BlockBasedTableOptions().format_version},
|
||||
{kCompatibilityBloomPolicy, false, /* legacy Bloom */ 4U},
|
||||
@@ -878,9 +880,8 @@ TEST_F(DBBloomFilterTest, BloomFilterCompatibility) {
|
||||
ASSERT_EQ("val", Get(prefix + "Z")); // Filter positive
|
||||
// Filter negative, with high probability
|
||||
ASSERT_EQ("NOT_FOUND", Get(prefix + "Q"));
|
||||
// FULL_POSITIVE does not include block-based filter case (j == 0)
|
||||
EXPECT_EQ(TestGetAndResetTickerCount(options, BLOOM_FILTER_FULL_POSITIVE),
|
||||
j == 0 ? 0 : 2);
|
||||
2);
|
||||
EXPECT_EQ(TestGetAndResetTickerCount(options, BLOOM_FILTER_USEFUL), 1);
|
||||
}
|
||||
}
|
||||
@@ -904,7 +905,7 @@ class ChargeFilterConstructionTestWithParam
|
||||
detect_filter_construct_corruption_(std::get<3>(GetParam())) {
|
||||
if (charge_filter_construction_ ==
|
||||
CacheEntryRoleOptions::Decision::kDisabled ||
|
||||
policy_ == kDeprecatedBlock || policy_ == kLegacyBloom) {
|
||||
policy_ == kLegacyBloom) {
|
||||
// For these cases, we only interested in whether filter construction
|
||||
// cache charging happens instead of its accuracy. Therefore we don't
|
||||
// need many keys.
|
||||
@@ -1031,8 +1032,6 @@ INSTANTIATE_TEST_CASE_P(
|
||||
std::make_tuple(CacheEntryRoleOptions::Decision::kEnabled,
|
||||
kStandard128Ribbon, true, true),
|
||||
|
||||
std::make_tuple(CacheEntryRoleOptions::Decision::kEnabled,
|
||||
kDeprecatedBlock, false, false),
|
||||
std::make_tuple(CacheEntryRoleOptions::Decision::kEnabled, kLegacyBloom,
|
||||
false, false)));
|
||||
|
||||
@@ -1109,11 +1108,10 @@ TEST_P(ChargeFilterConstructionTestWithParam, Basic) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (policy == kDeprecatedBlock || policy == kLegacyBloom) {
|
||||
if (policy == kLegacyBloom) {
|
||||
EXPECT_EQ(filter_construction_cache_res_peaks.size(), 0)
|
||||
<< "There shouldn't be filter construction cache charging as this "
|
||||
"feature does not support kDeprecatedBlock "
|
||||
"nor kLegacyBloom";
|
||||
"feature does not support kLegacyBloom";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1798,10 +1796,9 @@ class SliceTransformLimitedDomain : public SliceTransform {
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(DBBloomFilterTest, PrefixExtractorFullFilter) {
|
||||
TEST_F(DBBloomFilterTest, PrefixExtractorWithFilter1) {
|
||||
BlockBasedTableOptions bbto;
|
||||
// Full Filter Block
|
||||
bbto.filter_policy.reset(ROCKSDB_NAMESPACE::NewBloomFilterPolicy(10, false));
|
||||
bbto.filter_policy.reset(ROCKSDB_NAMESPACE::NewBloomFilterPolicy(10));
|
||||
bbto.whole_key_filtering = false;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
@@ -1827,10 +1824,9 @@ TEST_F(DBBloomFilterTest, PrefixExtractorFullFilter) {
|
||||
ASSERT_EQ(Get("zzzzz_AAAA"), "val5");
|
||||
}
|
||||
|
||||
TEST_F(DBBloomFilterTest, PrefixExtractorBlockFilter) {
|
||||
TEST_F(DBBloomFilterTest, PrefixExtractorWithFilter2) {
|
||||
BlockBasedTableOptions bbto;
|
||||
// Block Filter Block
|
||||
bbto.filter_policy.reset(ROCKSDB_NAMESPACE::NewBloomFilterPolicy(10, true));
|
||||
bbto.filter_policy.reset(ROCKSDB_NAMESPACE::NewBloomFilterPolicy(10));
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.prefix_extractor = std::make_shared<SliceTransformLimitedDomain>();
|
||||
@@ -2221,7 +2217,6 @@ class BloomStatsTestWithParam
|
||||
} else {
|
||||
BlockBasedTableOptions table_options;
|
||||
if (partition_filters_) {
|
||||
assert(bfp_impl_ != kDeprecatedBlock);
|
||||
table_options.partition_filters = partition_filters_;
|
||||
table_options.index_type =
|
||||
BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch;
|
||||
@@ -2346,9 +2341,7 @@ TEST_P(BloomStatsTestWithParam, BloomStatsTestWithIter) {
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(value3, iter->value().ToString());
|
||||
// The seek doesn't check block-based bloom filter because last index key
|
||||
// starts with the same prefix we're seeking to.
|
||||
uint64_t expected_hits = bfp_impl_ == kDeprecatedBlock ? 1 : 2;
|
||||
uint64_t expected_hits = 2;
|
||||
ASSERT_EQ(expected_hits, get_perf_context()->bloom_sst_hit_count);
|
||||
|
||||
iter->Seek(key2);
|
||||
@@ -2360,8 +2353,7 @@ TEST_P(BloomStatsTestWithParam, BloomStatsTestWithIter) {
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
BloomStatsTestWithParam, BloomStatsTestWithParam,
|
||||
::testing::Values(std::make_tuple(kDeprecatedBlock, false),
|
||||
std::make_tuple(kLegacyBloom, false),
|
||||
::testing::Values(std::make_tuple(kLegacyBloom, false),
|
||||
std::make_tuple(kLegacyBloom, true),
|
||||
std::make_tuple(kFastLocalBloom, false),
|
||||
std::make_tuple(kFastLocalBloom, true),
|
||||
@@ -2486,7 +2478,7 @@ TEST_F(DBBloomFilterTest, OptimizeFiltersForHits) {
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.cache_index_and_filter_blocks = true;
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10, true));
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10));
|
||||
bbto.whole_key_filtering = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
options.optimize_filters_for_hits = true;
|
||||
@@ -2674,7 +2666,6 @@ int CountIter(std::unique_ptr<Iterator>& iter, const Slice& key) {
|
||||
// as the upper bound and two keys are adjacent according to the comparator.
|
||||
TEST_F(DBBloomFilterTest, DynamicBloomFilterUpperBound) {
|
||||
for (const auto& bfp_impl : BloomLikeFilterPolicy::GetAllFixedImpls()) {
|
||||
int using_full_builder = bfp_impl != kDeprecatedBlock;
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.env = CurrentOptions().env;
|
||||
@@ -2726,8 +2717,7 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterUpperBound) {
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "abcdxx00"), 4);
|
||||
// should check bloom filter since upper bound meets requirement
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
2 + using_full_builder);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 3);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
}
|
||||
{
|
||||
@@ -2740,8 +2730,7 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterUpperBound) {
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "abcdxx01"), 4);
|
||||
// should skip bloom filter since upper bound is too long
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
2 + using_full_builder);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 3);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
}
|
||||
{
|
||||
@@ -2754,8 +2743,7 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterUpperBound) {
|
||||
ASSERT_EQ(CountIter(iter, "abcdxx02"), 4);
|
||||
// should check bloom filter since upper bound matches transformed seek
|
||||
// key
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
2 + using_full_builder * 2);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
}
|
||||
{
|
||||
@@ -2768,8 +2756,7 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterUpperBound) {
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "aaaaaaaa"), 0);
|
||||
// should skip bloom filter since mismatch is found
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
2 + using_full_builder * 2);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
}
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "fixed:3"}}));
|
||||
@@ -2782,23 +2769,52 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterUpperBound) {
|
||||
read_options.iterate_upper_bound = &upper_bound;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "abc"), 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
2 + using_full_builder * 2);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
}
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "capped:4"}}));
|
||||
// Same with re-open
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(3));
|
||||
Reopen(options);
|
||||
{
|
||||
Slice upper_bound("abd");
|
||||
ReadOptions read_options;
|
||||
read_options.prefix_same_as_start = true;
|
||||
read_options.iterate_upper_bound = &upper_bound;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "abc"), 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
}
|
||||
// Set back to capped:4 and verify BF is always read
|
||||
options.prefix_extractor.reset(NewCappedPrefixTransform(4));
|
||||
Reopen(options);
|
||||
{
|
||||
// set back to capped:4 and verify BF is always read
|
||||
Slice upper_bound("abd");
|
||||
ReadOptions read_options;
|
||||
read_options.prefix_same_as_start = true;
|
||||
read_options.iterate_upper_bound = &upper_bound;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "abc"), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
3 + using_full_builder * 2);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 5);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 1);
|
||||
}
|
||||
// Same if there's a problem initally loading prefix transform
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlockBasedTable::Open::ForceNullTablePrefixExtractor",
|
||||
[&](void* arg) { *static_cast<bool*>(arg) = true; });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Reopen(options);
|
||||
{
|
||||
Slice upper_bound("abd");
|
||||
ReadOptions read_options;
|
||||
read_options.prefix_same_as_start = true;
|
||||
read_options.iterate_upper_bound = &upper_bound;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "abc"), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 6);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 2);
|
||||
}
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2806,7 +2822,6 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterUpperBound) {
|
||||
// verify iterators can read all SST files using the latest config.
|
||||
TEST_F(DBBloomFilterTest, DynamicBloomFilterMultipleSST) {
|
||||
for (const auto& bfp_impl : BloomLikeFilterPolicy::GetAllFixedImpls()) {
|
||||
int using_full_builder = bfp_impl != kDeprecatedBlock;
|
||||
Options options;
|
||||
options.env = CurrentOptions().env;
|
||||
options.create_if_missing = true;
|
||||
@@ -2840,11 +2855,9 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterMultipleSST) {
|
||||
read_options.iterate_upper_bound = &upper_bound;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "foo"), 2);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
1 + using_full_builder);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 2);
|
||||
ASSERT_EQ(CountIter(iter, "gpk"), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
1 + using_full_builder);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 2);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
|
||||
// second SST with capped:3 BF
|
||||
@@ -2857,14 +2870,12 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterMultipleSST) {
|
||||
// BF is cappped:3 now
|
||||
std::unique_ptr<Iterator> iter_tmp(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter_tmp, "foo"), 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
2 + using_full_builder * 2);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
ASSERT_EQ(CountIter(iter_tmp, "gpk"), 0);
|
||||
// both counters are incremented because BF is "not changed" for 1 of the
|
||||
// 2 SST files, so filter is checked once and found no match.
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
3 + using_full_builder * 2);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 5);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 1);
|
||||
}
|
||||
|
||||
@@ -2882,25 +2893,21 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterMultipleSST) {
|
||||
std::unique_ptr<Iterator> iter_tmp(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter_tmp, "foo"), 9);
|
||||
// the first and last BF are checked
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
4 + using_full_builder * 3);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 7);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 1);
|
||||
ASSERT_EQ(CountIter(iter_tmp, "gpk"), 0);
|
||||
// only last BF is checked and not found
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
5 + using_full_builder * 3);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 8);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 2);
|
||||
}
|
||||
|
||||
// iter_old can only see the first SST, so checked plus 1
|
||||
ASSERT_EQ(CountIter(iter_old, "foo"), 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
6 + using_full_builder * 3);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 9);
|
||||
// iter was created after the first setoptions call so only full filter
|
||||
// will check the filter
|
||||
ASSERT_EQ(CountIter(iter, "foo"), 2);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
6 + using_full_builder * 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 10);
|
||||
|
||||
{
|
||||
// keys in all three SSTs are visible to iterator
|
||||
@@ -2908,12 +2915,10 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterMultipleSST) {
|
||||
// so +2 for checked counter
|
||||
std::unique_ptr<Iterator> iter_all(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter_all, "foo"), 9);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
7 + using_full_builder * 5);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 12);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 2);
|
||||
ASSERT_EQ(CountIter(iter_all, "gpk"), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
8 + using_full_builder * 5);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 13);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 3);
|
||||
}
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "capped:3"}}));
|
||||
@@ -2924,12 +2929,10 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterMultipleSST) {
|
||||
ASSERT_EQ(CountIter(iter_all, "foo"), 6);
|
||||
// all three SST are checked because the current options has the same as
|
||||
// the remaining SST (capped:3)
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
9 + using_full_builder * 7);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 16);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 3);
|
||||
ASSERT_EQ(CountIter(iter_all, "gpk"), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
10 + using_full_builder * 7);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 17);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 4);
|
||||
}
|
||||
// TODO(Zhongyi): Maybe also need to add Get calls to test point look up?
|
||||
|
||||
@@ -6387,6 +6387,74 @@ INSTANTIATE_TEST_CASE_P(DBCompactionTestBlobGC, DBCompactionTestBlobGC,
|
||||
::testing::Combine(::testing::Values(0.0, 0.5, 1.0),
|
||||
::testing::Bool()));
|
||||
|
||||
TEST_P(DBCompactionTestBlobGC, CompactionWithBlobGCOverrides) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.enable_blob_files = true;
|
||||
options.blob_file_size = 32; // one blob per file
|
||||
options.enable_blob_garbage_collection = true;
|
||||
options.blob_garbage_collection_age_cutoff = 0;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
for (int i = 0; i < 128; i += 2) {
|
||||
ASSERT_OK(Put("key" + std::to_string(i), "value" + std::to_string(i)));
|
||||
ASSERT_OK(
|
||||
Put("key" + std::to_string(i + 1), "value" + std::to_string(i + 1)));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
std::vector<uint64_t> original_blob_files = GetBlobFileNumbers();
|
||||
ASSERT_EQ(original_blob_files.size(), 128);
|
||||
|
||||
// Note: turning off enable_blob_files before the compaction results in
|
||||
// garbage collected values getting inlined.
|
||||
ASSERT_OK(db_->SetOptions({{"enable_blob_files", "false"}}));
|
||||
|
||||
CompactRangeOptions cro;
|
||||
cro.blob_garbage_collection_policy = BlobGarbageCollectionPolicy::kForce;
|
||||
cro.blob_garbage_collection_age_cutoff = blob_gc_age_cutoff_;
|
||||
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
|
||||
// Check that the GC stats are correct
|
||||
{
|
||||
VersionSet* const versions = dbfull()->GetVersionSet();
|
||||
assert(versions);
|
||||
assert(versions->GetColumnFamilySet());
|
||||
|
||||
ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault();
|
||||
assert(cfd);
|
||||
|
||||
const InternalStats* const internal_stats = cfd->internal_stats();
|
||||
assert(internal_stats);
|
||||
|
||||
const auto& compaction_stats = internal_stats->TEST_GetCompactionStats();
|
||||
ASSERT_GE(compaction_stats.size(), 2);
|
||||
|
||||
ASSERT_GE(compaction_stats[1].bytes_read_blob, 0);
|
||||
ASSERT_EQ(compaction_stats[1].bytes_written_blob, 0);
|
||||
}
|
||||
|
||||
const size_t cutoff_index = static_cast<size_t>(
|
||||
cro.blob_garbage_collection_age_cutoff * original_blob_files.size());
|
||||
const size_t expected_num_files = original_blob_files.size() - cutoff_index;
|
||||
|
||||
const std::vector<uint64_t> new_blob_files = GetBlobFileNumbers();
|
||||
|
||||
ASSERT_EQ(new_blob_files.size(), expected_num_files);
|
||||
|
||||
// Original blob files below the cutoff should be gone, original blob files
|
||||
// at or above the cutoff should be still there
|
||||
for (size_t i = cutoff_index; i < original_blob_files.size(); ++i) {
|
||||
ASSERT_EQ(new_blob_files[i - cutoff_index], original_blob_files[i]);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 128; ++i) {
|
||||
ASSERT_EQ(Get("key" + std::to_string(i)), "value" + std::to_string(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DBCompactionTestBlobGC, CompactionWithBlobGC) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
|
||||
+40
-1
@@ -124,6 +124,9 @@ Status DBImpl::GetLiveFiles(std::vector<std::string>& ret,
|
||||
}
|
||||
|
||||
Status DBImpl::GetSortedWalFiles(VectorLogPtr& files) {
|
||||
// Record tracked WALs as a (minimum) cross-check for directory scan
|
||||
std::vector<uint64_t> required_by_manifest;
|
||||
|
||||
// If caller disabled deletions, this function should return files that are
|
||||
// guaranteed not to be deleted until deletions are re-enabled. We need to
|
||||
// wait for pending purges to finish since WalManager doesn't know which
|
||||
@@ -137,6 +140,13 @@ Status DBImpl::GetSortedWalFiles(VectorLogPtr& files) {
|
||||
while (pending_purge_obsolete_files_ > 0 || bg_purge_scheduled_ > 0) {
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
|
||||
// Record tracked WALs as a (minimum) cross-check for directory scan
|
||||
const auto& manifest_wals = versions_->GetWalSet().GetWals();
|
||||
required_by_manifest.reserve(manifest_wals.size());
|
||||
for (const auto& wal : manifest_wals) {
|
||||
required_by_manifest.push_back(wal.first);
|
||||
}
|
||||
}
|
||||
|
||||
Status s = wal_manager_.GetSortedWalFiles(files);
|
||||
@@ -150,6 +160,29 @@ Status DBImpl::GetSortedWalFiles(VectorLogPtr& files) {
|
||||
assert(deletions_disabled.IsNotSupported());
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
// Verify includes those required by manifest (one sorted list is superset
|
||||
// of the other)
|
||||
auto required = required_by_manifest.begin();
|
||||
auto included = files.begin();
|
||||
|
||||
while (required != required_by_manifest.end()) {
|
||||
if (included == files.end() || *required < (*included)->LogNumber()) {
|
||||
// FAIL - did not find
|
||||
return Status::Corruption(
|
||||
"WAL file " + std::to_string(*required) +
|
||||
" required by manifest but not in directory list");
|
||||
}
|
||||
if (*required == (*included)->LogNumber()) {
|
||||
++required;
|
||||
++included;
|
||||
} else {
|
||||
assert(*required > (*included)->LogNumber());
|
||||
++included;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -349,7 +382,13 @@ Status DBImpl::GetLiveFilesStorageInfo(
|
||||
TEST_SYNC_POINT("CheckpointImpl::CreateCheckpoint:SavedLiveFiles2");
|
||||
|
||||
if (s.ok()) {
|
||||
s = FlushWAL(false /* sync */);
|
||||
// To maximize the effectiveness of track_and_verify_wals_in_manifest,
|
||||
// sync WAL when it is enabled.
|
||||
s = FlushWAL(
|
||||
immutable_db_options_.track_and_verify_wals_in_manifest /* sync */);
|
||||
if (s.IsNotSupported()) { // read-only DB or similar
|
||||
s = Status::OK();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT("CheckpointImpl::CreateCustomCheckpoint:AfterGetLive1");
|
||||
|
||||
@@ -40,17 +40,52 @@ size_t CompactedDBImpl::FindFile(const Slice& key) {
|
||||
|
||||
Status CompactedDBImpl::Get(const ReadOptions& options, ColumnFamilyHandle*,
|
||||
const Slice& key, PinnableSlice* value) {
|
||||
return Get(options, /*column_family*/ nullptr, key, value,
|
||||
/*timestamp*/ nullptr);
|
||||
}
|
||||
|
||||
Status CompactedDBImpl::Get(const ReadOptions& options, ColumnFamilyHandle*,
|
||||
const Slice& key, PinnableSlice* value,
|
||||
std::string* timestamp) {
|
||||
assert(user_comparator_);
|
||||
if (options.timestamp || user_comparator_->timestamp_size()) {
|
||||
// TODO: support timestamp
|
||||
return Status::NotSupported();
|
||||
if (options.timestamp) {
|
||||
const Status s = FailIfTsMismatchCf(
|
||||
DefaultColumnFamily(), *(options.timestamp), /*ts_for_read=*/true);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
} else {
|
||||
const Status s = FailIfCfHasTs(DefaultColumnFamily());
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the timestamps for returning results so that we can distinguish
|
||||
// between tombstone or key that has never been written
|
||||
if (timestamp) {
|
||||
timestamp->clear();
|
||||
}
|
||||
|
||||
GetWithTimestampReadCallback read_cb(kMaxSequenceNumber);
|
||||
std::string* ts =
|
||||
user_comparator_->timestamp_size() > 0 ? timestamp : nullptr;
|
||||
LookupKey lkey(key, kMaxSequenceNumber, options.timestamp);
|
||||
GetContext get_context(user_comparator_, nullptr, nullptr, nullptr,
|
||||
GetContext::kNotFound, key, value, nullptr, nullptr,
|
||||
nullptr, true, nullptr, nullptr);
|
||||
LookupKey lkey(key, kMaxSequenceNumber);
|
||||
Status s = files_.files[FindFile(key)].fd.table_reader->Get(
|
||||
options, lkey.internal_key(), &get_context, nullptr);
|
||||
GetContext::kNotFound, lkey.user_key(), value, ts,
|
||||
nullptr, nullptr, true, nullptr, nullptr, nullptr,
|
||||
nullptr, &read_cb);
|
||||
|
||||
const FdWithKeyRange& f = files_.files[FindFile(lkey.user_key())];
|
||||
if (user_comparator_->CompareWithoutTimestamp(
|
||||
key, /*a_has_ts=*/false,
|
||||
ExtractUserKeyAndStripTimestamp(f.smallest_key,
|
||||
user_comparator_->timestamp_size()),
|
||||
/*b_has_ts=*/false) < 0) {
|
||||
return Status::NotFound();
|
||||
}
|
||||
Status s = f.fd.table_reader->Get(options, lkey.internal_key(), &get_context,
|
||||
nullptr);
|
||||
if (!s.ok() && !s.IsNotFound()) {
|
||||
return s;
|
||||
}
|
||||
@@ -60,37 +95,73 @@ Status CompactedDBImpl::Get(const ReadOptions& options, ColumnFamilyHandle*,
|
||||
return Status::NotFound();
|
||||
}
|
||||
|
||||
std::vector<Status> CompactedDBImpl::MultiGet(const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>&,
|
||||
std::vector<Status> CompactedDBImpl::MultiGet(
|
||||
const ReadOptions& options, const std::vector<ColumnFamilyHandle*>&,
|
||||
const std::vector<Slice>& keys, std::vector<std::string>* values) {
|
||||
return MultiGet(options, keys, values, /*timestamps*/ nullptr);
|
||||
}
|
||||
|
||||
std::vector<Status> CompactedDBImpl::MultiGet(
|
||||
const ReadOptions& options, const std::vector<ColumnFamilyHandle*>&,
|
||||
const std::vector<Slice>& keys, std::vector<std::string>* values,
|
||||
std::vector<std::string>* timestamps) {
|
||||
assert(user_comparator_);
|
||||
if (user_comparator_->timestamp_size() || options.timestamp) {
|
||||
// TODO: support timestamp
|
||||
return std::vector<Status>(keys.size(), Status::NotSupported());
|
||||
size_t num_keys = keys.size();
|
||||
|
||||
if (options.timestamp) {
|
||||
Status s = FailIfTsMismatchCf(DefaultColumnFamily(), *(options.timestamp),
|
||||
/*ts_for_read=*/true);
|
||||
if (!s.ok()) {
|
||||
return std::vector<Status>(num_keys, s);
|
||||
}
|
||||
} else {
|
||||
Status s = FailIfCfHasTs(DefaultColumnFamily());
|
||||
if (!s.ok()) {
|
||||
return std::vector<Status>(num_keys, s);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the timestamps for returning results so that we can distinguish
|
||||
// between tombstone or key that has never been written
|
||||
if (timestamps) {
|
||||
for (auto& ts : *timestamps) {
|
||||
ts.clear();
|
||||
}
|
||||
}
|
||||
|
||||
GetWithTimestampReadCallback read_cb(kMaxSequenceNumber);
|
||||
autovector<TableReader*, 16> reader_list;
|
||||
for (const auto& key : keys) {
|
||||
const FdWithKeyRange& f = files_.files[FindFile(key)];
|
||||
if (user_comparator_->Compare(key, ExtractUserKey(f.smallest_key)) < 0) {
|
||||
LookupKey lkey(key, kMaxSequenceNumber, options.timestamp);
|
||||
const FdWithKeyRange& f = files_.files[FindFile(lkey.user_key())];
|
||||
if (user_comparator_->CompareWithoutTimestamp(
|
||||
key, /*a_has_ts=*/false,
|
||||
ExtractUserKeyAndStripTimestamp(f.smallest_key,
|
||||
user_comparator_->timestamp_size()),
|
||||
/*b_has_ts=*/false) < 0) {
|
||||
reader_list.push_back(nullptr);
|
||||
} else {
|
||||
LookupKey lkey(key, kMaxSequenceNumber);
|
||||
f.fd.table_reader->Prepare(lkey.internal_key());
|
||||
reader_list.push_back(f.fd.table_reader);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Status> statuses(keys.size(), Status::NotFound());
|
||||
values->resize(keys.size());
|
||||
std::vector<Status> statuses(num_keys, Status::NotFound());
|
||||
values->resize(num_keys);
|
||||
if (timestamps) {
|
||||
timestamps->resize(num_keys);
|
||||
}
|
||||
int idx = 0;
|
||||
for (auto* r : reader_list) {
|
||||
if (r != nullptr) {
|
||||
PinnableSlice pinnable_val;
|
||||
std::string& value = (*values)[idx];
|
||||
GetContext get_context(user_comparator_, nullptr, nullptr, nullptr,
|
||||
GetContext::kNotFound, keys[idx], &pinnable_val,
|
||||
nullptr, nullptr, nullptr, true, nullptr, nullptr);
|
||||
LookupKey lkey(keys[idx], kMaxSequenceNumber);
|
||||
LookupKey lkey(keys[idx], kMaxSequenceNumber, options.timestamp);
|
||||
std::string* timestamp = timestamps ? &(*timestamps)[idx] : nullptr;
|
||||
GetContext get_context(
|
||||
user_comparator_, nullptr, nullptr, nullptr, GetContext::kNotFound,
|
||||
lkey.user_key(), &pinnable_val,
|
||||
user_comparator_->timestamp_size() > 0 ? timestamp : nullptr, nullptr,
|
||||
nullptr, true, nullptr, nullptr, nullptr, nullptr, &read_cb);
|
||||
Status s = r->Get(options, lkey.internal_key(), &get_context, nullptr);
|
||||
assert(static_cast<size_t>(idx) < statuses.size());
|
||||
if (!s.ok() && !s.IsNotFound()) {
|
||||
|
||||
@@ -28,12 +28,25 @@ class CompactedDBImpl : public DBImpl {
|
||||
virtual Status Get(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value) override;
|
||||
|
||||
Status Get(const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* value,
|
||||
std::string* timestamp) override;
|
||||
|
||||
using DB::MultiGet;
|
||||
// Note that CompactedDBImpl::MultiGet is not the optimized version of
|
||||
// MultiGet to use.
|
||||
// TODO: optimize CompactedDBImpl::MultiGet, see DBImpl::MultiGet for details.
|
||||
virtual std::vector<Status> MultiGet(
|
||||
const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>&,
|
||||
const std::vector<Slice>& keys, std::vector<std::string>* values)
|
||||
override;
|
||||
const ReadOptions& options, const std::vector<ColumnFamilyHandle*>&,
|
||||
const std::vector<Slice>& keys,
|
||||
std::vector<std::string>* values) override;
|
||||
|
||||
std::vector<Status> MultiGet(const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>&,
|
||||
const std::vector<Slice>& keys,
|
||||
std::vector<std::string>* values,
|
||||
std::vector<std::string>* timestamps) override;
|
||||
|
||||
using DBImpl::Put;
|
||||
virtual Status Put(const WriteOptions& /*options*/,
|
||||
|
||||
+282
-66
@@ -101,6 +101,7 @@
|
||||
#include "util/compression.h"
|
||||
#include "util/crc32c.h"
|
||||
#include "util/defer.h"
|
||||
#include "util/distributed_mutex.h"
|
||||
#include "util/hash_containers.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/stop_watch.h"
|
||||
@@ -145,6 +146,8 @@ void DumpSupportInfo(Logger* logger) {
|
||||
}
|
||||
ROCKS_LOG_HEADER(logger, "Fast CRC32 supported: %s",
|
||||
crc32c::IsFastCrc32Supported().c_str());
|
||||
|
||||
ROCKS_LOG_HEADER(logger, "DMutex implementation: %s", DMutex::kName());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -153,7 +156,9 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
bool read_only)
|
||||
: dbname_(dbname),
|
||||
own_info_log_(options.info_log == nullptr),
|
||||
initial_db_options_(SanitizeOptions(dbname, options, read_only)),
|
||||
init_logger_creation_s_(),
|
||||
initial_db_options_(SanitizeOptions(dbname, options, read_only,
|
||||
&init_logger_creation_s_)),
|
||||
env_(initial_db_options_.env),
|
||||
io_tracer_(std::make_shared<IOTracer>()),
|
||||
immutable_db_options_(initial_db_options_),
|
||||
@@ -511,6 +516,19 @@ void DBImpl::CancelAllBackgroundWork(bool wait) {
|
||||
WaitForBackgroundWork();
|
||||
}
|
||||
|
||||
Status DBImpl::MaybeReleaseTimestampedSnapshotsAndCheck() {
|
||||
size_t num_snapshots = 0;
|
||||
ReleaseTimestampedSnapshotsOlderThan(std::numeric_limits<uint64_t>::max(),
|
||||
&num_snapshots);
|
||||
|
||||
// If there is unreleased snapshot, fail the close call
|
||||
if (num_snapshots > 0) {
|
||||
return Status::Aborted("Cannot close DB with unreleased snapshot.");
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DBImpl::CloseHelper() {
|
||||
// Guarantee that there is no background error recovery in progress before
|
||||
// continuing with the shutdown
|
||||
@@ -714,24 +732,40 @@ Status DBImpl::CloseHelper() {
|
||||
write_buffer_manager_->RemoveDBFromQueue(wbm_stall_.get());
|
||||
}
|
||||
|
||||
IOStatus io_s = directories_.Close(IOOptions(), nullptr /* dbg */);
|
||||
if (!io_s.ok()) {
|
||||
ret = io_s;
|
||||
}
|
||||
if (ret.IsAborted()) {
|
||||
// Reserve IsAborted() error for those where users didn't release
|
||||
// certain resource and they can release them and come back and
|
||||
// retry. In this case, we wrap this exception to something else.
|
||||
return Status::Incomplete(ret.ToString());
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Status DBImpl::CloseImpl() { return CloseHelper(); }
|
||||
|
||||
DBImpl::~DBImpl() {
|
||||
// TODO: remove this.
|
||||
init_logger_creation_s_.PermitUncheckedError();
|
||||
|
||||
InstrumentedMutexLock closing_lock_guard(&closing_mutex_);
|
||||
if (!closed_) {
|
||||
closed_ = true;
|
||||
closing_status_ = CloseHelper();
|
||||
closing_status_.PermitUncheckedError();
|
||||
if (closed_) {
|
||||
return;
|
||||
}
|
||||
|
||||
closed_ = true;
|
||||
|
||||
{
|
||||
const Status s = MaybeReleaseTimestampedSnapshotsAndCheck();
|
||||
s.PermitUncheckedError();
|
||||
}
|
||||
|
||||
closing_status_ = CloseImpl();
|
||||
closing_status_.PermitUncheckedError();
|
||||
}
|
||||
|
||||
void DBImpl::MaybeIgnoreError(Status* s) const {
|
||||
@@ -786,8 +820,9 @@ Status DBImpl::StartPeriodicWorkScheduler() {
|
||||
return periodic_work_scheduler_->Register(
|
||||
this, mutable_db_options_.stats_dump_period_sec,
|
||||
mutable_db_options_.stats_persist_period_sec);
|
||||
#endif // !ROCKSDB_LITE
|
||||
#else
|
||||
return Status::OK();
|
||||
#endif // !ROCKSDB_LITE
|
||||
}
|
||||
|
||||
// esitmate the total size of stats_history_
|
||||
@@ -1337,6 +1372,7 @@ Status DBImpl::FlushWAL(bool sync) {
|
||||
}
|
||||
|
||||
Status DBImpl::SyncWAL() {
|
||||
TEST_SYNC_POINT("DBImpl::SyncWAL:Begin");
|
||||
autovector<log::Writer*, 1> logs_to_sync;
|
||||
bool need_log_dir_sync;
|
||||
uint64_t current_log_number;
|
||||
@@ -1349,7 +1385,7 @@ Status DBImpl::SyncWAL() {
|
||||
current_log_number = logfile_number_;
|
||||
|
||||
while (logs_.front().number <= current_log_number &&
|
||||
logs_.front().getting_synced) {
|
||||
logs_.front().IsSyncing()) {
|
||||
log_sync_cv_.Wait();
|
||||
}
|
||||
// First check that logs are safe to sync in background.
|
||||
@@ -1366,8 +1402,7 @@ Status DBImpl::SyncWAL() {
|
||||
for (auto it = logs_.begin();
|
||||
it != logs_.end() && it->number <= current_log_number; ++it) {
|
||||
auto& log = *it;
|
||||
assert(!log.getting_synced);
|
||||
log.getting_synced = true;
|
||||
log.PrepareForSync();
|
||||
logs_to_sync.push_back(log.writer);
|
||||
}
|
||||
|
||||
@@ -1440,24 +1475,24 @@ Status DBImpl::MarkLogsSynced(uint64_t up_to, bool synced_dir) {
|
||||
VersionEdit synced_wals;
|
||||
for (auto it = logs_.begin(); it != logs_.end() && it->number <= up_to;) {
|
||||
auto& wal = *it;
|
||||
assert(wal.getting_synced);
|
||||
assert(wal.IsSyncing());
|
||||
|
||||
if (logs_.size() > 1) {
|
||||
if (immutable_db_options_.track_and_verify_wals_in_manifest &&
|
||||
wal.writer->file()->GetFileSize() > 0) {
|
||||
synced_wals.AddWal(wal.number,
|
||||
WalMetadata(wal.writer->file()->GetFileSize()));
|
||||
wal.GetPreSyncSize() > 0) {
|
||||
synced_wals.AddWal(wal.number, WalMetadata(wal.GetPreSyncSize()));
|
||||
}
|
||||
logs_to_free_.push_back(wal.ReleaseWriter());
|
||||
// To modify logs_ both mutex_ and log_write_mutex_ must be held
|
||||
InstrumentedMutexLock l(&log_write_mutex_);
|
||||
it = logs_.erase(it);
|
||||
} else {
|
||||
wal.getting_synced = false;
|
||||
wal.FinishSync();
|
||||
++it;
|
||||
}
|
||||
}
|
||||
assert(logs_.empty() || logs_[0].number > up_to ||
|
||||
(logs_.size() == 1 && !logs_[0].getting_synced));
|
||||
(logs_.size() == 1 && !logs_[0].IsSyncing()));
|
||||
|
||||
Status s;
|
||||
if (synced_wals.IsWalAddition()) {
|
||||
@@ -1477,8 +1512,7 @@ void DBImpl::MarkLogsNotSynced(uint64_t up_to) {
|
||||
for (auto it = logs_.begin(); it != logs_.end() && it->number <= up_to;
|
||||
++it) {
|
||||
auto& wal = *it;
|
||||
assert(wal.getting_synced);
|
||||
wal.getting_synced = false;
|
||||
wal.FinishSync();
|
||||
}
|
||||
log_sync_cv_.SignalAll();
|
||||
}
|
||||
@@ -1715,6 +1749,8 @@ Status DBImpl::Get(const ReadOptions& read_options,
|
||||
Status DBImpl::Get(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value, std::string* timestamp) {
|
||||
assert(value != nullptr);
|
||||
value->Reset();
|
||||
GetImplOptions get_impl_options;
|
||||
get_impl_options.column_family = column_family;
|
||||
get_impl_options.value = value;
|
||||
@@ -1731,8 +1767,9 @@ Status DBImpl::GetImpl(const ReadOptions& read_options, const Slice& key,
|
||||
assert(get_impl_options.column_family);
|
||||
|
||||
if (read_options.timestamp) {
|
||||
const Status s = FailIfTsSizesMismatch(get_impl_options.column_family,
|
||||
*(read_options.timestamp));
|
||||
const Status s = FailIfTsMismatchCf(get_impl_options.column_family,
|
||||
*(read_options.timestamp),
|
||||
/*ts_for_read=*/true);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -1743,6 +1780,12 @@ Status DBImpl::GetImpl(const ReadOptions& read_options, const Slice& key,
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the timestamps for returning results so that we can distinguish
|
||||
// between tombstone or key that has never been written
|
||||
if (get_impl_options.timestamp) {
|
||||
get_impl_options.timestamp->clear();
|
||||
}
|
||||
|
||||
GetWithTimestampReadCallback read_cb(0); // Will call Refresh
|
||||
|
||||
PERF_CPU_TIMER_GUARD(get_cpu_nanos, immutable_db_options_.clock);
|
||||
@@ -1784,11 +1827,7 @@ Status DBImpl::GetImpl(const ReadOptions& read_options, const Slice& key,
|
||||
// data for the snapshot, so the reader would see neither data that was be
|
||||
// visible to the snapshot before compaction nor the newer data inserted
|
||||
// afterwards.
|
||||
if (last_seq_same_as_publish_seq_) {
|
||||
snapshot = versions_->LastSequence();
|
||||
} else {
|
||||
snapshot = versions_->LastPublishedSequence();
|
||||
}
|
||||
snapshot = GetLastPublishedSequence();
|
||||
if (get_impl_options.callback) {
|
||||
// The unprep_seqs are not published for write unprepared, so it could be
|
||||
// that max_visible_seq is larger. Seek to the std::max of the two.
|
||||
@@ -1956,8 +1995,8 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
assert(column_family[i]);
|
||||
if (read_options.timestamp) {
|
||||
stat_list[i] =
|
||||
FailIfTsSizesMismatch(column_family[i], *(read_options.timestamp));
|
||||
stat_list[i] = FailIfTsMismatchCf(
|
||||
column_family[i], *(read_options.timestamp), /*ts_for_read=*/true);
|
||||
if (!stat_list[i].ok()) {
|
||||
should_fail = true;
|
||||
}
|
||||
@@ -2181,11 +2220,7 @@ bool DBImpl::MultiCFSnapshot(
|
||||
// version because a flush happening in between may compact away data for
|
||||
// the snapshot, but the snapshot is earlier than the data overwriting it,
|
||||
// so users may see wrong results.
|
||||
if (last_seq_same_as_publish_seq_) {
|
||||
*snapshot = versions_->LastSequence();
|
||||
} else {
|
||||
*snapshot = versions_->LastPublishedSequence();
|
||||
}
|
||||
*snapshot = GetLastPublishedSequence();
|
||||
}
|
||||
} else {
|
||||
// If we end up with the same issue of memtable geting sealed during 2
|
||||
@@ -2216,11 +2251,7 @@ bool DBImpl::MultiCFSnapshot(
|
||||
// acquire the lock so we're sure to succeed
|
||||
mutex_.Lock();
|
||||
}
|
||||
if (last_seq_same_as_publish_seq_) {
|
||||
*snapshot = versions_->LastSequence();
|
||||
} else {
|
||||
*snapshot = versions_->LastPublishedSequence();
|
||||
}
|
||||
*snapshot = GetLastPublishedSequence();
|
||||
} else {
|
||||
*snapshot =
|
||||
static_cast_with_check<const SnapshotImpl>(read_options.snapshot)
|
||||
@@ -2291,7 +2322,8 @@ void DBImpl::MultiGet(const ReadOptions& read_options, const size_t num_keys,
|
||||
ColumnFamilyHandle* cfh = column_families[i];
|
||||
assert(cfh);
|
||||
if (read_options.timestamp) {
|
||||
statuses[i] = FailIfTsSizesMismatch(cfh, *(read_options.timestamp));
|
||||
statuses[i] = FailIfTsMismatchCf(cfh, *(read_options.timestamp),
|
||||
/*ts_for_read=*/true);
|
||||
if (!statuses[i].ok()) {
|
||||
should_fail = true;
|
||||
}
|
||||
@@ -2326,6 +2358,7 @@ void DBImpl::MultiGet(const ReadOptions& read_options, const size_t num_keys,
|
||||
autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE> sorted_keys;
|
||||
sorted_keys.resize(num_keys);
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
values[i].Reset();
|
||||
key_context.emplace_back(column_families[i], keys[i], &values[i],
|
||||
timestamps ? ×tamps[i] : nullptr,
|
||||
&statuses[i]);
|
||||
@@ -2472,6 +2505,7 @@ void DBImpl::MultiGet(const ReadOptions& read_options,
|
||||
autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE> sorted_keys;
|
||||
sorted_keys.resize(num_keys);
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
values[i].Reset();
|
||||
key_context.emplace_back(column_family, keys[i], &values[i],
|
||||
timestamps ? ×tamps[i] : nullptr,
|
||||
&statuses[i]);
|
||||
@@ -2560,6 +2594,16 @@ Status DBImpl::MultiGetImpl(
|
||||
PERF_CPU_TIMER_GUARD(get_cpu_nanos, immutable_db_options_.clock);
|
||||
StopWatch sw(immutable_db_options_.clock, stats_, DB_MULTIGET);
|
||||
|
||||
assert(sorted_keys);
|
||||
// Clear the timestamps for returning results so that we can distinguish
|
||||
// between tombstone or key that has never been written
|
||||
for (auto* kctx : *sorted_keys) {
|
||||
assert(kctx);
|
||||
if (kctx->timestamp) {
|
||||
kctx->timestamp->clear();
|
||||
}
|
||||
}
|
||||
|
||||
// For each of the given keys, apply the entire "get" process as follows:
|
||||
// First look in the memtable, then in the immutable memtable (if any).
|
||||
// s is both in/out. When in, s could either be OK or MergeInProgress.
|
||||
@@ -2772,7 +2816,6 @@ Status DBImpl::CreateColumnFamilyImpl(const ColumnFamilyOptions& cf_options,
|
||||
s = cfd->AddDirectories(&dummy_created_dirs);
|
||||
}
|
||||
if (s.ok()) {
|
||||
single_column_family_mode_ = false;
|
||||
auto* cfd =
|
||||
versions_->GetColumnFamilySet()->GetColumnFamily(column_family_name);
|
||||
assert(cfd != nullptr);
|
||||
@@ -2942,8 +2985,8 @@ Iterator* DBImpl::NewIterator(const ReadOptions& read_options,
|
||||
assert(column_family);
|
||||
|
||||
if (read_options.timestamp) {
|
||||
const Status s =
|
||||
FailIfTsSizesMismatch(column_family, *(read_options.timestamp));
|
||||
const Status s = FailIfTsMismatchCf(
|
||||
column_family, *(read_options.timestamp), /*ts_for_read=*/true);
|
||||
if (!s.ok()) {
|
||||
return NewErrorIterator(s);
|
||||
}
|
||||
@@ -3084,7 +3127,8 @@ Status DBImpl::NewIterators(
|
||||
if (read_options.timestamp) {
|
||||
for (auto* cf : column_families) {
|
||||
assert(cf);
|
||||
const Status s = FailIfTsSizesMismatch(cf, *(read_options.timestamp));
|
||||
const Status s = FailIfTsMismatchCf(cf, *(read_options.timestamp),
|
||||
/*ts_for_read=*/true);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -3146,6 +3190,48 @@ const Snapshot* DBImpl::GetSnapshotForWriteConflictBoundary() {
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
std::pair<Status, std::shared_ptr<const Snapshot>>
|
||||
DBImpl::CreateTimestampedSnapshot(SequenceNumber snapshot_seq, uint64_t ts) {
|
||||
assert(ts != std::numeric_limits<uint64_t>::max());
|
||||
|
||||
auto ret = CreateTimestampedSnapshotImpl(snapshot_seq, ts, /*lock=*/true);
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::shared_ptr<const SnapshotImpl> DBImpl::GetTimestampedSnapshot(
|
||||
uint64_t ts) const {
|
||||
InstrumentedMutexLock lock_guard(&mutex_);
|
||||
return timestamped_snapshots_.GetSnapshot(ts);
|
||||
}
|
||||
|
||||
void DBImpl::ReleaseTimestampedSnapshotsOlderThan(uint64_t ts,
|
||||
size_t* remaining_total_ss) {
|
||||
autovector<std::shared_ptr<const SnapshotImpl>> snapshots_to_release;
|
||||
{
|
||||
InstrumentedMutexLock lock_guard(&mutex_);
|
||||
timestamped_snapshots_.ReleaseSnapshotsOlderThan(ts, snapshots_to_release);
|
||||
}
|
||||
snapshots_to_release.clear();
|
||||
|
||||
if (remaining_total_ss) {
|
||||
InstrumentedMutexLock lock_guard(&mutex_);
|
||||
*remaining_total_ss = static_cast<size_t>(snapshots_.count());
|
||||
}
|
||||
}
|
||||
|
||||
Status DBImpl::GetTimestampedSnapshots(
|
||||
uint64_t ts_lb, uint64_t ts_ub,
|
||||
std::vector<std::shared_ptr<const Snapshot>>& timestamped_snapshots) const {
|
||||
if (ts_lb >= ts_ub) {
|
||||
return Status::InvalidArgument(
|
||||
"timestamp lower bound must be smaller than upper bound");
|
||||
}
|
||||
timestamped_snapshots.clear();
|
||||
InstrumentedMutexLock lock_guard(&mutex_);
|
||||
timestamped_snapshots_.GetSnapshots(ts_lb, ts_ub, timestamped_snapshots);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
SnapshotImpl* DBImpl::GetSnapshotImpl(bool is_write_conflict_boundary,
|
||||
bool lock) {
|
||||
int64_t unix_time = 0;
|
||||
@@ -3155,6 +3241,8 @@ SnapshotImpl* DBImpl::GetSnapshotImpl(bool is_write_conflict_boundary,
|
||||
|
||||
if (lock) {
|
||||
mutex_.Lock();
|
||||
} else {
|
||||
mutex_.AssertHeld();
|
||||
}
|
||||
// returns null if the underlying memtable does not support snapshot.
|
||||
if (!is_snapshot_supported_) {
|
||||
@@ -3164,9 +3252,7 @@ SnapshotImpl* DBImpl::GetSnapshotImpl(bool is_write_conflict_boundary,
|
||||
delete s;
|
||||
return nullptr;
|
||||
}
|
||||
auto snapshot_seq = last_seq_same_as_publish_seq_
|
||||
? versions_->LastSequence()
|
||||
: versions_->LastPublishedSequence();
|
||||
auto snapshot_seq = GetLastPublishedSequence();
|
||||
SnapshotImpl* snapshot =
|
||||
snapshots_.New(s, snapshot_seq, unix_time, is_write_conflict_boundary);
|
||||
if (lock) {
|
||||
@@ -3175,6 +3261,115 @@ SnapshotImpl* DBImpl::GetSnapshotImpl(bool is_write_conflict_boundary,
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
std::pair<Status, std::shared_ptr<const SnapshotImpl>>
|
||||
DBImpl::CreateTimestampedSnapshotImpl(SequenceNumber snapshot_seq, uint64_t ts,
|
||||
bool lock) {
|
||||
int64_t unix_time = 0;
|
||||
immutable_db_options_.clock->GetCurrentTime(&unix_time)
|
||||
.PermitUncheckedError(); // Ignore error
|
||||
SnapshotImpl* s = new SnapshotImpl;
|
||||
|
||||
const bool need_update_seq = (snapshot_seq != kMaxSequenceNumber);
|
||||
|
||||
if (lock) {
|
||||
mutex_.Lock();
|
||||
} else {
|
||||
mutex_.AssertHeld();
|
||||
}
|
||||
// returns null if the underlying memtable does not support snapshot.
|
||||
if (!is_snapshot_supported_) {
|
||||
if (lock) {
|
||||
mutex_.Unlock();
|
||||
}
|
||||
delete s;
|
||||
return std::make_pair(
|
||||
Status::NotSupported("Memtable does not support snapshot"), nullptr);
|
||||
}
|
||||
|
||||
// Caller is not write thread, thus didn't provide a valid snapshot_seq.
|
||||
// Obtain seq from db.
|
||||
if (!need_update_seq) {
|
||||
snapshot_seq = GetLastPublishedSequence();
|
||||
}
|
||||
|
||||
std::shared_ptr<const SnapshotImpl> latest =
|
||||
timestamped_snapshots_.GetSnapshot(std::numeric_limits<uint64_t>::max());
|
||||
|
||||
// If there is already a latest timestamped snapshot, then we need to do some
|
||||
// checks.
|
||||
if (latest) {
|
||||
uint64_t latest_snap_ts = latest->GetTimestamp();
|
||||
SequenceNumber latest_snap_seq = latest->GetSequenceNumber();
|
||||
assert(latest_snap_seq <= snapshot_seq);
|
||||
bool needs_create_snap = true;
|
||||
Status status;
|
||||
std::shared_ptr<const SnapshotImpl> ret;
|
||||
if (latest_snap_ts > ts) {
|
||||
// A snapshot created later cannot have smaller timestamp than a previous
|
||||
// timestamped snapshot.
|
||||
needs_create_snap = false;
|
||||
std::ostringstream oss;
|
||||
oss << "snapshot exists with larger timestamp " << latest_snap_ts << " > "
|
||||
<< ts;
|
||||
status = Status::InvalidArgument(oss.str());
|
||||
} else if (latest_snap_ts == ts) {
|
||||
if (latest_snap_seq == snapshot_seq) {
|
||||
// We are requesting the same sequence number and timestamp, thus can
|
||||
// safely reuse (share) the current latest timestamped snapshot.
|
||||
needs_create_snap = false;
|
||||
ret = latest;
|
||||
} else if (latest_snap_seq < snapshot_seq) {
|
||||
// There may have been writes to the database since the latest
|
||||
// timestamped snapshot, yet we are still requesting the same
|
||||
// timestamp. In this case, we cannot create the new timestamped
|
||||
// snapshot.
|
||||
needs_create_snap = false;
|
||||
std::ostringstream oss;
|
||||
oss << "Allocated seq is " << snapshot_seq
|
||||
<< ", while snapshot exists with smaller seq " << latest_snap_seq
|
||||
<< " but same timestamp " << ts;
|
||||
status = Status::InvalidArgument(oss.str());
|
||||
}
|
||||
}
|
||||
if (!needs_create_snap) {
|
||||
if (lock) {
|
||||
mutex_.Unlock();
|
||||
}
|
||||
delete s;
|
||||
return std::make_pair(status, ret);
|
||||
} else {
|
||||
status.PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
|
||||
SnapshotImpl* snapshot =
|
||||
snapshots_.New(s, snapshot_seq, unix_time,
|
||||
/*is_write_conflict_boundary=*/true, ts);
|
||||
|
||||
std::shared_ptr<const SnapshotImpl> ret(
|
||||
snapshot,
|
||||
std::bind(&DBImpl::ReleaseSnapshot, this, std::placeholders::_1));
|
||||
timestamped_snapshots_.AddSnapshot(ret);
|
||||
|
||||
// Caller is from write thread, and we need to update database's sequence
|
||||
// number.
|
||||
if (need_update_seq) {
|
||||
assert(versions_);
|
||||
if (last_seq_same_as_publish_seq_) {
|
||||
versions_->SetLastSequence(snapshot_seq);
|
||||
} else {
|
||||
// TODO: support write-prepared/write-unprepared transactions with two
|
||||
// write queues.
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (lock) {
|
||||
mutex_.Unlock();
|
||||
}
|
||||
return std::make_pair(Status::OK(), ret);
|
||||
}
|
||||
|
||||
namespace {
|
||||
using CfdList = autovector<ColumnFamilyData*, 2>;
|
||||
bool CfdListContains(const CfdList& list, ColumnFamilyData* cfd) {
|
||||
@@ -3200,11 +3395,7 @@ void DBImpl::ReleaseSnapshot(const Snapshot* s) {
|
||||
snapshots_.Delete(casted_s);
|
||||
uint64_t oldest_snapshot;
|
||||
if (snapshots_.empty()) {
|
||||
if (last_seq_same_as_publish_seq_) {
|
||||
oldest_snapshot = versions_->LastSequence();
|
||||
} else {
|
||||
oldest_snapshot = versions_->LastPublishedSequence();
|
||||
}
|
||||
oldest_snapshot = GetLastPublishedSequence();
|
||||
} else {
|
||||
oldest_snapshot = snapshots_.oldest()->number_;
|
||||
}
|
||||
@@ -4095,13 +4286,14 @@ Status DBImpl::Close() {
|
||||
if (closed_) {
|
||||
return closing_status_;
|
||||
}
|
||||
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
// If there is unreleased snapshot, fail the close call
|
||||
if (!snapshots_.empty()) {
|
||||
return Status::Aborted("Cannot close DB with unreleased snapshot.");
|
||||
const Status s = MaybeReleaseTimestampedSnapshotsAndCheck();
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
closing_status_ = CloseImpl();
|
||||
closed_ = true;
|
||||
return closing_status_;
|
||||
@@ -4383,6 +4575,19 @@ Status DBImpl::RenameTempFileToOptionsFile(const std::string& file_name) {
|
||||
s = dir_obj->FsyncWithDirOptions(IOOptions(), nullptr,
|
||||
DirFsyncOptions(options_file_name));
|
||||
}
|
||||
if (s.ok()) {
|
||||
Status temp_s = dir_obj->Close(IOOptions(), nullptr);
|
||||
// The default Close() could return "NotSupproted" and we bypass it
|
||||
// if it is not impelmented. Detailed explanations can be found in
|
||||
// db/db_impl/db_impl.h
|
||||
if (!temp_s.ok()) {
|
||||
if (temp_s.IsNotSupported()) {
|
||||
temp_s.PermitUncheckedError();
|
||||
} else {
|
||||
s = temp_s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
@@ -4509,6 +4714,8 @@ Status DBImpl::GetLatestSequenceForKey(
|
||||
*timestamp != std::string(ts_sz, '\xff')) ||
|
||||
(*seq == kMaxSequenceNumber && timestamp->empty()));
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::GetLatestSequenceForKey:mem", timestamp);
|
||||
|
||||
if (*seq != kMaxSequenceNumber) {
|
||||
// Found a sequence number, no need to check immutable memtables
|
||||
*found_record_for_key = true;
|
||||
@@ -4573,6 +4780,7 @@ Status DBImpl::GetLatestSequenceForKey(
|
||||
(*seq != kMaxSequenceNumber &&
|
||||
*timestamp != std::string(ts_sz, '\xff')) ||
|
||||
(*seq == kMaxSequenceNumber && timestamp->empty()));
|
||||
|
||||
if (*seq != kMaxSequenceNumber) {
|
||||
// Found a sequence number, no need to check SST files
|
||||
assert(0 == ts_sz || *timestamp != std::string(ts_sz, '\xff'));
|
||||
@@ -4809,19 +5017,6 @@ Status DBImpl::IngestExternalFiles(
|
||||
}
|
||||
}
|
||||
if (status.ok()) {
|
||||
int consumed_seqno_count =
|
||||
ingestion_jobs[0].ConsumedSequenceNumbersCount();
|
||||
for (size_t i = 1; i != num_cfs; ++i) {
|
||||
consumed_seqno_count =
|
||||
std::max(consumed_seqno_count,
|
||||
ingestion_jobs[i].ConsumedSequenceNumbersCount());
|
||||
}
|
||||
if (consumed_seqno_count > 0) {
|
||||
const SequenceNumber last_seqno = versions_->LastSequence();
|
||||
versions_->SetLastAllocatedSequence(last_seqno + consumed_seqno_count);
|
||||
versions_->SetLastPublishedSequence(last_seqno + consumed_seqno_count);
|
||||
versions_->SetLastSequence(last_seqno + consumed_seqno_count);
|
||||
}
|
||||
autovector<ColumnFamilyData*> cfds_to_commit;
|
||||
autovector<const MutableCFOptions*> mutable_cf_options_list;
|
||||
autovector<autovector<VersionEdit*>> edit_lists;
|
||||
@@ -4851,6 +5046,27 @@ Status DBImpl::IngestExternalFiles(
|
||||
status =
|
||||
versions_->LogAndApply(cfds_to_commit, mutable_cf_options_list,
|
||||
edit_lists, &mutex_, directories_.GetDbDir());
|
||||
// It is safe to update VersionSet last seqno here after LogAndApply since
|
||||
// LogAndApply persists last sequence number from VersionEdits,
|
||||
// which are from file's largest seqno and not from VersionSet.
|
||||
//
|
||||
// It is necessary to update last seqno here since LogAndApply releases
|
||||
// mutex when persisting MANIFEST file, and the snapshots taken during
|
||||
// that period will not be stable if VersionSet last seqno is updated
|
||||
// before LogAndApply.
|
||||
int consumed_seqno_count =
|
||||
ingestion_jobs[0].ConsumedSequenceNumbersCount();
|
||||
for (size_t i = 1; i != num_cfs; ++i) {
|
||||
consumed_seqno_count =
|
||||
std::max(consumed_seqno_count,
|
||||
ingestion_jobs[i].ConsumedSequenceNumbersCount());
|
||||
}
|
||||
if (consumed_seqno_count > 0) {
|
||||
const SequenceNumber last_seqno = versions_->LastSequence();
|
||||
versions_->SetLastAllocatedSequence(last_seqno + consumed_seqno_count);
|
||||
versions_->SetLastPublishedSequence(last_seqno + consumed_seqno_count);
|
||||
versions_->SetLastSequence(last_seqno + consumed_seqno_count);
|
||||
}
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
|
||||
+207
-29
@@ -32,6 +32,7 @@
|
||||
#include "db/log_writer.h"
|
||||
#include "db/logs_with_prep_tracker.h"
|
||||
#include "db/memtable_list.h"
|
||||
#include "db/post_memtable_callback.h"
|
||||
#include "db/pre_release_callback.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "db/read_callback.h"
|
||||
@@ -113,6 +114,49 @@ class Directories {
|
||||
|
||||
FSDirectory* GetDbDir() { return db_dir_.get(); }
|
||||
|
||||
IOStatus Close(const IOOptions& options, IODebugContext* dbg) {
|
||||
// close all directories for all database paths
|
||||
IOStatus s = IOStatus::OK();
|
||||
|
||||
// The default implementation for Close() in Directory/FSDirectory class
|
||||
// "NotSupported" status, the upper level interface should be able to
|
||||
// handle this error so that Close() does not fail after upgrading when
|
||||
// run on FileSystems that have not implemented `Directory::Close()` or
|
||||
// `FSDirectory::Close()` yet
|
||||
|
||||
if (db_dir_) {
|
||||
IOStatus temp_s = db_dir_->Close(options, dbg);
|
||||
if (!temp_s.ok() && !temp_s.IsNotSupported() && s.ok()) {
|
||||
s = std::move(temp_s);
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to close everything even if one fails
|
||||
s.PermitUncheckedError();
|
||||
|
||||
if (wal_dir_) {
|
||||
IOStatus temp_s = wal_dir_->Close(options, dbg);
|
||||
if (!temp_s.ok() && !temp_s.IsNotSupported() && s.ok()) {
|
||||
s = std::move(temp_s);
|
||||
}
|
||||
}
|
||||
|
||||
s.PermitUncheckedError();
|
||||
|
||||
for (auto& data_dir_ptr : data_dirs_) {
|
||||
if (data_dir_ptr) {
|
||||
IOStatus temp_s = data_dir_ptr->Close(options, dbg);
|
||||
if (!temp_s.ok() && !temp_s.IsNotSupported() && s.ok()) {
|
||||
s = std::move(temp_s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ready for caller
|
||||
s.MustCheck();
|
||||
return s;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<FSDirectory> db_dir_;
|
||||
std::vector<std::unique_ptr<FSDirectory>> data_dirs_;
|
||||
@@ -283,6 +327,19 @@ class DBImpl : public DB {
|
||||
|
||||
virtual const Snapshot* GetSnapshot() override;
|
||||
virtual void ReleaseSnapshot(const Snapshot* snapshot) override;
|
||||
// Create a timestamped snapshot. This snapshot can be shared by multiple
|
||||
// readers. If any of them uses it for write conflict checking, then
|
||||
// is_write_conflict_boundary is true. For simplicity, set it to true by
|
||||
// default.
|
||||
std::pair<Status, std::shared_ptr<const Snapshot>> CreateTimestampedSnapshot(
|
||||
SequenceNumber snapshot_seq, uint64_t ts);
|
||||
std::shared_ptr<const SnapshotImpl> GetTimestampedSnapshot(uint64_t ts) const;
|
||||
void ReleaseTimestampedSnapshotsOlderThan(
|
||||
uint64_t ts, size_t* remaining_total_ss = nullptr);
|
||||
Status GetTimestampedSnapshots(uint64_t ts_lb, uint64_t ts_ub,
|
||||
std::vector<std::shared_ptr<const Snapshot>>&
|
||||
timestamped_snapshots) const;
|
||||
|
||||
using DB::GetProperty;
|
||||
virtual bool GetProperty(ColumnFamilyHandle* column_family,
|
||||
const Slice& property, std::string* value) override;
|
||||
@@ -1160,6 +1217,8 @@ class DBImpl : public DB {
|
||||
static void TEST_ResetDbSessionIdGen();
|
||||
static std::string GenerateDbSessionId(Env* env);
|
||||
|
||||
bool seq_per_batch() const { return seq_per_batch_; }
|
||||
|
||||
protected:
|
||||
const std::string dbname_;
|
||||
// TODO(peterd): unify with VersionSet::db_id_
|
||||
@@ -1170,6 +1229,7 @@ class DBImpl : public DB {
|
||||
std::unique_ptr<VersionSet> versions_;
|
||||
// Flag to check whether we allocated and own the info log file
|
||||
bool own_info_log_;
|
||||
Status init_logger_creation_s_;
|
||||
const DBOptions initial_db_options_;
|
||||
Env* const env_;
|
||||
std::shared_ptr<IOTracer> io_tracer_;
|
||||
@@ -1183,6 +1243,9 @@ class DBImpl : public DB {
|
||||
InstrumentedMutex trace_mutex_;
|
||||
BlockCacheTracer block_cache_tracer_;
|
||||
|
||||
// constant false canceled flag, used when the compaction is not manual
|
||||
const std::atomic<bool> kManualCompactionCanceledFalse_{false};
|
||||
|
||||
// State below is protected by mutex_
|
||||
// With two_write_queues enabled, some of the variables that accessed during
|
||||
// WriteToWAL need different synchronization: log_empty_, alive_log_files_,
|
||||
@@ -1207,9 +1270,6 @@ class DBImpl : public DB {
|
||||
// only used for dynamically adjusting max_total_wal_size. it is a sum of
|
||||
// [write_buffer_size * max_write_buffer_number] over all column families
|
||||
uint64_t max_total_in_memory_state_;
|
||||
// If true, we have only one (default) column family. We use this to optimize
|
||||
// some code-paths
|
||||
bool single_column_family_mode_;
|
||||
|
||||
// The options to access storage files
|
||||
const FileOptions file_options_;
|
||||
@@ -1240,6 +1300,39 @@ class DBImpl : public DB {
|
||||
|
||||
std::atomic<bool> shutting_down_;
|
||||
|
||||
// RecoveryContext struct stores the context about version edits along
|
||||
// with corresponding column_family_data and column_family_options.
|
||||
class RecoveryContext {
|
||||
public:
|
||||
~RecoveryContext() {
|
||||
for (auto& edit_list : edit_lists_) {
|
||||
for (auto* edit : edit_list) {
|
||||
delete edit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateVersionEdits(ColumnFamilyData* cfd, const VersionEdit& edit) {
|
||||
assert(cfd != nullptr);
|
||||
if (map_.find(cfd->GetID()) == map_.end()) {
|
||||
uint32_t size = static_cast<uint32_t>(map_.size());
|
||||
map_.emplace(cfd->GetID(), size);
|
||||
cfds_.emplace_back(cfd);
|
||||
mutable_cf_opts_.emplace_back(cfd->GetLatestMutableCFOptions());
|
||||
edit_lists_.emplace_back(autovector<VersionEdit*>());
|
||||
}
|
||||
uint32_t i = map_[cfd->GetID()];
|
||||
edit_lists_[i].emplace_back(new VersionEdit(edit));
|
||||
}
|
||||
|
||||
std::unordered_map<uint32_t, uint32_t> map_; // cf_id to index;
|
||||
autovector<ColumnFamilyData*> cfds_;
|
||||
autovector<const MutableCFOptions*> mutable_cf_opts_;
|
||||
autovector<autovector<VersionEdit*>> edit_lists_;
|
||||
// files_to_delete_ contains sst files
|
||||
std::unordered_set<std::string> files_to_delete_;
|
||||
};
|
||||
|
||||
// Except in DB::Open(), WriteOptionsFile can only be called when:
|
||||
// Persist options to options file.
|
||||
// If need_mutex_lock = false, the method will lock DB mutex.
|
||||
@@ -1309,7 +1402,8 @@ class DBImpl : public DB {
|
||||
uint64_t* log_used = nullptr, uint64_t log_ref = 0,
|
||||
bool disable_memtable = false, uint64_t* seq_used = nullptr,
|
||||
size_t batch_cnt = 0,
|
||||
PreReleaseCallback* pre_release_callback = nullptr);
|
||||
PreReleaseCallback* pre_release_callback = nullptr,
|
||||
PostMemTableCallback* post_memtable_callback = nullptr);
|
||||
|
||||
Status PipelinedWriteImpl(const WriteOptions& options, WriteBatch* updates,
|
||||
WriteCallback* callback = nullptr,
|
||||
@@ -1356,16 +1450,21 @@ class DBImpl : public DB {
|
||||
// be made to the descriptor are added to *edit.
|
||||
// recovered_seq is set to less than kMaxSequenceNumber if the log's tail is
|
||||
// skipped.
|
||||
// recovery_ctx stores the context about version edits and all those
|
||||
// edits are persisted to new Manifest after successfully syncing the new WAL.
|
||||
virtual Status Recover(
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool read_only = false, bool error_if_wal_file_exists = false,
|
||||
bool error_if_data_exists_in_wals = false,
|
||||
uint64_t* recovered_seq = nullptr);
|
||||
uint64_t* recovered_seq = nullptr,
|
||||
RecoveryContext* recovery_ctx = nullptr);
|
||||
|
||||
virtual bool OwnTablesAndLogs() const { return true; }
|
||||
|
||||
// Set DB identity file, and write DB ID to manifest if necessary.
|
||||
Status SetDBId(bool read_only);
|
||||
// Setup DB identity file, and write DB ID to manifest if necessary.
|
||||
Status SetupDBId(bool read_only, RecoveryContext* recovery_ctx);
|
||||
// Assign db_id_ and write DB ID to manifest if necessary.
|
||||
void SetDBId(std::string&& id, bool read_only, RecoveryContext* recovery_ctx);
|
||||
|
||||
// REQUIRES: db mutex held when calling this function, but the db mutex can
|
||||
// be released and re-acquired. Db mutex will be held when the function
|
||||
@@ -1374,20 +1473,31 @@ class DBImpl : public DB {
|
||||
// not referenced in the MANIFEST (e.g.
|
||||
// 1. It's best effort recovery;
|
||||
// 2. The VersionEdits referencing the SST files are appended to
|
||||
// MANIFEST, DB crashes when syncing the MANIFEST, the VersionEdits are
|
||||
// RecoveryContext, DB crashes when syncing the MANIFEST, the VersionEdits are
|
||||
// still not synced to MANIFEST during recovery.)
|
||||
// We delete these SST files. In the
|
||||
// It stores the SST files to be deleted in RecoveryContext. In the
|
||||
// meantime, we find out the largest file number present in the paths, and
|
||||
// bump up the version set's next_file_number_ to be 1 + largest_file_number.
|
||||
Status DeleteUnreferencedSstFiles();
|
||||
// recovery_ctx stores the context about version edits and files to be
|
||||
// deleted. All those edits are persisted to new Manifest after successfully
|
||||
// syncing the new WAL.
|
||||
Status DeleteUnreferencedSstFiles(RecoveryContext* recovery_ctx);
|
||||
|
||||
// SetDbSessionId() should be called in the constuctor DBImpl()
|
||||
// to ensure that db_session_id_ gets updated every time the DB is opened
|
||||
void SetDbSessionId();
|
||||
|
||||
Status FailIfCfHasTs(const ColumnFamilyHandle* column_family) const;
|
||||
Status FailIfTsSizesMismatch(const ColumnFamilyHandle* column_family,
|
||||
const Slice& ts) const;
|
||||
Status FailIfTsMismatchCf(ColumnFamilyHandle* column_family, const Slice& ts,
|
||||
bool ts_for_read) const;
|
||||
|
||||
// recovery_ctx stores the context about version edits and
|
||||
// LogAndApplyForRecovery persist all those edits to new Manifest after
|
||||
// successfully syncing new WAL.
|
||||
// LogAndApplyForRecovery should be called only once during recovery and it
|
||||
// should be called when RocksDB writes to a first new MANIFEST since this
|
||||
// recovery.
|
||||
Status LogAndApplyForRecovery(const RecoveryContext& recovery_ctx);
|
||||
|
||||
private:
|
||||
friend class DB;
|
||||
@@ -1465,12 +1575,38 @@ class DBImpl : public DB {
|
||||
return s;
|
||||
}
|
||||
|
||||
bool IsSyncing() { return getting_synced; }
|
||||
|
||||
uint64_t GetPreSyncSize() {
|
||||
assert(getting_synced);
|
||||
return pre_sync_size;
|
||||
}
|
||||
|
||||
void PrepareForSync() {
|
||||
assert(!getting_synced);
|
||||
// Size is expected to be monotonically increasing.
|
||||
assert(writer->file()->GetFlushedSize() >= pre_sync_size);
|
||||
getting_synced = true;
|
||||
pre_sync_size = writer->file()->GetFlushedSize();
|
||||
}
|
||||
|
||||
void FinishSync() {
|
||||
assert(getting_synced);
|
||||
getting_synced = false;
|
||||
}
|
||||
|
||||
uint64_t number;
|
||||
// Visual Studio doesn't support deque's member to be noncopyable because
|
||||
// of a std::unique_ptr as a member.
|
||||
log::Writer* writer; // own
|
||||
|
||||
private:
|
||||
// true for some prefix of logs_
|
||||
bool getting_synced = false;
|
||||
// The size of the file before the sync happens. This amount is guaranteed
|
||||
// to be persisted even if appends happen during sync so it can be used for
|
||||
// tracking the synced size in MANIFEST.
|
||||
uint64_t pre_sync_size = 0;
|
||||
};
|
||||
|
||||
// PurgeFileInfo is a structure to hold information of files to be deleted in
|
||||
@@ -1526,7 +1662,11 @@ class DBImpl : public DB {
|
||||
output_path_id(_output_path_id),
|
||||
exclusive(_exclusive),
|
||||
disallow_trivial_move(_disallow_trivial_move),
|
||||
canceled(_canceled) {}
|
||||
canceled(_canceled ? *_canceled : canceled_internal_storage) {}
|
||||
// When _canceled is not provided by ther user, we assign the reference of
|
||||
// canceled_internal_storage to it to consolidate canceled and
|
||||
// manual_compaction_paused since DisableManualCompaction() might be
|
||||
// called
|
||||
|
||||
ColumnFamilyData* cfd;
|
||||
int input_level;
|
||||
@@ -1543,7 +1683,12 @@ class DBImpl : public DB {
|
||||
InternalKey* manual_end = nullptr; // how far we are compacting
|
||||
InternalKey tmp_storage; // Used to keep track of compaction progress
|
||||
InternalKey tmp_storage1; // Used to keep track of compaction progress
|
||||
std::atomic<bool>* canceled; // Compaction canceled by the user?
|
||||
|
||||
// When the user provides a canceled pointer in CompactRangeOptions, the
|
||||
// above varaibe is the reference of the user-provided
|
||||
// `canceled`, otherwise, it is the reference of canceled_internal_storage
|
||||
std::atomic<bool> canceled_internal_storage = false;
|
||||
std::atomic<bool>& canceled; // Compaction canceled pointer reference
|
||||
};
|
||||
struct PrepickedCompaction {
|
||||
// background compaction takes ownership of `compaction`.
|
||||
@@ -1645,7 +1790,8 @@ class DBImpl : public DB {
|
||||
// corrupted_log_found is set to true if we recover from a corrupted log file.
|
||||
Status RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
SequenceNumber* next_sequence, bool read_only,
|
||||
bool* corrupted_log_found);
|
||||
bool* corrupted_log_found,
|
||||
RecoveryContext* recovery_ctx);
|
||||
|
||||
// The following two methods are used to flush a memtable to
|
||||
// storage. The first one is used at database RecoveryTime (when the
|
||||
@@ -1779,9 +1925,12 @@ class DBImpl : public DB {
|
||||
Status PreprocessWrite(const WriteOptions& write_options, bool* need_log_sync,
|
||||
WriteContext* write_context);
|
||||
|
||||
WriteBatch* MergeBatch(const WriteThread::WriteGroup& write_group,
|
||||
WriteBatch* tmp_batch, size_t* write_with_wal,
|
||||
WriteBatch** to_be_cached_state);
|
||||
// Merge write batches in the write group into merged_batch.
|
||||
// Returns OK if merge is successful.
|
||||
// Returns Corruption if corruption in write batch is detected.
|
||||
Status MergeBatch(const WriteThread::WriteGroup& write_group,
|
||||
WriteBatch* tmp_batch, WriteBatch** merged_batch,
|
||||
size_t* write_with_wal, WriteBatch** to_be_cached_state);
|
||||
|
||||
// rate_limiter_priority is used to charge `DBOptions::rate_limiter`
|
||||
// for automatic WAL flush (`Options::manual_wal_flush` == false)
|
||||
@@ -1789,12 +1938,13 @@ class DBImpl : public DB {
|
||||
IOStatus WriteToWAL(const WriteBatch& merged_batch, log::Writer* log_writer,
|
||||
uint64_t* log_used, uint64_t* log_size,
|
||||
Env::IOPriority rate_limiter_priority,
|
||||
bool with_db_mutex = false, bool with_log_mutex = false);
|
||||
LogFileNumberSize& log_file_number_size);
|
||||
|
||||
IOStatus WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
log::Writer* log_writer, uint64_t* log_used,
|
||||
bool need_log_sync, bool need_log_dir_sync,
|
||||
SequenceNumber sequence);
|
||||
SequenceNumber sequence,
|
||||
LogFileNumberSize& log_file_number_size);
|
||||
|
||||
IOStatus ConcurrentWriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
uint64_t* log_used,
|
||||
@@ -1919,10 +2069,24 @@ class DBImpl : public DB {
|
||||
SnapshotImpl* GetSnapshotImpl(bool is_write_conflict_boundary,
|
||||
bool lock = true);
|
||||
|
||||
// If snapshot_seq != kMaxSequenceNumber, then this function can only be
|
||||
// called from the write thread that publishes sequence numbers to readers.
|
||||
// For 1) write-committed, or 2) write-prepared + one-write-queue, this will
|
||||
// be the write thread performing memtable writes. For write-prepared with
|
||||
// two write queues, this will be the write thread writing commit marker to
|
||||
// the WAL.
|
||||
// If snapshot_seq == kMaxSequenceNumber, this function is called by a caller
|
||||
// ensuring no writes to the database.
|
||||
std::pair<Status, std::shared_ptr<const SnapshotImpl>>
|
||||
CreateTimestampedSnapshotImpl(SequenceNumber snapshot_seq, uint64_t ts,
|
||||
bool lock = true);
|
||||
|
||||
uint64_t GetMaxTotalWalSize() const;
|
||||
|
||||
FSDirectory* GetDataDir(ColumnFamilyData* cfd, size_t path_id) const;
|
||||
|
||||
Status MaybeReleaseTimestampedSnapshotsAndCheck();
|
||||
|
||||
Status CloseHelper();
|
||||
|
||||
void WaitForBackgroundWork();
|
||||
@@ -2124,11 +2288,7 @@ class DBImpl : public DB {
|
||||
// are protected by locking both mutex_ and log_write_mutex_, and reads must
|
||||
// be under either mutex_ or log_write_mutex_.
|
||||
std::deque<LogFileNumberSize> alive_log_files_;
|
||||
// Caching the result of `alive_log_files_.back()` so that we do not have to
|
||||
// call `alive_log_files_.back()` in the write thread (WriteToWAL()) which
|
||||
// requires locking db mutex if log_mutex_ is not already held in
|
||||
// two-write-queues mode.
|
||||
std::deque<LogFileNumberSize>::reverse_iterator alive_log_files_tail_;
|
||||
|
||||
// Log files that aren't fully synced, and the current log file.
|
||||
// Synchronization:
|
||||
// - push_back() is done from write_thread_ with locked mutex_ and
|
||||
@@ -2192,6 +2352,8 @@ class DBImpl : public DB {
|
||||
|
||||
SnapshotList snapshots_;
|
||||
|
||||
TimestampedSnapshotList timestamped_snapshots_;
|
||||
|
||||
// For each background job, pending_outputs_ keeps the current file number at
|
||||
// the time that background job started.
|
||||
// FindObsoleteFiles()/PurgeObsoleteFiles() never deletes any file that has
|
||||
@@ -2410,10 +2572,12 @@ class GetWithTimestampReadCallback : public ReadCallback {
|
||||
};
|
||||
|
||||
extern Options SanitizeOptions(const std::string& db, const Options& src,
|
||||
bool read_only = false);
|
||||
bool read_only = false,
|
||||
Status* logger_creation_s = nullptr);
|
||||
|
||||
extern DBOptions SanitizeOptions(const std::string& db, const DBOptions& src,
|
||||
bool read_only = false);
|
||||
bool read_only = false,
|
||||
Status* logger_creation_s = nullptr);
|
||||
|
||||
extern CompressionType GetCompressionFlush(
|
||||
const ImmutableCFOptions& ioptions,
|
||||
@@ -2479,8 +2643,9 @@ inline Status DBImpl::FailIfCfHasTs(
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
inline Status DBImpl::FailIfTsSizesMismatch(
|
||||
const ColumnFamilyHandle* column_family, const Slice& ts) const {
|
||||
inline Status DBImpl::FailIfTsMismatchCf(ColumnFamilyHandle* column_family,
|
||||
const Slice& ts,
|
||||
bool ts_for_read) const {
|
||||
if (!column_family) {
|
||||
return Status::InvalidArgument("column family handle cannot be null");
|
||||
}
|
||||
@@ -2500,6 +2665,19 @@ inline Status DBImpl::FailIfTsSizesMismatch(
|
||||
<< ts_sz << " given";
|
||||
return Status::InvalidArgument(oss.str());
|
||||
}
|
||||
if (ts_for_read) {
|
||||
auto cfh = static_cast_with_check<ColumnFamilyHandleImpl>(column_family);
|
||||
auto cfd = cfh->cfd();
|
||||
std::string current_ts_low = cfd->GetFullHistoryTsLow();
|
||||
if (!current_ts_low.empty() &&
|
||||
ucmp->CompareTimestamp(ts, current_ts_low) < 0) {
|
||||
std::stringstream oss;
|
||||
oss << "Read timestamp: " << ts.ToString(true)
|
||||
<< " is smaller than full_history_ts_low: "
|
||||
<< Slice(current_ts_low).ToString(true) << std::endl;
|
||||
return Status::InvalidArgument(oss.str());
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
|
||||
@@ -88,14 +88,13 @@ IOStatus DBImpl::SyncClosedLogs(JobContext* job_context) {
|
||||
autovector<log::Writer*, 1> logs_to_sync;
|
||||
uint64_t current_log_number = logfile_number_;
|
||||
while (logs_.front().number < current_log_number &&
|
||||
logs_.front().getting_synced) {
|
||||
logs_.front().IsSyncing()) {
|
||||
log_sync_cv_.Wait();
|
||||
}
|
||||
for (auto it = logs_.begin();
|
||||
it != logs_.end() && it->number < current_log_number; ++it) {
|
||||
auto& log = *it;
|
||||
assert(!log.getting_synced);
|
||||
log.getting_synced = true;
|
||||
log.PrepareForSync();
|
||||
logs_to_sync.push_back(log.writer);
|
||||
}
|
||||
|
||||
@@ -952,6 +951,8 @@ Status DBImpl::IncreaseFullHistoryTsLowImpl(ColumnFamilyData* cfd,
|
||||
VersionEdit edit;
|
||||
edit.SetColumnFamily(cfd->GetID());
|
||||
edit.SetFullHistoryTsLow(ts_low);
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::IncreaseFullHistoryTsLowImpl:BeforeEdit",
|
||||
&edit);
|
||||
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
std::string current_ts_low = cfd->GetFullHistoryTsLow();
|
||||
@@ -959,12 +960,25 @@ Status DBImpl::IncreaseFullHistoryTsLowImpl(ColumnFamilyData* cfd,
|
||||
assert(ucmp->timestamp_size() == ts_low.size() && !ts_low.empty());
|
||||
if (!current_ts_low.empty() &&
|
||||
ucmp->CompareTimestamp(ts_low, current_ts_low) < 0) {
|
||||
return Status::InvalidArgument(
|
||||
"Cannot decrease full_history_timestamp_low");
|
||||
return Status::InvalidArgument("Cannot decrease full_history_ts_low");
|
||||
}
|
||||
|
||||
return versions_->LogAndApply(cfd, *cfd->GetLatestMutableCFOptions(), &edit,
|
||||
&mutex_);
|
||||
Status s = versions_->LogAndApply(cfd, *cfd->GetLatestMutableCFOptions(),
|
||||
&edit, &mutex_);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
current_ts_low = cfd->GetFullHistoryTsLow();
|
||||
if (!current_ts_low.empty() &&
|
||||
ucmp->CompareTimestamp(current_ts_low, ts_low) > 0) {
|
||||
std::stringstream oss;
|
||||
oss << "full_history_ts_low: " << Slice(current_ts_low).ToString(true)
|
||||
<< " is set to be higher than the requested "
|
||||
"timestamp: "
|
||||
<< Slice(ts_low).ToString(true) << std::endl;
|
||||
return Status::TryAgain(oss.str());
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DBImpl::CompactRangeInternal(const CompactRangeOptions& options,
|
||||
@@ -1217,6 +1231,10 @@ Status DBImpl::CompactFiles(const CompactionOptions& compact_options,
|
||||
|
||||
// Perform CompactFiles
|
||||
TEST_SYNC_POINT("TestCompactFiles::IngestExternalFile2");
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"TestCompactFiles:PausingManualCompaction:3",
|
||||
reinterpret_cast<void*>(
|
||||
const_cast<std::atomic<int>*>(&manual_compaction_paused_)));
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
|
||||
@@ -1372,7 +1390,7 @@ Status DBImpl::CompactFilesImpl(
|
||||
c->mutable_cf_options()->paranoid_file_checks,
|
||||
c->mutable_cf_options()->report_bg_io_stats, dbname_,
|
||||
&compaction_job_stats, Env::Priority::USER, io_tracer_,
|
||||
&manual_compaction_paused_, nullptr, db_id_, db_session_id_,
|
||||
kManualCompactionCanceledFalse_, db_id_, db_session_id_,
|
||||
c->column_family_data()->GetFullHistoryTsLow(), c->trim_ts(),
|
||||
&blob_callback_);
|
||||
|
||||
@@ -1838,8 +1856,7 @@ Status DBImpl::RunManualCompaction(
|
||||
// and `CompactRangeOptions::canceled` might not work well together.
|
||||
while (bg_bottom_compaction_scheduled_ > 0 ||
|
||||
bg_compaction_scheduled_ > 0) {
|
||||
if (manual_compaction_paused_ > 0 ||
|
||||
(manual.canceled != nullptr && *manual.canceled == true)) {
|
||||
if (manual_compaction_paused_ > 0 || manual.canceled == true) {
|
||||
// Pretend the error came from compaction so the below cleanup/error
|
||||
// handling code can process it.
|
||||
manual.done = true;
|
||||
@@ -2376,10 +2393,18 @@ Status DBImpl::EnableAutoCompaction(
|
||||
return s;
|
||||
}
|
||||
|
||||
// NOTE: Calling DisableManualCompaction() may overwrite the
|
||||
// user-provided canceled variable in CompactRangeOptions
|
||||
void DBImpl::DisableManualCompaction() {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
manual_compaction_paused_.fetch_add(1, std::memory_order_release);
|
||||
|
||||
// Mark the canceled as true when the cancellation is triggered by
|
||||
// manual_compaction_paused (may overwrite user-provided `canceled`)
|
||||
for (const auto& manual_compaction : manual_compaction_dequeue_) {
|
||||
manual_compaction->canceled = true;
|
||||
}
|
||||
|
||||
// Wake up manual compactions waiting to start.
|
||||
bg_cv_.SignalAll();
|
||||
|
||||
@@ -2392,6 +2417,11 @@ void DBImpl::DisableManualCompaction() {
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: In contrast to DisableManualCompaction(), calling
|
||||
// EnableManualCompaction() does NOT overwrite the user-provided *canceled
|
||||
// variable to be false since there is NO CHANCE a canceled compaction
|
||||
// is uncanceled. In other words, a canceled compaction must have been
|
||||
// dropped out of the manual compaction queue, when we disable it.
|
||||
void DBImpl::EnableManualCompaction() {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
assert(manual_compaction_paused_ > 0);
|
||||
@@ -3037,10 +3067,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
if (shutting_down_.load(std::memory_order_acquire)) {
|
||||
status = Status::ShutdownInProgress();
|
||||
} else if (is_manual &&
|
||||
manual_compaction_paused_.load(std::memory_order_acquire) > 0) {
|
||||
status = Status::Incomplete(Status::SubCode::kManualCompactionPaused);
|
||||
} else if (is_manual && manual_compaction->canceled &&
|
||||
manual_compaction->canceled->load(std::memory_order_acquire)) {
|
||||
manual_compaction->canceled.load(std::memory_order_acquire)) {
|
||||
status = Status::Incomplete(Status::SubCode::kManualCompactionPaused);
|
||||
}
|
||||
} else {
|
||||
@@ -3357,6 +3384,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
GetSnapshotContext(job_context, &snapshot_seqs,
|
||||
&earliest_write_conflict_snapshot, &snapshot_checker);
|
||||
assert(is_snapshot_supported_ || snapshots_.empty());
|
||||
|
||||
CompactionJob compaction_job(
|
||||
job_context->job_id, c.get(), immutable_db_options_,
|
||||
mutable_db_options_, file_options_for_compaction_, versions_.get(),
|
||||
@@ -3368,9 +3396,9 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
c->mutable_cf_options()->paranoid_file_checks,
|
||||
c->mutable_cf_options()->report_bg_io_stats, dbname_,
|
||||
&compaction_job_stats, thread_pri, io_tracer_,
|
||||
is_manual ? &manual_compaction_paused_ : nullptr,
|
||||
is_manual ? manual_compaction->canceled : nullptr, db_id_,
|
||||
db_session_id_, c->column_family_data()->GetFullHistoryTsLow(),
|
||||
is_manual ? manual_compaction->canceled
|
||||
: kManualCompactionCanceledFalse_,
|
||||
db_id_, db_session_id_, c->column_family_data()->GetFullHistoryTsLow(),
|
||||
c->trim_ts(), &blob_callback_);
|
||||
compaction_job.Prepare();
|
||||
|
||||
|
||||
+65
-57
@@ -166,8 +166,8 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
job_context->log_number = MinLogNumberToKeep();
|
||||
job_context->prev_log_number = versions_->prev_log_number();
|
||||
|
||||
versions_->AddLiveFiles(&job_context->sst_live, &job_context->blob_live);
|
||||
if (doing_the_full_scan) {
|
||||
versions_->AddLiveFiles(&job_context->sst_live, &job_context->blob_live);
|
||||
InfoLogPrefix info_log_prefix(!immutable_db_options_.db_log_dir.empty(),
|
||||
dbname_);
|
||||
std::set<std::string> paths;
|
||||
@@ -242,6 +242,14 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
log_file, immutable_db_options_.db_log_dir);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Instead of filling ob_context->sst_live and job_context->blob_live,
|
||||
// directly remove files that show up in any Version. This is because
|
||||
// candidate files tend to be a small percentage of all files, so it is
|
||||
// usually cheaper to check them against every version, compared to
|
||||
// building a map for all files.
|
||||
versions_->RemoveLiveFiles(job_context->sst_delete_files,
|
||||
job_context->blob_delete_files);
|
||||
}
|
||||
|
||||
// logs_ is empty when called during recovery, in which case there can't yet
|
||||
@@ -280,7 +288,7 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
}
|
||||
while (!logs_.empty() && logs_.front().number < min_log_number) {
|
||||
auto& log = logs_.front();
|
||||
if (log.getting_synced) {
|
||||
if (log.IsSyncing()) {
|
||||
log_sync_cv_.Wait();
|
||||
// logs_ could have changed while we were waiting.
|
||||
continue;
|
||||
@@ -395,8 +403,10 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
state.manifest_delete_files.size());
|
||||
// We may ignore the dbname when generating the file names.
|
||||
for (auto& file : state.sst_delete_files) {
|
||||
candidate_files.emplace_back(
|
||||
MakeTableFileName(file.metadata->fd.GetNumber()), file.path);
|
||||
if (!file.only_delete_metadata) {
|
||||
candidate_files.emplace_back(
|
||||
MakeTableFileName(file.metadata->fd.GetNumber()), file.path);
|
||||
}
|
||||
if (file.metadata->table_reader_handle) {
|
||||
table_cache_->Release(file.metadata->table_reader_handle);
|
||||
}
|
||||
@@ -863,49 +873,61 @@ uint64_t PrecomputeMinLogNumberToKeep2PC(
|
||||
return min_log_number_to_keep;
|
||||
}
|
||||
|
||||
Status DBImpl::SetDBId(bool read_only) {
|
||||
void DBImpl::SetDBId(std::string&& id, bool read_only,
|
||||
RecoveryContext* recovery_ctx) {
|
||||
assert(db_id_.empty());
|
||||
assert(!id.empty());
|
||||
db_id_ = std::move(id);
|
||||
if (!read_only && immutable_db_options_.write_dbid_to_manifest) {
|
||||
assert(recovery_ctx != nullptr);
|
||||
assert(versions_->GetColumnFamilySet() != nullptr);
|
||||
VersionEdit edit;
|
||||
edit.SetDBId(db_id_);
|
||||
versions_->db_id_ = db_id_;
|
||||
recovery_ctx->UpdateVersionEdits(
|
||||
versions_->GetColumnFamilySet()->GetDefault(), edit);
|
||||
}
|
||||
}
|
||||
|
||||
Status DBImpl::SetupDBId(bool read_only, RecoveryContext* recovery_ctx) {
|
||||
Status s;
|
||||
// Happens when immutable_db_options_.write_dbid_to_manifest is set to true
|
||||
// the very first time.
|
||||
if (db_id_.empty()) {
|
||||
// Check for the IDENTITY file and create it if not there.
|
||||
s = fs_->FileExists(IdentityFileName(dbname_), IOOptions(), nullptr);
|
||||
// Typically Identity file is created in NewDB() and for some reason if
|
||||
// it is no longer available then at this point DB ID is not in Identity
|
||||
// file or Manifest.
|
||||
if (s.IsNotFound()) {
|
||||
// Create a new DB ID, saving to file only if allowed
|
||||
if (read_only) {
|
||||
db_id_ = env_->GenerateUniqueId();
|
||||
return Status::OK();
|
||||
} else {
|
||||
s = SetIdentityFile(env_, dbname_);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
// Check for the IDENTITY file and create it if not there or
|
||||
// broken or not matching manifest
|
||||
std::string db_id_in_file;
|
||||
s = fs_->FileExists(IdentityFileName(dbname_), IOOptions(), nullptr);
|
||||
if (s.ok()) {
|
||||
s = GetDbIdentityFromIdentityFile(&db_id_in_file);
|
||||
if (s.ok() && !db_id_in_file.empty()) {
|
||||
if (db_id_.empty()) {
|
||||
// Loaded from file and wasn't already known from manifest
|
||||
SetDBId(std::move(db_id_in_file), read_only, recovery_ctx);
|
||||
return s;
|
||||
} else if (db_id_ == db_id_in_file) {
|
||||
// Loaded from file and matches manifest
|
||||
return s;
|
||||
}
|
||||
} else if (!s.ok()) {
|
||||
assert(s.IsIOError());
|
||||
return s;
|
||||
}
|
||||
s = GetDbIdentityFromIdentityFile(&db_id_);
|
||||
if (immutable_db_options_.write_dbid_to_manifest && s.ok()) {
|
||||
VersionEdit edit;
|
||||
edit.SetDBId(db_id_);
|
||||
Options options;
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
versions_->db_id_ = db_id_;
|
||||
s = versions_->LogAndApply(versions_->GetColumnFamilySet()->GetDefault(),
|
||||
mutable_cf_options, &edit, &mutex_, nullptr,
|
||||
/* new_descriptor_log */ false);
|
||||
}
|
||||
} else if (!read_only) {
|
||||
}
|
||||
if (s.IsNotFound()) {
|
||||
s = Status::OK();
|
||||
}
|
||||
if (!s.ok()) {
|
||||
assert(s.IsIOError());
|
||||
return s;
|
||||
}
|
||||
// Otherwise IDENTITY file is missing or no good.
|
||||
// Generate new id if needed
|
||||
if (db_id_.empty()) {
|
||||
SetDBId(env_->GenerateUniqueId(), read_only, recovery_ctx);
|
||||
}
|
||||
// Persist it to IDENTITY file if allowed
|
||||
if (!read_only) {
|
||||
s = SetIdentityFile(env_, dbname_, db_id_);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::DeleteUnreferencedSstFiles() {
|
||||
Status DBImpl::DeleteUnreferencedSstFiles(RecoveryContext* recovery_ctx) {
|
||||
mutex_.AssertHeld();
|
||||
std::vector<std::string> paths;
|
||||
paths.push_back(NormalizePath(dbname_ + std::string(1, kFilePathSeparator)));
|
||||
@@ -925,7 +947,6 @@ Status DBImpl::DeleteUnreferencedSstFiles() {
|
||||
|
||||
uint64_t next_file_number = versions_->current_next_file_number();
|
||||
uint64_t largest_file_number = next_file_number;
|
||||
std::set<std::string> files_to_delete;
|
||||
Status s;
|
||||
for (const auto& path : paths) {
|
||||
std::vector<std::string> files;
|
||||
@@ -943,8 +964,9 @@ Status DBImpl::DeleteUnreferencedSstFiles() {
|
||||
const std::string normalized_fpath = path + fname;
|
||||
largest_file_number = std::max(largest_file_number, number);
|
||||
if (type == kTableFile && number >= next_file_number &&
|
||||
files_to_delete.find(normalized_fpath) == files_to_delete.end()) {
|
||||
files_to_delete.insert(normalized_fpath);
|
||||
recovery_ctx->files_to_delete_.find(normalized_fpath) ==
|
||||
recovery_ctx->files_to_delete_.end()) {
|
||||
recovery_ctx->files_to_delete_.emplace(normalized_fpath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -961,21 +983,7 @@ Status DBImpl::DeleteUnreferencedSstFiles() {
|
||||
assert(versions_->GetColumnFamilySet());
|
||||
ColumnFamilyData* default_cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
assert(default_cfd);
|
||||
s = versions_->LogAndApply(
|
||||
default_cfd, *default_cfd->GetLatestMutableCFOptions(), &edit, &mutex_,
|
||||
directories_.GetDbDir(), /*new_descriptor_log*/ false);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
mutex_.Unlock();
|
||||
for (const auto& fname : files_to_delete) {
|
||||
s = env_->DeleteFile(fname);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_.Lock();
|
||||
recovery_ctx->UpdateVersionEdits(default_cfd, edit);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
+133
-87
@@ -27,8 +27,9 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
Options SanitizeOptions(const std::string& dbname, const Options& src,
|
||||
bool read_only) {
|
||||
auto db_options = SanitizeOptions(dbname, DBOptions(src), read_only);
|
||||
bool read_only, Status* logger_creation_s) {
|
||||
auto db_options =
|
||||
SanitizeOptions(dbname, DBOptions(src), read_only, logger_creation_s);
|
||||
ImmutableDBOptions immutable_db_options(db_options);
|
||||
auto cf_options =
|
||||
SanitizeOptions(immutable_db_options, ColumnFamilyOptions(src));
|
||||
@@ -36,7 +37,7 @@ Options SanitizeOptions(const std::string& dbname, const Options& src,
|
||||
}
|
||||
|
||||
DBOptions SanitizeOptions(const std::string& dbname, const DBOptions& src,
|
||||
bool read_only) {
|
||||
bool read_only, Status* logger_creation_s) {
|
||||
DBOptions result(src);
|
||||
|
||||
if (result.env == nullptr) {
|
||||
@@ -59,6 +60,9 @@ DBOptions SanitizeOptions(const std::string& dbname, const DBOptions& src,
|
||||
if (!s.ok()) {
|
||||
// No place suitable for logging
|
||||
result.info_log = nullptr;
|
||||
if (logger_creation_s) {
|
||||
*logger_creation_s = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,7 +403,7 @@ IOStatus Directories::SetDirectories(FileSystem* fs, const std::string& dbname,
|
||||
Status DBImpl::Recover(
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families, bool read_only,
|
||||
bool error_if_wal_file_exists, bool error_if_data_exists_in_wals,
|
||||
uint64_t* recovered_seq) {
|
||||
uint64_t* recovered_seq, RecoveryContext* recovery_ctx) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
bool is_new_db = false;
|
||||
@@ -524,15 +528,17 @@ Status DBImpl::Recover(
|
||||
return s;
|
||||
}
|
||||
}
|
||||
s = SetDBId(read_only);
|
||||
s = SetupDBId(read_only, recovery_ctx);
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "DB ID: %s\n", db_id_.c_str());
|
||||
if (s.ok() && !read_only) {
|
||||
s = DeleteUnreferencedSstFiles();
|
||||
s = DeleteUnreferencedSstFiles(recovery_ctx);
|
||||
}
|
||||
|
||||
if (immutable_db_options_.paranoid_checks && s.ok()) {
|
||||
s = CheckConsistency();
|
||||
}
|
||||
if (s.ok() && !read_only) {
|
||||
// TODO: share file descriptors (FSDirectory) with SetDirectories above
|
||||
std::map<std::string, std::shared_ptr<FSDirectory>> created_dirs;
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
s = cfd->AddDirectories(&created_dirs);
|
||||
@@ -541,10 +547,6 @@ Status DBImpl::Recover(
|
||||
}
|
||||
}
|
||||
}
|
||||
// DB mutex is already held
|
||||
if (s.ok() && immutable_db_options_.persist_stats_to_disk) {
|
||||
s = InitPersistStatsColumnFamily();
|
||||
}
|
||||
|
||||
std::vector<std::string> files_in_wal_dir;
|
||||
if (s.ok()) {
|
||||
@@ -561,10 +563,6 @@ Status DBImpl::Recover(
|
||||
default_cf_handle_ = new ColumnFamilyHandleImpl(
|
||||
versions_->GetColumnFamilySet()->GetDefault(), this, &mutex_);
|
||||
default_cf_internal_stats_ = default_cf_handle_->cfd()->internal_stats();
|
||||
// TODO(Zhongyi): handle single_column_family_mode_ when
|
||||
// persistent_stats is enabled
|
||||
single_column_family_mode_ =
|
||||
versions_->GetColumnFamilySet()->NumberOfColumnFamilies() == 1;
|
||||
|
||||
// Recover from all newer log files than the ones named in the
|
||||
// descriptor (new log files may have been added by the previous
|
||||
@@ -614,7 +612,10 @@ Status DBImpl::Recover(
|
||||
WalNumber max_wal_number =
|
||||
versions_->GetWalSet().GetWals().rbegin()->first;
|
||||
edit.DeleteWalsBefore(max_wal_number + 1);
|
||||
s = versions_->LogAndApplyToDefaultColumnFamily(&edit, &mutex_);
|
||||
assert(recovery_ctx != nullptr);
|
||||
assert(versions_->GetColumnFamilySet() != nullptr);
|
||||
recovery_ctx->UpdateVersionEdits(
|
||||
versions_->GetColumnFamilySet()->GetDefault(), edit);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -650,8 +651,8 @@ Status DBImpl::Recover(
|
||||
std::sort(wals.begin(), wals.end());
|
||||
|
||||
bool corrupted_wal_found = false;
|
||||
s = RecoverLogFiles(wals, &next_sequence, read_only,
|
||||
&corrupted_wal_found);
|
||||
s = RecoverLogFiles(wals, &next_sequence, read_only, &corrupted_wal_found,
|
||||
recovery_ctx);
|
||||
if (corrupted_wal_found && recovered_seq != nullptr) {
|
||||
*recovered_seq = next_sequence;
|
||||
}
|
||||
@@ -830,10 +831,30 @@ Status DBImpl::InitPersistStatsColumnFamily() {
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::LogAndApplyForRecovery(const RecoveryContext& recovery_ctx) {
|
||||
mutex_.AssertHeld();
|
||||
assert(versions_->descriptor_log_ == nullptr);
|
||||
Status s = versions_->LogAndApply(
|
||||
recovery_ctx.cfds_, recovery_ctx.mutable_cf_opts_,
|
||||
recovery_ctx.edit_lists_, &mutex_, directories_.GetDbDir());
|
||||
if (s.ok() && !(recovery_ctx.files_to_delete_.empty())) {
|
||||
mutex_.Unlock();
|
||||
for (const auto& fname : recovery_ctx.files_to_delete_) {
|
||||
s = env_->DeleteFile(fname);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_.Lock();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// REQUIRES: wal_numbers are sorted in ascending order
|
||||
Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& wal_numbers,
|
||||
SequenceNumber* next_sequence, bool read_only,
|
||||
bool* corrupted_wal_found) {
|
||||
bool* corrupted_wal_found,
|
||||
RecoveryContext* recovery_ctx) {
|
||||
struct LogReporter : public log::Reader::Reporter {
|
||||
Env* env;
|
||||
Logger* info_log;
|
||||
@@ -987,12 +1008,17 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& wal_numbers,
|
||||
|
||||
// We create a new batch and initialize with a valid prot_info_ to store
|
||||
// the data checksums
|
||||
WriteBatch batch(0, 0, 8, 0);
|
||||
WriteBatch batch;
|
||||
|
||||
status = WriteBatchInternal::SetContents(&batch, record);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
status = WriteBatchInternal::UpdateProtectionInfo(&batch,
|
||||
8 /* bytes_per_key */);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
SequenceNumber sequence = WriteBatchInternal::Sequence(&batch);
|
||||
|
||||
@@ -1291,44 +1317,36 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& wal_numbers,
|
||||
// VersionSet::next_file_number_ always to be strictly greater than any
|
||||
// log number
|
||||
versions_->MarkFileNumberUsed(max_wal_number + 1);
|
||||
assert(recovery_ctx != nullptr);
|
||||
|
||||
autovector<ColumnFamilyData*> cfds;
|
||||
autovector<const MutableCFOptions*> cf_opts;
|
||||
autovector<autovector<VersionEdit*>> edit_lists;
|
||||
for (auto* cfd : *versions_->GetColumnFamilySet()) {
|
||||
cfds.push_back(cfd);
|
||||
cf_opts.push_back(cfd->GetLatestMutableCFOptions());
|
||||
auto iter = version_edits.find(cfd->GetID());
|
||||
assert(iter != version_edits.end());
|
||||
edit_lists.push_back({&iter->second});
|
||||
recovery_ctx->UpdateVersionEdits(cfd, iter->second);
|
||||
}
|
||||
|
||||
std::unique_ptr<VersionEdit> wal_deletion;
|
||||
if (flushed) {
|
||||
wal_deletion = std::make_unique<VersionEdit>();
|
||||
VersionEdit wal_deletion;
|
||||
if (immutable_db_options_.track_and_verify_wals_in_manifest) {
|
||||
wal_deletion->DeleteWalsBefore(max_wal_number + 1);
|
||||
wal_deletion.DeleteWalsBefore(max_wal_number + 1);
|
||||
}
|
||||
if (!allow_2pc()) {
|
||||
// In non-2pc mode, flushing the memtables of the column families
|
||||
// means we can advance min_log_number_to_keep.
|
||||
wal_deletion->SetMinLogNumberToKeep(max_wal_number + 1);
|
||||
wal_deletion.SetMinLogNumberToKeep(max_wal_number + 1);
|
||||
}
|
||||
edit_lists.back().push_back(wal_deletion.get());
|
||||
assert(versions_->GetColumnFamilySet() != nullptr);
|
||||
recovery_ctx->UpdateVersionEdits(
|
||||
versions_->GetColumnFamilySet()->GetDefault(), wal_deletion);
|
||||
}
|
||||
|
||||
// write MANIFEST with update
|
||||
status = versions_->LogAndApply(cfds, cf_opts, edit_lists, &mutex_,
|
||||
directories_.GetDbDir(),
|
||||
/*new_descriptor_log=*/true);
|
||||
}
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
if (data_seen && !flushed) {
|
||||
status = RestoreAliveLogFiles(wal_numbers);
|
||||
} else {
|
||||
// If there's no data in the WAL, or we flushed all the data, still
|
||||
} else if (!wal_numbers.empty()) { // If there's no data in the WAL, or we
|
||||
// flushed all the data, still
|
||||
// truncate the log file. If the process goes into a crash loop before
|
||||
// the file is deleted, the preallocated space will never get freed.
|
||||
const bool truncate = !read_only;
|
||||
@@ -1413,7 +1431,6 @@ Status DBImpl::RestoreAliveLogFiles(const std::vector<uint64_t>& wal_numbers) {
|
||||
total_log_size_ += log.size;
|
||||
alive_log_files_.push_back(log);
|
||||
}
|
||||
alive_log_files_tail_ = alive_log_files_.rbegin();
|
||||
if (two_write_queues_) {
|
||||
log_write_mutex_.Unlock();
|
||||
}
|
||||
@@ -1724,6 +1741,7 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
}
|
||||
|
||||
*dbptr = nullptr;
|
||||
assert(handles);
|
||||
handles->clear();
|
||||
|
||||
size_t max_write_buffer_size = 0;
|
||||
@@ -1733,6 +1751,13 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
}
|
||||
|
||||
DBImpl* impl = new DBImpl(db_options, dbname, seq_per_batch, batch_per_txn);
|
||||
if (!impl->immutable_db_options_.info_log) {
|
||||
s = impl->init_logger_creation_s_;
|
||||
delete impl;
|
||||
return s;
|
||||
} else {
|
||||
assert(impl->init_logger_creation_s_.ok());
|
||||
}
|
||||
s = impl->env_->CreateDirIfMissing(impl->immutable_db_options_.GetWalDir());
|
||||
if (s.ok()) {
|
||||
std::vector<std::string> paths;
|
||||
@@ -1766,11 +1791,13 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
}
|
||||
|
||||
impl->wal_in_db_path_ = impl->immutable_db_options_.IsWalDirSameAsDBPath();
|
||||
|
||||
RecoveryContext recovery_ctx;
|
||||
impl->mutex_.Lock();
|
||||
|
||||
// Handles create_if_missing, error_if_exists
|
||||
uint64_t recovered_seq(kMaxSequenceNumber);
|
||||
s = impl->Recover(column_families, false, false, false, &recovered_seq);
|
||||
s = impl->Recover(column_families, false, false, false, &recovered_seq,
|
||||
&recovery_ctx);
|
||||
if (s.ok()) {
|
||||
uint64_t new_log_number = impl->versions_->NewFileNumber();
|
||||
log::Writer* new_log = nullptr;
|
||||
@@ -1787,46 +1814,11 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
// set column family handles
|
||||
for (auto cf : column_families) {
|
||||
auto cfd =
|
||||
impl->versions_->GetColumnFamilySet()->GetColumnFamily(cf.name);
|
||||
if (cfd != nullptr) {
|
||||
handles->push_back(
|
||||
new ColumnFamilyHandleImpl(cfd, impl, &impl->mutex_));
|
||||
impl->NewThreadStatusCfInfo(cfd);
|
||||
} else {
|
||||
if (db_options.create_missing_column_families) {
|
||||
// missing column family, create it
|
||||
ColumnFamilyHandle* handle;
|
||||
impl->mutex_.Unlock();
|
||||
s = impl->CreateColumnFamily(cf.options, cf.name, &handle);
|
||||
impl->mutex_.Lock();
|
||||
if (s.ok()) {
|
||||
handles->push_back(handle);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
s = Status::InvalidArgument("Column family not found", cf.name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
SuperVersionContext sv_context(/* create_superversion */ true);
|
||||
for (auto cfd : *impl->versions_->GetColumnFamilySet()) {
|
||||
impl->InstallSuperVersionAndScheduleWork(
|
||||
cfd, &sv_context, *cfd->GetLatestMutableCFOptions());
|
||||
}
|
||||
sv_context.Clean();
|
||||
if (impl->two_write_queues_) {
|
||||
impl->log_write_mutex_.Lock();
|
||||
}
|
||||
impl->alive_log_files_.push_back(
|
||||
DBImpl::LogFileNumberSize(impl->logfile_number_));
|
||||
impl->alive_log_files_tail_ = impl->alive_log_files_.rbegin();
|
||||
if (impl->two_write_queues_) {
|
||||
impl->log_write_mutex_.Unlock();
|
||||
}
|
||||
@@ -1847,8 +1839,12 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
WriteOptions write_options;
|
||||
uint64_t log_used, log_size;
|
||||
log::Writer* log_writer = impl->logs_.back().writer;
|
||||
LogFileNumberSize& log_file_number_size = impl->alive_log_files_.back();
|
||||
|
||||
assert(log_writer->get_log_number() == log_file_number_size.number);
|
||||
impl->mutex_.AssertHeld();
|
||||
s = impl->WriteToWAL(empty_batch, log_writer, &log_used, &log_size,
|
||||
Env::IO_TOTAL, /*with_db_mutex==*/true);
|
||||
Env::IO_TOTAL, log_file_number_size);
|
||||
if (s.ok()) {
|
||||
// Need to fsync, otherwise it might get lost after a power reset.
|
||||
s = impl->FlushWAL(false);
|
||||
@@ -1860,6 +1856,53 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = impl->LogAndApplyForRecovery(recovery_ctx);
|
||||
}
|
||||
|
||||
if (s.ok() && impl->immutable_db_options_.persist_stats_to_disk) {
|
||||
impl->mutex_.AssertHeld();
|
||||
s = impl->InitPersistStatsColumnFamily();
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
// set column family handles
|
||||
for (auto cf : column_families) {
|
||||
auto cfd =
|
||||
impl->versions_->GetColumnFamilySet()->GetColumnFamily(cf.name);
|
||||
if (cfd != nullptr) {
|
||||
handles->push_back(
|
||||
new ColumnFamilyHandleImpl(cfd, impl, &impl->mutex_));
|
||||
impl->NewThreadStatusCfInfo(cfd);
|
||||
} else {
|
||||
if (db_options.create_missing_column_families) {
|
||||
// missing column family, create it
|
||||
ColumnFamilyHandle* handle = nullptr;
|
||||
impl->mutex_.Unlock();
|
||||
s = impl->CreateColumnFamily(cf.options, cf.name, &handle);
|
||||
impl->mutex_.Lock();
|
||||
if (s.ok()) {
|
||||
handles->push_back(handle);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
s = Status::InvalidArgument("Column family not found", cf.name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
SuperVersionContext sv_context(/* create_superversion */ true);
|
||||
for (auto cfd : *impl->versions_->GetColumnFamilySet()) {
|
||||
impl->InstallSuperVersionAndScheduleWork(
|
||||
cfd, &sv_context, *cfd->GetLatestMutableCFOptions());
|
||||
}
|
||||
sv_context.Clean();
|
||||
}
|
||||
|
||||
if (s.ok() && impl->immutable_db_options_.persist_stats_to_disk) {
|
||||
// try to read format version
|
||||
s = impl->PersistentStatsProcessFormatVersion();
|
||||
@@ -1930,21 +1973,24 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
// vast majority of all files), we'll pass the size to SstFileManager.
|
||||
// For all other files SstFileManager will query the size from filesystem.
|
||||
|
||||
std::vector<LiveFileMetaData> metadata;
|
||||
|
||||
// TODO: Once GetLiveFilesMetaData supports blob files, update the logic
|
||||
// below to get known_file_sizes for blob files.
|
||||
impl->mutex_.Lock();
|
||||
impl->versions_->GetLiveFilesMetaData(&metadata);
|
||||
impl->mutex_.Unlock();
|
||||
std::vector<ColumnFamilyMetaData> metadata;
|
||||
impl->GetAllColumnFamilyMetaData(&metadata);
|
||||
|
||||
std::unordered_map<std::string, uint64_t> known_file_sizes;
|
||||
for (const auto& md : metadata) {
|
||||
std::string name = md.name;
|
||||
if (!name.empty() && name[0] == '/') {
|
||||
name = name.substr(1);
|
||||
for (const auto& lmd : md.levels) {
|
||||
for (const auto& fmd : lmd.files) {
|
||||
known_file_sizes[fmd.relative_filename] = fmd.size;
|
||||
}
|
||||
}
|
||||
for (const auto& bmd : md.blob_files) {
|
||||
std::string name = bmd.blob_file_name;
|
||||
// The BlobMetaData.blob_file_name may start with "/".
|
||||
if (!name.empty() && name[0] == '/') {
|
||||
name = name.substr(1);
|
||||
}
|
||||
known_file_sizes[name] = bmd.blob_file_size;
|
||||
}
|
||||
known_file_sizes[name] = md.size;
|
||||
}
|
||||
|
||||
std::vector<std::string> paths;
|
||||
|
||||
@@ -47,8 +47,8 @@ Status DBImplReadOnly::Get(const ReadOptions& read_options,
|
||||
|
||||
assert(column_family);
|
||||
if (read_options.timestamp) {
|
||||
const Status s =
|
||||
FailIfTsSizesMismatch(column_family, *(read_options.timestamp));
|
||||
const Status s = FailIfTsMismatchCf(
|
||||
column_family, *(read_options.timestamp), /*ts_for_read=*/true);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -58,6 +58,13 @@ Status DBImplReadOnly::Get(const ReadOptions& read_options,
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the timestamps for returning results so that we can distinguish
|
||||
// between tombstone or key that has never been written
|
||||
if (timestamp) {
|
||||
timestamp->clear();
|
||||
}
|
||||
|
||||
const Comparator* ucmp = column_family->GetComparator();
|
||||
assert(ucmp);
|
||||
std::string* ts = ucmp->timestamp_size() > 0 ? timestamp : nullptr;
|
||||
@@ -107,8 +114,8 @@ Iterator* DBImplReadOnly::NewIterator(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family) {
|
||||
assert(column_family);
|
||||
if (read_options.timestamp) {
|
||||
const Status s =
|
||||
FailIfTsSizesMismatch(column_family, *(read_options.timestamp));
|
||||
const Status s = FailIfTsMismatchCf(
|
||||
column_family, *(read_options.timestamp), /*ts_for_read=*/true);
|
||||
if (!s.ok()) {
|
||||
return NewErrorIterator(s);
|
||||
}
|
||||
@@ -148,7 +155,8 @@ Status DBImplReadOnly::NewIterators(
|
||||
if (read_options.timestamp) {
|
||||
for (auto* cf : column_families) {
|
||||
assert(cf);
|
||||
const Status s = FailIfTsSizesMismatch(cf, *(read_options.timestamp));
|
||||
const Status s = FailIfTsMismatchCf(cf, *(read_options.timestamp),
|
||||
/*ts_for_read=*/true);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,8 @@ DBImplSecondary::~DBImplSecondary() {}
|
||||
Status DBImplSecondary::Recover(
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool /*readonly*/, bool /*error_if_wal_file_exists*/,
|
||||
bool /*error_if_data_exists_in_wals*/, uint64_t*) {
|
||||
bool /*error_if_data_exists_in_wals*/, uint64_t*,
|
||||
RecoveryContext* /*recovery_ctx*/) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
JobContext job_context(0);
|
||||
@@ -61,8 +62,6 @@ Status DBImplSecondary::Recover(
|
||||
default_cf_handle_ = new ColumnFamilyHandleImpl(
|
||||
versions_->GetColumnFamilySet()->GetDefault(), this, &mutex_);
|
||||
default_cf_internal_stats_ = default_cf_handle_->cfd()->internal_stats();
|
||||
single_column_family_mode_ =
|
||||
versions_->GetColumnFamilySet()->NumberOfColumnFamilies() == 1;
|
||||
|
||||
std::unordered_set<ColumnFamilyData*> cfds_changed;
|
||||
s = FindAndRecoverLogFiles(&cfds_changed, &job_context);
|
||||
@@ -329,23 +328,43 @@ Status DBImplSecondary::RecoverLogFiles(
|
||||
Status DBImplSecondary::Get(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value) {
|
||||
return GetImpl(read_options, column_family, key, value);
|
||||
return GetImpl(read_options, column_family, key, value,
|
||||
/*timestamp*/ nullptr);
|
||||
}
|
||||
|
||||
Status DBImplSecondary::Get(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value, std::string* timestamp) {
|
||||
return GetImpl(read_options, column_family, key, value, timestamp);
|
||||
}
|
||||
|
||||
Status DBImplSecondary::GetImpl(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* pinnable_val) {
|
||||
const Slice& key, PinnableSlice* pinnable_val,
|
||||
std::string* timestamp) {
|
||||
assert(pinnable_val != nullptr);
|
||||
PERF_CPU_TIMER_GUARD(get_cpu_nanos, immutable_db_options_.clock);
|
||||
StopWatch sw(immutable_db_options_.clock, stats_, DB_GET);
|
||||
PERF_TIMER_GUARD(get_snapshot_time);
|
||||
|
||||
assert(column_family);
|
||||
const Comparator* ucmp = column_family->GetComparator();
|
||||
assert(ucmp);
|
||||
if (ucmp->timestamp_size() || read_options.timestamp) {
|
||||
// TODO: support timestamp
|
||||
return Status::NotSupported();
|
||||
if (read_options.timestamp) {
|
||||
const Status s = FailIfTsMismatchCf(
|
||||
column_family, *(read_options.timestamp), /*ts_for_read=*/true);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
} else {
|
||||
const Status s = FailIfCfHasTs(column_family);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the timestamp for returning results so that we can distinguish
|
||||
// between tombstone or key that has never been written later.
|
||||
if (timestamp) {
|
||||
timestamp->clear();
|
||||
}
|
||||
|
||||
auto cfh = static_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
@@ -359,23 +378,27 @@ Status DBImplSecondary::GetImpl(const ReadOptions& read_options,
|
||||
// Acquire SuperVersion
|
||||
SuperVersion* super_version = GetAndRefSuperVersion(cfd);
|
||||
SequenceNumber snapshot = versions_->LastSequence();
|
||||
GetWithTimestampReadCallback read_cb(snapshot);
|
||||
MergeContext merge_context;
|
||||
SequenceNumber max_covering_tombstone_seq = 0;
|
||||
Status s;
|
||||
LookupKey lkey(key, snapshot);
|
||||
LookupKey lkey(key, snapshot, read_options.timestamp);
|
||||
PERF_TIMER_STOP(get_snapshot_time);
|
||||
|
||||
bool done = false;
|
||||
if (super_version->mem->Get(lkey, pinnable_val->GetSelf(),
|
||||
/*timestamp=*/nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, read_options)) {
|
||||
const Comparator* ucmp = column_family->GetComparator();
|
||||
assert(ucmp);
|
||||
std::string* ts = ucmp->timestamp_size() > 0 ? timestamp : nullptr;
|
||||
if (super_version->mem->Get(lkey, pinnable_val->GetSelf(), ts, &s,
|
||||
&merge_context, &max_covering_tombstone_seq,
|
||||
read_options, &read_cb)) {
|
||||
done = true;
|
||||
pinnable_val->PinSelf();
|
||||
RecordTick(stats_, MEMTABLE_HIT);
|
||||
} else if ((s.ok() || s.IsMergeInProgress()) &&
|
||||
super_version->imm->Get(
|
||||
lkey, pinnable_val->GetSelf(), /*timestamp=*/nullptr, &s,
|
||||
&merge_context, &max_covering_tombstone_seq, read_options)) {
|
||||
lkey, pinnable_val->GetSelf(), ts, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, read_options, &read_cb)) {
|
||||
done = true;
|
||||
pinnable_val->PinSelf();
|
||||
RecordTick(stats_, MEMTABLE_HIT);
|
||||
@@ -387,9 +410,11 @@ Status DBImplSecondary::GetImpl(const ReadOptions& read_options,
|
||||
if (!done) {
|
||||
PERF_TIMER_GUARD(get_from_output_files_time);
|
||||
PinnedIteratorsManager pinned_iters_mgr;
|
||||
super_version->current->Get(read_options, lkey, pinnable_val,
|
||||
/*timestamp=*/nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, &pinned_iters_mgr);
|
||||
super_version->current->Get(
|
||||
read_options, lkey, pinnable_val, ts, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, &pinned_iters_mgr, /*value_found*/ nullptr,
|
||||
/*key_exists*/ nullptr, /*seq*/ nullptr, &read_cb, /*is_blob*/ nullptr,
|
||||
/*do_merge*/ true);
|
||||
RecordTick(stats_, MEMTABLE_MISS);
|
||||
}
|
||||
{
|
||||
@@ -416,11 +441,17 @@ Iterator* DBImplSecondary::NewIterator(const ReadOptions& read_options,
|
||||
}
|
||||
|
||||
assert(column_family);
|
||||
const Comparator* ucmp = column_family->GetComparator();
|
||||
assert(ucmp);
|
||||
if (ucmp->timestamp_size() || read_options.timestamp) {
|
||||
// TODO: support timestamp
|
||||
return NewErrorIterator(Status::NotSupported());
|
||||
if (read_options.timestamp) {
|
||||
const Status s = FailIfTsMismatchCf(
|
||||
column_family, *(read_options.timestamp), /*ts_for_read=*/true);
|
||||
if (!s.ok()) {
|
||||
return NewErrorIterator(s);
|
||||
}
|
||||
} else {
|
||||
const Status s = FailIfCfHasTs(column_family);
|
||||
if (!s.ok()) {
|
||||
return NewErrorIterator(s);
|
||||
}
|
||||
}
|
||||
|
||||
Iterator* result = nullptr;
|
||||
@@ -479,17 +510,22 @@ Status DBImplSecondary::NewIterators(
|
||||
if (iterators == nullptr) {
|
||||
return Status::InvalidArgument("iterators not allowed to be nullptr");
|
||||
}
|
||||
|
||||
if (read_options.timestamp) {
|
||||
// TODO: support timestamp
|
||||
return Status::NotSupported();
|
||||
for (auto* cf : column_families) {
|
||||
assert(cf);
|
||||
const Status s = FailIfTsMismatchCf(cf, *(read_options.timestamp),
|
||||
/*ts_for_read=*/true);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (auto* cf : column_families) {
|
||||
assert(cf);
|
||||
const Comparator* ucmp = cf->GetComparator();
|
||||
assert(ucmp);
|
||||
if (ucmp->timestamp_size()) {
|
||||
// TODO: support timestamp
|
||||
return Status::NotSupported();
|
||||
const Status s = FailIfCfHasTs(cf);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -502,7 +538,7 @@ Status DBImplSecondary::NewIterators(
|
||||
// TODO (yanqin) support snapshot.
|
||||
return Status::NotSupported("snapshot not supported in secondary mode");
|
||||
} else {
|
||||
SequenceNumber read_seq = versions_->LastSequence();
|
||||
SequenceNumber read_seq(kMaxSequenceNumber);
|
||||
for (auto cfh : column_families) {
|
||||
ColumnFamilyData* cfd = static_cast<ColumnFamilyHandleImpl*>(cfh)->cfd();
|
||||
iterators->push_back(
|
||||
@@ -659,6 +695,7 @@ Status DB::OpenAsSecondary(
|
||||
s = CreateLoggerFromOptions(secondary_path, tmp_opts, &tmp_opts.info_log);
|
||||
if (!s.ok()) {
|
||||
tmp_opts.info_log = nullptr;
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -783,8 +820,8 @@ Status DBImplSecondary::CompactWithoutInstallation(
|
||||
file_options_for_compaction_, versions_.get(), &shutting_down_,
|
||||
&log_buffer, output_dir.get(), stats_, &mutex_, &error_handler_,
|
||||
input.snapshots, table_cache_, &event_logger_, dbname_, io_tracer_,
|
||||
options.canceled, input.db_id, db_session_id_, secondary_path_, input,
|
||||
result);
|
||||
options.canceled ? *options.canceled : kManualCompactionCanceledFalse_,
|
||||
input.db_id, db_session_id_, secondary_path_, input, result);
|
||||
|
||||
mutex_.Unlock();
|
||||
s = compaction_job.Run();
|
||||
|
||||
@@ -81,16 +81,21 @@ class DBImplSecondary : public DBImpl {
|
||||
// and log_readers_ to facilitate future operations.
|
||||
Status Recover(const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool read_only, bool error_if_wal_file_exists,
|
||||
bool error_if_data_exists_in_wals,
|
||||
uint64_t* = nullptr) override;
|
||||
bool error_if_data_exists_in_wals, uint64_t* = nullptr,
|
||||
RecoveryContext* recovery_ctx = nullptr) override;
|
||||
|
||||
// Implementations of the DB interface
|
||||
using DB::Get;
|
||||
Status Get(const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* value) override;
|
||||
|
||||
Status Get(const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* value,
|
||||
std::string* timestamp) override;
|
||||
|
||||
Status GetImpl(const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* value);
|
||||
const Slice& key, PinnableSlice* value,
|
||||
std::string* timestamp);
|
||||
|
||||
using DBImpl::NewIterator;
|
||||
Iterator* NewIterator(const ReadOptions&,
|
||||
|
||||
+174
-79
@@ -30,7 +30,7 @@ Status DBImpl::Put(const WriteOptions& o, ColumnFamilyHandle* column_family,
|
||||
|
||||
Status DBImpl::Put(const WriteOptions& o, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, const Slice& ts, const Slice& val) {
|
||||
const Status s = FailIfTsSizesMismatch(column_family, ts);
|
||||
const Status s = FailIfTsMismatchCf(column_family, ts, /*ts_for_read=*/false);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -63,7 +63,7 @@ Status DBImpl::Delete(const WriteOptions& write_options,
|
||||
Status DBImpl::Delete(const WriteOptions& write_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
const Slice& ts) {
|
||||
const Status s = FailIfTsSizesMismatch(column_family, ts);
|
||||
const Status s = FailIfTsMismatchCf(column_family, ts, /*ts_for_read=*/false);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -83,7 +83,7 @@ Status DBImpl::SingleDelete(const WriteOptions& write_options,
|
||||
Status DBImpl::SingleDelete(const WriteOptions& write_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
const Slice& ts) {
|
||||
const Status s = FailIfTsSizesMismatch(column_family, ts);
|
||||
const Status s = FailIfTsMismatchCf(column_family, ts, /*ts_for_read=*/false);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -106,15 +106,31 @@ void DBImpl::SetRecoverableStatePreReleaseCallback(
|
||||
}
|
||||
|
||||
Status DBImpl::Write(const WriteOptions& write_options, WriteBatch* my_batch) {
|
||||
return WriteImpl(write_options, my_batch, /*callback=*/nullptr,
|
||||
/*log_used=*/nullptr);
|
||||
Status s;
|
||||
if (write_options.protection_bytes_per_key > 0) {
|
||||
s = WriteBatchInternal::UpdateProtectionInfo(
|
||||
my_batch, write_options.protection_bytes_per_key);
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = WriteImpl(write_options, my_batch, /*callback=*/nullptr,
|
||||
/*log_used=*/nullptr);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
Status DBImpl::WriteWithCallback(const WriteOptions& write_options,
|
||||
WriteBatch* my_batch,
|
||||
WriteCallback* callback) {
|
||||
return WriteImpl(write_options, my_batch, callback, nullptr);
|
||||
Status s;
|
||||
if (write_options.protection_bytes_per_key > 0) {
|
||||
s = WriteBatchInternal::UpdateProtectionInfo(
|
||||
my_batch, write_options.protection_bytes_per_key);
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = WriteImpl(write_options, my_batch, callback, nullptr);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
@@ -126,8 +142,13 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
uint64_t* log_used, uint64_t log_ref,
|
||||
bool disable_memtable, uint64_t* seq_used,
|
||||
size_t batch_cnt,
|
||||
PreReleaseCallback* pre_release_callback) {
|
||||
PreReleaseCallback* pre_release_callback,
|
||||
PostMemTableCallback* post_memtable_callback) {
|
||||
assert(!seq_per_batch_ || batch_cnt != 0);
|
||||
assert(my_batch == nullptr || my_batch->Count() == 0 ||
|
||||
write_options.protection_bytes_per_key == 0 ||
|
||||
write_options.protection_bytes_per_key ==
|
||||
my_batch->GetProtectionBytesPerKey());
|
||||
if (my_batch == nullptr) {
|
||||
return Status::InvalidArgument("Batch is nullptr!");
|
||||
} else if (!disable_memtable &&
|
||||
@@ -155,6 +176,10 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
"rate-limiting automatic WAL flush, which requires "
|
||||
"`WriteOptions::disableWAL` and "
|
||||
"`DBOptions::manual_wal_flush` both set to false");
|
||||
} else if (write_options.protection_bytes_per_key != 0 &&
|
||||
write_options.protection_bytes_per_key != 8) {
|
||||
return Status::InvalidArgument(
|
||||
"`WriteOptions::protection_bytes_per_key` must be zero or eight");
|
||||
}
|
||||
// TODO: this use of operator bool on `tracer_` can avoid unnecessary lock
|
||||
// grabs but does not seem thread-safe.
|
||||
@@ -185,6 +210,15 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
return Status::NotSupported(
|
||||
"pipelined_writes is not compatible with unordered_write");
|
||||
}
|
||||
if (immutable_db_options_.enable_pipelined_write &&
|
||||
post_memtable_callback != nullptr) {
|
||||
return Status::NotSupported(
|
||||
"pipelined write currently does not honor post_memtable_callback");
|
||||
}
|
||||
if (seq_per_batch_ && post_memtable_callback != nullptr) {
|
||||
return Status::NotSupported(
|
||||
"seq_per_batch currently does not honor post_memtable_callback");
|
||||
}
|
||||
// Otherwise IsLatestPersistentState optimization does not make sense
|
||||
assert(!WriteBatchInternal::IsLatestPersistentState(my_batch) ||
|
||||
disable_memtable);
|
||||
@@ -241,7 +275,8 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
|
||||
PERF_TIMER_GUARD(write_pre_and_post_process_time);
|
||||
WriteThread::Writer w(write_options, my_batch, callback, log_ref,
|
||||
disable_memtable, batch_cnt, pre_release_callback);
|
||||
disable_memtable, batch_cnt, pre_release_callback,
|
||||
post_memtable_callback);
|
||||
StopWatch write_sw(immutable_db_options_.clock, stats_, DB_WRITE);
|
||||
|
||||
write_thread_.JoinBatchGroup(&w);
|
||||
@@ -268,6 +303,16 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
// we're responsible for exit batch group
|
||||
// TODO(myabandeh): propagate status to write_group
|
||||
auto last_sequence = w.write_group->last_sequence;
|
||||
for (auto* tmp_w : *(w.write_group)) {
|
||||
assert(tmp_w);
|
||||
if (tmp_w->post_memtable_callback) {
|
||||
Status tmp_s =
|
||||
(*tmp_w->post_memtable_callback)(last_sequence, disable_memtable);
|
||||
// TODO: propagate the execution status of post_memtable_callback to
|
||||
// caller.
|
||||
assert(tmp_s.ok());
|
||||
}
|
||||
}
|
||||
versions_->SetLastSequence(last_sequence);
|
||||
MemTableInsertStatusCheck(w.status);
|
||||
write_thread_.ExitAsBatchGroupFollower(&w);
|
||||
@@ -319,7 +364,11 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
|
||||
PERF_TIMER_START(write_pre_and_post_process_time);
|
||||
}
|
||||
|
||||
log::Writer* log_writer = logs_.back().writer;
|
||||
LogFileNumberSize& log_file_number_size = alive_log_files_.back();
|
||||
|
||||
assert(log_writer->get_log_number() == log_file_number_size.number);
|
||||
|
||||
mutex_.Unlock();
|
||||
|
||||
@@ -419,7 +468,8 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
if (status.ok() && !write_options.disableWAL) {
|
||||
PERF_TIMER_GUARD(write_wal_time);
|
||||
io_s = WriteToWAL(write_group, log_writer, log_used, need_log_sync,
|
||||
need_log_dir_sync, last_sequence + 1);
|
||||
need_log_dir_sync, last_sequence + 1,
|
||||
log_file_number_size);
|
||||
}
|
||||
} else {
|
||||
if (status.ok() && !write_options.disableWAL) {
|
||||
@@ -507,15 +557,18 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
}
|
||||
PERF_TIMER_START(write_pre_and_post_process_time);
|
||||
|
||||
if (!io_s.ok()) {
|
||||
// Check WriteToWAL status
|
||||
IOStatusCheck(io_s);
|
||||
}
|
||||
if (!w.CallbackFailed()) {
|
||||
if (!io_s.ok()) {
|
||||
assert(pre_release_cb_status.ok());
|
||||
IOStatusCheck(io_s);
|
||||
} else {
|
||||
WriteStatusCheck(pre_release_cb_status);
|
||||
}
|
||||
} else {
|
||||
assert(io_s.ok() && pre_release_cb_status.ok());
|
||||
assert(pre_release_cb_status.ok());
|
||||
}
|
||||
|
||||
if (need_log_sync) {
|
||||
@@ -545,6 +598,16 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
}
|
||||
if (should_exit_batch_group) {
|
||||
if (status.ok()) {
|
||||
for (auto* tmp_w : write_group) {
|
||||
assert(tmp_w);
|
||||
if (tmp_w->post_memtable_callback) {
|
||||
Status tmp_s =
|
||||
(*tmp_w->post_memtable_callback)(last_sequence, disable_memtable);
|
||||
// TODO: propagate the execution status of post_memtable_callback to
|
||||
// caller.
|
||||
assert(tmp_s.ok());
|
||||
}
|
||||
}
|
||||
// Note: if we are to resume after non-OK statuses we need to revisit how
|
||||
// we reacts to non-OK statuses here.
|
||||
versions_->SetLastSequence(last_sequence);
|
||||
@@ -586,6 +649,10 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
w.status = PreprocessWrite(write_options, &need_log_sync, &write_context);
|
||||
PERF_TIMER_START(write_pre_and_post_process_time);
|
||||
log::Writer* log_writer = logs_.back().writer;
|
||||
LogFileNumberSize& log_file_number_size = alive_log_files_.back();
|
||||
|
||||
assert(log_writer->get_log_number() == log_file_number_size.number);
|
||||
|
||||
mutex_.Unlock();
|
||||
|
||||
// This can set non-OK status if callback fail.
|
||||
@@ -649,17 +716,17 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
wal_write_group.size - 1);
|
||||
RecordTick(stats_, WRITE_DONE_BY_OTHER, wal_write_group.size - 1);
|
||||
}
|
||||
io_s = WriteToWAL(wal_write_group, log_writer, log_used, need_log_sync,
|
||||
need_log_dir_sync, current_sequence);
|
||||
io_s =
|
||||
WriteToWAL(wal_write_group, log_writer, log_used, need_log_sync,
|
||||
need_log_dir_sync, current_sequence, log_file_number_size);
|
||||
w.status = io_s;
|
||||
}
|
||||
|
||||
if (!w.CallbackFailed()) {
|
||||
if (!io_s.ok()) {
|
||||
IOStatusCheck(io_s);
|
||||
} else {
|
||||
WriteStatusCheck(w.status);
|
||||
}
|
||||
if (!io_s.ok()) {
|
||||
// Check WriteToWAL status
|
||||
IOStatusCheck(io_s);
|
||||
} else if (!w.CallbackFailed()) {
|
||||
WriteStatusCheck(w.status);
|
||||
}
|
||||
|
||||
if (need_log_sync) {
|
||||
@@ -895,11 +962,18 @@ Status DBImpl::WriteImplWALOnly(
|
||||
seq_inc = total_batch_cnt;
|
||||
}
|
||||
Status status;
|
||||
IOStatus io_s;
|
||||
io_s.PermitUncheckedError(); // Allow io_s to be uninitialized
|
||||
if (!write_options.disableWAL) {
|
||||
io_s = ConcurrentWriteToWAL(write_group, log_used, &last_sequence, seq_inc);
|
||||
IOStatus io_s =
|
||||
ConcurrentWriteToWAL(write_group, log_used, &last_sequence, seq_inc);
|
||||
status = io_s;
|
||||
// last_sequence may not be set if there is an error
|
||||
// This error checking and return is moved up to avoid using uninitialized
|
||||
// last_sequence.
|
||||
if (!io_s.ok()) {
|
||||
IOStatusCheck(io_s);
|
||||
write_thread->ExitAsBatchGroupLeader(write_group, status);
|
||||
return status;
|
||||
}
|
||||
} else {
|
||||
// Otherwise we inc seq number to do solely the seq allocation
|
||||
last_sequence = versions_->FetchAddLastAllocatedSequence(seq_inc);
|
||||
@@ -934,11 +1008,7 @@ Status DBImpl::WriteImplWALOnly(
|
||||
PERF_TIMER_START(write_pre_and_post_process_time);
|
||||
|
||||
if (!w.CallbackFailed()) {
|
||||
if (!io_s.ok()) {
|
||||
IOStatusCheck(io_s);
|
||||
} else {
|
||||
WriteStatusCheck(status);
|
||||
}
|
||||
WriteStatusCheck(status);
|
||||
}
|
||||
if (status.ok()) {
|
||||
size_t index = 0;
|
||||
@@ -1041,12 +1111,18 @@ Status DBImpl::PreprocessWrite(const WriteOptions& write_options,
|
||||
|
||||
PERF_TIMER_GUARD(write_scheduling_flushes_compactions_time);
|
||||
|
||||
assert(!single_column_family_mode_ ||
|
||||
versions_->GetColumnFamilySet()->NumberOfColumnFamilies() == 1);
|
||||
if (UNLIKELY(status.ok() && !single_column_family_mode_ &&
|
||||
total_log_size_ > GetMaxTotalWalSize())) {
|
||||
WaitForPendingWrites();
|
||||
status = SwitchWAL(write_context);
|
||||
if (UNLIKELY(status.ok() && total_log_size_ > GetMaxTotalWalSize())) {
|
||||
assert(versions_);
|
||||
const ColumnFamilySet* const column_families =
|
||||
versions_->GetColumnFamilySet();
|
||||
assert(column_families);
|
||||
size_t num_cfs = column_families->NumberOfColumnFamilies();
|
||||
|
||||
assert(num_cfs >= 1);
|
||||
if (num_cfs > 1) {
|
||||
WaitForPendingWrites();
|
||||
status = SwitchWAL(write_context);
|
||||
}
|
||||
}
|
||||
|
||||
if (UNLIKELY(status.ok() && write_buffer_manager_->ShouldFlush())) {
|
||||
@@ -1105,17 +1181,16 @@ Status DBImpl::PreprocessWrite(const WriteOptions& write_options,
|
||||
// Note: there does not seem to be a reason to wait for parallel sync at
|
||||
// this early step but it is not important since parallel sync (SyncWAL) and
|
||||
// need_log_sync are usually not used together.
|
||||
while (logs_.front().getting_synced) {
|
||||
while (logs_.front().IsSyncing()) {
|
||||
log_sync_cv_.Wait();
|
||||
}
|
||||
for (auto& log : logs_) {
|
||||
assert(!log.getting_synced);
|
||||
// This is just to prevent the logs to be synced by a parallel SyncWAL
|
||||
// call. We will do the actual syncing later after we will write to the
|
||||
// WAL.
|
||||
// Note: there does not seem to be a reason to set this early before we
|
||||
// actually write to the WAL
|
||||
log.getting_synced = true;
|
||||
log.PrepareForSync();
|
||||
}
|
||||
} else {
|
||||
*need_log_sync = false;
|
||||
@@ -1124,13 +1199,13 @@ Status DBImpl::PreprocessWrite(const WriteOptions& write_options,
|
||||
return status;
|
||||
}
|
||||
|
||||
WriteBatch* DBImpl::MergeBatch(const WriteThread::WriteGroup& write_group,
|
||||
WriteBatch* tmp_batch, size_t* write_with_wal,
|
||||
WriteBatch** to_be_cached_state) {
|
||||
Status DBImpl::MergeBatch(const WriteThread::WriteGroup& write_group,
|
||||
WriteBatch* tmp_batch, WriteBatch** merged_batch,
|
||||
size_t* write_with_wal,
|
||||
WriteBatch** to_be_cached_state) {
|
||||
assert(write_with_wal != nullptr);
|
||||
assert(tmp_batch != nullptr);
|
||||
assert(*to_be_cached_state == nullptr);
|
||||
WriteBatch* merged_batch = nullptr;
|
||||
*write_with_wal = 0;
|
||||
auto* leader = write_group.leader;
|
||||
assert(!leader->disable_wal); // Same holds for all in the batch group
|
||||
@@ -1139,22 +1214,24 @@ WriteBatch* DBImpl::MergeBatch(const WriteThread::WriteGroup& write_group,
|
||||
// we simply write the first WriteBatch to WAL if the group only
|
||||
// contains one batch, that batch should be written to the WAL,
|
||||
// and the batch is not wanting to be truncated
|
||||
merged_batch = leader->batch;
|
||||
if (WriteBatchInternal::IsLatestPersistentState(merged_batch)) {
|
||||
*to_be_cached_state = merged_batch;
|
||||
*merged_batch = leader->batch;
|
||||
if (WriteBatchInternal::IsLatestPersistentState(*merged_batch)) {
|
||||
*to_be_cached_state = *merged_batch;
|
||||
}
|
||||
*write_with_wal = 1;
|
||||
} else {
|
||||
// WAL needs all of the batches flattened into a single batch.
|
||||
// We could avoid copying here with an iov-like AddRecord
|
||||
// interface
|
||||
merged_batch = tmp_batch;
|
||||
*merged_batch = tmp_batch;
|
||||
for (auto writer : write_group) {
|
||||
if (!writer->CallbackFailed()) {
|
||||
Status s = WriteBatchInternal::Append(merged_batch, writer->batch,
|
||||
Status s = WriteBatchInternal::Append(*merged_batch, writer->batch,
|
||||
/*WAL_only*/ true);
|
||||
// Always returns Status::OK.
|
||||
assert(s.ok());
|
||||
if (!s.ok()) {
|
||||
tmp_batch->Clear();
|
||||
return s;
|
||||
}
|
||||
if (WriteBatchInternal::IsLatestPersistentState(writer->batch)) {
|
||||
// We only need to cache the last of such write batch
|
||||
*to_be_cached_state = writer->batch;
|
||||
@@ -1163,7 +1240,8 @@ WriteBatch* DBImpl::MergeBatch(const WriteThread::WriteGroup& write_group,
|
||||
}
|
||||
}
|
||||
}
|
||||
return merged_batch;
|
||||
// return merged_batch;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// When two_write_queues_ is disabled, this function is called from the only
|
||||
@@ -1172,18 +1250,15 @@ IOStatus DBImpl::WriteToWAL(const WriteBatch& merged_batch,
|
||||
log::Writer* log_writer, uint64_t* log_used,
|
||||
uint64_t* log_size,
|
||||
Env::IOPriority rate_limiter_priority,
|
||||
bool with_db_mutex, bool with_log_mutex) {
|
||||
LogFileNumberSize& log_file_number_size) {
|
||||
assert(log_size != nullptr);
|
||||
|
||||
// Assert mutex explicitly.
|
||||
if (with_db_mutex) {
|
||||
mutex_.AssertHeld();
|
||||
} else if (two_write_queues_) {
|
||||
log_write_mutex_.AssertHeld();
|
||||
assert(with_log_mutex);
|
||||
}
|
||||
|
||||
Slice log_entry = WriteBatchInternal::Contents(&merged_batch);
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::WriteToWAL:log_entry", &log_entry);
|
||||
auto s = merged_batch.VerifyChecksum();
|
||||
if (!s.ok()) {
|
||||
return status_to_io_status(std::move(s));
|
||||
}
|
||||
*log_size = log_entry.size();
|
||||
// When two_write_queues_ WriteToWAL has to be protected from concurretn calls
|
||||
// from the two queues anyway and log_write_mutex_ is already held. Otherwise
|
||||
@@ -1205,12 +1280,7 @@ IOStatus DBImpl::WriteToWAL(const WriteBatch& merged_batch,
|
||||
*log_used = logfile_number_;
|
||||
}
|
||||
total_log_size_ += log_entry.size();
|
||||
if (with_db_mutex || with_log_mutex) {
|
||||
assert(alive_log_files_tail_ == alive_log_files_.rbegin());
|
||||
assert(alive_log_files_tail_ != alive_log_files_.rend());
|
||||
}
|
||||
LogFileNumberSize& last_alive_log = *alive_log_files_tail_;
|
||||
last_alive_log.AddSize(*log_size);
|
||||
log_file_number_size.AddSize(*log_size);
|
||||
log_empty_ = false;
|
||||
return io_s;
|
||||
}
|
||||
@@ -1218,15 +1288,21 @@ IOStatus DBImpl::WriteToWAL(const WriteBatch& merged_batch,
|
||||
IOStatus DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
log::Writer* log_writer, uint64_t* log_used,
|
||||
bool need_log_sync, bool need_log_dir_sync,
|
||||
SequenceNumber sequence) {
|
||||
SequenceNumber sequence,
|
||||
LogFileNumberSize& log_file_number_size) {
|
||||
IOStatus io_s;
|
||||
assert(!two_write_queues_);
|
||||
assert(!write_group.leader->disable_wal);
|
||||
// Same holds for all in the batch group
|
||||
size_t write_with_wal = 0;
|
||||
WriteBatch* to_be_cached_state = nullptr;
|
||||
WriteBatch* merged_batch = MergeBatch(write_group, &tmp_batch_,
|
||||
&write_with_wal, &to_be_cached_state);
|
||||
WriteBatch* merged_batch;
|
||||
io_s = status_to_io_status(MergeBatch(write_group, &tmp_batch_, &merged_batch,
|
||||
&write_with_wal, &to_be_cached_state));
|
||||
if (UNLIKELY(!io_s.ok())) {
|
||||
return io_s;
|
||||
}
|
||||
|
||||
if (merged_batch == write_group.leader->batch) {
|
||||
write_group.leader->log_used = logfile_number_;
|
||||
} else if (write_with_wal > 1) {
|
||||
@@ -1239,7 +1315,8 @@ IOStatus DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
|
||||
uint64_t log_size;
|
||||
io_s = WriteToWAL(*merged_batch, log_writer, log_used, &log_size,
|
||||
write_group.leader->rate_limiter_priority);
|
||||
write_group.leader->rate_limiter_priority,
|
||||
log_file_number_size);
|
||||
if (to_be_cached_state) {
|
||||
cached_recoverable_state_ = *to_be_cached_state;
|
||||
cached_recoverable_state_empty_ = false;
|
||||
@@ -1315,8 +1392,12 @@ IOStatus DBImpl::ConcurrentWriteToWAL(
|
||||
WriteBatch tmp_batch;
|
||||
size_t write_with_wal = 0;
|
||||
WriteBatch* to_be_cached_state = nullptr;
|
||||
WriteBatch* merged_batch =
|
||||
MergeBatch(write_group, &tmp_batch, &write_with_wal, &to_be_cached_state);
|
||||
WriteBatch* merged_batch;
|
||||
io_s = status_to_io_status(MergeBatch(write_group, &tmp_batch, &merged_batch,
|
||||
&write_with_wal, &to_be_cached_state));
|
||||
if (UNLIKELY(!io_s.ok())) {
|
||||
return io_s;
|
||||
}
|
||||
|
||||
// We need to lock log_write_mutex_ since logs_ and alive_log_files might be
|
||||
// pushed back concurrently
|
||||
@@ -1333,10 +1414,14 @@ IOStatus DBImpl::ConcurrentWriteToWAL(
|
||||
WriteBatchInternal::SetSequence(merged_batch, sequence);
|
||||
|
||||
log::Writer* log_writer = logs_.back().writer;
|
||||
LogFileNumberSize& log_file_number_size = alive_log_files_.back();
|
||||
|
||||
assert(log_writer->get_log_number() == log_file_number_size.number);
|
||||
|
||||
uint64_t log_size;
|
||||
io_s = WriteToWAL(*merged_batch, log_writer, log_used, &log_size,
|
||||
write_group.leader->rate_limiter_priority,
|
||||
/*with_db_mutex=*/false, /*with_log_mutex=*/true);
|
||||
log_file_number_size);
|
||||
if (to_be_cached_state) {
|
||||
cached_recoverable_state_ = *to_be_cached_state;
|
||||
cached_recoverable_state_empty_ = false;
|
||||
@@ -1992,7 +2077,6 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
log_dir_synced_ = false;
|
||||
logs_.emplace_back(logfile_number_, new_log);
|
||||
alive_log_files_.push_back(LogFileNumberSize(logfile_number_));
|
||||
alive_log_files_tail_ = alive_log_files_.rbegin();
|
||||
}
|
||||
log_write_mutex_.Unlock();
|
||||
}
|
||||
@@ -2127,7 +2211,8 @@ Status DB::Put(const WriteOptions& opt, ColumnFamilyHandle* column_family,
|
||||
// Pre-allocate size of write batch conservatively.
|
||||
// 8 bytes are taken by header, 4 bytes for count, 1 byte for type,
|
||||
// and we allocate 11 extra bytes for key length, as well as value length.
|
||||
WriteBatch batch(key.size() + value.size() + 24);
|
||||
WriteBatch batch(key.size() + value.size() + 24, 0 /* max_bytes */,
|
||||
opt.protection_bytes_per_key, 0 /* default_cf_ts_sz */);
|
||||
Status s = batch.Put(column_family, key, value);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -2141,7 +2226,9 @@ Status DB::Put(const WriteOptions& opt, ColumnFamilyHandle* column_family,
|
||||
assert(default_cf);
|
||||
const Comparator* const default_cf_ucmp = default_cf->GetComparator();
|
||||
assert(default_cf_ucmp);
|
||||
WriteBatch batch(0, 0, 0, default_cf_ucmp->timestamp_size());
|
||||
WriteBatch batch(0 /* reserved_bytes */, 0 /* max_bytes */,
|
||||
opt.protection_bytes_per_key,
|
||||
default_cf_ucmp->timestamp_size());
|
||||
Status s = batch.Put(column_family, key, ts, value);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -2151,7 +2238,8 @@ Status DB::Put(const WriteOptions& opt, ColumnFamilyHandle* column_family,
|
||||
|
||||
Status DB::Delete(const WriteOptions& opt, ColumnFamilyHandle* column_family,
|
||||
const Slice& key) {
|
||||
WriteBatch batch;
|
||||
WriteBatch batch(0 /* reserved_bytes */, 0 /* max_bytes */,
|
||||
opt.protection_bytes_per_key, 0 /* default_cf_ts_sz */);
|
||||
Status s = batch.Delete(column_family, key);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -2165,7 +2253,9 @@ Status DB::Delete(const WriteOptions& opt, ColumnFamilyHandle* column_family,
|
||||
assert(default_cf);
|
||||
const Comparator* const default_cf_ucmp = default_cf->GetComparator();
|
||||
assert(default_cf_ucmp);
|
||||
WriteBatch batch(0, 0, 0, default_cf_ucmp->timestamp_size());
|
||||
WriteBatch batch(0 /* reserved_bytes */, 0 /* max_bytes */,
|
||||
opt.protection_bytes_per_key,
|
||||
default_cf_ucmp->timestamp_size());
|
||||
Status s = batch.Delete(column_family, key, ts);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -2175,7 +2265,8 @@ Status DB::Delete(const WriteOptions& opt, ColumnFamilyHandle* column_family,
|
||||
|
||||
Status DB::SingleDelete(const WriteOptions& opt,
|
||||
ColumnFamilyHandle* column_family, const Slice& key) {
|
||||
WriteBatch batch;
|
||||
WriteBatch batch(0 /* reserved_bytes */, 0 /* max_bytes */,
|
||||
opt.protection_bytes_per_key, 0 /* default_cf_ts_sz */);
|
||||
Status s = batch.SingleDelete(column_family, key);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -2190,7 +2281,9 @@ Status DB::SingleDelete(const WriteOptions& opt,
|
||||
assert(default_cf);
|
||||
const Comparator* const default_cf_ucmp = default_cf->GetComparator();
|
||||
assert(default_cf_ucmp);
|
||||
WriteBatch batch(0, 0, 0, default_cf_ucmp->timestamp_size());
|
||||
WriteBatch batch(0 /* reserved_bytes */, 0 /* max_bytes */,
|
||||
opt.protection_bytes_per_key,
|
||||
default_cf_ucmp->timestamp_size());
|
||||
Status s = batch.SingleDelete(column_family, key, ts);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -2201,7 +2294,8 @@ Status DB::SingleDelete(const WriteOptions& opt,
|
||||
Status DB::DeleteRange(const WriteOptions& opt,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice& begin_key, const Slice& end_key) {
|
||||
WriteBatch batch;
|
||||
WriteBatch batch(0 /* reserved_bytes */, 0 /* max_bytes */,
|
||||
opt.protection_bytes_per_key, 0 /* default_cf_ts_sz */);
|
||||
Status s = batch.DeleteRange(column_family, begin_key, end_key);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -2211,7 +2305,8 @@ Status DB::DeleteRange(const WriteOptions& opt,
|
||||
|
||||
Status DB::Merge(const WriteOptions& opt, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, const Slice& value) {
|
||||
WriteBatch batch;
|
||||
WriteBatch batch(0 /* reserved_bytes */, 0 /* max_bytes */,
|
||||
opt.protection_bytes_per_key, 0 /* default_cf_ts_sz */);
|
||||
Status s = batch.Merge(column_family, key, value);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
|
||||
+469
-69
@@ -15,7 +15,6 @@ enum class WriteBatchOpType {
|
||||
kSingleDelete,
|
||||
kDeleteRange,
|
||||
kMerge,
|
||||
kBlobIndex,
|
||||
kNum,
|
||||
};
|
||||
|
||||
@@ -25,56 +24,82 @@ WriteBatchOpType operator+(WriteBatchOpType lhs, const int rhs) {
|
||||
return static_cast<WriteBatchOpType>(static_cast<T>(lhs) + rhs);
|
||||
}
|
||||
|
||||
class DbKvChecksumTest
|
||||
: public DBTestBase,
|
||||
public ::testing::WithParamInterface<std::tuple<WriteBatchOpType, char>> {
|
||||
enum class WriteMode {
|
||||
// `Write()` a `WriteBatch` constructed with `protection_bytes_per_key > 0`.
|
||||
kWriteProtectedBatch = 0,
|
||||
// `Write()` a `WriteBatch` constructed with `protection_bytes_per_key == 0`.
|
||||
// Protection is enabled via `WriteOptions::protection_bytes_per_key > 0`.
|
||||
kWriteUnprotectedBatch,
|
||||
// TODO(ajkr): add a mode that uses `Write()` wrappers, e.g., `Put()`.
|
||||
kNum,
|
||||
};
|
||||
|
||||
// Integer addition is needed for `::testing::Range()` to take the enum type.
|
||||
WriteMode operator+(WriteMode lhs, const int rhs) {
|
||||
using T = std::underlying_type<WriteMode>::type;
|
||||
return static_cast<WriteMode>(static_cast<T>(lhs) + rhs);
|
||||
}
|
||||
|
||||
std::pair<WriteBatch, Status> GetWriteBatch(ColumnFamilyHandle* cf_handle,
|
||||
size_t protection_bytes_per_key,
|
||||
WriteBatchOpType op_type) {
|
||||
Status s;
|
||||
WriteBatch wb(0 /* reserved_bytes */, 0 /* max_bytes */,
|
||||
protection_bytes_per_key, 0 /* default_cf_ts_sz */);
|
||||
switch (op_type) {
|
||||
case WriteBatchOpType::kPut:
|
||||
s = wb.Put(cf_handle, "key", "val");
|
||||
break;
|
||||
case WriteBatchOpType::kDelete:
|
||||
s = wb.Delete(cf_handle, "key");
|
||||
break;
|
||||
case WriteBatchOpType::kSingleDelete:
|
||||
s = wb.SingleDelete(cf_handle, "key");
|
||||
break;
|
||||
case WriteBatchOpType::kDeleteRange:
|
||||
s = wb.DeleteRange(cf_handle, "begin", "end");
|
||||
break;
|
||||
case WriteBatchOpType::kMerge:
|
||||
s = wb.Merge(cf_handle, "key", "val");
|
||||
break;
|
||||
case WriteBatchOpType::kNum:
|
||||
assert(false);
|
||||
}
|
||||
return {std::move(wb), std::move(s)};
|
||||
}
|
||||
|
||||
class DbKvChecksumTest : public DBTestBase,
|
||||
public ::testing::WithParamInterface<
|
||||
std::tuple<WriteBatchOpType, char, WriteMode>> {
|
||||
public:
|
||||
DbKvChecksumTest()
|
||||
: DBTestBase("db_kv_checksum_test", /*env_do_fsync=*/false) {
|
||||
op_type_ = std::get<0>(GetParam());
|
||||
corrupt_byte_addend_ = std::get<1>(GetParam());
|
||||
write_mode_ = std::get<2>(GetParam());
|
||||
}
|
||||
|
||||
std::pair<WriteBatch, Status> GetWriteBatch(ColumnFamilyHandle* cf_handle) {
|
||||
Status s;
|
||||
WriteBatch wb(0 /* reserved_bytes */, 0 /* max_bytes */,
|
||||
8 /* protection_bytes_per_entry */, 0 /* default_cf_ts_sz */);
|
||||
switch (op_type_) {
|
||||
case WriteBatchOpType::kPut:
|
||||
s = wb.Put(cf_handle, "key", "val");
|
||||
break;
|
||||
case WriteBatchOpType::kDelete:
|
||||
s = wb.Delete(cf_handle, "key");
|
||||
break;
|
||||
case WriteBatchOpType::kSingleDelete:
|
||||
s = wb.SingleDelete(cf_handle, "key");
|
||||
break;
|
||||
case WriteBatchOpType::kDeleteRange:
|
||||
s = wb.DeleteRange(cf_handle, "begin", "end");
|
||||
break;
|
||||
case WriteBatchOpType::kMerge:
|
||||
s = wb.Merge(cf_handle, "key", "val");
|
||||
break;
|
||||
case WriteBatchOpType::kBlobIndex: {
|
||||
// TODO(ajkr): use public API once available.
|
||||
uint32_t cf_id;
|
||||
if (cf_handle == nullptr) {
|
||||
cf_id = 0;
|
||||
} else {
|
||||
cf_id = cf_handle->GetID();
|
||||
}
|
||||
|
||||
std::string blob_index;
|
||||
BlobIndex::EncodeInlinedTTL(&blob_index, /* expiration */ 9876543210,
|
||||
"val");
|
||||
|
||||
s = WriteBatchInternal::PutBlobIndex(&wb, cf_id, "key", blob_index);
|
||||
break;
|
||||
Status ExecuteWrite(ColumnFamilyHandle* cf_handle) {
|
||||
switch (write_mode_) {
|
||||
case WriteMode::kWriteProtectedBatch: {
|
||||
auto batch_and_status = GetWriteBatch(
|
||||
cf_handle, 8 /* protection_bytes_per_key */, op_type_);
|
||||
assert(batch_and_status.second.ok());
|
||||
return db_->Write(WriteOptions(), &batch_and_status.first);
|
||||
}
|
||||
case WriteBatchOpType::kNum:
|
||||
case WriteMode::kWriteUnprotectedBatch: {
|
||||
auto batch_and_status = GetWriteBatch(
|
||||
cf_handle, 0 /* protection_bytes_per_key */, op_type_);
|
||||
assert(batch_and_status.second.ok());
|
||||
WriteOptions write_opts;
|
||||
write_opts.protection_bytes_per_key = 8;
|
||||
return db_->Write(write_opts, &batch_and_status.first);
|
||||
}
|
||||
case WriteMode::kNum:
|
||||
assert(false);
|
||||
}
|
||||
return {std::move(wb), std::move(s)};
|
||||
return Status::NotSupported("WriteMode " +
|
||||
std::to_string(static_cast<int>(write_mode_)));
|
||||
}
|
||||
|
||||
void CorruptNextByteCallBack(void* arg) {
|
||||
@@ -95,46 +120,60 @@ class DbKvChecksumTest
|
||||
protected:
|
||||
WriteBatchOpType op_type_;
|
||||
char corrupt_byte_addend_;
|
||||
WriteMode write_mode_;
|
||||
size_t corrupt_byte_offset_ = 0;
|
||||
size_t entry_len_ = std::numeric_limits<size_t>::max();
|
||||
};
|
||||
|
||||
std::string GetTestNameSuffix(
|
||||
::testing::TestParamInfo<std::tuple<WriteBatchOpType, char>> info) {
|
||||
std::ostringstream oss;
|
||||
switch (std::get<0>(info.param)) {
|
||||
std::string GetOpTypeString(const WriteBatchOpType& op_type) {
|
||||
switch (op_type) {
|
||||
case WriteBatchOpType::kPut:
|
||||
oss << "Put";
|
||||
break;
|
||||
return "Put";
|
||||
case WriteBatchOpType::kDelete:
|
||||
oss << "Delete";
|
||||
break;
|
||||
return "Delete";
|
||||
case WriteBatchOpType::kSingleDelete:
|
||||
oss << "SingleDelete";
|
||||
break;
|
||||
return "SingleDelete";
|
||||
case WriteBatchOpType::kDeleteRange:
|
||||
oss << "DeleteRange";
|
||||
return "DeleteRange";
|
||||
break;
|
||||
case WriteBatchOpType::kMerge:
|
||||
oss << "Merge";
|
||||
break;
|
||||
case WriteBatchOpType::kBlobIndex:
|
||||
oss << "BlobIndex";
|
||||
return "Merge";
|
||||
break;
|
||||
case WriteBatchOpType::kNum:
|
||||
assert(false);
|
||||
}
|
||||
oss << "Add"
|
||||
<< static_cast<int>(static_cast<unsigned char>(std::get<1>(info.param)));
|
||||
return oss.str();
|
||||
assert(false);
|
||||
return "";
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
DbKvChecksumTest, DbKvChecksumTest,
|
||||
::testing::Combine(::testing::Range(static_cast<WriteBatchOpType>(0),
|
||||
WriteBatchOpType::kNum),
|
||||
::testing::Values(2, 103, 251)),
|
||||
GetTestNameSuffix);
|
||||
::testing::Values(2, 103, 251),
|
||||
::testing::Range(static_cast<WriteMode>(0),
|
||||
WriteMode::kNum)),
|
||||
[](const testing::TestParamInfo<
|
||||
std::tuple<WriteBatchOpType, char, WriteMode>>& args) {
|
||||
std::ostringstream oss;
|
||||
oss << GetOpTypeString(std::get<0>(args.param)) << "Add"
|
||||
<< static_cast<int>(
|
||||
static_cast<unsigned char>(std::get<1>(args.param)));
|
||||
switch (std::get<2>(args.param)) {
|
||||
case WriteMode::kWriteProtectedBatch:
|
||||
oss << "WriteProtectedBatch";
|
||||
break;
|
||||
case WriteMode::kWriteUnprotectedBatch:
|
||||
oss << "WriteUnprotectedBatch";
|
||||
break;
|
||||
case WriteMode::kNum:
|
||||
assert(false);
|
||||
}
|
||||
return oss.str();
|
||||
});
|
||||
|
||||
// TODO(ajkr): add a test that corrupts the `WriteBatch` contents. Such
|
||||
// corruptions should only be detectable in `WriteMode::kWriteProtectedBatch`.
|
||||
|
||||
TEST_P(DbKvChecksumTest, MemTableAddCorrupted) {
|
||||
// This test repeatedly attempts to write `WriteBatch`es containing a single
|
||||
@@ -157,11 +196,13 @@ TEST_P(DbKvChecksumTest, MemTableAddCorrupted) {
|
||||
Reopen(options);
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
auto batch_and_status = GetWriteBatch(nullptr /* cf_handle */);
|
||||
ASSERT_OK(batch_and_status.second);
|
||||
ASSERT_TRUE(
|
||||
db_->Write(WriteOptions(), &batch_and_status.first).IsCorruption());
|
||||
ASSERT_TRUE(ExecuteWrite(nullptr /* cf_handle */).IsCorruption());
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
// In case the above callback is not invoked, this test will run
|
||||
// numeric_limits<size_t>::max() times until it reports an error (or will
|
||||
// exhaust disk space). Added this assert to report error early.
|
||||
ASSERT_TRUE(entry_len_ < std::numeric_limits<size_t>::max());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,14 +229,373 @@ TEST_P(DbKvChecksumTest, MemTableAddWithColumnFamilyCorrupted) {
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options);
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
auto batch_and_status = GetWriteBatch(handles_[1]);
|
||||
ASSERT_OK(batch_and_status.second);
|
||||
ASSERT_TRUE(
|
||||
db_->Write(WriteOptions(), &batch_and_status.first).IsCorruption());
|
||||
ASSERT_TRUE(ExecuteWrite(handles_[1]).IsCorruption());
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
// In case the above callback is not invoked, this test will run
|
||||
// numeric_limits<size_t>::max() times until it reports an error (or will
|
||||
// exhaust disk space). Added this assert to report error early.
|
||||
ASSERT_TRUE(entry_len_ < std::numeric_limits<size_t>::max());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DbKvChecksumTest, NoCorruptionCase) {
|
||||
// If this test fails, we may have found a piece of malfunctioned hardware
|
||||
auto batch_and_status =
|
||||
GetWriteBatch(nullptr, 8 /* protection_bytes_per_key */, op_type_);
|
||||
ASSERT_OK(batch_and_status.second);
|
||||
ASSERT_OK(batch_and_status.first.VerifyChecksum());
|
||||
}
|
||||
|
||||
TEST_P(DbKvChecksumTest, WriteToWALCorrupted) {
|
||||
// This test repeatedly attempts to write `WriteBatch`es containing a single
|
||||
// entry of type `op_type_`. Each attempt has one byte corrupted by adding
|
||||
// `corrupt_byte_addend_` to its original value. The test repeats until an
|
||||
// attempt has been made on each byte in the encoded write batch. All attempts
|
||||
// are expected to fail with `Status::Corruption`
|
||||
Options options = CurrentOptions();
|
||||
if (op_type_ == WriteBatchOpType::kMerge) {
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
}
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::WriteToWAL:log_entry",
|
||||
std::bind(&DbKvChecksumTest::CorruptNextByteCallBack, this,
|
||||
std::placeholders::_1));
|
||||
// First 8 bytes are for sequence number which is not protected in write batch
|
||||
corrupt_byte_offset_ = 8;
|
||||
|
||||
while (MoreBytesToCorrupt()) {
|
||||
// Corrupted write batch leads to read-only mode, so we have to
|
||||
// reopen for every attempt.
|
||||
Reopen(options);
|
||||
auto log_size_pre_write = dbfull()->TEST_total_log_size();
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
ASSERT_TRUE(ExecuteWrite(nullptr /* cf_handle */).IsCorruption());
|
||||
// Confirm that nothing was written to WAL
|
||||
ASSERT_EQ(log_size_pre_write, dbfull()->TEST_total_log_size());
|
||||
ASSERT_TRUE(dbfull()->TEST_GetBGError().IsCorruption());
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
// In case the above callback is not invoked, this test will run
|
||||
// numeric_limits<size_t>::max() times until it reports an error (or will
|
||||
// exhaust disk space). Added this assert to report error early.
|
||||
ASSERT_TRUE(entry_len_ < std::numeric_limits<size_t>::max());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DbKvChecksumTest, WriteToWALWithColumnFamilyCorrupted) {
|
||||
// This test repeatedly attempts to write `WriteBatch`es containing a single
|
||||
// entry of type `op_type_`. Each attempt has one byte corrupted by adding
|
||||
// `corrupt_byte_addend_` to its original value. The test repeats until an
|
||||
// attempt has been made on each byte in the encoded write batch. All attempts
|
||||
// are expected to fail with `Status::Corruption`
|
||||
Options options = CurrentOptions();
|
||||
if (op_type_ == WriteBatchOpType::kMerge) {
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
}
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::WriteToWAL:log_entry",
|
||||
std::bind(&DbKvChecksumTest::CorruptNextByteCallBack, this,
|
||||
std::placeholders::_1));
|
||||
// First 8 bytes are for sequence number which is not protected in write batch
|
||||
corrupt_byte_offset_ = 8;
|
||||
|
||||
while (MoreBytesToCorrupt()) {
|
||||
// Corrupted write batch leads to read-only mode, so we have to
|
||||
// reopen for every attempt.
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options);
|
||||
auto log_size_pre_write = dbfull()->TEST_total_log_size();
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
ASSERT_TRUE(ExecuteWrite(nullptr /* cf_handle */).IsCorruption());
|
||||
// Confirm that nothing was written to WAL
|
||||
ASSERT_EQ(log_size_pre_write, dbfull()->TEST_total_log_size());
|
||||
ASSERT_TRUE(dbfull()->TEST_GetBGError().IsCorruption());
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
// In case the above callback is not invoked, this test will run
|
||||
// numeric_limits<size_t>::max() times until it reports an error (or will
|
||||
// exhaust disk space). Added this assert to report error early.
|
||||
ASSERT_TRUE(entry_len_ < std::numeric_limits<size_t>::max());
|
||||
}
|
||||
}
|
||||
|
||||
class DbKvChecksumTestMergedBatch
|
||||
: public DBTestBase,
|
||||
public ::testing::WithParamInterface<
|
||||
std::tuple<WriteBatchOpType, WriteBatchOpType, char>> {
|
||||
public:
|
||||
DbKvChecksumTestMergedBatch()
|
||||
: DBTestBase("db_kv_checksum_test", /*env_do_fsync=*/false) {
|
||||
op_type1_ = std::get<0>(GetParam());
|
||||
op_type2_ = std::get<1>(GetParam());
|
||||
corrupt_byte_addend_ = std::get<2>(GetParam());
|
||||
}
|
||||
|
||||
protected:
|
||||
WriteBatchOpType op_type1_;
|
||||
WriteBatchOpType op_type2_;
|
||||
char corrupt_byte_addend_;
|
||||
};
|
||||
|
||||
void CorruptWriteBatch(Slice* content, size_t offset,
|
||||
char corrupt_byte_addend) {
|
||||
ASSERT_TRUE(offset < content->size());
|
||||
char* buf = const_cast<char*>(content->data());
|
||||
buf[offset] += corrupt_byte_addend;
|
||||
}
|
||||
|
||||
TEST_P(DbKvChecksumTestMergedBatch, NoCorruptionCase) {
|
||||
// Veirfy write batch checksum after write batch append
|
||||
auto batch1 = GetWriteBatch(nullptr /* cf_handle */,
|
||||
8 /* protection_bytes_per_key */, op_type1_);
|
||||
ASSERT_OK(batch1.second);
|
||||
auto batch2 = GetWriteBatch(nullptr /* cf_handle */,
|
||||
8 /* protection_bytes_per_key */, op_type2_);
|
||||
ASSERT_OK(batch2.second);
|
||||
ASSERT_OK(WriteBatchInternal::Append(&batch1.first, &batch2.first));
|
||||
ASSERT_OK(batch1.first.VerifyChecksum());
|
||||
}
|
||||
|
||||
TEST_P(DbKvChecksumTestMergedBatch, WriteToWALCorrupted) {
|
||||
// This test has two writers repeatedly attempt to write `WriteBatch`es
|
||||
// containing a single entry of type op_type1_ and op_type2_ respectively. The
|
||||
// leader of the write group writes the batch containinng the entry of type
|
||||
// op_type1_. One byte of the pre-merged write batches is corrupted by adding
|
||||
// `corrupt_byte_addend_` to the batch's original value during each attempt.
|
||||
// The test repeats until an attempt has been made on each byte in both
|
||||
// pre-merged write batches. All attempts are expected to fail with
|
||||
// `Status::Corruption`.
|
||||
Options options = CurrentOptions();
|
||||
if (op_type1_ == WriteBatchOpType::kMerge ||
|
||||
op_type2_ == WriteBatchOpType::kMerge) {
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
}
|
||||
|
||||
auto leader_batch_and_status = GetWriteBatch(
|
||||
nullptr /* cf_handle */, 8 /* protection_bytes_per_key */, op_type1_);
|
||||
ASSERT_OK(leader_batch_and_status.second);
|
||||
auto follower_batch_and_status = GetWriteBatch(
|
||||
nullptr /* cf_handle */, 8 /* protection_bytes_per_key */, op_type2_);
|
||||
size_t leader_batch_size = leader_batch_and_status.first.GetDataSize();
|
||||
size_t total_bytes =
|
||||
leader_batch_size + follower_batch_and_status.first.GetDataSize();
|
||||
// First 8 bytes are for sequence number which is not protected in write batch
|
||||
size_t corrupt_byte_offset = 8;
|
||||
|
||||
std::atomic<bool> follower_joined{false};
|
||||
std::atomic<int> leader_count{0};
|
||||
port::Thread follower_thread;
|
||||
// This callback should only be called by the leader thread
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"WriteThread::JoinBatchGroup:Wait2", [&](void* arg_leader) {
|
||||
auto* leader = reinterpret_cast<WriteThread::Writer*>(arg_leader);
|
||||
ASSERT_EQ(leader->state, WriteThread::STATE_GROUP_LEADER);
|
||||
|
||||
// This callback should only be called by the follower thread
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"WriteThread::JoinBatchGroup:Wait", [&](void* arg_follower) {
|
||||
auto* follower =
|
||||
reinterpret_cast<WriteThread::Writer*>(arg_follower);
|
||||
// The leader thread will wait on this bool and hence wait until
|
||||
// this writer joins the write group
|
||||
ASSERT_NE(follower->state, WriteThread::STATE_GROUP_LEADER);
|
||||
if (corrupt_byte_offset >= leader_batch_size) {
|
||||
Slice batch_content = follower->batch->Data();
|
||||
CorruptWriteBatch(&batch_content,
|
||||
corrupt_byte_offset - leader_batch_size,
|
||||
corrupt_byte_addend_);
|
||||
}
|
||||
// Leader busy waits on this flag
|
||||
follower_joined = true;
|
||||
// So the follower does not enter the outer callback at
|
||||
// WriteThread::JoinBatchGroup:Wait2
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
});
|
||||
|
||||
// Start the other writer thread which will join the write group as
|
||||
// follower
|
||||
follower_thread = port::Thread([&]() {
|
||||
follower_batch_and_status =
|
||||
GetWriteBatch(nullptr /* cf_handle */,
|
||||
8 /* protection_bytes_per_key */, op_type2_);
|
||||
ASSERT_OK(follower_batch_and_status.second);
|
||||
ASSERT_TRUE(
|
||||
db_->Write(WriteOptions(), &follower_batch_and_status.first)
|
||||
.IsCorruption());
|
||||
});
|
||||
|
||||
ASSERT_EQ(leader->batch->GetDataSize(), leader_batch_size);
|
||||
if (corrupt_byte_offset < leader_batch_size) {
|
||||
Slice batch_content = leader->batch->Data();
|
||||
CorruptWriteBatch(&batch_content, corrupt_byte_offset,
|
||||
corrupt_byte_addend_);
|
||||
}
|
||||
leader_count++;
|
||||
while (!follower_joined) {
|
||||
// busy waiting
|
||||
}
|
||||
});
|
||||
while (corrupt_byte_offset < total_bytes) {
|
||||
// Reopen DB since it failed WAL write which lead to read-only mode
|
||||
Reopen(options);
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
auto log_size_pre_write = dbfull()->TEST_total_log_size();
|
||||
leader_batch_and_status = GetWriteBatch(
|
||||
nullptr /* cf_handle */, 8 /* protection_bytes_per_key */, op_type1_);
|
||||
ASSERT_OK(leader_batch_and_status.second);
|
||||
ASSERT_TRUE(db_->Write(WriteOptions(), &leader_batch_and_status.first)
|
||||
.IsCorruption());
|
||||
follower_thread.join();
|
||||
// Prevent leader thread from entering this callback
|
||||
SyncPoint::GetInstance()->ClearCallBack("WriteThread::JoinBatchGroup:Wait");
|
||||
ASSERT_EQ(1, leader_count);
|
||||
// Nothing should have been written to WAL
|
||||
ASSERT_EQ(log_size_pre_write, dbfull()->TEST_total_log_size());
|
||||
ASSERT_TRUE(dbfull()->TEST_GetBGError().IsCorruption());
|
||||
|
||||
corrupt_byte_offset++;
|
||||
if (corrupt_byte_offset == leader_batch_size) {
|
||||
// skip over the sequence number part of follower's write batch
|
||||
corrupt_byte_offset += 8;
|
||||
}
|
||||
follower_joined = false;
|
||||
leader_count = 0;
|
||||
}
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_P(DbKvChecksumTestMergedBatch, WriteToWALWithColumnFamilyCorrupted) {
|
||||
// This test has two writers repeatedly attempt to write `WriteBatch`es
|
||||
// containing a single entry of type op_type1_ and op_type2_ respectively. The
|
||||
// leader of the write group writes the batch containinng the entry of type
|
||||
// op_type1_. One byte of the pre-merged write batches is corrupted by adding
|
||||
// `corrupt_byte_addend_` to the batch's original value during each attempt.
|
||||
// The test repeats until an attempt has been made on each byte in both
|
||||
// pre-merged write batches. All attempts are expected to fail with
|
||||
// `Status::Corruption`.
|
||||
Options options = CurrentOptions();
|
||||
if (op_type1_ == WriteBatchOpType::kMerge ||
|
||||
op_type2_ == WriteBatchOpType::kMerge) {
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
}
|
||||
CreateAndReopenWithCF({"ramen"}, options);
|
||||
|
||||
auto leader_batch_and_status =
|
||||
GetWriteBatch(handles_[1], 8 /* protection_bytes_per_key */, op_type1_);
|
||||
ASSERT_OK(leader_batch_and_status.second);
|
||||
auto follower_batch_and_status =
|
||||
GetWriteBatch(handles_[1], 8 /* protection_bytes_per_key */, op_type2_);
|
||||
size_t leader_batch_size = leader_batch_and_status.first.GetDataSize();
|
||||
size_t total_bytes =
|
||||
leader_batch_size + follower_batch_and_status.first.GetDataSize();
|
||||
// First 8 bytes are for sequence number which is not protected in write batch
|
||||
size_t corrupt_byte_offset = 8;
|
||||
|
||||
std::atomic<bool> follower_joined{false};
|
||||
std::atomic<int> leader_count{0};
|
||||
port::Thread follower_thread;
|
||||
// This callback should only be called by the leader thread
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"WriteThread::JoinBatchGroup:Wait2", [&](void* arg_leader) {
|
||||
auto* leader = reinterpret_cast<WriteThread::Writer*>(arg_leader);
|
||||
ASSERT_EQ(leader->state, WriteThread::STATE_GROUP_LEADER);
|
||||
|
||||
// This callback should only be called by the follower thread
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"WriteThread::JoinBatchGroup:Wait", [&](void* arg_follower) {
|
||||
auto* follower =
|
||||
reinterpret_cast<WriteThread::Writer*>(arg_follower);
|
||||
// The leader thread will wait on this bool and hence wait until
|
||||
// this writer joins the write group
|
||||
ASSERT_NE(follower->state, WriteThread::STATE_GROUP_LEADER);
|
||||
if (corrupt_byte_offset >= leader_batch_size) {
|
||||
Slice batch_content =
|
||||
WriteBatchInternal::Contents(follower->batch);
|
||||
CorruptWriteBatch(&batch_content,
|
||||
corrupt_byte_offset - leader_batch_size,
|
||||
corrupt_byte_addend_);
|
||||
}
|
||||
follower_joined = true;
|
||||
// So the follower does not enter the outer callback at
|
||||
// WriteThread::JoinBatchGroup:Wait2
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
});
|
||||
|
||||
// Start the other writer thread which will join the write group as
|
||||
// follower
|
||||
follower_thread = port::Thread([&]() {
|
||||
follower_batch_and_status = GetWriteBatch(
|
||||
handles_[1], 8 /* protection_bytes_per_key */, op_type2_);
|
||||
ASSERT_OK(follower_batch_and_status.second);
|
||||
ASSERT_TRUE(
|
||||
db_->Write(WriteOptions(), &follower_batch_and_status.first)
|
||||
.IsCorruption());
|
||||
});
|
||||
|
||||
ASSERT_EQ(leader->batch->GetDataSize(), leader_batch_size);
|
||||
if (corrupt_byte_offset < leader_batch_size) {
|
||||
Slice batch_content = WriteBatchInternal::Contents(leader->batch);
|
||||
CorruptWriteBatch(&batch_content, corrupt_byte_offset,
|
||||
corrupt_byte_addend_);
|
||||
}
|
||||
leader_count++;
|
||||
while (!follower_joined) {
|
||||
// busy waiting
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
while (corrupt_byte_offset < total_bytes) {
|
||||
// Reopen DB since it failed WAL write which lead to read-only mode
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "ramen"}, options);
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
auto log_size_pre_write = dbfull()->TEST_total_log_size();
|
||||
leader_batch_and_status =
|
||||
GetWriteBatch(handles_[1], 8 /* protection_bytes_per_key */, op_type1_);
|
||||
ASSERT_OK(leader_batch_and_status.second);
|
||||
ASSERT_TRUE(db_->Write(WriteOptions(), &leader_batch_and_status.first)
|
||||
.IsCorruption());
|
||||
follower_thread.join();
|
||||
// Prevent leader thread from entering this callback
|
||||
SyncPoint::GetInstance()->ClearCallBack("WriteThread::JoinBatchGroup:Wait");
|
||||
|
||||
ASSERT_EQ(1, leader_count);
|
||||
// Nothing should have been written to WAL
|
||||
ASSERT_EQ(log_size_pre_write, dbfull()->TEST_total_log_size());
|
||||
ASSERT_TRUE(dbfull()->TEST_GetBGError().IsCorruption());
|
||||
|
||||
corrupt_byte_offset++;
|
||||
if (corrupt_byte_offset == leader_batch_size) {
|
||||
// skip over the sequence number part of follower's write batch
|
||||
corrupt_byte_offset += 8;
|
||||
}
|
||||
follower_joined = false;
|
||||
leader_count = 0;
|
||||
}
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
DbKvChecksumTestMergedBatch, DbKvChecksumTestMergedBatch,
|
||||
::testing::Combine(::testing::Range(static_cast<WriteBatchOpType>(0),
|
||||
WriteBatchOpType::kNum),
|
||||
::testing::Range(static_cast<WriteBatchOpType>(0),
|
||||
WriteBatchOpType::kNum),
|
||||
::testing::Values(2, 103, 251)),
|
||||
[](const testing::TestParamInfo<
|
||||
std::tuple<WriteBatchOpType, WriteBatchOpType, char>>& args) {
|
||||
std::ostringstream oss;
|
||||
oss << GetOpTypeString(std::get<0>(args.param))
|
||||
<< GetOpTypeString(std::get<1>(args.param)) << "Add"
|
||||
<< static_cast<int>(
|
||||
static_cast<unsigned char>(std::get<2>(args.param)));
|
||||
return oss.str();
|
||||
});
|
||||
|
||||
// TODO: add test for transactions
|
||||
// TODO: add test for corrupted write batch with WAL disabled
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -220,6 +220,7 @@ TEST_F(DBOptionsTest, SetMutableTableOptions) {
|
||||
|
||||
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
|
||||
Options c_opts = dbfull()->GetOptions(cfh);
|
||||
|
||||
const auto* c_bbto =
|
||||
c_opts.table_factory->GetOptions<BlockBasedTableOptions>();
|
||||
ASSERT_NE(c_bbto, nullptr);
|
||||
|
||||
@@ -139,8 +139,6 @@ TEST_P(DBRateLimiterOnReadTest, Get) {
|
||||
}
|
||||
|
||||
TEST_P(DBRateLimiterOnReadTest, NewMultiGet) {
|
||||
// The new void-returning `MultiGet()` APIs use `MultiRead()`, which does not
|
||||
// yet support rate limiting.
|
||||
if (use_direct_io_ && !IsDirectIOSupported()) {
|
||||
return;
|
||||
}
|
||||
@@ -149,6 +147,7 @@ TEST_P(DBRateLimiterOnReadTest, NewMultiGet) {
|
||||
ASSERT_EQ(0, options_.rate_limiter->GetTotalRequests(Env::IO_USER));
|
||||
|
||||
const int kNumKeys = kNumFiles * kNumKeysPerFile;
|
||||
int64_t expected = 0;
|
||||
{
|
||||
std::vector<std::string> key_bufs;
|
||||
key_bufs.reserve(kNumKeys);
|
||||
@@ -160,13 +159,19 @@ TEST_P(DBRateLimiterOnReadTest, NewMultiGet) {
|
||||
}
|
||||
std::vector<Status> statuses(kNumKeys);
|
||||
std::vector<PinnableSlice> values(kNumKeys);
|
||||
const int64_t prev_total_rl_req = options_.rate_limiter->GetTotalRequests();
|
||||
db_->MultiGet(GetReadOptions(), dbfull()->DefaultColumnFamily(), kNumKeys,
|
||||
keys.data(), values.data(), statuses.data());
|
||||
const int64_t cur_total_rl_req = options_.rate_limiter->GetTotalRequests();
|
||||
for (int i = 0; i < kNumKeys; ++i) {
|
||||
ASSERT_TRUE(statuses[i].IsNotSupported());
|
||||
ASSERT_TRUE(statuses[i].ok());
|
||||
}
|
||||
ASSERT_GT(cur_total_rl_req, prev_total_rl_req);
|
||||
ASSERT_EQ(cur_total_rl_req - prev_total_rl_req,
|
||||
options_.rate_limiter->GetTotalRequests(Env::IO_USER));
|
||||
}
|
||||
ASSERT_EQ(0, options_.rate_limiter->GetTotalRequests(Env::IO_USER));
|
||||
expected += kNumKeys;
|
||||
ASSERT_EQ(expected, options_.rate_limiter->GetTotalRequests(Env::IO_USER));
|
||||
}
|
||||
|
||||
TEST_P(DBRateLimiterOnReadTest, OldMultiGet) {
|
||||
|
||||
@@ -15,6 +15,55 @@ class DBReadOnlyTestWithTimestamp : public DBBasicTestWithTimestampBase {
|
||||
public:
|
||||
DBReadOnlyTestWithTimestamp()
|
||||
: DBBasicTestWithTimestampBase("db_readonly_test_with_timestamp") {}
|
||||
|
||||
protected:
|
||||
#ifndef ROCKSDB_LITE
|
||||
void CheckDBOpenedAsCompactedDBWithOneLevel0File() {
|
||||
VersionSet* const versions = dbfull()->GetVersionSet();
|
||||
ASSERT_NE(versions, nullptr);
|
||||
|
||||
ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault();
|
||||
ASSERT_NE(cfd, nullptr);
|
||||
|
||||
Version* const current = cfd->current();
|
||||
ASSERT_NE(current, nullptr);
|
||||
|
||||
const VersionStorageInfo* const storage_info = current->storage_info();
|
||||
ASSERT_NE(storage_info, nullptr);
|
||||
|
||||
// Only 1 L0 file.
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
// L0 is the max level.
|
||||
ASSERT_EQ(storage_info->num_non_empty_levels(), 1);
|
||||
}
|
||||
|
||||
void CheckDBOpenedAsCompactedDBWithOnlyHighestNonEmptyLevelFiles() {
|
||||
VersionSet* const versions = dbfull()->GetVersionSet();
|
||||
ASSERT_NE(versions, nullptr);
|
||||
|
||||
ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault();
|
||||
ASSERT_NE(cfd, nullptr);
|
||||
|
||||
Version* const current = cfd->current();
|
||||
ASSERT_NE(current, nullptr);
|
||||
|
||||
const VersionStorageInfo* const storage_info = current->storage_info();
|
||||
ASSERT_NE(storage_info, nullptr);
|
||||
|
||||
// L0 has no files.
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
|
||||
// All other levels have no files except the highest level with files.
|
||||
for (int i = 1; i < storage_info->num_non_empty_levels() - 1; ++i) {
|
||||
ASSERT_FALSE(storage_info->LevelFilesBrief(i).num_files > 0);
|
||||
}
|
||||
|
||||
// The highest level with files have some files.
|
||||
int highest_non_empty_level = storage_info->num_non_empty_levels() - 1;
|
||||
ASSERT_TRUE(
|
||||
storage_info->LevelFilesBrief(highest_non_empty_level).num_files > 0);
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
};
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
@@ -101,6 +150,46 @@ TEST_F(DBReadOnlyTestWithTimestamp,
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBReadOnlyTestWithTimestamp,
|
||||
IteratorAndGetWriteWithTimestampReadWithoutTimestamp) {
|
||||
const int kNumKeysPerFile = 128;
|
||||
const uint64_t kMaxKey = 1024;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
DestroyAndReopen(options);
|
||||
const std::string write_timestamp = Timestamp(1, 0);
|
||||
WriteOptions write_opts;
|
||||
for (uint64_t key = 0; key <= kMaxKey; ++key) {
|
||||
Status s = db_->Put(write_opts, Key1(key), write_timestamp,
|
||||
"value" + std::to_string(key));
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
// Reopen the database in read only mode to test its timestamp support.
|
||||
Close();
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
ReadOptions read_opts;
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_opts));
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_TRUE(iter->status().IsInvalidArgument());
|
||||
}
|
||||
|
||||
for (uint64_t key = 0; key <= kMaxKey; ++key) {
|
||||
std::string value_from_get;
|
||||
ASSERT_TRUE(
|
||||
db_->Get(read_opts, Key1(key), &value_from_get).IsInvalidArgument());
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBReadOnlyTestWithTimestamp, IteratorAndGet) {
|
||||
const int kNumKeysPerFile = 128;
|
||||
const uint64_t kMaxKey = 1024;
|
||||
@@ -320,6 +409,546 @@ TEST_F(DBReadOnlyTestWithTimestamp,
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBReadOnlyTestWithTimestamp,
|
||||
IteratorsWriteWithTimestampReadWithoutTimestamp) {
|
||||
const int kNumKeysPerFile = 128;
|
||||
const uint64_t kMaxKey = 1024;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
DestroyAndReopen(options);
|
||||
const std::string write_timestamp = Timestamp(1, 0);
|
||||
WriteOptions write_opts;
|
||||
for (uint64_t key = 0; key <= kMaxKey; ++key) {
|
||||
Status s = db_->Put(write_opts, Key1(key), write_timestamp,
|
||||
"value" + std::to_string(key));
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
// Reopen the database in read only mode to test its timestamp support.
|
||||
Close();
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
ReadOptions read_opts;
|
||||
{
|
||||
std::vector<Iterator*> iters;
|
||||
ASSERT_TRUE(
|
||||
db_->NewIterators(read_opts, {db_->DefaultColumnFamily()}, &iters)
|
||||
.IsInvalidArgument());
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBReadOnlyTestWithTimestamp, CompactedDBGetReadTimestampSizeMismatch) {
|
||||
const int kNumKeysPerFile = 1026;
|
||||
const uint64_t kMaxKey = 1024;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
DestroyAndReopen(options);
|
||||
std::string write_timestamp = Timestamp(1, 0);
|
||||
WriteOptions write_opts;
|
||||
for (uint64_t key = 0; key <= kMaxKey; ++key) {
|
||||
Status s = db_->Put(write_opts, Key1(key), write_timestamp,
|
||||
"value" + std::to_string(0));
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
Close();
|
||||
|
||||
// Reopen the database in read only mode as a Compacted DB to test its
|
||||
// timestamp support.
|
||||
options.max_open_files = -1;
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
CheckDBOpenedAsCompactedDBWithOneLevel0File();
|
||||
|
||||
ReadOptions read_opts;
|
||||
std::string different_size_read_timestamp;
|
||||
PutFixed32(&different_size_read_timestamp, 2);
|
||||
Slice different_size_read_ts = different_size_read_timestamp;
|
||||
read_opts.timestamp = &different_size_read_ts;
|
||||
for (uint64_t key = 0; key <= kMaxKey; ++key) {
|
||||
std::string value_from_get;
|
||||
std::string timestamp;
|
||||
ASSERT_TRUE(db_->Get(read_opts, Key1(key), &value_from_get, ×tamp)
|
||||
.IsInvalidArgument());
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBReadOnlyTestWithTimestamp,
|
||||
CompactedDBGetReadTimestampSpecifiedWithoutWriteTimestamp) {
|
||||
const int kNumKeysPerFile = 1026;
|
||||
const uint64_t kMaxKey = 1024;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
DestroyAndReopen(options);
|
||||
WriteOptions write_opts;
|
||||
for (uint64_t key = 0; key <= kMaxKey; ++key) {
|
||||
Status s = db_->Put(write_opts, Key1(key), "value" + std::to_string(0));
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
Close();
|
||||
|
||||
// Reopen the database in read only mode as a Compacted DB to test its
|
||||
// timestamp support.
|
||||
options.max_open_files = -1;
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
CheckDBOpenedAsCompactedDBWithOneLevel0File();
|
||||
|
||||
ReadOptions read_opts;
|
||||
const std::string read_timestamp = Timestamp(2, 0);
|
||||
Slice read_ts = read_timestamp;
|
||||
read_opts.timestamp = &read_ts;
|
||||
for (uint64_t key = 0; key <= kMaxKey; ++key) {
|
||||
std::string value_from_get;
|
||||
std::string timestamp;
|
||||
ASSERT_TRUE(db_->Get(read_opts, Key1(key), &value_from_get, ×tamp)
|
||||
.IsInvalidArgument());
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBReadOnlyTestWithTimestamp,
|
||||
CompactedDBGetWriteWithTimestampReadWithoutTimestamp) {
|
||||
const int kNumKeysPerFile = 1026;
|
||||
const uint64_t kMaxKey = 1024;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
DestroyAndReopen(options);
|
||||
std::string write_timestamp = Timestamp(1, 0);
|
||||
WriteOptions write_opts;
|
||||
for (uint64_t key = 0; key <= kMaxKey; ++key) {
|
||||
Status s = db_->Put(write_opts, Key1(key), write_timestamp,
|
||||
"value" + std::to_string(0));
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
Close();
|
||||
|
||||
// Reopen the database in read only mode as a Compacted DB to test its
|
||||
// timestamp support.
|
||||
options.max_open_files = -1;
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
CheckDBOpenedAsCompactedDBWithOneLevel0File();
|
||||
|
||||
ReadOptions read_opts;
|
||||
for (uint64_t key = 0; key <= kMaxKey; ++key) {
|
||||
std::string value_from_get;
|
||||
ASSERT_TRUE(
|
||||
db_->Get(read_opts, Key1(key), &value_from_get).IsInvalidArgument());
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBReadOnlyTestWithTimestamp, CompactedDBGetWithOnlyOneL0File) {
|
||||
const int kNumKeysPerFile = 1026 * 2;
|
||||
const uint64_t kMaxKey = 1024;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
DestroyAndReopen(options);
|
||||
const std::vector<uint64_t> start_keys = {1, 0};
|
||||
const std::vector<std::string> write_timestamps = {Timestamp(1, 0),
|
||||
Timestamp(3, 0)};
|
||||
const std::vector<std::string> read_timestamps = {Timestamp(2, 0),
|
||||
Timestamp(4, 0)};
|
||||
for (size_t i = 0; i < write_timestamps.size(); ++i) {
|
||||
WriteOptions write_opts;
|
||||
for (uint64_t key = start_keys[i]; key <= kMaxKey; ++key) {
|
||||
Status s = db_->Put(write_opts, Key1(key), write_timestamps[i],
|
||||
"value" + std::to_string(i));
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
}
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
Close();
|
||||
|
||||
// Reopen the database in read only mode as a Compacted DB to test its
|
||||
// timestamp support.
|
||||
options.max_open_files = -1;
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
CheckDBOpenedAsCompactedDBWithOneLevel0File();
|
||||
|
||||
for (size_t i = 0; i < read_timestamps.size(); ++i) {
|
||||
ReadOptions read_opts;
|
||||
Slice read_ts = read_timestamps[i];
|
||||
read_opts.timestamp = &read_ts;
|
||||
int count = 0;
|
||||
for (uint64_t key = start_keys[i]; key <= kMaxKey; ++key, ++count) {
|
||||
std::string value_from_get;
|
||||
std::string timestamp;
|
||||
ASSERT_OK(db_->Get(read_opts, Key1(key), &value_from_get, ×tamp));
|
||||
ASSERT_EQ("value" + std::to_string(i), value_from_get);
|
||||
ASSERT_EQ(write_timestamps[i], timestamp);
|
||||
}
|
||||
size_t expected_count = kMaxKey - start_keys[i] + 1;
|
||||
ASSERT_EQ(expected_count, count);
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBReadOnlyTestWithTimestamp,
|
||||
CompactedDBGetWithOnlyHighestNonEmptyLevelFiles) {
|
||||
const int kNumKeysPerFile = 128;
|
||||
const uint64_t kMaxKey = 1024;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
DestroyAndReopen(options);
|
||||
const std::vector<uint64_t> start_keys = {1, 0};
|
||||
const std::vector<std::string> write_timestamps = {Timestamp(1, 0),
|
||||
Timestamp(3, 0)};
|
||||
const std::vector<std::string> read_timestamps = {Timestamp(2, 0),
|
||||
Timestamp(4, 0)};
|
||||
for (size_t i = 0; i < write_timestamps.size(); ++i) {
|
||||
WriteOptions write_opts;
|
||||
for (uint64_t key = start_keys[i]; key <= kMaxKey; ++key) {
|
||||
Status s = db_->Put(write_opts, Key1(key), write_timestamps[i],
|
||||
"value" + std::to_string(i));
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
}
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
Close();
|
||||
|
||||
// Reopen the database in read only mode as a Compacted DB to test its
|
||||
// timestamp support.
|
||||
options.max_open_files = -1;
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
CheckDBOpenedAsCompactedDBWithOnlyHighestNonEmptyLevelFiles();
|
||||
|
||||
for (size_t i = 0; i < read_timestamps.size(); ++i) {
|
||||
ReadOptions read_opts;
|
||||
Slice read_ts = read_timestamps[i];
|
||||
read_opts.timestamp = &read_ts;
|
||||
int count = 0;
|
||||
for (uint64_t key = start_keys[i]; key <= kMaxKey; ++key, ++count) {
|
||||
std::string value_from_get;
|
||||
std::string timestamp;
|
||||
ASSERT_OK(db_->Get(read_opts, Key1(key), &value_from_get, ×tamp));
|
||||
ASSERT_EQ("value" + std::to_string(i), value_from_get);
|
||||
ASSERT_EQ(write_timestamps[i], timestamp);
|
||||
}
|
||||
size_t expected_count = kMaxKey - start_keys[i] + 1;
|
||||
ASSERT_EQ(expected_count, count);
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBReadOnlyTestWithTimestamp,
|
||||
CompactedDBMultiGetReadTimestampSizeMismatch) {
|
||||
const int kNumKeysPerFile = 1026;
|
||||
const uint64_t kMaxKey = 1024;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
DestroyAndReopen(options);
|
||||
std::string write_timestamp = Timestamp(1, 0);
|
||||
WriteOptions write_opts;
|
||||
for (uint64_t key = 0; key <= kMaxKey; ++key) {
|
||||
Status s = db_->Put(write_opts, Key1(key), write_timestamp,
|
||||
"value" + std::to_string(0));
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
Close();
|
||||
|
||||
// Reopen the database in read only mode as a Compacted DB to test its
|
||||
// timestamp support.
|
||||
options.max_open_files = -1;
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
CheckDBOpenedAsCompactedDBWithOneLevel0File();
|
||||
|
||||
ReadOptions read_opts;
|
||||
std::string different_size_read_timestamp;
|
||||
PutFixed32(&different_size_read_timestamp, 2);
|
||||
Slice different_size_read_ts = different_size_read_timestamp;
|
||||
read_opts.timestamp = &different_size_read_ts;
|
||||
std::vector<std::string> key_strs;
|
||||
std::vector<Slice> keys;
|
||||
for (uint64_t key = 0; key <= kMaxKey; ++key) {
|
||||
key_strs.push_back(Key1(key));
|
||||
}
|
||||
for (const auto& key_str : key_strs) {
|
||||
keys.emplace_back(key_str);
|
||||
}
|
||||
std::vector<std::string> values;
|
||||
std::vector<std::string> timestamps;
|
||||
std::vector<Status> status_list =
|
||||
db_->MultiGet(read_opts, keys, &values, ×tamps);
|
||||
for (const auto& status : status_list) {
|
||||
ASSERT_TRUE(status.IsInvalidArgument());
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBReadOnlyTestWithTimestamp,
|
||||
CompactedDBMultiGetReadTimestampSpecifiedWithoutWriteTimestamp) {
|
||||
const int kNumKeysPerFile = 1026;
|
||||
const uint64_t kMaxKey = 1024;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
DestroyAndReopen(options);
|
||||
WriteOptions write_opts;
|
||||
for (uint64_t key = 0; key <= kMaxKey; ++key) {
|
||||
Status s = db_->Put(write_opts, Key1(key), "value" + std::to_string(0));
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
Close();
|
||||
|
||||
// Reopen the database in read only mode as a Compacted DB to test its
|
||||
// timestamp support.
|
||||
options.max_open_files = -1;
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
CheckDBOpenedAsCompactedDBWithOneLevel0File();
|
||||
|
||||
ReadOptions read_opts;
|
||||
std::string read_timestamp = Timestamp(2, 0);
|
||||
Slice read_ts = read_timestamp;
|
||||
read_opts.timestamp = &read_ts;
|
||||
std::vector<std::string> key_strs;
|
||||
std::vector<Slice> keys;
|
||||
for (uint64_t key = 0; key <= kMaxKey; ++key) {
|
||||
key_strs.push_back(Key1(key));
|
||||
}
|
||||
for (const auto& key_str : key_strs) {
|
||||
keys.emplace_back(key_str);
|
||||
}
|
||||
std::vector<std::string> values;
|
||||
std::vector<std::string> timestamps;
|
||||
std::vector<Status> status_list =
|
||||
db_->MultiGet(read_opts, keys, &values, ×tamps);
|
||||
for (const auto& status : status_list) {
|
||||
ASSERT_TRUE(status.IsInvalidArgument());
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBReadOnlyTestWithTimestamp,
|
||||
CompactedDBMultiGetWriteWithTimestampReadWithoutTimestamp) {
|
||||
const int kNumKeysPerFile = 1026;
|
||||
const uint64_t kMaxKey = 1024;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
DestroyAndReopen(options);
|
||||
std::string write_timestamp = Timestamp(1, 0);
|
||||
WriteOptions write_opts;
|
||||
for (uint64_t key = 0; key <= kMaxKey; ++key) {
|
||||
Status s = db_->Put(write_opts, Key1(key), write_timestamp,
|
||||
"value" + std::to_string(0));
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
Close();
|
||||
|
||||
// Reopen the database in read only mode as a Compacted DB to test its
|
||||
// timestamp support.
|
||||
options.max_open_files = -1;
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
CheckDBOpenedAsCompactedDBWithOneLevel0File();
|
||||
|
||||
ReadOptions read_opts;
|
||||
std::vector<std::string> key_strs;
|
||||
std::vector<Slice> keys;
|
||||
for (uint64_t key = 0; key <= kMaxKey; ++key) {
|
||||
key_strs.push_back(Key1(key));
|
||||
}
|
||||
for (const auto& key_str : key_strs) {
|
||||
keys.emplace_back(key_str);
|
||||
}
|
||||
std::vector<std::string> values;
|
||||
std::vector<Status> status_list = db_->MultiGet(read_opts, keys, &values);
|
||||
for (const auto& status : status_list) {
|
||||
ASSERT_TRUE(status.IsInvalidArgument());
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBReadOnlyTestWithTimestamp, CompactedDBMultiGetWithOnlyOneL0File) {
|
||||
const int kNumKeysPerFile = 1026 * 2;
|
||||
const uint64_t kMaxKey = 1024;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
DestroyAndReopen(options);
|
||||
const std::vector<uint64_t> start_keys = {1, 0};
|
||||
const std::vector<std::string> write_timestamps = {Timestamp(1, 0),
|
||||
Timestamp(3, 0)};
|
||||
const std::vector<std::string> read_timestamps = {Timestamp(2, 0),
|
||||
Timestamp(4, 0)};
|
||||
for (size_t i = 0; i < write_timestamps.size(); ++i) {
|
||||
WriteOptions write_opts;
|
||||
for (uint64_t key = start_keys[i]; key <= kMaxKey; ++key) {
|
||||
Status s = db_->Put(write_opts, Key1(key), write_timestamps[i],
|
||||
"value" + std::to_string(i));
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
}
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
Close();
|
||||
|
||||
// Reopen the database in read only mode as a Compacted DB to test its
|
||||
// timestamp support.
|
||||
options.max_open_files = -1;
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
CheckDBOpenedAsCompactedDBWithOneLevel0File();
|
||||
|
||||
for (size_t i = 0; i < write_timestamps.size(); ++i) {
|
||||
ReadOptions read_opts;
|
||||
Slice read_ts = read_timestamps[i];
|
||||
read_opts.timestamp = &read_ts;
|
||||
std::vector<std::string> key_strs;
|
||||
std::vector<Slice> keys;
|
||||
for (uint64_t key = start_keys[i]; key <= kMaxKey; ++key) {
|
||||
key_strs.push_back(Key1(key));
|
||||
}
|
||||
for (const auto& key_str : key_strs) {
|
||||
keys.emplace_back(key_str);
|
||||
}
|
||||
size_t batch_size = kMaxKey - start_keys[i] + 1;
|
||||
std::vector<std::string> values;
|
||||
std::vector<std::string> timestamps;
|
||||
std::vector<Status> status_list =
|
||||
db_->MultiGet(read_opts, keys, &values, ×tamps);
|
||||
ASSERT_EQ(batch_size, values.size());
|
||||
ASSERT_EQ(batch_size, timestamps.size());
|
||||
for (uint64_t idx = 0; idx < values.size(); ++idx) {
|
||||
ASSERT_EQ("value" + std::to_string(i), values[idx]);
|
||||
ASSERT_EQ(write_timestamps[i], timestamps[idx]);
|
||||
ASSERT_OK(status_list[idx]);
|
||||
}
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBReadOnlyTestWithTimestamp,
|
||||
CompactedDBMultiGetWithOnlyHighestNonEmptyLevelFiles) {
|
||||
const int kNumKeysPerFile = 128;
|
||||
const uint64_t kMaxKey = 1024;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
DestroyAndReopen(options);
|
||||
const std::vector<uint64_t> start_keys = {1, 0};
|
||||
const std::vector<std::string> write_timestamps = {Timestamp(1, 0),
|
||||
Timestamp(3, 0)};
|
||||
const std::vector<std::string> read_timestamps = {Timestamp(2, 0),
|
||||
Timestamp(4, 0)};
|
||||
for (size_t i = 0; i < write_timestamps.size(); ++i) {
|
||||
WriteOptions write_opts;
|
||||
for (uint64_t key = start_keys[i]; key <= kMaxKey; ++key) {
|
||||
Status s = db_->Put(write_opts, Key1(key), write_timestamps[i],
|
||||
"value" + std::to_string(i));
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
}
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
Close();
|
||||
|
||||
// Reopen the database in read only mode as a Compacted DB to test its
|
||||
// timestamp support.
|
||||
options.max_open_files = -1;
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
CheckDBOpenedAsCompactedDBWithOnlyHighestNonEmptyLevelFiles();
|
||||
|
||||
for (size_t i = 0; i < write_timestamps.size(); ++i) {
|
||||
ReadOptions read_opts;
|
||||
Slice read_ts = read_timestamps[i];
|
||||
read_opts.timestamp = &read_ts;
|
||||
std::vector<std::string> key_strs;
|
||||
std::vector<Slice> keys;
|
||||
for (uint64_t key = start_keys[i]; key <= kMaxKey; ++key) {
|
||||
key_strs.push_back(Key1(key));
|
||||
}
|
||||
for (const auto& key_str : key_strs) {
|
||||
keys.emplace_back(key_str);
|
||||
}
|
||||
size_t batch_size = kMaxKey - start_keys[i] + 1;
|
||||
std::vector<std::string> values;
|
||||
std::vector<std::string> timestamps;
|
||||
std::vector<Status> status_list =
|
||||
db_->MultiGet(read_opts, keys, &values, ×tamps);
|
||||
ASSERT_EQ(batch_size, values.size());
|
||||
ASSERT_EQ(batch_size, timestamps.size());
|
||||
for (uint64_t idx = 0; idx < values.size(); ++idx) {
|
||||
ASSERT_EQ("value" + std::to_string(i), values[idx]);
|
||||
ASSERT_EQ(write_timestamps[i], timestamps[idx]);
|
||||
ASSERT_OK(status_list[idx]);
|
||||
}
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
|
||||
+438
-10
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "db/db_impl/db_impl_secondary.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/db_with_timestamp_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/utilities/transaction_db.h"
|
||||
#include "test_util/sync_point.h"
|
||||
@@ -18,10 +19,10 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
class DBSecondaryTest : public DBTestBase {
|
||||
class DBSecondaryTestBase : public DBBasicTestWithTimestampBase {
|
||||
public:
|
||||
DBSecondaryTest()
|
||||
: DBTestBase("db_secondary_test", /*env_do_fsync=*/true),
|
||||
explicit DBSecondaryTestBase(const std::string& dbname)
|
||||
: DBBasicTestWithTimestampBase(dbname),
|
||||
secondary_path_(),
|
||||
handles_secondary_(),
|
||||
db_secondary_(nullptr) {
|
||||
@@ -29,7 +30,7 @@ class DBSecondaryTest : public DBTestBase {
|
||||
test::PerThreadDBPath(env_, "/db_secondary_test_secondary");
|
||||
}
|
||||
|
||||
~DBSecondaryTest() override {
|
||||
~DBSecondaryTestBase() override {
|
||||
CloseSecondary();
|
||||
if (getenv("KEEP_DB") != nullptr) {
|
||||
fprintf(stdout, "Secondary DB is still at %s\n", secondary_path_.c_str());
|
||||
@@ -73,17 +74,17 @@ class DBSecondaryTest : public DBTestBase {
|
||||
DB* db_secondary_;
|
||||
};
|
||||
|
||||
void DBSecondaryTest::OpenSecondary(const Options& options) {
|
||||
void DBSecondaryTestBase::OpenSecondary(const Options& options) {
|
||||
ASSERT_OK(TryOpenSecondary(options));
|
||||
}
|
||||
|
||||
Status DBSecondaryTest::TryOpenSecondary(const Options& options) {
|
||||
Status DBSecondaryTestBase::TryOpenSecondary(const Options& options) {
|
||||
Status s =
|
||||
DB::OpenAsSecondary(options, dbname_, secondary_path_, &db_secondary_);
|
||||
return s;
|
||||
}
|
||||
|
||||
void DBSecondaryTest::OpenSecondaryWithColumnFamilies(
|
||||
void DBSecondaryTestBase::OpenSecondaryWithColumnFamilies(
|
||||
const std::vector<std::string>& column_families, const Options& options) {
|
||||
std::vector<ColumnFamilyDescriptor> cf_descs;
|
||||
cf_descs.emplace_back(kDefaultColumnFamilyName, options);
|
||||
@@ -95,9 +96,10 @@ void DBSecondaryTest::OpenSecondaryWithColumnFamilies(
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
void DBSecondaryTest::CheckFileTypeCounts(const std::string& dir,
|
||||
int expected_log, int expected_sst,
|
||||
int expected_manifest) const {
|
||||
void DBSecondaryTestBase::CheckFileTypeCounts(const std::string& dir,
|
||||
int expected_log,
|
||||
int expected_sst,
|
||||
int expected_manifest) const {
|
||||
std::vector<std::string> filenames;
|
||||
ASSERT_OK(env_->GetChildren(dir, &filenames));
|
||||
|
||||
@@ -116,6 +118,35 @@ void DBSecondaryTest::CheckFileTypeCounts(const std::string& dir,
|
||||
ASSERT_EQ(expected_manifest, manifest_cnt);
|
||||
}
|
||||
|
||||
class DBSecondaryTest : public DBSecondaryTestBase {
|
||||
public:
|
||||
explicit DBSecondaryTest() : DBSecondaryTestBase("db_secondary_test") {}
|
||||
};
|
||||
|
||||
TEST_F(DBSecondaryTest, FailOpenIfLoggerCreationFail) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
Reopen(options);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"rocksdb::CreateLoggerFromOptions:AfterGetPath", [&](void* arg) {
|
||||
auto* s = reinterpret_cast<Status*>(arg);
|
||||
assert(s);
|
||||
*s = Status::IOError("Injected");
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
options.max_open_files = -1;
|
||||
Status s = TryOpenSecondary(options);
|
||||
ASSERT_EQ(nullptr, options.info_log);
|
||||
ASSERT_TRUE(s.IsIOError());
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, NonExistingDb) {
|
||||
Destroy(last_options_);
|
||||
|
||||
@@ -1254,6 +1285,403 @@ TEST_F(DBSecondaryTest, OpenWithTransactionDB) {
|
||||
ASSERT_OK(TryOpenSecondary(options));
|
||||
}
|
||||
|
||||
class DBSecondaryTestWithTimestamp : public DBSecondaryTestBase {
|
||||
public:
|
||||
explicit DBSecondaryTestWithTimestamp()
|
||||
: DBSecondaryTestBase("db_secondary_test_with_timestamp") {}
|
||||
};
|
||||
TEST_F(DBSecondaryTestWithTimestamp, IteratorAndGetReadTimestampSizeMismatch) {
|
||||
const int kNumKeysPerFile = 128;
|
||||
const uint64_t kMaxKey = 1024;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
DestroyAndReopen(options);
|
||||
const std::string write_timestamp = Timestamp(1, 0);
|
||||
WriteOptions write_opts;
|
||||
for (uint64_t key = 0; key <= kMaxKey; ++key) {
|
||||
Status s = db_->Put(write_opts, Key1(key), write_timestamp,
|
||||
"value" + std::to_string(key));
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
// Reopen the database as secondary instance to test its timestamp support.
|
||||
Close();
|
||||
options.max_open_files = -1;
|
||||
ASSERT_OK(ReopenAsSecondary(options));
|
||||
|
||||
ReadOptions read_opts;
|
||||
std::string different_size_read_timestamp;
|
||||
PutFixed32(&different_size_read_timestamp, 2);
|
||||
Slice different_size_read_ts = different_size_read_timestamp;
|
||||
read_opts.timestamp = &different_size_read_ts;
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_opts));
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_TRUE(iter->status().IsInvalidArgument());
|
||||
}
|
||||
|
||||
for (uint64_t key = 0; key <= kMaxKey; ++key) {
|
||||
std::string value_from_get;
|
||||
std::string timestamp;
|
||||
ASSERT_TRUE(db_->Get(read_opts, Key1(key), &value_from_get, ×tamp)
|
||||
.IsInvalidArgument());
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTestWithTimestamp,
|
||||
IteratorAndGetReadTimestampSpecifiedWithoutWriteTimestamp) {
|
||||
const int kNumKeysPerFile = 128;
|
||||
const uint64_t kMaxKey = 1024;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
DestroyAndReopen(options);
|
||||
WriteOptions write_opts;
|
||||
for (uint64_t key = 0; key <= kMaxKey; ++key) {
|
||||
Status s = db_->Put(write_opts, Key1(key), "value" + std::to_string(key));
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
// Reopen the database as secondary instance to test its timestamp support.
|
||||
Close();
|
||||
options.max_open_files = -1;
|
||||
ASSERT_OK(ReopenAsSecondary(options));
|
||||
|
||||
ReadOptions read_opts;
|
||||
const std::string read_timestamp = Timestamp(2, 0);
|
||||
Slice read_ts = read_timestamp;
|
||||
read_opts.timestamp = &read_ts;
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_opts));
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_TRUE(iter->status().IsInvalidArgument());
|
||||
}
|
||||
|
||||
for (uint64_t key = 0; key <= kMaxKey; ++key) {
|
||||
std::string value_from_get;
|
||||
std::string timestamp;
|
||||
ASSERT_TRUE(db_->Get(read_opts, Key1(key), &value_from_get, ×tamp)
|
||||
.IsInvalidArgument());
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTestWithTimestamp,
|
||||
IteratorAndGetWriteWithTimestampReadWithoutTimestamp) {
|
||||
const int kNumKeysPerFile = 128;
|
||||
const uint64_t kMaxKey = 1024;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
DestroyAndReopen(options);
|
||||
const std::string write_timestamp = Timestamp(1, 0);
|
||||
WriteOptions write_opts;
|
||||
for (uint64_t key = 0; key <= kMaxKey; ++key) {
|
||||
Status s = db_->Put(write_opts, Key1(key), write_timestamp,
|
||||
"value" + std::to_string(key));
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
// Reopen the database as secondary instance to test its timestamp support.
|
||||
Close();
|
||||
options.max_open_files = -1;
|
||||
ASSERT_OK(ReopenAsSecondary(options));
|
||||
|
||||
ReadOptions read_opts;
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_opts));
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_TRUE(iter->status().IsInvalidArgument());
|
||||
}
|
||||
|
||||
for (uint64_t key = 0; key <= kMaxKey; ++key) {
|
||||
std::string value_from_get;
|
||||
ASSERT_TRUE(
|
||||
db_->Get(read_opts, Key1(key), &value_from_get).IsInvalidArgument());
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTestWithTimestamp, IteratorAndGet) {
|
||||
const int kNumKeysPerFile = 128;
|
||||
const uint64_t kMaxKey = 1024;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
DestroyAndReopen(options);
|
||||
const std::vector<uint64_t> start_keys = {1, 0};
|
||||
const std::vector<std::string> write_timestamps = {Timestamp(1, 0),
|
||||
Timestamp(3, 0)};
|
||||
const std::vector<std::string> read_timestamps = {Timestamp(2, 0),
|
||||
Timestamp(4, 0)};
|
||||
for (size_t i = 0; i < write_timestamps.size(); ++i) {
|
||||
WriteOptions write_opts;
|
||||
for (uint64_t key = start_keys[i]; key <= kMaxKey; ++key) {
|
||||
Status s = db_->Put(write_opts, Key1(key), write_timestamps[i],
|
||||
"value" + std::to_string(i));
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
}
|
||||
|
||||
// Reopen the database as secondary instance to test its timestamp support.
|
||||
Close();
|
||||
options.max_open_files = -1;
|
||||
ASSERT_OK(ReopenAsSecondary(options));
|
||||
|
||||
auto get_value_and_check = [](DB* db, ReadOptions read_opts, Slice key,
|
||||
Slice expected_value, std::string expected_ts) {
|
||||
std::string value_from_get;
|
||||
std::string timestamp;
|
||||
ASSERT_OK(db->Get(read_opts, key.ToString(), &value_from_get, ×tamp));
|
||||
ASSERT_EQ(expected_value, value_from_get);
|
||||
ASSERT_EQ(expected_ts, timestamp);
|
||||
};
|
||||
for (size_t i = 0; i < read_timestamps.size(); ++i) {
|
||||
ReadOptions read_opts;
|
||||
Slice read_ts = read_timestamps[i];
|
||||
read_opts.timestamp = &read_ts;
|
||||
std::unique_ptr<Iterator> it(db_->NewIterator(read_opts));
|
||||
int count = 0;
|
||||
uint64_t key = 0;
|
||||
// Forward iterate.
|
||||
for (it->Seek(Key1(0)), key = start_keys[i]; it->Valid();
|
||||
it->Next(), ++count, ++key) {
|
||||
CheckIterUserEntry(it.get(), Key1(key), kTypeValue,
|
||||
"value" + std::to_string(i), write_timestamps[i]);
|
||||
get_value_and_check(db_, read_opts, it->key(), it->value(),
|
||||
write_timestamps[i]);
|
||||
}
|
||||
size_t expected_count = kMaxKey - start_keys[i] + 1;
|
||||
ASSERT_EQ(expected_count, count);
|
||||
|
||||
// Backward iterate.
|
||||
count = 0;
|
||||
for (it->SeekForPrev(Key1(kMaxKey)), key = kMaxKey; it->Valid();
|
||||
it->Prev(), ++count, --key) {
|
||||
CheckIterUserEntry(it.get(), Key1(key), kTypeValue,
|
||||
"value" + std::to_string(i), write_timestamps[i]);
|
||||
get_value_and_check(db_, read_opts, it->key(), it->value(),
|
||||
write_timestamps[i]);
|
||||
}
|
||||
ASSERT_EQ(static_cast<size_t>(kMaxKey) - start_keys[i] + 1, count);
|
||||
|
||||
// SeekToFirst()/SeekToLast() with lower/upper bounds.
|
||||
// Then iter with lower and upper bounds.
|
||||
uint64_t l = 0;
|
||||
uint64_t r = kMaxKey + 1;
|
||||
while (l < r) {
|
||||
std::string lb_str = Key1(l);
|
||||
Slice lb = lb_str;
|
||||
std::string ub_str = Key1(r);
|
||||
Slice ub = ub_str;
|
||||
read_opts.iterate_lower_bound = &lb;
|
||||
read_opts.iterate_upper_bound = &ub;
|
||||
it.reset(db_->NewIterator(read_opts));
|
||||
for (it->SeekToFirst(), key = std::max(l, start_keys[i]), count = 0;
|
||||
it->Valid(); it->Next(), ++key, ++count) {
|
||||
CheckIterUserEntry(it.get(), Key1(key), kTypeValue,
|
||||
"value" + std::to_string(i), write_timestamps[i]);
|
||||
get_value_and_check(db_, read_opts, it->key(), it->value(),
|
||||
write_timestamps[i]);
|
||||
}
|
||||
ASSERT_EQ(r - std::max(l, start_keys[i]), count);
|
||||
|
||||
for (it->SeekToLast(), key = std::min(r, kMaxKey + 1), count = 0;
|
||||
it->Valid(); it->Prev(), --key, ++count) {
|
||||
CheckIterUserEntry(it.get(), Key1(key - 1), kTypeValue,
|
||||
"value" + std::to_string(i), write_timestamps[i]);
|
||||
get_value_and_check(db_, read_opts, it->key(), it->value(),
|
||||
write_timestamps[i]);
|
||||
}
|
||||
l += (kMaxKey / 100);
|
||||
r -= (kMaxKey / 100);
|
||||
}
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTestWithTimestamp, IteratorsReadTimestampSizeMismatch) {
|
||||
const int kNumKeysPerFile = 128;
|
||||
const uint64_t kMaxKey = 1024;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
DestroyAndReopen(options);
|
||||
const std::string write_timestamp = Timestamp(1, 0);
|
||||
WriteOptions write_opts;
|
||||
for (uint64_t key = 0; key <= kMaxKey; ++key) {
|
||||
Status s = db_->Put(write_opts, Key1(key), write_timestamp,
|
||||
"value" + std::to_string(key));
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
// Reopen the database as secondary instance to test its timestamp support.
|
||||
Close();
|
||||
options.max_open_files = -1;
|
||||
ASSERT_OK(ReopenAsSecondary(options));
|
||||
|
||||
ReadOptions read_opts;
|
||||
std::string different_size_read_timestamp;
|
||||
PutFixed32(&different_size_read_timestamp, 2);
|
||||
Slice different_size_read_ts = different_size_read_timestamp;
|
||||
read_opts.timestamp = &different_size_read_ts;
|
||||
{
|
||||
std::vector<Iterator*> iters;
|
||||
ASSERT_TRUE(
|
||||
db_->NewIterators(read_opts, {db_->DefaultColumnFamily()}, &iters)
|
||||
.IsInvalidArgument());
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTestWithTimestamp,
|
||||
IteratorsReadTimestampSpecifiedWithoutWriteTimestamp) {
|
||||
const int kNumKeysPerFile = 128;
|
||||
const uint64_t kMaxKey = 1024;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
DestroyAndReopen(options);
|
||||
WriteOptions write_opts;
|
||||
for (uint64_t key = 0; key <= kMaxKey; ++key) {
|
||||
Status s = db_->Put(write_opts, Key1(key), "value" + std::to_string(key));
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
// Reopen the database as secondary instance to test its timestamp support.
|
||||
Close();
|
||||
options.max_open_files = -1;
|
||||
ASSERT_OK(ReopenAsSecondary(options));
|
||||
|
||||
ReadOptions read_opts;
|
||||
const std::string read_timestamp = Timestamp(2, 0);
|
||||
Slice read_ts = read_timestamp;
|
||||
read_opts.timestamp = &read_ts;
|
||||
{
|
||||
std::vector<Iterator*> iters;
|
||||
ASSERT_TRUE(
|
||||
db_->NewIterators(read_opts, {db_->DefaultColumnFamily()}, &iters)
|
||||
.IsInvalidArgument());
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTestWithTimestamp,
|
||||
IteratorsWriteWithTimestampReadWithoutTimestamp) {
|
||||
const int kNumKeysPerFile = 128;
|
||||
const uint64_t kMaxKey = 1024;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
DestroyAndReopen(options);
|
||||
const std::string write_timestamp = Timestamp(1, 0);
|
||||
WriteOptions write_opts;
|
||||
for (uint64_t key = 0; key <= kMaxKey; ++key) {
|
||||
Status s = db_->Put(write_opts, Key1(key), write_timestamp,
|
||||
"value" + std::to_string(key));
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
// Reopen the database as secondary instance to test its timestamp support.
|
||||
Close();
|
||||
options.max_open_files = -1;
|
||||
ASSERT_OK(ReopenAsSecondary(options));
|
||||
|
||||
ReadOptions read_opts;
|
||||
{
|
||||
std::vector<Iterator*> iters;
|
||||
ASSERT_TRUE(
|
||||
db_->NewIterators(read_opts, {db_->DefaultColumnFamily()}, &iters)
|
||||
.IsInvalidArgument());
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTestWithTimestamp, Iterators) {
|
||||
const int kNumKeysPerFile = 128;
|
||||
const uint64_t kMaxKey = 1024;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
DestroyAndReopen(options);
|
||||
const std::string write_timestamp = Timestamp(1, 0);
|
||||
const std::string read_timestamp = Timestamp(2, 0);
|
||||
WriteOptions write_opts;
|
||||
for (uint64_t key = 0; key <= kMaxKey; ++key) {
|
||||
Status s = db_->Put(write_opts, Key1(key), write_timestamp,
|
||||
"value" + std::to_string(key));
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
// Reopen the database as secondary instance to test its timestamp support.
|
||||
Close();
|
||||
options.max_open_files = -1;
|
||||
ASSERT_OK(ReopenAsSecondary(options));
|
||||
|
||||
ReadOptions read_opts;
|
||||
Slice read_ts = read_timestamp;
|
||||
read_opts.timestamp = &read_ts;
|
||||
std::vector<Iterator*> iters;
|
||||
ASSERT_OK(db_->NewIterators(read_opts, {db_->DefaultColumnFamily()}, &iters));
|
||||
ASSERT_EQ(static_cast<uint64_t>(1), iters.size());
|
||||
|
||||
int count = 0;
|
||||
uint64_t key = 0;
|
||||
// Forward iterate.
|
||||
for (iters[0]->Seek(Key1(0)), key = 0; iters[0]->Valid();
|
||||
iters[0]->Next(), ++count, ++key) {
|
||||
CheckIterUserEntry(iters[0], Key1(key), kTypeValue,
|
||||
"value" + std::to_string(key), write_timestamp);
|
||||
}
|
||||
|
||||
size_t expected_count = kMaxKey - 0 + 1;
|
||||
ASSERT_EQ(expected_count, count);
|
||||
delete iters[0];
|
||||
|
||||
Close();
|
||||
}
|
||||
#endif //! ROCKSDB_LITE
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
+43
-2
@@ -8,6 +8,7 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "env/mock_env.h"
|
||||
#include "file/sst_file_manager_impl.h"
|
||||
#include "port/port.h"
|
||||
#include "port/stack_trace.h"
|
||||
@@ -961,7 +962,6 @@ TEST_F(DBSSTTest, OpenDBWithExistingTrash) {
|
||||
ASSERT_NOK(env_->FileExists(dbname_ + "/" + "003.sst.trash"));
|
||||
}
|
||||
|
||||
|
||||
// Create a DB with 2 db_paths, and generate multiple files in the 2
|
||||
// db_paths using CompactRangeOptions, make sure that files that were
|
||||
// deleted from first db_path were deleted using DeleteScheduler and
|
||||
@@ -1233,7 +1233,9 @@ TEST_F(DBSSTTest, CancellingCompactionsWorks) {
|
||||
ASSERT_GT(completed_compactions, 0);
|
||||
ASSERT_EQ(sfm->GetCompactionsReservedSize(), 0);
|
||||
// Make sure the stat is bumped
|
||||
ASSERT_GT(dbfull()->immutable_db_options().statistics.get()->getTickerCount(COMPACTION_CANCELLED), 0);
|
||||
ASSERT_GT(dbfull()->immutable_db_options().statistics.get()->getTickerCount(
|
||||
COMPACTION_CANCELLED),
|
||||
0);
|
||||
ASSERT_EQ(0,
|
||||
dbfull()->immutable_db_options().statistics.get()->getTickerCount(
|
||||
FILES_MARKED_TRASH));
|
||||
@@ -1629,6 +1631,45 @@ TEST_F(DBSSTTest, GetTotalSstFilesSize) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, OpenDBWithoutGetFileSizeInvocations) {
|
||||
Options options = CurrentOptions();
|
||||
std::unique_ptr<MockEnv> env{MockEnv::Create(Env::Default())};
|
||||
options.env = env.get();
|
||||
options.disable_auto_compactions = true;
|
||||
options.compression = kNoCompression;
|
||||
options.enable_blob_files = true;
|
||||
options.blob_file_size = 32; // create one blob per file
|
||||
options.skip_checking_sst_file_sizes_on_db_open = true;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
// Generate 5 files in L0
|
||||
for (int i = 0; i < 5; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
std::string val = "val_file_" + std::to_string(i);
|
||||
ASSERT_OK(Put(Key(j), val));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
Close();
|
||||
|
||||
bool is_get_file_size_called = false;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"MockFileSystem::GetFileSize:CheckFileType", [&](void* arg) {
|
||||
std::string* filename = reinterpret_cast<std::string*>(arg);
|
||||
if (filename->find(".blob") != std::string::npos) {
|
||||
is_get_file_size_called = true;
|
||||
}
|
||||
});
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Reopen(options);
|
||||
ASSERT_FALSE(is_get_file_size_called);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, GetTotalSstFilesSizeVersionsFilesShared) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
|
||||
+162
-29
@@ -321,7 +321,7 @@ TEST_F(DBTest, MixedSlowdownOptions) {
|
||||
// We need the 2nd write to trigger delay. This is because delay is
|
||||
// estimated based on the last write size which is 0 for the first write.
|
||||
ASSERT_OK(dbfull()->Put(wo, "foo2", "bar2"));
|
||||
token.reset();
|
||||
token.reset();
|
||||
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
@@ -379,7 +379,7 @@ TEST_F(DBTest, MixedSlowdownOptionsInQueue) {
|
||||
// We need the 2nd write to trigger delay. This is because delay is
|
||||
// estimated based on the last write size which is 0 for the first write.
|
||||
ASSERT_OK(dbfull()->Put(wo, "foo2", "bar2"));
|
||||
token.reset();
|
||||
token.reset();
|
||||
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
@@ -448,7 +448,7 @@ TEST_F(DBTest, MixedSlowdownOptionsStop) {
|
||||
// We need the 2nd write to trigger delay. This is because delay is
|
||||
// estimated based on the last write size which is 0 for the first write.
|
||||
ASSERT_OK(dbfull()->Put(wo, "foo2", "bar2"));
|
||||
token.reset();
|
||||
token.reset();
|
||||
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
@@ -483,7 +483,6 @@ TEST_F(DBTest, LevelLimitReopen) {
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
|
||||
TEST_F(DBTest, PutSingleDeleteGet) {
|
||||
do {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
@@ -782,7 +781,6 @@ TEST_F(DBTest, GetFromImmutableLayer) {
|
||||
} while (ChangeOptions());
|
||||
}
|
||||
|
||||
|
||||
TEST_F(DBTest, GetLevel0Ordering) {
|
||||
do {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
@@ -1844,6 +1842,8 @@ TEST_F(DBTest, Snapshot) {
|
||||
uint64_t time_snap1 = GetTimeOldestSnapshots();
|
||||
ASSERT_GT(time_snap1, 0U);
|
||||
ASSERT_EQ(GetSequenceOldestSnapshots(), s1->GetSequenceNumber());
|
||||
ASSERT_EQ(GetTimeOldestSnapshots(),
|
||||
static_cast<uint64_t>(s1->GetUnixTime()));
|
||||
ASSERT_OK(Put(0, "foo", "0v2"));
|
||||
ASSERT_OK(Put(1, "foo", "1v2"));
|
||||
|
||||
@@ -1853,6 +1853,8 @@ TEST_F(DBTest, Snapshot) {
|
||||
ASSERT_EQ(2U, GetNumSnapshots());
|
||||
ASSERT_EQ(time_snap1, GetTimeOldestSnapshots());
|
||||
ASSERT_EQ(GetSequenceOldestSnapshots(), s1->GetSequenceNumber());
|
||||
ASSERT_EQ(GetTimeOldestSnapshots(),
|
||||
static_cast<uint64_t>(s1->GetUnixTime()));
|
||||
ASSERT_OK(Put(0, "foo", "0v3"));
|
||||
ASSERT_OK(Put(1, "foo", "1v3"));
|
||||
|
||||
@@ -1861,6 +1863,8 @@ TEST_F(DBTest, Snapshot) {
|
||||
ASSERT_EQ(3U, GetNumSnapshots());
|
||||
ASSERT_EQ(time_snap1, GetTimeOldestSnapshots());
|
||||
ASSERT_EQ(GetSequenceOldestSnapshots(), s1->GetSequenceNumber());
|
||||
ASSERT_EQ(GetTimeOldestSnapshots(),
|
||||
static_cast<uint64_t>(s1->GetUnixTime()));
|
||||
|
||||
ASSERT_OK(Put(0, "foo", "0v4"));
|
||||
ASSERT_OK(Put(1, "foo", "1v4"));
|
||||
@@ -1877,6 +1881,8 @@ TEST_F(DBTest, Snapshot) {
|
||||
ASSERT_EQ(2U, GetNumSnapshots());
|
||||
ASSERT_EQ(time_snap1, GetTimeOldestSnapshots());
|
||||
ASSERT_EQ(GetSequenceOldestSnapshots(), s1->GetSequenceNumber());
|
||||
ASSERT_EQ(GetTimeOldestSnapshots(),
|
||||
static_cast<uint64_t>(s1->GetUnixTime()));
|
||||
ASSERT_EQ("0v1", Get(0, "foo", s1));
|
||||
ASSERT_EQ("1v1", Get(1, "foo", s1));
|
||||
ASSERT_EQ("0v2", Get(0, "foo", s2));
|
||||
@@ -1892,6 +1898,8 @@ TEST_F(DBTest, Snapshot) {
|
||||
ASSERT_EQ(1U, GetNumSnapshots());
|
||||
ASSERT_LT(time_snap1, GetTimeOldestSnapshots());
|
||||
ASSERT_EQ(GetSequenceOldestSnapshots(), s2->GetSequenceNumber());
|
||||
ASSERT_EQ(GetTimeOldestSnapshots(),
|
||||
static_cast<uint64_t>(s2->GetUnixTime()));
|
||||
|
||||
db_->ReleaseSnapshot(s2);
|
||||
ASSERT_EQ(0U, GetNumSnapshots());
|
||||
@@ -2859,6 +2867,18 @@ class ModelDB : public DB {
|
||||
assert(false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int64_t GetUnixTime() const override {
|
||||
// no need to call this
|
||||
assert(false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t GetTimestamp() const override {
|
||||
// no need to call this
|
||||
assert(false);
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
explicit ModelDB(const Options& options) : options_(options) {}
|
||||
@@ -3451,7 +3471,40 @@ TEST_F(DBTest, BlockBasedTablePrefixIndexTest) {
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("k2", "v2"));
|
||||
|
||||
// Reopen it without prefix extractor, make sure everything still works.
|
||||
// Reopen with different prefix extractor, make sure everything still works.
|
||||
// RocksDB should just fall back to the binary index.
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(2));
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_EQ("v1", Get("k1"));
|
||||
ASSERT_EQ("v2", Get("k2"));
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Back to original
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "fixed:1"}}));
|
||||
ASSERT_EQ("v1", Get("k1"));
|
||||
ASSERT_EQ("v2", Get("k2"));
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
// Same if there's a problem initally loading prefix transform
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlockBasedTable::Open::ForceNullTablePrefixExtractor",
|
||||
[&](void* arg) { *static_cast<bool*>(arg) = true; });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Reopen(options);
|
||||
ASSERT_EQ("v1", Get("k1"));
|
||||
ASSERT_EQ("v2", Get("k2"));
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Change again
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "fixed:2"}}));
|
||||
ASSERT_EQ("v1", Get("k1"));
|
||||
ASSERT_EQ("v2", Get("k2"));
|
||||
#endif // !ROCKSDB_LITE
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
// Reopen with no prefix extractor, make sure everything still works.
|
||||
// RocksDB should just fall back to the binary index.
|
||||
table_options.index_type = BlockBasedTableOptions::kBinarySearch;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
@@ -3461,6 +3514,7 @@ TEST_F(DBTest, BlockBasedTablePrefixIndexTest) {
|
||||
ASSERT_EQ("v1", Get("k1"));
|
||||
ASSERT_EQ("v2", Get("k2"));
|
||||
}
|
||||
|
||||
TEST_F(DBTest, BlockBasedTablePrefixHashIndexTest) {
|
||||
// create a DB with block prefix index
|
||||
BlockBasedTableOptions table_options;
|
||||
@@ -3751,7 +3805,7 @@ TEST_F(DBTest, FIFOCompactionWithTTLTest) {
|
||||
|
||||
options.compaction_options_fifo.max_table_files_size = 150 << 10; // 150KB
|
||||
options.compaction_options_fifo.allow_compaction = false;
|
||||
options.ttl = 1 * 60 * 60 ; // 1 hour
|
||||
options.ttl = 1 * 60 * 60; // 1 hour
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
@@ -3825,7 +3879,7 @@ TEST_F(DBTest, FIFOCompactionWithTTLTest) {
|
||||
options.write_buffer_size = 10 << 10; // 10KB
|
||||
options.compaction_options_fifo.max_table_files_size = 150 << 10; // 150KB
|
||||
options.compaction_options_fifo.allow_compaction = false;
|
||||
options.ttl = 1 * 60 * 60; // 1 hour
|
||||
options.ttl = 1 * 60 * 60; // 1 hour
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
@@ -4038,9 +4092,6 @@ class MockedRateLimiterWithNoOptionalAPIImpl : public RateLimiter {
|
||||
|
||||
~MockedRateLimiterWithNoOptionalAPIImpl() override {}
|
||||
|
||||
const char* Name() const override {
|
||||
return "MockedRateLimiterWithNoOptionalAPI";
|
||||
}
|
||||
void SetBytesPerSecond(int64_t bytes_per_second) override {
|
||||
(void)bytes_per_second;
|
||||
}
|
||||
@@ -4217,7 +4268,9 @@ TEST_F(DBTest, ConcurrentFlushWAL) {
|
||||
threads.emplace_back([&] {
|
||||
for (size_t i = cnt; i < 2 * cnt; i++) {
|
||||
auto istr = std::to_string(i);
|
||||
WriteBatch batch;
|
||||
WriteBatch batch(0 /* reserved_bytes */, 0 /* max_bytes */,
|
||||
wopt.protection_bytes_per_key,
|
||||
0 /* default_cf_ts_sz */);
|
||||
ASSERT_OK(batch.Put("a" + istr, "b" + istr));
|
||||
ASSERT_OK(
|
||||
dbfull()->WriteImpl(wopt, &batch, nullptr, nullptr, 0, true));
|
||||
@@ -6014,7 +6067,6 @@ TEST_F(DBTest, DISABLED_SuggestCompactRangeTest) {
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(1));
|
||||
}
|
||||
|
||||
|
||||
TEST_F(DBTest, PromoteL0) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
@@ -6195,13 +6247,12 @@ TEST_F(DBTest, CompactFilesShouldTriggerAutoCompaction) {
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
port::Thread manual_compaction_thread([&]() {
|
||||
auto s = db_->CompactFiles(CompactionOptions(),
|
||||
db_->DefaultColumnFamily(), input_files, 0);
|
||||
ASSERT_OK(s);
|
||||
auto s = db_->CompactFiles(CompactionOptions(), db_->DefaultColumnFamily(),
|
||||
input_files, 0);
|
||||
ASSERT_OK(s);
|
||||
});
|
||||
|
||||
TEST_SYNC_POINT(
|
||||
"DBTest::CompactFilesShouldTriggerAutoCompaction:Begin");
|
||||
TEST_SYNC_POINT("DBTest::CompactFilesShouldTriggerAutoCompaction:Begin");
|
||||
// generate enough files to trigger compaction
|
||||
for (int i = 0; i < 20; ++i) {
|
||||
for (int j = 0; j < 2; ++j) {
|
||||
@@ -6211,16 +6262,15 @@ TEST_F(DBTest, CompactFilesShouldTriggerAutoCompaction) {
|
||||
}
|
||||
db_->GetColumnFamilyMetaData(db_->DefaultColumnFamily(), &cf_meta_data);
|
||||
ASSERT_GT(cf_meta_data.levels[0].files.size(),
|
||||
options.level0_file_num_compaction_trigger);
|
||||
TEST_SYNC_POINT(
|
||||
"DBTest::CompactFilesShouldTriggerAutoCompaction:End");
|
||||
options.level0_file_num_compaction_trigger);
|
||||
TEST_SYNC_POINT("DBTest::CompactFilesShouldTriggerAutoCompaction:End");
|
||||
|
||||
manual_compaction_thread.join();
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
db_->GetColumnFamilyMetaData(db_->DefaultColumnFamily(), &cf_meta_data);
|
||||
ASSERT_LE(cf_meta_data.levels[0].files.size(),
|
||||
options.level0_file_num_compaction_trigger);
|
||||
options.level0_file_num_compaction_trigger);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
@@ -6445,8 +6495,9 @@ class WriteStallListener : public EventListener {
|
||||
MutexLock l(&mutex_);
|
||||
return expected == condition_;
|
||||
}
|
||||
|
||||
private:
|
||||
port::Mutex mutex_;
|
||||
port::Mutex mutex_;
|
||||
WriteStallCondition condition_;
|
||||
};
|
||||
|
||||
@@ -6674,7 +6725,8 @@ TEST_F(DBTest, LastWriteBufferDelay) {
|
||||
sleeping_task.WakeUp();
|
||||
sleeping_task.WaitUntilDone();
|
||||
}
|
||||
#endif // !defined(ROCKSDB_LITE) && !defined(ROCKSDB_DISABLE_STALL_NOTIFICATION)
|
||||
#endif // !defined(ROCKSDB_LITE) &&
|
||||
// !defined(ROCKSDB_DISABLE_STALL_NOTIFICATION)
|
||||
|
||||
TEST_F(DBTest, FailWhenCompressionNotSupportedTest) {
|
||||
CompressionType compressions[] = {kZlibCompression, kBZip2Compression,
|
||||
@@ -6759,6 +6811,89 @@ TEST_F(DBTest, PinnableSliceAndRowCache) {
|
||||
1);
|
||||
}
|
||||
|
||||
TEST_F(DBTest, ReusePinnableSlice) {
|
||||
Options options = CurrentOptions();
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
options.row_cache = NewLRUCache(8192);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_EQ(Get("foo"), "bar");
|
||||
ASSERT_EQ(
|
||||
reinterpret_cast<LRUCache*>(options.row_cache.get())->TEST_GetLRUSize(),
|
||||
1);
|
||||
|
||||
{
|
||||
PinnableSlice pin_slice;
|
||||
ASSERT_EQ(Get("foo", &pin_slice), Status::OK());
|
||||
ASSERT_EQ(Get("foo", &pin_slice), Status::OK());
|
||||
ASSERT_EQ(pin_slice.ToString(), "bar");
|
||||
|
||||
// Entry is already in cache, lookup will remove the element from lru
|
||||
ASSERT_EQ(
|
||||
reinterpret_cast<LRUCache*>(options.row_cache.get())->TEST_GetLRUSize(),
|
||||
0);
|
||||
}
|
||||
// After PinnableSlice destruction element is added back in LRU
|
||||
ASSERT_EQ(
|
||||
reinterpret_cast<LRUCache*>(options.row_cache.get())->TEST_GetLRUSize(),
|
||||
1);
|
||||
|
||||
{
|
||||
std::vector<Slice> multiget_keys;
|
||||
multiget_keys.push_back("foo");
|
||||
std::vector<PinnableSlice> multiget_values(1);
|
||||
std::vector<Status> statuses({Status::NotFound()});
|
||||
ReadOptions ropt;
|
||||
dbfull()->MultiGet(ropt, dbfull()->DefaultColumnFamily(),
|
||||
multiget_keys.size(), multiget_keys.data(),
|
||||
multiget_values.data(), statuses.data());
|
||||
ASSERT_EQ(Status::OK(), statuses[0]);
|
||||
dbfull()->MultiGet(ropt, dbfull()->DefaultColumnFamily(),
|
||||
multiget_keys.size(), multiget_keys.data(),
|
||||
multiget_values.data(), statuses.data());
|
||||
ASSERT_EQ(Status::OK(), statuses[0]);
|
||||
|
||||
// Entry is already in cache, lookup will remove the element from lru
|
||||
ASSERT_EQ(
|
||||
reinterpret_cast<LRUCache*>(options.row_cache.get())->TEST_GetLRUSize(),
|
||||
0);
|
||||
}
|
||||
// After PinnableSlice destruction element is added back in LRU
|
||||
ASSERT_EQ(
|
||||
reinterpret_cast<LRUCache*>(options.row_cache.get())->TEST_GetLRUSize(),
|
||||
1);
|
||||
|
||||
{
|
||||
std::vector<ColumnFamilyHandle*> multiget_cfs;
|
||||
multiget_cfs.push_back(dbfull()->DefaultColumnFamily());
|
||||
std::vector<Slice> multiget_keys;
|
||||
multiget_keys.push_back("foo");
|
||||
std::vector<PinnableSlice> multiget_values(1);
|
||||
std::vector<Status> statuses({Status::NotFound()});
|
||||
ReadOptions ropt;
|
||||
dbfull()->MultiGet(ropt, multiget_keys.size(), multiget_cfs.data(),
|
||||
multiget_keys.data(), multiget_values.data(),
|
||||
statuses.data());
|
||||
ASSERT_EQ(Status::OK(), statuses[0]);
|
||||
dbfull()->MultiGet(ropt, multiget_keys.size(), multiget_cfs.data(),
|
||||
multiget_keys.data(), multiget_values.data(),
|
||||
statuses.data());
|
||||
ASSERT_EQ(Status::OK(), statuses[0]);
|
||||
|
||||
// Entry is already in cache, lookup will remove the element from lru
|
||||
ASSERT_EQ(
|
||||
reinterpret_cast<LRUCache*>(options.row_cache.get())->TEST_GetLRUSize(),
|
||||
0);
|
||||
}
|
||||
// After PinnableSlice destruction element is added back in LRU
|
||||
ASSERT_EQ(
|
||||
reinterpret_cast<LRUCache*>(options.row_cache.get())->TEST_GetLRUSize(),
|
||||
1);
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBTest, DeletingOldWalAfterDrop) {
|
||||
@@ -6838,9 +6973,7 @@ TEST_F(DBTest, PauseBackgroundWorkTest) {
|
||||
TEST_F(DBTest, ThreadLocalPtrDeadlock) {
|
||||
std::atomic<int> flushes_done{0};
|
||||
std::atomic<int> threads_destroyed{0};
|
||||
auto done = [&] {
|
||||
return flushes_done.load() > 10;
|
||||
};
|
||||
auto done = [&] { return flushes_done.load() > 10; };
|
||||
|
||||
port::Thread flushing_thread([&] {
|
||||
for (int i = 0; !done(); ++i) {
|
||||
@@ -6853,7 +6986,7 @@ TEST_F(DBTest, ThreadLocalPtrDeadlock) {
|
||||
});
|
||||
|
||||
std::vector<port::Thread> thread_spawning_threads(10);
|
||||
for (auto& t: thread_spawning_threads) {
|
||||
for (auto& t : thread_spawning_threads) {
|
||||
t = port::Thread([&] {
|
||||
while (!done()) {
|
||||
{
|
||||
@@ -6869,7 +7002,7 @@ TEST_F(DBTest, ThreadLocalPtrDeadlock) {
|
||||
});
|
||||
}
|
||||
|
||||
for (auto& t: thread_spawning_threads) {
|
||||
for (auto& t : thread_spawning_threads) {
|
||||
t.join();
|
||||
}
|
||||
flushing_thread.join();
|
||||
|
||||
+200
-42
@@ -1326,7 +1326,8 @@ TEST_F(DBTest2, PresetCompressionDict) {
|
||||
options.compression_opts.zstd_max_train_bytes = 0;
|
||||
break;
|
||||
case kWithZSTDfinalizeDict:
|
||||
if (compression_type != kZSTD) {
|
||||
if (compression_type != kZSTD ||
|
||||
!ZSTD_FinalizeDictionarySupported()) {
|
||||
continue;
|
||||
}
|
||||
options.compression_opts.max_dict_bytes = kBlockSizeBytes;
|
||||
@@ -1334,7 +1335,7 @@ TEST_F(DBTest2, PresetCompressionDict) {
|
||||
options.compression_opts.use_zstd_dict_trainer = false;
|
||||
break;
|
||||
case kWithZSTDTrainedDict:
|
||||
if (compression_type != kZSTD) {
|
||||
if (compression_type != kZSTD || !ZSTD_TrainDictionarySupported()) {
|
||||
continue;
|
||||
}
|
||||
options.compression_opts.max_dict_bytes = kBlockSizeBytes;
|
||||
@@ -3080,10 +3081,21 @@ TEST_F(DBTest2, PausingManualCompaction1) {
|
||||
int manual_compactions_paused = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionJob::Run():PausingManualCompaction:1", [&](void* arg) {
|
||||
auto canceled = static_cast<std::atomic<bool>*>(arg);
|
||||
// CompactRange triggers manual compaction and cancel the compaction
|
||||
// by set *canceled as true
|
||||
if (canceled != nullptr) {
|
||||
canceled->store(true, std::memory_order_release);
|
||||
}
|
||||
manual_compactions_paused += 1;
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"TestCompactFiles:PausingManualCompaction:3", [&](void* arg) {
|
||||
auto paused = static_cast<std::atomic<int>*>(arg);
|
||||
// CompactFiles() relies on manual_compactions_paused to
|
||||
// determine if thie compaction should be paused or not
|
||||
ASSERT_EQ(0, paused->load(std::memory_order_acquire));
|
||||
paused->fetch_add(1, std::memory_order_release);
|
||||
manual_compactions_paused += 1;
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
@@ -3148,7 +3160,7 @@ TEST_F(DBTest2, PausingManualCompaction2) {
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < 2; i++) {
|
||||
// Generate a file containing 10 keys.
|
||||
// Generate a file containing 100 keys.
|
||||
for (int j = 0; j < 100; j++) {
|
||||
ASSERT_OK(Put(Key(j), rnd.RandomString(50)));
|
||||
}
|
||||
@@ -3247,10 +3259,21 @@ TEST_F(DBTest2, PausingManualCompaction4) {
|
||||
int run_manual_compactions = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionJob::Run():PausingManualCompaction:2", [&](void* arg) {
|
||||
auto canceled = static_cast<std::atomic<bool>*>(arg);
|
||||
// CompactRange triggers manual compaction and cancel the compaction
|
||||
// by set *canceled as true
|
||||
if (canceled != nullptr) {
|
||||
canceled->store(true, std::memory_order_release);
|
||||
}
|
||||
run_manual_compactions++;
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"TestCompactFiles:PausingManualCompaction:3", [&](void* arg) {
|
||||
auto paused = static_cast<std::atomic<int>*>(arg);
|
||||
// CompactFiles() relies on manual_compactions_paused to
|
||||
// determine if thie compaction should be paused or not
|
||||
ASSERT_EQ(0, paused->load(std::memory_order_acquire));
|
||||
paused->fetch_add(1, std::memory_order_release);
|
||||
run_manual_compactions++;
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
@@ -3265,7 +3288,6 @@ TEST_F(DBTest2, PausingManualCompaction4) {
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearCallBack(
|
||||
"CompactionJob::Run():PausingManualCompaction:2");
|
||||
dbfull()->EnableManualCompaction();
|
||||
ASSERT_OK(dbfull()->CompactRange(compact_options, nullptr, nullptr));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact(true));
|
||||
#ifndef ROCKSDB_LITE
|
||||
@@ -3514,8 +3536,9 @@ TEST_F(DBTest2, CancelManualCompactionWithListener) {
|
||||
"CompactionJob::FinishCompactionOutputFile1",
|
||||
[&](void* /*arg*/) { running_compaction++; });
|
||||
|
||||
// Case I: 1 Notify begin compaction, 2 DisableManualCompaction, 3 Compaction
|
||||
// not run, 4 Notify compaction end.
|
||||
// Case I: 1 Notify begin compaction, 2 Set *canceled as true to disable
|
||||
// manual compaction in the callback function, 3 Compaction not run,
|
||||
// 4 Notify compaction end.
|
||||
listener->code_ = Status::kIncomplete;
|
||||
listener->subcode_ = Status::SubCode::kManualCompactionPaused;
|
||||
|
||||
@@ -3532,8 +3555,9 @@ TEST_F(DBTest2, CancelManualCompactionWithListener) {
|
||||
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).
|
||||
// Case II: 1 Set *canceled as true in the callback function to disable manual
|
||||
// compaction, 2 Notify begin compaction (return without notifying), 3 Notify
|
||||
// compaction end (return without notifying).
|
||||
ASSERT_TRUE(dbfull()
|
||||
->CompactRange(compact_options, nullptr, nullptr)
|
||||
.IsManualCompactionPaused());
|
||||
@@ -3544,8 +3568,8 @@ TEST_F(DBTest2, CancelManualCompactionWithListener) {
|
||||
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);
|
||||
// 3. Set *canceled as true in the callback function to disable manual
|
||||
// compaction, 4 Notify compaction end.
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearCallBack(
|
||||
"CompactionIterator:ProcessKV");
|
||||
|
||||
@@ -6352,86 +6376,85 @@ TEST_F(DBTest2, AutoPrefixMode1) {
|
||||
ReadOptions ro;
|
||||
ro.total_order_seek = false;
|
||||
ro.auto_prefix_mode = true;
|
||||
|
||||
const auto stat = BLOOM_FILTER_PREFIX_CHECKED;
|
||||
{
|
||||
std::unique_ptr<Iterator> iterator(db_->NewIterator(ro));
|
||||
iterator->Seek("b1");
|
||||
ASSERT_TRUE(iterator->Valid());
|
||||
ASSERT_EQ("x1", iterator->key().ToString());
|
||||
ASSERT_EQ(0, TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED));
|
||||
EXPECT_EQ(0, TestGetAndResetTickerCount(options, stat));
|
||||
ASSERT_OK(iterator->status());
|
||||
}
|
||||
|
||||
std::string ub_str = "b9";
|
||||
Slice ub(ub_str);
|
||||
Slice ub;
|
||||
ro.iterate_upper_bound = &ub;
|
||||
|
||||
ub = "b9";
|
||||
{
|
||||
std::unique_ptr<Iterator> iterator(db_->NewIterator(ro));
|
||||
iterator->Seek("b1");
|
||||
ASSERT_FALSE(iterator->Valid());
|
||||
ASSERT_EQ(1, TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED));
|
||||
EXPECT_EQ(1, TestGetAndResetTickerCount(options, stat));
|
||||
ASSERT_OK(iterator->status());
|
||||
}
|
||||
|
||||
ub_str = "z";
|
||||
ub = Slice(ub_str);
|
||||
ub = "z";
|
||||
{
|
||||
std::unique_ptr<Iterator> iterator(db_->NewIterator(ro));
|
||||
iterator->Seek("b1");
|
||||
ASSERT_TRUE(iterator->Valid());
|
||||
ASSERT_EQ("x1", iterator->key().ToString());
|
||||
ASSERT_EQ(1, TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED));
|
||||
EXPECT_EQ(0, TestGetAndResetTickerCount(options, stat));
|
||||
ASSERT_OK(iterator->status());
|
||||
}
|
||||
|
||||
ub_str = "c";
|
||||
ub = Slice(ub_str);
|
||||
ub = "c";
|
||||
{
|
||||
std::unique_ptr<Iterator> iterator(db_->NewIterator(ro));
|
||||
iterator->Seek("b1");
|
||||
ASSERT_FALSE(iterator->Valid());
|
||||
ASSERT_EQ(2, TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED));
|
||||
EXPECT_EQ(1, TestGetAndResetTickerCount(options, stat));
|
||||
ASSERT_OK(iterator->status());
|
||||
}
|
||||
|
||||
ub = "c1";
|
||||
{
|
||||
std::unique_ptr<Iterator> iterator(db_->NewIterator(ro));
|
||||
iterator->Seek("b1");
|
||||
ASSERT_FALSE(iterator->Valid());
|
||||
EXPECT_EQ(0, TestGetAndResetTickerCount(options, stat));
|
||||
ASSERT_OK(iterator->status());
|
||||
}
|
||||
|
||||
// The same queries without recreating iterator
|
||||
{
|
||||
ub_str = "b9";
|
||||
ub = Slice(ub_str);
|
||||
ro.iterate_upper_bound = &ub;
|
||||
|
||||
std::unique_ptr<Iterator> iterator(db_->NewIterator(ro));
|
||||
|
||||
ub = "b9";
|
||||
iterator->Seek("b1");
|
||||
ASSERT_FALSE(iterator->Valid());
|
||||
ASSERT_EQ(3, TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED));
|
||||
EXPECT_EQ(1, TestGetAndResetTickerCount(options, stat));
|
||||
ASSERT_OK(iterator->status());
|
||||
|
||||
ub_str = "z";
|
||||
ub = Slice(ub_str);
|
||||
|
||||
ub = "z";
|
||||
iterator->Seek("b1");
|
||||
ASSERT_TRUE(iterator->Valid());
|
||||
ASSERT_EQ("x1", iterator->key().ToString());
|
||||
ASSERT_EQ(3, TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED));
|
||||
|
||||
ub_str = "c";
|
||||
ub = Slice(ub_str);
|
||||
EXPECT_EQ(0, TestGetAndResetTickerCount(options, stat));
|
||||
|
||||
ub = "c";
|
||||
iterator->Seek("b1");
|
||||
ASSERT_FALSE(iterator->Valid());
|
||||
ASSERT_EQ(4, TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED));
|
||||
EXPECT_EQ(1, TestGetAndResetTickerCount(options, stat));
|
||||
|
||||
ub_str = "b9";
|
||||
ub = Slice(ub_str);
|
||||
ro.iterate_upper_bound = &ub;
|
||||
ub = "b9";
|
||||
iterator->SeekForPrev("b1");
|
||||
ASSERT_TRUE(iterator->Valid());
|
||||
ASSERT_EQ("a1", iterator->key().ToString());
|
||||
ASSERT_EQ(4, TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED));
|
||||
EXPECT_EQ(0, TestGetAndResetTickerCount(options, stat));
|
||||
|
||||
ub_str = "zz";
|
||||
ub = Slice(ub_str);
|
||||
ro.iterate_upper_bound = &ub;
|
||||
ub = "zz";
|
||||
iterator->SeekToLast();
|
||||
ASSERT_TRUE(iterator->Valid());
|
||||
ASSERT_EQ("y1", iterator->key().ToString());
|
||||
@@ -6440,6 +6463,136 @@ TEST_F(DBTest2, AutoPrefixMode1) {
|
||||
ASSERT_TRUE(iterator->Valid());
|
||||
ASSERT_EQ("a1", iterator->key().ToString());
|
||||
}
|
||||
|
||||
// Similar, now with reverse comparator
|
||||
// Technically, we are violating axiom 2 of prefix_extractors, but
|
||||
// it should be revised because of major use-cases using
|
||||
// ReverseBytewiseComparator with capped/fixed prefix Seek. (FIXME)
|
||||
options.comparator = ReverseBytewiseComparator();
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put("a1", large_value));
|
||||
ASSERT_OK(Put("x1", large_value));
|
||||
ASSERT_OK(Put("y1", large_value));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
{
|
||||
std::unique_ptr<Iterator> iterator(db_->NewIterator(ro));
|
||||
|
||||
ub = "b1";
|
||||
iterator->Seek("b9");
|
||||
ASSERT_FALSE(iterator->Valid());
|
||||
EXPECT_EQ(1, TestGetAndResetTickerCount(options, stat));
|
||||
ASSERT_OK(iterator->status());
|
||||
|
||||
ub = "b1";
|
||||
iterator->Seek("z");
|
||||
ASSERT_TRUE(iterator->Valid());
|
||||
ASSERT_EQ("y1", iterator->key().ToString());
|
||||
EXPECT_EQ(0, TestGetAndResetTickerCount(options, stat));
|
||||
|
||||
ub = "b1";
|
||||
iterator->Seek("c");
|
||||
ASSERT_FALSE(iterator->Valid());
|
||||
EXPECT_EQ(0, TestGetAndResetTickerCount(options, stat));
|
||||
|
||||
ub = "b";
|
||||
iterator->Seek("c9");
|
||||
ASSERT_FALSE(iterator->Valid());
|
||||
// Fails if ReverseBytewiseComparator::IsSameLengthImmediateSuccessor
|
||||
// is "correctly" implemented.
|
||||
EXPECT_EQ(0, TestGetAndResetTickerCount(options, stat));
|
||||
|
||||
ub = "a";
|
||||
iterator->Seek("b9");
|
||||
// Fails if ReverseBytewiseComparator::IsSameLengthImmediateSuccessor
|
||||
// is "correctly" implemented.
|
||||
ASSERT_TRUE(iterator->Valid());
|
||||
ASSERT_EQ("a1", iterator->key().ToString());
|
||||
EXPECT_EQ(0, TestGetAndResetTickerCount(options, stat));
|
||||
|
||||
ub = "b";
|
||||
iterator->Seek("a");
|
||||
ASSERT_FALSE(iterator->Valid());
|
||||
// Fails if ReverseBytewiseComparator::IsSameLengthImmediateSuccessor
|
||||
// matches BytewiseComparator::IsSameLengthImmediateSuccessor. Upper
|
||||
// comparing before seek key prevents a real bug from surfacing.
|
||||
EXPECT_EQ(0, TestGetAndResetTickerCount(options, stat));
|
||||
|
||||
ub = "b1";
|
||||
iterator->SeekForPrev("b9");
|
||||
ASSERT_TRUE(iterator->Valid());
|
||||
// Fails if ReverseBytewiseComparator::IsSameLengthImmediateSuccessor
|
||||
// is "correctly" implemented.
|
||||
ASSERT_EQ("x1", iterator->key().ToString());
|
||||
EXPECT_EQ(0, TestGetAndResetTickerCount(options, stat));
|
||||
|
||||
ub = "a";
|
||||
iterator->SeekToLast();
|
||||
ASSERT_TRUE(iterator->Valid());
|
||||
ASSERT_EQ("a1", iterator->key().ToString());
|
||||
|
||||
iterator->SeekToFirst();
|
||||
ASSERT_TRUE(iterator->Valid());
|
||||
ASSERT_EQ("y1", iterator->key().ToString());
|
||||
}
|
||||
|
||||
// Now something a bit different, related to "short" keys that
|
||||
// auto_prefix_mode can omit. See "BUG" section of auto_prefix_mode.
|
||||
options.comparator = BytewiseComparator();
|
||||
for (const auto config : {"fixed:2", "capped:2"}) {
|
||||
ASSERT_OK(SliceTransform::CreateFromString(ConfigOptions(), config,
|
||||
&options.prefix_extractor));
|
||||
|
||||
// FIXME: kHashSearch, etc. requires all keys be InDomain
|
||||
if (StartsWith(config, "fixed") &&
|
||||
(table_options.index_type == BlockBasedTableOptions::kHashSearch ||
|
||||
StartsWith(options.memtable_factory->Name(), "Hash"))) {
|
||||
continue;
|
||||
}
|
||||
DestroyAndReopen(options);
|
||||
|
||||
const char* a_end_stuff = "a\xffXYZ";
|
||||
const char* b_begin_stuff = "b\x00XYZ";
|
||||
ASSERT_OK(Put("a", large_value));
|
||||
ASSERT_OK(Put("b", large_value));
|
||||
ASSERT_OK(Put(Slice(b_begin_stuff, 3), large_value));
|
||||
ASSERT_OK(Put("c", large_value));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// control showing valid optimization with auto_prefix mode
|
||||
ub = Slice(a_end_stuff, 4);
|
||||
ro.iterate_upper_bound = &ub;
|
||||
|
||||
std::unique_ptr<Iterator> iterator(db_->NewIterator(ro));
|
||||
iterator->Seek(Slice(a_end_stuff, 2));
|
||||
ASSERT_FALSE(iterator->Valid());
|
||||
EXPECT_EQ(1, TestGetAndResetTickerCount(options, stat));
|
||||
ASSERT_OK(iterator->status());
|
||||
|
||||
// test, cannot be validly optimized with auto_prefix_mode
|
||||
ub = Slice(b_begin_stuff, 2);
|
||||
ro.iterate_upper_bound = &ub;
|
||||
|
||||
iterator->Seek(Slice(a_end_stuff, 2));
|
||||
// !!! BUG !!! See "BUG" section of auto_prefix_mode.
|
||||
ASSERT_FALSE(iterator->Valid());
|
||||
EXPECT_EQ(1, TestGetAndResetTickerCount(options, stat));
|
||||
ASSERT_OK(iterator->status());
|
||||
|
||||
// To prove that is the wrong result, now use total order seek
|
||||
ReadOptions tos_ro = ro;
|
||||
tos_ro.total_order_seek = true;
|
||||
tos_ro.auto_prefix_mode = false;
|
||||
iterator.reset(db_->NewIterator(tos_ro));
|
||||
iterator->Seek(Slice(a_end_stuff, 2));
|
||||
ASSERT_TRUE(iterator->Valid());
|
||||
ASSERT_EQ("b", iterator->key().ToString());
|
||||
EXPECT_EQ(0, TestGetAndResetTickerCount(options, stat));
|
||||
ASSERT_OK(iterator->status());
|
||||
}
|
||||
} while (ChangeOptions(kSkipPlainTable));
|
||||
}
|
||||
|
||||
@@ -6882,6 +7035,11 @@ TEST_F(DBTest2, BottommostTemperatureUniversal) {
|
||||
ASSERT_EQ(size, 0);
|
||||
size = GetSstSizeHelper(Temperature::kCold);
|
||||
ASSERT_GT(size, 0);
|
||||
|
||||
// kLastTemperature is an invalid temperature
|
||||
options.bottommost_temperature = Temperature::kLastTemperature;
|
||||
s = TryReopen(options);
|
||||
ASSERT_TRUE(s.IsIOError());
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, LastLevelStatistics) {
|
||||
|
||||
@@ -1744,5 +1744,6 @@ template class TargetCacheChargeTrackingCache<
|
||||
CacheEntryRole::kFilterConstruction>;
|
||||
template class TargetCacheChargeTrackingCache<
|
||||
CacheEntryRole::kBlockBasedTableReader>;
|
||||
template class TargetCacheChargeTrackingCache<CacheEntryRole::kFileMetadata>;
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -588,6 +588,7 @@ class SpecialEnv : public EnvWrapper {
|
||||
~NoopDirectory() {}
|
||||
|
||||
Status Fsync() override { return Status::OK(); }
|
||||
Status Close() override { return Status::OK(); }
|
||||
};
|
||||
|
||||
result->reset(new NoopDirectory());
|
||||
|
||||
@@ -2165,6 +2165,59 @@ TEST_F(DBWALTest, ReadOnlyRecoveryNoTruncate) {
|
||||
#endif // ROCKSDB_FALLOCATE_PRESENT
|
||||
#endif // ROCKSDB_PLATFORM_POSIX
|
||||
|
||||
TEST_F(DBWALTest, WalInManifestButNotInSortedWals) {
|
||||
Options options = CurrentOptions();
|
||||
options.track_and_verify_wals_in_manifest = true;
|
||||
options.wal_recovery_mode = WALRecoveryMode::kAbsoluteConsistency;
|
||||
|
||||
// Build a way to make wal files selectively go missing
|
||||
bool wals_go_missing = false;
|
||||
struct MissingWalFs : public FileSystemWrapper {
|
||||
MissingWalFs(const std::shared_ptr<FileSystem>& t,
|
||||
bool* _wals_go_missing_flag)
|
||||
: FileSystemWrapper(t), wals_go_missing_flag(_wals_go_missing_flag) {}
|
||||
bool* wals_go_missing_flag;
|
||||
IOStatus GetChildren(const std::string& dir, const IOOptions& io_opts,
|
||||
std::vector<std::string>* r,
|
||||
IODebugContext* dbg) override {
|
||||
IOStatus s = target_->GetChildren(dir, io_opts, r, dbg);
|
||||
if (s.ok() && *wals_go_missing_flag) {
|
||||
for (size_t i = 0; i < r->size();) {
|
||||
if (EndsWith(r->at(i), ".log")) {
|
||||
r->erase(r->begin() + i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
const char* Name() const override { return "MissingWalFs"; }
|
||||
};
|
||||
auto my_fs =
|
||||
std::make_shared<MissingWalFs>(env_->GetFileSystem(), &wals_go_missing);
|
||||
std::unique_ptr<Env> my_env(NewCompositeEnv(my_fs));
|
||||
options.env = my_env.get();
|
||||
|
||||
CreateAndReopenWithCF({"blah"}, options);
|
||||
|
||||
// Currently necessary to get a WAL tracked in manifest; see
|
||||
// https://github.com/facebook/rocksdb/issues/10080
|
||||
ASSERT_OK(Put(0, "x", "y"));
|
||||
ASSERT_OK(db_->SyncWAL());
|
||||
ASSERT_OK(Put(1, "x", "y"));
|
||||
ASSERT_OK(db_->SyncWAL());
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
ASSERT_FALSE(dbfull()->GetVersionSet()->GetWalSet().GetWals().empty());
|
||||
std::vector<std::unique_ptr<LogFile>> wals;
|
||||
ASSERT_OK(db_->GetSortedWalFiles(wals));
|
||||
wals_go_missing = true;
|
||||
ASSERT_NOK(db_->GetSortedWalFiles(wals));
|
||||
wals_go_missing = false;
|
||||
Close();
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBWALTest, WalTermTest) {
|
||||
@@ -2190,6 +2243,43 @@ TEST_F(DBWALTest, WalTermTest) {
|
||||
ASSERT_EQ("bar", Get(1, "foo"));
|
||||
ASSERT_EQ("NOT_FOUND", Get(1, "foo2"));
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBWALTest, GetCompressedWalsAfterSync) {
|
||||
if (db_->GetOptions().wal_compression == kNoCompression) {
|
||||
ROCKSDB_GTEST_BYPASS("stream compression not present");
|
||||
return;
|
||||
}
|
||||
Options options = GetDefaultOptions();
|
||||
options.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery;
|
||||
options.create_if_missing = true;
|
||||
options.env = env_;
|
||||
options.avoid_flush_during_recovery = true;
|
||||
options.track_and_verify_wals_in_manifest = true;
|
||||
// Enable WAL compression so that the newly-created WAL will be non-empty
|
||||
// after DB open, even if point-in-time WAL recovery encounters no
|
||||
// corruption.
|
||||
options.wal_compression = kZSTD;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Write something to memtable and WAL so that log_empty_ will be false after
|
||||
// next DB::Open().
|
||||
ASSERT_OK(Put("a", "v"));
|
||||
|
||||
Reopen(options);
|
||||
|
||||
// New WAL is created, thanks to !log_empty_.
|
||||
ASSERT_OK(dbfull()->TEST_SwitchWAL());
|
||||
|
||||
ASSERT_OK(Put("b", "v"));
|
||||
|
||||
ASSERT_OK(db_->SyncWAL());
|
||||
|
||||
VectorLogPtr wals;
|
||||
Status s = dbfull()->GetSortedWalFiles(wals);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -230,6 +230,10 @@ TEST_F(DBBasicTestWithTimestamp, GcPreserveLatestVersionBelowFullHistoryLow) {
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ("v1", value);
|
||||
|
||||
std::string key_ts;
|
||||
ASSERT_TRUE(db_->Get(ropts, "k3", &value, &key_ts).IsNotFound());
|
||||
ASSERT_EQ(Timestamp(2, 0), key_ts);
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
@@ -268,7 +272,6 @@ TEST_F(DBBasicTestWithTimestamp, UpdateFullHistoryTsLow) {
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// TODO return a non-ok for read ts < current_ts_low and test it.
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ReadOptions read_opts;
|
||||
std::string ts_str = Timestamp(i, 0);
|
||||
@@ -276,8 +279,8 @@ TEST_F(DBBasicTestWithTimestamp, UpdateFullHistoryTsLow) {
|
||||
read_opts.timestamp = &ts;
|
||||
std::string value;
|
||||
Status status = db_->Get(read_opts, kKey, &value);
|
||||
if (i < current_ts_low - 1) {
|
||||
ASSERT_TRUE(status.IsNotFound());
|
||||
if (i < current_ts_low) {
|
||||
ASSERT_TRUE(status.IsInvalidArgument());
|
||||
} else {
|
||||
ASSERT_OK(status);
|
||||
ASSERT_TRUE(value.compare(Key(i)) == 0);
|
||||
@@ -301,7 +304,6 @@ TEST_F(DBBasicTestWithTimestamp, UpdateFullHistoryTsLow) {
|
||||
result_ts_low = cfd->GetFullHistoryTsLow();
|
||||
ASSERT_TRUE(test_cmp.CompareTimestamp(ts_low, result_ts_low) == 0);
|
||||
|
||||
// TODO return a non-ok for read ts < current_ts_low and test it.
|
||||
for (int i = current_ts_low; i < 20; i++) {
|
||||
ReadOptions read_opts;
|
||||
std::string ts_str = Timestamp(i, 0);
|
||||
@@ -543,23 +545,29 @@ TEST_F(DBBasicTestWithTimestamp, TrimHistoryTest) {
|
||||
options.comparator = &test_cmp;
|
||||
DestroyAndReopen(options);
|
||||
auto check_value_by_ts = [](DB* db, Slice key, std::string readTs,
|
||||
Status status, std::string checkValue) {
|
||||
Status status, std::string checkValue,
|
||||
std::string expected_ts) {
|
||||
ReadOptions ropts;
|
||||
Slice ts = readTs;
|
||||
ropts.timestamp = &ts;
|
||||
std::string value;
|
||||
Status s = db->Get(ropts, key, &value);
|
||||
std::string key_ts;
|
||||
Status s = db->Get(ropts, key, &value, &key_ts);
|
||||
ASSERT_TRUE(s == status);
|
||||
if (s.ok()) {
|
||||
ASSERT_EQ(checkValue, value);
|
||||
}
|
||||
if (s.ok() || s.IsNotFound()) {
|
||||
ASSERT_EQ(expected_ts, key_ts);
|
||||
}
|
||||
};
|
||||
// Construct data of different versions with different ts
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "k1", Timestamp(2, 0), "v1"));
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "k1", Timestamp(4, 0), "v2"));
|
||||
ASSERT_OK(db_->Delete(WriteOptions(), "k1", Timestamp(5, 0)));
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "k1", Timestamp(6, 0), "v3"));
|
||||
check_value_by_ts(db_, "k1", Timestamp(7, 0), Status::OK(), "v3");
|
||||
check_value_by_ts(db_, "k1", Timestamp(7, 0), Status::OK(), "v3",
|
||||
Timestamp(6, 0));
|
||||
ASSERT_OK(Flush());
|
||||
Close();
|
||||
|
||||
@@ -572,13 +580,15 @@ TEST_F(DBBasicTestWithTimestamp, TrimHistoryTest) {
|
||||
// Trim data whose version > Timestamp(5, 0), read(k1, ts(7)) <- NOT_FOUND.
|
||||
ASSERT_OK(DB::OpenAndTrimHistory(db_options, dbname_, column_families,
|
||||
&handles_, &db_, Timestamp(5, 0)));
|
||||
check_value_by_ts(db_, "k1", Timestamp(7, 0), Status::NotFound(), "");
|
||||
check_value_by_ts(db_, "k1", Timestamp(7, 0), Status::NotFound(), "",
|
||||
Timestamp(5, 0));
|
||||
Close();
|
||||
|
||||
// Trim data whose timestamp > Timestamp(4, 0), read(k1, ts(7)) <- v2
|
||||
ASSERT_OK(DB::OpenAndTrimHistory(db_options, dbname_, column_families,
|
||||
&handles_, &db_, Timestamp(4, 0)));
|
||||
check_value_by_ts(db_, "k1", Timestamp(7, 0), Status::OK(), "v2");
|
||||
check_value_by_ts(db_, "k1", Timestamp(7, 0), Status::OK(), "v2",
|
||||
Timestamp(4, 0));
|
||||
Close();
|
||||
}
|
||||
|
||||
@@ -1210,12 +1220,27 @@ TEST_F(DBBasicTestWithTimestamp, MultiGetWithFastLocalBloom) {
|
||||
std::vector<Slice> keys(batch_size);
|
||||
std::vector<PinnableSlice> values(batch_size);
|
||||
std::vector<Status> statuses(batch_size);
|
||||
std::vector<std::string> timestamps(batch_size);
|
||||
keys[0] = "foo";
|
||||
ColumnFamilyHandle* cfh = db_->DefaultColumnFamily();
|
||||
db_->MultiGet(read_opts, cfh, batch_size, keys.data(), values.data(),
|
||||
statuses.data());
|
||||
timestamps.data(), statuses.data(), true);
|
||||
|
||||
ASSERT_OK(statuses[0]);
|
||||
ASSERT_EQ(Timestamp(1, 0), timestamps[0]);
|
||||
for (auto& elem : values) {
|
||||
elem.Reset();
|
||||
}
|
||||
|
||||
ASSERT_OK(db_->SingleDelete(WriteOptions(), "foo", Timestamp(2, 0)));
|
||||
ts = Timestamp(3, 0);
|
||||
read_ts = ts;
|
||||
read_opts.timestamp = &read_ts;
|
||||
db_->MultiGet(read_opts, cfh, batch_size, keys.data(), values.data(),
|
||||
timestamps.data(), statuses.data(), true);
|
||||
ASSERT_TRUE(statuses[0].IsNotFound());
|
||||
ASSERT_EQ(Timestamp(2, 0), timestamps[0]);
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
@@ -1251,12 +1276,31 @@ TEST_P(DBBasicTestWithTimestampTableOptions, MultiGetWithPrefix) {
|
||||
std::vector<Slice> keys(batch_size);
|
||||
std::vector<PinnableSlice> values(batch_size);
|
||||
std::vector<Status> statuses(batch_size);
|
||||
std::vector<std::string> timestamps(batch_size);
|
||||
keys[0] = "foo";
|
||||
ColumnFamilyHandle* cfh = db_->DefaultColumnFamily();
|
||||
db_->MultiGet(read_opts, cfh, batch_size, keys.data(), values.data(),
|
||||
statuses.data());
|
||||
timestamps.data(), statuses.data(), true);
|
||||
|
||||
ASSERT_OK(statuses[0]);
|
||||
ASSERT_EQ(Timestamp(1, 0), timestamps[0]);
|
||||
for (auto& elem : values) {
|
||||
elem.Reset();
|
||||
}
|
||||
|
||||
ASSERT_OK(db_->SingleDelete(WriteOptions(), "foo", Timestamp(2, 0)));
|
||||
// TODO re-enable after fixing a bug of kHashSearch
|
||||
if (GetParam() != BlockBasedTableOptions::IndexType::kHashSearch) {
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
ts = Timestamp(3, 0);
|
||||
read_ts = ts;
|
||||
db_->MultiGet(read_opts, cfh, batch_size, keys.data(), values.data(),
|
||||
timestamps.data(), statuses.data(), true);
|
||||
ASSERT_TRUE(statuses[0].IsNotFound());
|
||||
ASSERT_EQ(Timestamp(2, 0), timestamps[0]);
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
@@ -1507,8 +1551,10 @@ TEST_F(DBBasicTestWithTimestamp, CompactDeletionWithTimestampMarkerToBottom) {
|
||||
ts = Timestamp(3, 0);
|
||||
read_ts = ts;
|
||||
read_opts.timestamp = &read_ts;
|
||||
s = db_->Get(read_opts, "a", &value);
|
||||
std::string key_ts;
|
||||
s = db_->Get(read_opts, "a", &value, &key_ts);
|
||||
ASSERT_TRUE(s.IsNotFound());
|
||||
ASSERT_EQ(Timestamp(3, 0), key_ts);
|
||||
|
||||
// Time-travel to the past before deletion
|
||||
ts = Timestamp(2, 0);
|
||||
@@ -1613,9 +1659,8 @@ TEST_P(DBBasicTestWithTimestampFilterPrefixSettings, GetAndMultiGet) {
|
||||
std::unique_ptr<Iterator> it1(db_->NewIterator(read_opts));
|
||||
ASSERT_NE(nullptr, it1);
|
||||
ASSERT_OK(it1->status());
|
||||
// TODO(zjay) Fix seek with prefix
|
||||
// it1->Seek(keys[i]);
|
||||
// ASSERT_TRUE(it1->Valid());
|
||||
it1->Seek(keys[i]);
|
||||
ASSERT_TRUE(it1->Valid());
|
||||
}
|
||||
|
||||
for (int i = 2; i < 4; i++) {
|
||||
@@ -2400,12 +2445,18 @@ TEST_P(DBBasicTestWithTimestampCompressionSettings, PutDeleteGet) {
|
||||
ropts.timestamp = &ts;
|
||||
for (uint64_t i = 0; i != static_cast<uint64_t>(kNumKeysPerFile); ++i) {
|
||||
std::string value;
|
||||
Status s = db_->Get(ropts, Key1(i), &value);
|
||||
std::string key_ts;
|
||||
Status s = db_->Get(ropts, Key1(i), &value, &key_ts);
|
||||
if ((i % 3) == 2) {
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ("new_value_2", value);
|
||||
ASSERT_EQ(Timestamp(5, 0), key_ts);
|
||||
} else if ((i % 3) == 1) {
|
||||
ASSERT_TRUE(s.IsNotFound());
|
||||
ASSERT_EQ(Timestamp(5, 0), key_ts);
|
||||
} else {
|
||||
ASSERT_TRUE(s.IsNotFound());
|
||||
ASSERT_EQ(Timestamp(3, 0), key_ts);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2971,6 +3022,48 @@ INSTANTIATE_TEST_CASE_P(
|
||||
BlockBasedTableOptions::IndexType::kBinarySearchWithFirstKey)));
|
||||
#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
|
||||
class UpdateFullHistoryTsLowTest : public DBBasicTestWithTimestampBase {
|
||||
public:
|
||||
UpdateFullHistoryTsLowTest()
|
||||
: DBBasicTestWithTimestampBase("/update_full_history_ts_low_test") {}
|
||||
};
|
||||
|
||||
TEST_F(UpdateFullHistoryTsLowTest, ConcurrentUpdate) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
std::string lower_ts_low = Timestamp(10, 0);
|
||||
std::string higher_ts_low = Timestamp(25, 0);
|
||||
const size_t kTimestampSize = lower_ts_low.size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
// This workaround swaps `lower_ts_low` originally used for update by the
|
||||
// caller to `higher_ts_low` after its writer is queued to make sure
|
||||
// the caller will always get a TryAgain error.
|
||||
// It mimics cases where two threads update full_history_ts_low concurrently
|
||||
// with one thread writing a higher ts_low and one thread writing a lower
|
||||
// ts_low.
|
||||
VersionEdit* version_edit;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::IncreaseFullHistoryTsLowImpl:BeforeEdit",
|
||||
[&](void* arg) { version_edit = reinterpret_cast<VersionEdit*>(arg); });
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::LogAndApply:BeforeWriterWaiting",
|
||||
[&](void* /*arg*/) { version_edit->SetFullHistoryTsLow(higher_ts_low); });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
ASSERT_TRUE(
|
||||
db_->IncreaseFullHistoryTsLow(db_->DefaultColumnFamily(), lower_ts_low)
|
||||
.IsTryAgain());
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -334,6 +334,41 @@ TEST_P(DBWriteTest, ManualWalFlushInEffect) {
|
||||
ASSERT_TRUE(dbfull()->TEST_WALBufferIsEmpty());
|
||||
}
|
||||
|
||||
TEST_P(DBWriteTest, UnflushedPutRaceWithTrackedWalSync) {
|
||||
// Repro race condition bug where unflushed WAL data extended the synced size
|
||||
// recorded to MANIFEST despite being unrecoverable.
|
||||
Options options = GetOptions();
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(env_));
|
||||
options.env = fault_env.get();
|
||||
options.manual_wal_flush = true;
|
||||
options.track_and_verify_wals_in_manifest = true;
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(Put("key1", "val1"));
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::SyncWAL:Begin",
|
||||
[this](void* /* arg */) { ASSERT_OK(Put("key2", "val2")); });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(db_->FlushWAL(true /* sync */));
|
||||
|
||||
// Ensure callback ran.
|
||||
ASSERT_EQ("val2", Get("key2"));
|
||||
|
||||
Close();
|
||||
|
||||
// Simulate full loss of unsynced data. This drops "key2" -> "val2" from the
|
||||
// DB WAL.
|
||||
fault_env->DropUnsyncedFileData();
|
||||
|
||||
Reopen(options);
|
||||
|
||||
// Need to close before `fault_env` goes out of scope.
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_P(DBWriteTest, IOErrorOnWALWriteTriggersReadOnlyMode) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> mock_env(
|
||||
new FaultInjectionTestEnv(env_));
|
||||
|
||||
+2
-2
@@ -320,7 +320,7 @@ class InternalKey {
|
||||
}
|
||||
|
||||
Slice user_key() const { return ExtractUserKey(rep_); }
|
||||
size_t size() { return rep_.size(); }
|
||||
size_t size() const { return rep_.size(); }
|
||||
|
||||
void Set(const Slice& _user_key, SequenceNumber s, ValueType t) {
|
||||
SetFrom(ParsedInternalKey(_user_key, s, t));
|
||||
@@ -625,7 +625,7 @@ class IterKey {
|
||||
};
|
||||
|
||||
// Convert from a SliceTransform of user keys, to a SliceTransform of
|
||||
// user keys.
|
||||
// internal keys.
|
||||
class InternalKeySliceTransform : public SliceTransform {
|
||||
public:
|
||||
explicit InternalKeySliceTransform(const SliceTransform* transform)
|
||||
|
||||
@@ -1923,6 +1923,60 @@ TEST_F(ExternalSSTFileBasicTest, VerifySstUniqueId) {
|
||||
ASSERT_EQ(skipped, 1);
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileBasicTest, StableSnapshotWhileLoggingToManifest) {
|
||||
const std::string kPutVal = "put_val";
|
||||
const std::string kIngestedVal = "ingested_val";
|
||||
|
||||
ASSERT_OK(Put("k", kPutVal, WriteOptions()));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
std::string external_file = sst_files_dir_ + "/file_to_ingest.sst";
|
||||
{
|
||||
SstFileWriter sst_file_writer{EnvOptions(), CurrentOptions()};
|
||||
ASSERT_OK(sst_file_writer.Open(external_file));
|
||||
ASSERT_OK(sst_file_writer.Put("k", kIngestedVal));
|
||||
ASSERT_OK(sst_file_writer.Finish());
|
||||
}
|
||||
|
||||
const Snapshot* snapshot = nullptr;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::LogAndApply:WriteManifest", [&](void* /* arg */) {
|
||||
// prevent background compaction job to call this callback
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
snapshot = db_->GetSnapshot();
|
||||
ReadOptions read_opts;
|
||||
read_opts.snapshot = snapshot;
|
||||
std::string value;
|
||||
ASSERT_OK(db_->Get(read_opts, "k", &value));
|
||||
ASSERT_EQ(kPutVal, value);
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(db_->IngestExternalFile(db_->DefaultColumnFamily(), {external_file},
|
||||
IngestExternalFileOptions()));
|
||||
auto ingested_file_seqno = db_->GetLatestSequenceNumber();
|
||||
ASSERT_NE(nullptr, snapshot);
|
||||
// snapshot is taken before SST ingestion is done
|
||||
ASSERT_EQ(ingested_file_seqno, snapshot->GetSequenceNumber() + 1);
|
||||
|
||||
ReadOptions read_opts;
|
||||
read_opts.snapshot = snapshot;
|
||||
std::string value;
|
||||
ASSERT_OK(db_->Get(read_opts, "k", &value));
|
||||
ASSERT_EQ(kPutVal, value);
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
|
||||
// After reopen, sequence number should be up current such that
|
||||
// ingested value is read
|
||||
Reopen(CurrentOptions());
|
||||
ASSERT_OK(db_->Get(ReadOptions(), "k", &value));
|
||||
ASSERT_EQ(kIngestedVal, value);
|
||||
|
||||
// New write should get higher seqno compared to ingested file
|
||||
ASSERT_OK(Put("k", kPutVal, WriteOptions()));
|
||||
ASSERT_EQ(db_->GetLatestSequenceNumber(), ingested_file_seqno + 1);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(ExternalSSTFileBasicTest, ExternalSSTFileBasicTest,
|
||||
testing::Values(std::make_tuple(true, true),
|
||||
std::make_tuple(true, false),
|
||||
|
||||
+3
-3
@@ -456,6 +456,7 @@ Status FlushJob::MemPurge() {
|
||||
snapshot_checker_);
|
||||
assert(job_context_);
|
||||
SequenceNumber job_snapshot_seq = job_context_->GetJobSnapshotSequence();
|
||||
const std::atomic<bool> kManualCompactionCanceledFalse{false};
|
||||
CompactionIterator c_iter(
|
||||
iter.get(), (cfd_->internal_comparator()).user_comparator(), &merge,
|
||||
kMaxSequenceNumber, &existing_snapshots_,
|
||||
@@ -464,10 +465,9 @@ Status FlushJob::MemPurge() {
|
||||
true /* internal key corruption is not ok */, range_del_agg.get(),
|
||||
nullptr, ioptions->allow_data_in_errors,
|
||||
ioptions->enforce_single_del_contracts,
|
||||
/*manual_compaction_canceled=*/kManualCompactionCanceledFalse,
|
||||
/*compaction=*/nullptr, compaction_filter.get(),
|
||||
/*shutting_down=*/nullptr,
|
||||
/*manual_compaction_paused=*/nullptr,
|
||||
/*manual_compaction_canceled=*/nullptr, ioptions->info_log,
|
||||
/*shutting_down=*/nullptr, ioptions->info_log,
|
||||
&(cfd_->GetFullHistoryTsLow()));
|
||||
|
||||
// Set earliest sequence number in the new memtable
|
||||
|
||||
+22
-4
@@ -303,8 +303,14 @@ void Reader::UnmarkEOFInternal() {
|
||||
}
|
||||
|
||||
Slice read_buffer;
|
||||
Status status = file_->Read(remaining, &read_buffer,
|
||||
backing_store_ + eof_offset_);
|
||||
// TODO: rate limit log reader with approriate priority.
|
||||
// TODO: avoid overcharging rate limiter:
|
||||
// Note that the Read here might overcharge SequentialFileReader's internal
|
||||
// rate limiter if priority is not IO_TOTAL, e.g., when there is not enough
|
||||
// content left until EOF to read.
|
||||
Status status =
|
||||
file_->Read(remaining, &read_buffer, backing_store_ + eof_offset_,
|
||||
Env::IO_TOTAL /* rate_limiter_priority */);
|
||||
|
||||
size_t added = read_buffer.size();
|
||||
end_of_buffer_offset_ += added;
|
||||
@@ -349,7 +355,13 @@ bool Reader::ReadMore(size_t* drop_size, int *error) {
|
||||
if (!eof_ && !read_error_) {
|
||||
// Last read was a full read, so this is a trailer to skip
|
||||
buffer_.clear();
|
||||
Status status = file_->Read(kBlockSize, &buffer_, backing_store_);
|
||||
// TODO: rate limit log reader with approriate priority.
|
||||
// TODO: avoid overcharging rate limiter:
|
||||
// Note that the Read here might overcharge SequentialFileReader's internal
|
||||
// rate limiter if priority is not IO_TOTAL, e.g., when there is not enough
|
||||
// content left until EOF to read.
|
||||
Status status = file_->Read(kBlockSize, &buffer_, backing_store_,
|
||||
Env::IO_TOTAL /* rate_limiter_priority */);
|
||||
TEST_SYNC_POINT_CALLBACK("LogReader::ReadMore:AfterReadFile", &status);
|
||||
end_of_buffer_offset_ += buffer_.size();
|
||||
if (!status.ok()) {
|
||||
@@ -639,7 +651,13 @@ bool FragmentBufferedReader::TryReadMore(size_t* drop_size, int* error) {
|
||||
if (!eof_ && !read_error_) {
|
||||
// Last read was a full read, so this is a trailer to skip
|
||||
buffer_.clear();
|
||||
Status status = file_->Read(kBlockSize, &buffer_, backing_store_);
|
||||
// TODO: rate limit log reader with approriate priority.
|
||||
// TODO: avoid overcharging rate limiter:
|
||||
// Note that the Read here might overcharge SequentialFileReader's internal
|
||||
// rate limiter if priority is not IO_TOTAL, e.g., when there is not enough
|
||||
// content left until EOF to read.
|
||||
Status status = file_->Read(kBlockSize, &buffer_, backing_store_,
|
||||
Env::IO_TOTAL /* rate_limiter_priority */);
|
||||
end_of_buffer_offset_ += buffer_.size();
|
||||
if (!status.ok()) {
|
||||
buffer_.clear();
|
||||
|
||||
@@ -103,6 +103,10 @@ class Reader {
|
||||
return static_cast<size_t>(end_of_buffer_offset_);
|
||||
}
|
||||
|
||||
bool IsCompressedAndEmptyFile() {
|
||||
return !first_record_read_ && compression_type_record_read_;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::shared_ptr<Logger> info_log_;
|
||||
const std::unique_ptr<SequentialFileReader> file_;
|
||||
|
||||
@@ -813,6 +813,10 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
}
|
||||
} else {
|
||||
*(s->status) = Status::NotFound();
|
||||
if (ts_sz > 0 && s->timestamp != nullptr) {
|
||||
Slice ts = ExtractTimestampFromUserKey(user_key_slice, ts_sz);
|
||||
s->timestamp->assign(ts.data(), ts.size());
|
||||
}
|
||||
}
|
||||
*(s->found_final_value) = true;
|
||||
return false;
|
||||
@@ -1155,6 +1159,7 @@ Status MemTable::UpdateCallback(SequenceNumber seq, const Slice& key,
|
||||
if (VarintLength(new_prev_size) < VarintLength(prev_size)) {
|
||||
// shift the value buffer as well.
|
||||
memcpy(p, prev_buffer, new_prev_size);
|
||||
prev_buffer = p;
|
||||
}
|
||||
}
|
||||
RecordTick(moptions_.statistics, NUMBER_KEYS_UPDATED);
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/types.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// Callback invoked after finishing writing to the memtable but before
|
||||
// publishing the sequence number to readers.
|
||||
// Note that with write-prepared/write-unprepared transactions with
|
||||
// two-write-queues, PreReleaseCallback is called before publishing the
|
||||
// sequence numbers to readers.
|
||||
class PostMemTableCallback {
|
||||
public:
|
||||
virtual ~PostMemTableCallback() {}
|
||||
|
||||
virtual Status operator()(SequenceNumber seq, bool disable_memtable) = 0;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+1
-1
@@ -358,7 +358,7 @@ class Repairer {
|
||||
std::unique_ptr<SequentialFileReader> lfile_reader;
|
||||
Status status = SequentialFileReader::Create(
|
||||
fs, logname, fs->OptimizeForLogRead(file_options_), &lfile_reader,
|
||||
nullptr);
|
||||
nullptr /* dbg */, nullptr /* rate limiter */);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
+3
-2
@@ -16,6 +16,7 @@
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/transaction_log.h"
|
||||
#include "table/unique_id_impl.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -49,8 +50,8 @@ class RepairTest : public DBTestBase {
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"Version::VerifySstUniqueIds::Passed", [&](void* arg) {
|
||||
// override job status
|
||||
auto id = static_cast<std::string*>(arg);
|
||||
assert(!id->empty());
|
||||
auto id = static_cast<UniqueId64x2*>(arg);
|
||||
assert(*id != kNullUniqueId64x2);
|
||||
verify_passed++;
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
+68
-3
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "util/autovector.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -27,7 +28,11 @@ class SnapshotImpl : public Snapshot {
|
||||
// scope of queries to IsInSnapshot.
|
||||
SequenceNumber min_uncommitted_ = kMinUnCommittedSeq;
|
||||
|
||||
virtual SequenceNumber GetSequenceNumber() const override { return number_; }
|
||||
SequenceNumber GetSequenceNumber() const override { return number_; }
|
||||
|
||||
int64_t GetUnixTime() const override { return unix_time_; }
|
||||
|
||||
uint64_t GetTimestamp() const override { return timestamp_; }
|
||||
|
||||
private:
|
||||
friend class SnapshotList;
|
||||
@@ -40,6 +45,8 @@ class SnapshotImpl : public Snapshot {
|
||||
|
||||
int64_t unix_time_;
|
||||
|
||||
uint64_t timestamp_;
|
||||
|
||||
// Will this snapshot be used by a Transaction to do write-conflict checking?
|
||||
bool is_write_conflict_boundary_;
|
||||
};
|
||||
@@ -53,6 +60,7 @@ class SnapshotList {
|
||||
// Set all the variables to make UBSAN happy.
|
||||
list_.list_ = nullptr;
|
||||
list_.unix_time_ = 0;
|
||||
list_.timestamp_ = 0;
|
||||
list_.is_write_conflict_boundary_ = false;
|
||||
count_ = 0;
|
||||
}
|
||||
@@ -60,14 +68,19 @@ class SnapshotList {
|
||||
// No copy-construct.
|
||||
SnapshotList(const SnapshotList&) = delete;
|
||||
|
||||
bool empty() const { return list_.next_ == &list_; }
|
||||
bool empty() const {
|
||||
assert(list_.next_ != &list_ || 0 == count_);
|
||||
return list_.next_ == &list_;
|
||||
}
|
||||
SnapshotImpl* oldest() const { assert(!empty()); return list_.next_; }
|
||||
SnapshotImpl* newest() const { assert(!empty()); return list_.prev_; }
|
||||
|
||||
SnapshotImpl* New(SnapshotImpl* s, SequenceNumber seq, uint64_t unix_time,
|
||||
bool is_write_conflict_boundary) {
|
||||
bool is_write_conflict_boundary,
|
||||
uint64_t ts = std::numeric_limits<uint64_t>::max()) {
|
||||
s->number_ = seq;
|
||||
s->unix_time_ = unix_time;
|
||||
s->timestamp_ = ts;
|
||||
s->is_write_conflict_boundary_ = is_write_conflict_boundary;
|
||||
s->list_ = this;
|
||||
s->next_ = &list_;
|
||||
@@ -165,4 +178,56 @@ class SnapshotList {
|
||||
uint64_t count_;
|
||||
};
|
||||
|
||||
// All operations on TimestampedSnapshotList must be protected by db mutex.
|
||||
class TimestampedSnapshotList {
|
||||
public:
|
||||
explicit TimestampedSnapshotList() = default;
|
||||
|
||||
std::shared_ptr<const SnapshotImpl> GetSnapshot(uint64_t ts) const {
|
||||
if (ts == std::numeric_limits<uint64_t>::max() && !snapshots_.empty()) {
|
||||
auto it = snapshots_.rbegin();
|
||||
assert(it != snapshots_.rend());
|
||||
return it->second;
|
||||
}
|
||||
auto it = snapshots_.find(ts);
|
||||
if (it == snapshots_.end()) {
|
||||
return std::shared_ptr<const SnapshotImpl>();
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void GetSnapshots(
|
||||
uint64_t ts_lb, uint64_t ts_ub,
|
||||
std::vector<std::shared_ptr<const Snapshot>>& snapshots) const {
|
||||
assert(ts_lb < ts_ub);
|
||||
auto it_low = snapshots_.lower_bound(ts_lb);
|
||||
auto it_high = snapshots_.lower_bound(ts_ub);
|
||||
for (auto it = it_low; it != it_high; ++it) {
|
||||
snapshots.emplace_back(it->second);
|
||||
}
|
||||
}
|
||||
|
||||
void AddSnapshot(const std::shared_ptr<const SnapshotImpl>& snapshot) {
|
||||
assert(snapshot);
|
||||
snapshots_.try_emplace(snapshot->GetTimestamp(), snapshot);
|
||||
}
|
||||
|
||||
// snapshots_to_release: the container to where the timestamped snapshots will
|
||||
// be moved so that it retains the last reference to the snapshots and the
|
||||
// snapshots won't be actually released which requires db mutex. The
|
||||
// snapshots will be released by caller of ReleaseSnapshotsOlderThan().
|
||||
void ReleaseSnapshotsOlderThan(
|
||||
uint64_t ts,
|
||||
autovector<std::shared_ptr<const SnapshotImpl>>& snapshots_to_release) {
|
||||
auto ub = snapshots_.lower_bound(ts);
|
||||
for (auto it = snapshots_.begin(); it != ub; ++it) {
|
||||
snapshots_to_release.emplace_back(it->second);
|
||||
}
|
||||
snapshots_.erase(snapshots_.begin(), ub);
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<uint64_t, std::shared_ptr<const SnapshotImpl>> snapshots_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -67,8 +67,9 @@ Status TransactionLogIteratorImpl::OpenLogFile(
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
file_reader->reset(new SequentialFileReader(
|
||||
std::move(file), fname, io_tracer_, options_->listeners));
|
||||
file_reader->reset(new SequentialFileReader(std::move(file), fname,
|
||||
io_tracer_, options_->listeners,
|
||||
options_->rate_limiter.get()));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
+39
-10
@@ -23,6 +23,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "cache/cache_reservation_manager.h"
|
||||
#include "db/blob/blob_file_meta.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/internal_stats.h"
|
||||
@@ -255,10 +256,13 @@ class VersionBuilder::Rep {
|
||||
// version edits.
|
||||
std::map<uint64_t, MutableBlobFileMetaData> mutable_blob_file_metas_;
|
||||
|
||||
std::shared_ptr<CacheReservationManager> file_metadata_cache_res_mgr_;
|
||||
|
||||
public:
|
||||
Rep(const FileOptions& file_options, const ImmutableCFOptions* ioptions,
|
||||
TableCache* table_cache, VersionStorageInfo* base_vstorage,
|
||||
VersionSet* version_set)
|
||||
VersionSet* version_set,
|
||||
std::shared_ptr<CacheReservationManager> file_metadata_cache_res_mgr)
|
||||
: file_options_(file_options),
|
||||
ioptions_(ioptions),
|
||||
table_cache_(table_cache),
|
||||
@@ -266,7 +270,8 @@ class VersionBuilder::Rep {
|
||||
version_set_(version_set),
|
||||
num_levels_(base_vstorage->num_levels()),
|
||||
has_invalid_levels_(false),
|
||||
level_nonzero_cmp_(base_vstorage_->InternalComparator()) {
|
||||
level_nonzero_cmp_(base_vstorage_->InternalComparator()),
|
||||
file_metadata_cache_res_mgr_(file_metadata_cache_res_mgr) {
|
||||
assert(ioptions_);
|
||||
|
||||
levels_ = new LevelState[num_levels_];
|
||||
@@ -291,6 +296,12 @@ class VersionBuilder::Rep {
|
||||
table_cache_->ReleaseHandle(f->table_reader_handle);
|
||||
f->table_reader_handle = nullptr;
|
||||
}
|
||||
|
||||
if (file_metadata_cache_res_mgr_) {
|
||||
Status s = file_metadata_cache_res_mgr_->UpdateCacheReservation(
|
||||
f->ApproximateMemoryUsage(), false /* increase */);
|
||||
s.PermitUncheckedError();
|
||||
}
|
||||
delete f;
|
||||
}
|
||||
}
|
||||
@@ -763,6 +774,22 @@ class VersionBuilder::Rep {
|
||||
FileMetaData* const f = new FileMetaData(meta);
|
||||
f->refs = 1;
|
||||
|
||||
if (file_metadata_cache_res_mgr_) {
|
||||
Status s = file_metadata_cache_res_mgr_->UpdateCacheReservation(
|
||||
f->ApproximateMemoryUsage(), true /* increase */);
|
||||
if (!s.ok()) {
|
||||
delete f;
|
||||
s = Status::MemoryLimit(
|
||||
"Can't allocate " +
|
||||
kCacheEntryRoleToCamelString[static_cast<std::uint32_t>(
|
||||
CacheEntryRole::kFileMetadata)] +
|
||||
" due to exceeding the memory limit "
|
||||
"based on "
|
||||
"cache capacity");
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
auto& add_files = level_state.added_files;
|
||||
assert(add_files.find(file_number) == add_files.end());
|
||||
add_files.emplace(file_number, f);
|
||||
@@ -1239,13 +1266,13 @@ class VersionBuilder::Rep {
|
||||
}
|
||||
};
|
||||
|
||||
VersionBuilder::VersionBuilder(const FileOptions& file_options,
|
||||
const ImmutableCFOptions* ioptions,
|
||||
TableCache* table_cache,
|
||||
VersionStorageInfo* base_vstorage,
|
||||
VersionSet* version_set)
|
||||
VersionBuilder::VersionBuilder(
|
||||
const FileOptions& file_options, const ImmutableCFOptions* ioptions,
|
||||
TableCache* table_cache, VersionStorageInfo* base_vstorage,
|
||||
VersionSet* version_set,
|
||||
std::shared_ptr<CacheReservationManager> file_metadata_cache_res_mgr)
|
||||
: rep_(new Rep(file_options, ioptions, table_cache, base_vstorage,
|
||||
version_set)) {}
|
||||
version_set, file_metadata_cache_res_mgr)) {}
|
||||
|
||||
VersionBuilder::~VersionBuilder() = default;
|
||||
|
||||
@@ -1280,7 +1307,8 @@ BaseReferencedVersionBuilder::BaseReferencedVersionBuilder(
|
||||
: version_builder_(new VersionBuilder(
|
||||
cfd->current()->version_set()->file_options(), cfd->ioptions(),
|
||||
cfd->table_cache(), cfd->current()->storage_info(),
|
||||
cfd->current()->version_set())),
|
||||
cfd->current()->version_set(),
|
||||
cfd->GetFileMetadataCacheReservationManager())),
|
||||
version_(cfd->current()) {
|
||||
version_->Ref();
|
||||
}
|
||||
@@ -1289,7 +1317,8 @@ BaseReferencedVersionBuilder::BaseReferencedVersionBuilder(
|
||||
ColumnFamilyData* cfd, Version* v)
|
||||
: version_builder_(new VersionBuilder(
|
||||
cfd->current()->version_set()->file_options(), cfd->ioptions(),
|
||||
cfd->table_cache(), v->storage_info(), v->version_set())),
|
||||
cfd->table_cache(), v->storage_info(), v->version_set(),
|
||||
cfd->GetFileMetadataCacheReservationManager())),
|
||||
version_(v) {
|
||||
assert(version_ != cfd->current());
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ class InternalStats;
|
||||
class Version;
|
||||
class VersionSet;
|
||||
class ColumnFamilyData;
|
||||
class CacheReservationManager;
|
||||
|
||||
// A helper class so we can efficiently apply a whole sequence
|
||||
// of edits to a particular state without creating intermediate
|
||||
@@ -33,7 +34,9 @@ class VersionBuilder {
|
||||
public:
|
||||
VersionBuilder(const FileOptions& file_options,
|
||||
const ImmutableCFOptions* ioptions, TableCache* table_cache,
|
||||
VersionStorageInfo* base_vstorage, VersionSet* version_set);
|
||||
VersionStorageInfo* base_vstorage, VersionSet* version_set,
|
||||
std::shared_ptr<CacheReservationManager>
|
||||
file_metadata_cache_res_mgr = nullptr);
|
||||
~VersionBuilder();
|
||||
|
||||
bool CheckConsistencyForNumLevels();
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "db/dbformat.h"
|
||||
#include "db/wal_edit.h"
|
||||
#include "memory/arena.h"
|
||||
#include "port/malloc.h"
|
||||
#include "rocksdb/advanced_options.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "table/table_reader.h"
|
||||
@@ -293,6 +294,25 @@ struct FileMetaData {
|
||||
}
|
||||
return kUnknownFileCreationTime;
|
||||
}
|
||||
|
||||
// WARNING: manual update to this function is needed
|
||||
// whenever a new string property is added to FileMetaData
|
||||
// to reduce approximation error.
|
||||
//
|
||||
// TODO: eliminate the need of manually updating this function
|
||||
// for new string properties
|
||||
size_t ApproximateMemoryUsage() const {
|
||||
size_t usage = 0;
|
||||
#ifdef ROCKSDB_MALLOC_USABLE_SIZE
|
||||
usage += malloc_usable_size(const_cast<FileMetaData*>(this));
|
||||
#else
|
||||
usage += sizeof(*this);
|
||||
#endif // ROCKSDB_MALLOC_USABLE_SIZE
|
||||
usage += smallest.size() + largest.size() + file_checksum.size() +
|
||||
file_checksum_func_name.size() + min_timestamp.size() +
|
||||
max_timestamp.size();
|
||||
return usage;
|
||||
}
|
||||
};
|
||||
|
||||
// A compressed copy of file meta data that just contain minimum data needed
|
||||
|
||||
+75
-13
@@ -775,7 +775,8 @@ Version::~Version() {
|
||||
uint32_t path_id = f->fd.GetPathId();
|
||||
assert(path_id < cfd_->ioptions()->cf_paths.size());
|
||||
vset_->obsolete_files_.push_back(
|
||||
ObsoleteFileInfo(f, cfd_->ioptions()->cf_paths[path_id].path));
|
||||
ObsoleteFileInfo(f, cfd_->ioptions()->cf_paths[path_id].path,
|
||||
cfd_->GetFileMetadataCacheReservationManager()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2251,8 +2252,8 @@ void Version::MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
Status s;
|
||||
uint64_t num_index_read = 0;
|
||||
uint64_t num_filter_read = 0;
|
||||
uint64_t num_data_read = 0;
|
||||
uint64_t num_sst_read = 0;
|
||||
uint64_t num_level_read = 0;
|
||||
|
||||
MultiGetRange keys_with_blobs_range(*range, range->begin(), range->end());
|
||||
// blob_file => [[blob_idx, it], ...]
|
||||
@@ -2274,7 +2275,7 @@ void Version::MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
s = MultiGetFromSST(read_options, fp.CurrentFileRange(),
|
||||
fp.GetHitFileLevel(), fp.IsHitFileLastInLevel(), f,
|
||||
blob_rqs, num_filter_read, num_index_read,
|
||||
num_data_read, num_sst_read);
|
||||
num_sst_read);
|
||||
if (fp.GetHitFileLevel() == 0) {
|
||||
dump_stats_for_l0_file = true;
|
||||
}
|
||||
@@ -2289,13 +2290,14 @@ void Version::MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
mget_tasks.emplace_back(MultiGetFromSSTCoroutine(
|
||||
read_options, fp.CurrentFileRange(), fp.GetHitFileLevel(),
|
||||
fp.IsHitFileLastInLevel(), f, blob_rqs, num_filter_read,
|
||||
num_index_read, num_data_read, num_sst_read));
|
||||
num_index_read, num_sst_read));
|
||||
if (fp.KeyMaySpanNextFile()) {
|
||||
break;
|
||||
}
|
||||
f = fp.GetNextFileInLevel();
|
||||
}
|
||||
if (mget_tasks.size() > 0) {
|
||||
RecordTick(db_statistics_, MULTIGET_COROUTINE_COUNT, mget_tasks.size());
|
||||
// Collect all results so far
|
||||
std::vector<Status> statuses = folly::coro::blockingWait(
|
||||
folly::coro::collectAllRange(std::move(mget_tasks))
|
||||
@@ -2327,18 +2329,18 @@ void Version::MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
(prev_level != 0 && prev_level != (int)fp.GetHitFileLevel())) {
|
||||
// Dump the stats if the search has moved to the next level and
|
||||
// reset for next level.
|
||||
if (num_sst_read || (num_filter_read + num_index_read)) {
|
||||
if (num_filter_read + num_index_read) {
|
||||
RecordInHistogram(db_statistics_,
|
||||
NUM_INDEX_AND_FILTER_BLOCKS_READ_PER_LEVEL,
|
||||
num_index_read + num_filter_read);
|
||||
RecordInHistogram(db_statistics_, NUM_DATA_BLOCKS_READ_PER_LEVEL,
|
||||
num_data_read);
|
||||
}
|
||||
if (num_sst_read) {
|
||||
RecordInHistogram(db_statistics_, NUM_SST_READ_PER_LEVEL,
|
||||
num_sst_read);
|
||||
num_level_read++;
|
||||
}
|
||||
num_filter_read = 0;
|
||||
num_index_read = 0;
|
||||
num_data_read = 0;
|
||||
num_sst_read = 0;
|
||||
}
|
||||
prev_level = fp.GetHitFileLevel();
|
||||
@@ -2346,11 +2348,19 @@ void Version::MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
}
|
||||
|
||||
// Dump stats for most recent level
|
||||
RecordInHistogram(db_statistics_, NUM_INDEX_AND_FILTER_BLOCKS_READ_PER_LEVEL,
|
||||
num_index_read + num_filter_read);
|
||||
RecordInHistogram(db_statistics_, NUM_DATA_BLOCKS_READ_PER_LEVEL,
|
||||
num_data_read);
|
||||
RecordInHistogram(db_statistics_, NUM_SST_READ_PER_LEVEL, num_sst_read);
|
||||
if (num_filter_read + num_index_read) {
|
||||
RecordInHistogram(db_statistics_,
|
||||
NUM_INDEX_AND_FILTER_BLOCKS_READ_PER_LEVEL,
|
||||
num_index_read + num_filter_read);
|
||||
}
|
||||
if (num_sst_read) {
|
||||
RecordInHistogram(db_statistics_, NUM_SST_READ_PER_LEVEL, num_sst_read);
|
||||
num_level_read++;
|
||||
}
|
||||
if (num_level_read) {
|
||||
RecordInHistogram(db_statistics_, NUM_LEVEL_READ_PER_MULTIGET,
|
||||
num_level_read);
|
||||
}
|
||||
|
||||
if (s.ok() && !blob_rqs.empty()) {
|
||||
MultiGetBlob(read_options, keys_with_blobs_range, blob_rqs);
|
||||
@@ -3941,6 +3951,26 @@ void Version::AddLiveFiles(std::vector<uint64_t>* live_table_files,
|
||||
}
|
||||
}
|
||||
|
||||
void Version::RemoveLiveFiles(
|
||||
std::vector<ObsoleteFileInfo>& sst_delete_candidates,
|
||||
std::vector<ObsoleteBlobFileInfo>& blob_delete_candidates) const {
|
||||
for (ObsoleteFileInfo& fi : sst_delete_candidates) {
|
||||
if (!fi.only_delete_metadata &&
|
||||
storage_info()->GetFileLocation(fi.metadata->fd.GetNumber()) !=
|
||||
VersionStorageInfo::FileLocation::Invalid()) {
|
||||
fi.only_delete_metadata = true;
|
||||
}
|
||||
}
|
||||
|
||||
blob_delete_candidates.erase(
|
||||
std::remove_if(
|
||||
blob_delete_candidates.begin(), blob_delete_candidates.end(),
|
||||
[this](ObsoleteBlobFileInfo& x) {
|
||||
return storage_info()->GetBlobFileMetaData(x.GetBlobFileNumber());
|
||||
}),
|
||||
blob_delete_candidates.end());
|
||||
}
|
||||
|
||||
std::string Version::DebugString(bool hex, bool print_stats) const {
|
||||
std::string r;
|
||||
for (int level = 0; level < storage_info_.num_levels_; level++) {
|
||||
@@ -5745,6 +5775,38 @@ uint64_t VersionSet::ApproximateSize(Version* v, const FdWithKeyRange& f,
|
||||
v->GetMutableCFOptions().prefix_extractor);
|
||||
}
|
||||
|
||||
void VersionSet::RemoveLiveFiles(
|
||||
std::vector<ObsoleteFileInfo>& sst_delete_candidates,
|
||||
std::vector<ObsoleteBlobFileInfo>& blob_delete_candidates) const {
|
||||
assert(column_family_set_);
|
||||
for (auto cfd : *column_family_set_) {
|
||||
assert(cfd);
|
||||
if (!cfd->initialized()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* current = cfd->current();
|
||||
bool found_current = false;
|
||||
|
||||
Version* const dummy_versions = cfd->dummy_versions();
|
||||
assert(dummy_versions);
|
||||
|
||||
for (Version* v = dummy_versions->next_; v != dummy_versions;
|
||||
v = v->next_) {
|
||||
v->RemoveLiveFiles(sst_delete_candidates, blob_delete_candidates);
|
||||
if (v == current) {
|
||||
found_current = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_current && current != nullptr) {
|
||||
// Should never happen unless it is a bug.
|
||||
assert(false);
|
||||
current->RemoveLiveFiles(sst_delete_candidates, blob_delete_candidates);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VersionSet::AddLiveFiles(std::vector<uint64_t>* live_table_files,
|
||||
std::vector<uint64_t>* live_blob_files) const {
|
||||
assert(live_table_files);
|
||||
|
||||
+73
-44
@@ -688,6 +688,68 @@ class VersionStorageInfo {
|
||||
friend class VersionSet;
|
||||
};
|
||||
|
||||
struct ObsoleteFileInfo {
|
||||
FileMetaData* metadata;
|
||||
std::string path;
|
||||
// If true, the FileMataData should be destroyed but the file should
|
||||
// not be deleted. This is because another FileMetaData still references
|
||||
// the file, usually because the file is trivial moved so two FileMetadata
|
||||
// is managing the file.
|
||||
bool only_delete_metadata = false;
|
||||
|
||||
ObsoleteFileInfo() noexcept
|
||||
: metadata(nullptr), only_delete_metadata(false) {}
|
||||
ObsoleteFileInfo(FileMetaData* f, const std::string& file_path,
|
||||
std::shared_ptr<CacheReservationManager>
|
||||
file_metadata_cache_res_mgr_arg = nullptr)
|
||||
: metadata(f),
|
||||
path(file_path),
|
||||
only_delete_metadata(false),
|
||||
file_metadata_cache_res_mgr(file_metadata_cache_res_mgr_arg) {}
|
||||
|
||||
ObsoleteFileInfo(const ObsoleteFileInfo&) = delete;
|
||||
ObsoleteFileInfo& operator=(const ObsoleteFileInfo&) = delete;
|
||||
|
||||
ObsoleteFileInfo(ObsoleteFileInfo&& rhs) noexcept : ObsoleteFileInfo() {
|
||||
*this = std::move(rhs);
|
||||
}
|
||||
|
||||
ObsoleteFileInfo& operator=(ObsoleteFileInfo&& rhs) noexcept {
|
||||
path = std::move(rhs.path);
|
||||
metadata = rhs.metadata;
|
||||
rhs.metadata = nullptr;
|
||||
file_metadata_cache_res_mgr = rhs.file_metadata_cache_res_mgr;
|
||||
rhs.file_metadata_cache_res_mgr = nullptr;
|
||||
|
||||
return *this;
|
||||
}
|
||||
void DeleteMetadata() {
|
||||
if (file_metadata_cache_res_mgr) {
|
||||
Status s = file_metadata_cache_res_mgr->UpdateCacheReservation(
|
||||
metadata->ApproximateMemoryUsage(), false /* increase */);
|
||||
s.PermitUncheckedError();
|
||||
}
|
||||
delete metadata;
|
||||
metadata = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<CacheReservationManager> file_metadata_cache_res_mgr;
|
||||
};
|
||||
|
||||
class ObsoleteBlobFileInfo {
|
||||
public:
|
||||
ObsoleteBlobFileInfo(uint64_t blob_file_number, std::string path)
|
||||
: blob_file_number_(blob_file_number), path_(std::move(path)) {}
|
||||
|
||||
uint64_t GetBlobFileNumber() const { return blob_file_number_; }
|
||||
const std::string& GetPath() const { return path_; }
|
||||
|
||||
private:
|
||||
uint64_t blob_file_number_;
|
||||
std::string path_;
|
||||
};
|
||||
|
||||
using MultiGetRange = MultiGetContext::Range;
|
||||
// A column family's version consists of the table and blob files owned by
|
||||
// the column family at a certain point in time.
|
||||
@@ -789,6 +851,11 @@ class Version {
|
||||
void AddLiveFiles(std::vector<uint64_t>* live_table_files,
|
||||
std::vector<uint64_t>* live_blob_files) const;
|
||||
|
||||
// Remove live files that are in the delete candidate lists.
|
||||
void RemoveLiveFiles(
|
||||
std::vector<ObsoleteFileInfo>& sst_delete_candidates,
|
||||
std::vector<ObsoleteBlobFileInfo>& blob_delete_candidates) const;
|
||||
|
||||
// Return a human readable string that describes this version's contents.
|
||||
std::string DebugString(bool hex = false, bool print_stats = false) const;
|
||||
|
||||
@@ -891,7 +958,7 @@ class Version {
|
||||
int hit_file_level, bool is_hit_file_last_in_level, FdWithKeyRange* f,
|
||||
std::unordered_map<uint64_t, BlobReadRequests>& blob_rqs,
|
||||
uint64_t& num_filter_read, uint64_t& num_index_read,
|
||||
uint64_t& num_data_read, uint64_t& num_sst_read);
|
||||
uint64_t& num_sst_read);
|
||||
|
||||
ColumnFamilyData* cfd_; // ColumnFamilyData to which this Version belongs
|
||||
Logger* info_log_;
|
||||
@@ -927,49 +994,6 @@ class Version {
|
||||
void operator=(const Version&) = delete;
|
||||
};
|
||||
|
||||
struct ObsoleteFileInfo {
|
||||
FileMetaData* metadata;
|
||||
std::string path;
|
||||
|
||||
ObsoleteFileInfo() noexcept : metadata(nullptr) {}
|
||||
ObsoleteFileInfo(FileMetaData* f, const std::string& file_path)
|
||||
: metadata(f), path(file_path) {}
|
||||
|
||||
ObsoleteFileInfo(const ObsoleteFileInfo&) = delete;
|
||||
ObsoleteFileInfo& operator=(const ObsoleteFileInfo&) = delete;
|
||||
|
||||
ObsoleteFileInfo(ObsoleteFileInfo&& rhs) noexcept :
|
||||
ObsoleteFileInfo() {
|
||||
*this = std::move(rhs);
|
||||
}
|
||||
|
||||
ObsoleteFileInfo& operator=(ObsoleteFileInfo&& rhs) noexcept {
|
||||
path = std::move(rhs.path);
|
||||
metadata = rhs.metadata;
|
||||
rhs.metadata = nullptr;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void DeleteMetadata() {
|
||||
delete metadata;
|
||||
metadata = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
class ObsoleteBlobFileInfo {
|
||||
public:
|
||||
ObsoleteBlobFileInfo(uint64_t blob_file_number, std::string path)
|
||||
: blob_file_number_(blob_file_number), path_(std::move(path)) {}
|
||||
|
||||
uint64_t GetBlobFileNumber() const { return blob_file_number_; }
|
||||
const std::string& GetPath() const { return path_; }
|
||||
|
||||
private:
|
||||
uint64_t blob_file_number_;
|
||||
std::string path_;
|
||||
};
|
||||
|
||||
class BaseReferencedVersionBuilder;
|
||||
|
||||
class AtomicGroupReadBuffer {
|
||||
@@ -1287,6 +1311,11 @@ class VersionSet {
|
||||
void AddLiveFiles(std::vector<uint64_t>* live_table_files,
|
||||
std::vector<uint64_t>* live_blob_files) const;
|
||||
|
||||
// Remove live files that are in the delete candidate lists.
|
||||
void RemoveLiveFiles(
|
||||
std::vector<ObsoleteFileInfo>& sst_delete_candidates,
|
||||
std::vector<ObsoleteBlobFileInfo>& blob_delete_candidates) const;
|
||||
|
||||
// Return the approximate size of data to be scanned for range [start, end)
|
||||
// in levels [start_level, end_level). If end_level == -1 it will search
|
||||
// through all non-empty levels
|
||||
|
||||
@@ -15,8 +15,7 @@ DEFINE_SYNC_AND_ASYNC(Status, Version::MultiGetFromSST)
|
||||
(const ReadOptions& read_options, MultiGetRange file_range, int hit_file_level,
|
||||
bool is_hit_file_last_in_level, FdWithKeyRange* f,
|
||||
std::unordered_map<uint64_t, BlobReadRequests>& blob_rqs,
|
||||
uint64_t& num_filter_read, uint64_t& num_index_read, uint64_t& num_data_read,
|
||||
uint64_t& num_sst_read) {
|
||||
uint64_t& num_filter_read, uint64_t& num_index_read, uint64_t& num_sst_read) {
|
||||
bool timer_enabled = GetPerfLevel() >= PerfLevel::kEnableTimeExceptForMutex &&
|
||||
get_perf_context()->per_level_perf_context_enabled;
|
||||
|
||||
@@ -63,12 +62,10 @@ DEFINE_SYNC_AND_ASYNC(Status, Version::MultiGetFromSST)
|
||||
batch_size++;
|
||||
num_index_read += get_context.get_context_stats_.num_index_read;
|
||||
num_filter_read += get_context.get_context_stats_.num_filter_read;
|
||||
num_data_read += get_context.get_context_stats_.num_data_read;
|
||||
num_sst_read += get_context.get_context_stats_.num_sst_read;
|
||||
// Reset these stats since they're specific to a level
|
||||
get_context.get_context_stats_.num_index_read = 0;
|
||||
get_context.get_context_stats_.num_filter_read = 0;
|
||||
get_context.get_context_stats_.num_data_read = 0;
|
||||
get_context.get_context_stats_.num_sst_read = 0;
|
||||
|
||||
// report the counters before returning
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <algorithm>
|
||||
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/log_writer.h"
|
||||
#include "rocksdb/advanced_options.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
@@ -3446,6 +3447,124 @@ TEST_F(VersionSetTestMissingFiles, MinLogNumberToKeep2PC) {
|
||||
}
|
||||
}
|
||||
|
||||
class ChargeFileMetadataTest : public DBTestBase {
|
||||
public:
|
||||
ChargeFileMetadataTest()
|
||||
: DBTestBase("charge_file_metadata_test", /*env_do_fsync=*/true) {}
|
||||
};
|
||||
|
||||
class ChargeFileMetadataTestWithParam
|
||||
: public ChargeFileMetadataTest,
|
||||
public testing::WithParamInterface<CacheEntryRoleOptions::Decision> {
|
||||
public:
|
||||
ChargeFileMetadataTestWithParam() {}
|
||||
};
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
ChargeFileMetadataTestWithParam, ChargeFileMetadataTestWithParam,
|
||||
::testing::Values(CacheEntryRoleOptions::Decision::kEnabled,
|
||||
CacheEntryRoleOptions::Decision::kDisabled));
|
||||
|
||||
TEST_P(ChargeFileMetadataTestWithParam, Basic) {
|
||||
Options options;
|
||||
BlockBasedTableOptions table_options;
|
||||
CacheEntryRoleOptions::Decision charge_file_metadata = GetParam();
|
||||
table_options.cache_usage_options.options_overrides.insert(
|
||||
{CacheEntryRole::kFileMetadata, {/*.charged = */ charge_file_metadata}});
|
||||
std::shared_ptr<TargetCacheChargeTrackingCache<CacheEntryRole::kFileMetadata>>
|
||||
file_metadata_charge_only_cache = std::make_shared<
|
||||
TargetCacheChargeTrackingCache<CacheEntryRole::kFileMetadata>>(
|
||||
NewLRUCache(
|
||||
4 * CacheReservationManagerImpl<
|
||||
CacheEntryRole::kFileMetadata>::GetDummyEntrySize(),
|
||||
0 /* num_shard_bits */, true /* strict_capacity_limit */));
|
||||
table_options.block_cache = file_metadata_charge_only_cache;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Create 128 file metadata, each of which is roughly 1024 bytes.
|
||||
// This results in 1 *
|
||||
// CacheReservationManagerImpl<CacheEntryRole::kFileMetadata>::GetDummyEntrySize()
|
||||
// cache reservation for file metadata.
|
||||
for (int i = 1; i <= 128; ++i) {
|
||||
ASSERT_OK(Put(std::string(1024, 'a'), "va"));
|
||||
ASSERT_OK(Put("b", "vb"));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
if (charge_file_metadata == CacheEntryRoleOptions::Decision::kEnabled) {
|
||||
EXPECT_EQ(file_metadata_charge_only_cache->GetCacheCharge(),
|
||||
1 * CacheReservationManagerImpl<
|
||||
CacheEntryRole::kFileMetadata>::GetDummyEntrySize());
|
||||
|
||||
} else {
|
||||
EXPECT_EQ(file_metadata_charge_only_cache->GetCacheCharge(), 0);
|
||||
}
|
||||
|
||||
// Create another 128 file metadata.
|
||||
// This increases the file metadata cache reservation to 2 *
|
||||
// CacheReservationManagerImpl<CacheEntryRole::kFileMetadata>::GetDummyEntrySize().
|
||||
for (int i = 1; i <= 128; ++i) {
|
||||
ASSERT_OK(Put(std::string(1024, 'a'), "vva"));
|
||||
ASSERT_OK(Put("b", "vvb"));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
if (charge_file_metadata == CacheEntryRoleOptions::Decision::kEnabled) {
|
||||
EXPECT_EQ(file_metadata_charge_only_cache->GetCacheCharge(),
|
||||
2 * CacheReservationManagerImpl<
|
||||
CacheEntryRole::kFileMetadata>::GetDummyEntrySize());
|
||||
} else {
|
||||
EXPECT_EQ(file_metadata_charge_only_cache->GetCacheCharge(), 0);
|
||||
}
|
||||
// Compaction will create 1 new file metadata, obsolete and delete all 256
|
||||
// file metadata above. This results in 1 *
|
||||
// CacheReservationManagerImpl<CacheEntryRole::kFileMetadata>::GetDummyEntrySize()
|
||||
// cache reservation for file metadata.
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ("0,1", FilesPerLevel(0));
|
||||
|
||||
if (charge_file_metadata == CacheEntryRoleOptions::Decision::kEnabled) {
|
||||
EXPECT_EQ(file_metadata_charge_only_cache->GetCacheCharge(),
|
||||
1 * CacheReservationManagerImpl<
|
||||
CacheEntryRole::kFileMetadata>::GetDummyEntrySize());
|
||||
} else {
|
||||
EXPECT_EQ(file_metadata_charge_only_cache->GetCacheCharge(), 0);
|
||||
}
|
||||
|
||||
// Destroying the db will delete the remaining 1 new file metadata
|
||||
// This results in no cache reservation for file metadata.
|
||||
Destroy(options);
|
||||
EXPECT_EQ(file_metadata_charge_only_cache->GetCacheCharge(),
|
||||
0 * CacheReservationManagerImpl<
|
||||
CacheEntryRole::kFileMetadata>::GetDummyEntrySize());
|
||||
|
||||
// Reopen the db with a smaller cache in order to test failure in allocating
|
||||
// file metadata due to memory limit based on cache capacity
|
||||
file_metadata_charge_only_cache = std::make_shared<
|
||||
TargetCacheChargeTrackingCache<CacheEntryRole::kFileMetadata>>(
|
||||
NewLRUCache(1 * CacheReservationManagerImpl<
|
||||
CacheEntryRole::kFileMetadata>::GetDummyEntrySize(),
|
||||
0 /* num_shard_bits */, true /* strict_capacity_limit */));
|
||||
table_options.block_cache = file_metadata_charge_only_cache;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put(std::string(1024, 'a'), "va"));
|
||||
ASSERT_OK(Put("b", "vb"));
|
||||
Status s = Flush();
|
||||
if (charge_file_metadata == CacheEntryRoleOptions::Decision::kEnabled) {
|
||||
EXPECT_TRUE(s.IsMemoryLimit());
|
||||
EXPECT_TRUE(s.ToString().find(
|
||||
kCacheEntryRoleToCamelString[static_cast<std::uint32_t>(
|
||||
CacheEntryRole::kFileMetadata)]) != std::string::npos);
|
||||
EXPECT_TRUE(s.ToString().find("memory limit based on cache capacity") !=
|
||||
std::string::npos);
|
||||
} else {
|
||||
EXPECT_TRUE(s.ok());
|
||||
}
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+14
-4
@@ -507,10 +507,20 @@ Status WalManager::ReadFirstLine(const std::string& fname,
|
||||
}
|
||||
}
|
||||
|
||||
// ReadRecord might have returned false on EOF, which means that the log file
|
||||
// is empty. Or, a failure may have occurred while processing the first entry.
|
||||
// In any case, return status and set sequence number to 0.
|
||||
*sequence = 0;
|
||||
if (status.ok() && reader.IsCompressedAndEmptyFile()) {
|
||||
// In case of wal_compression, it writes a `kSetCompressionType` record
|
||||
// which is not associated with any sequence number. As result for an empty
|
||||
// file, GetSortedWalsOfType() will skip these WALs causing the operations
|
||||
// to fail.
|
||||
// Therefore, in order to avoid that failure, it sets sequence_number to 1
|
||||
// indicating those WALs should be included.
|
||||
*sequence = 1;
|
||||
} else {
|
||||
// ReadRecord might have returned false on EOF, which means that the log
|
||||
// file is empty. Or, a failure may have occurred while processing the first
|
||||
// entry. In any case, return status and set sequence number to 0.
|
||||
*sequence = 0;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
@@ -85,9 +85,25 @@ class WalManager {
|
||||
Status RetainProbableWalFiles(VectorLogPtr& all_logs,
|
||||
const SequenceNumber target);
|
||||
|
||||
// ReadFirstRecord checks the read_first_record_cache_ to see if the entry
|
||||
// exists or not. If not, it will read the WAL file.
|
||||
// In case of wal_compression, WAL contains a `kSetCompressionType` record
|
||||
// which is not associated with any sequence number. So the sequence_number is
|
||||
// set to 1 if that WAL doesn't include any other record (basically empty) in
|
||||
// order to include that WAL and is inserted in read_first_record_cache_.
|
||||
// Therefore, sequence_number is used as boolean if WAL should be included or
|
||||
// not and that sequence_number shouldn't be use for any other purpose.
|
||||
Status ReadFirstRecord(const WalFileType type, const uint64_t number,
|
||||
SequenceNumber* sequence);
|
||||
|
||||
// In case of no wal_compression, ReadFirstLine returns status.ok() and
|
||||
// sequence == 0 if the file exists, but is empty.
|
||||
// In case of wal_compression, WAL contains
|
||||
// `kSetCompressionType` record which is not associated with any sequence
|
||||
// number if that WAL doesn't include any other record (basically empty). As
|
||||
// result for an empty file, GetSortedWalsOfType() will skip these WALs
|
||||
// causing the operations to fail. To avoid that, it sets sequence_number to
|
||||
// 1 inorder to include that WAL.
|
||||
Status ReadFirstLine(const std::string& fname, const uint64_t number,
|
||||
SequenceNumber* sequence);
|
||||
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/wide/wide_column_serialization.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <limits>
|
||||
|
||||
#include "rocksdb/slice.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/coding.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
Status WideColumnSerialization::Serialize(const WideColumns& columns,
|
||||
std::string& output) {
|
||||
// Column names should be strictly ascending
|
||||
assert(std::adjacent_find(columns.cbegin(), columns.cend(),
|
||||
[](const WideColumn& lhs, const WideColumn& rhs) {
|
||||
return lhs.name().compare(rhs.name()) > 0;
|
||||
}) == columns.cend());
|
||||
|
||||
if (columns.size() >
|
||||
static_cast<size_t>(std::numeric_limits<uint32_t>::max())) {
|
||||
return Status::InvalidArgument("Too many wide columns");
|
||||
}
|
||||
|
||||
PutVarint32(&output, kCurrentVersion);
|
||||
|
||||
PutVarint32(&output, static_cast<uint32_t>(columns.size()));
|
||||
|
||||
for (const auto& column : columns) {
|
||||
const Slice& name = column.name();
|
||||
if (name.size() >
|
||||
static_cast<size_t>(std::numeric_limits<uint32_t>::max())) {
|
||||
return Status::InvalidArgument("Wide column name too long");
|
||||
}
|
||||
|
||||
const Slice& value = column.value();
|
||||
if (value.size() >
|
||||
static_cast<size_t>(std::numeric_limits<uint32_t>::max())) {
|
||||
return Status::InvalidArgument("Wide column value too long");
|
||||
}
|
||||
|
||||
PutLengthPrefixedSlice(&output, name);
|
||||
PutVarint32(&output, static_cast<uint32_t>(value.size()));
|
||||
}
|
||||
|
||||
for (const auto& column : columns) {
|
||||
const Slice& value = column.value();
|
||||
|
||||
output.append(value.data(), value.size());
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status WideColumnSerialization::Deserialize(Slice& input,
|
||||
WideColumns& columns) {
|
||||
assert(columns.empty());
|
||||
|
||||
uint32_t version = 0;
|
||||
if (!GetVarint32(&input, &version)) {
|
||||
return Status::Corruption("Error decoding wide column version");
|
||||
}
|
||||
|
||||
if (version > kCurrentVersion) {
|
||||
return Status::NotSupported("Unsupported wide column version");
|
||||
}
|
||||
|
||||
uint32_t num_columns = 0;
|
||||
if (!GetVarint32(&input, &num_columns)) {
|
||||
return Status::Corruption("Error decoding number of wide columns");
|
||||
}
|
||||
|
||||
if (!num_columns) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
columns.reserve(num_columns);
|
||||
|
||||
autovector<uint32_t, 16> column_value_sizes;
|
||||
column_value_sizes.reserve(num_columns);
|
||||
|
||||
for (uint32_t i = 0; i < num_columns; ++i) {
|
||||
Slice name;
|
||||
if (!GetLengthPrefixedSlice(&input, &name)) {
|
||||
return Status::Corruption("Error decoding wide column name");
|
||||
}
|
||||
|
||||
if (!columns.empty() && columns.back().name().compare(name) >= 0) {
|
||||
return Status::Corruption("Wide columns out of order");
|
||||
}
|
||||
|
||||
columns.emplace_back(name, Slice());
|
||||
|
||||
uint32_t value_size = 0;
|
||||
if (!GetVarint32(&input, &value_size)) {
|
||||
return Status::Corruption("Error decoding wide column value size");
|
||||
}
|
||||
|
||||
column_value_sizes.emplace_back(value_size);
|
||||
}
|
||||
|
||||
const Slice data(input);
|
||||
size_t pos = 0;
|
||||
|
||||
for (uint32_t i = 0; i < num_columns; ++i) {
|
||||
const uint32_t value_size = column_value_sizes[i];
|
||||
|
||||
if (pos + value_size > data.size()) {
|
||||
return Status::Corruption("Error decoding wide column value payload");
|
||||
}
|
||||
|
||||
columns[i].value() = Slice(data.data() + pos, value_size);
|
||||
|
||||
pos += value_size;
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
WideColumns::const_iterator WideColumnSerialization::Find(
|
||||
const WideColumns& columns, const Slice& column_name) {
|
||||
const auto it =
|
||||
std::lower_bound(columns.cbegin(), columns.cend(), column_name,
|
||||
[](const WideColumn& lhs, const Slice& rhs) {
|
||||
return lhs.name().compare(rhs) < 0;
|
||||
});
|
||||
|
||||
if (it == columns.cend() || it->name() != column_name) {
|
||||
return columns.cend();
|
||||
}
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/wide_columns.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class Slice;
|
||||
|
||||
// Wide-column serialization/deserialization primitives.
|
||||
//
|
||||
// The two main parts of the layout are 1) a sorted index containing the column
|
||||
// names and column value sizes and 2) the column values themselves. Keeping the
|
||||
// index and the values separate will enable selectively reading column values
|
||||
// down the line. Note that currently the index has to be fully parsed in order
|
||||
// to find out the offset of each column value.
|
||||
//
|
||||
// Legend: cn = column name, cv = column value, cns = column name size, cvs =
|
||||
// column value size.
|
||||
//
|
||||
// +----------+--------------+----------+-------+----------+---...
|
||||
// | version | # of columns | cns 1 | cn 1 | cvs 1 |
|
||||
// +----------+--------------+------------------+--------- +---...
|
||||
// | varint32 | varint32 | varint32 | bytes | varint32 |
|
||||
// +----------+--------------+----------+-------+----------+---...
|
||||
//
|
||||
// ... continued ...
|
||||
//
|
||||
// ...---+----------+-------+----------+-------+---...---+-------+
|
||||
// | cns N | cn N | cvs N | cv 1 | | cv N |
|
||||
// ...---+----------+-------+----------+-------+---...---+-------+
|
||||
// | varint32 | bytes | varint32 | bytes | | bytes |
|
||||
// ...---+----------+-------+----------+-------+---...---+-------+
|
||||
|
||||
class WideColumnSerialization {
|
||||
public:
|
||||
static Status Serialize(const WideColumns& columns, std::string& output);
|
||||
static Status Deserialize(Slice& input, WideColumns& columns);
|
||||
|
||||
static WideColumns::const_iterator Find(const WideColumns& columns,
|
||||
const Slice& column_name);
|
||||
|
||||
static constexpr uint32_t kCurrentVersion = 1;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -0,0 +1,292 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/wide/wide_column_serialization.h"
|
||||
|
||||
#include "test_util/testharness.h"
|
||||
#include "util/coding.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
TEST(WideColumnSerializationTest, Construct) {
|
||||
constexpr char foo[] = "foo";
|
||||
constexpr char bar[] = "bar";
|
||||
|
||||
const std::string foo_str(foo);
|
||||
const std::string bar_str(bar);
|
||||
|
||||
const Slice foo_slice(foo_str);
|
||||
const Slice bar_slice(bar_str);
|
||||
|
||||
{
|
||||
WideColumn column(foo, bar);
|
||||
ASSERT_EQ(column.name(), foo);
|
||||
ASSERT_EQ(column.value(), bar);
|
||||
}
|
||||
|
||||
{
|
||||
WideColumn column(foo_str, bar);
|
||||
ASSERT_EQ(column.name(), foo_str);
|
||||
ASSERT_EQ(column.value(), bar);
|
||||
}
|
||||
|
||||
{
|
||||
WideColumn column(foo_slice, bar);
|
||||
ASSERT_EQ(column.name(), foo_slice);
|
||||
ASSERT_EQ(column.value(), bar);
|
||||
}
|
||||
|
||||
{
|
||||
WideColumn column(foo, bar_str);
|
||||
ASSERT_EQ(column.name(), foo);
|
||||
ASSERT_EQ(column.value(), bar_str);
|
||||
}
|
||||
|
||||
{
|
||||
WideColumn column(foo_str, bar_str);
|
||||
ASSERT_EQ(column.name(), foo_str);
|
||||
ASSERT_EQ(column.value(), bar_str);
|
||||
}
|
||||
|
||||
{
|
||||
WideColumn column(foo_slice, bar_str);
|
||||
ASSERT_EQ(column.name(), foo_slice);
|
||||
ASSERT_EQ(column.value(), bar_str);
|
||||
}
|
||||
|
||||
{
|
||||
WideColumn column(foo, bar_slice);
|
||||
ASSERT_EQ(column.name(), foo);
|
||||
ASSERT_EQ(column.value(), bar_slice);
|
||||
}
|
||||
|
||||
{
|
||||
WideColumn column(foo_str, bar_slice);
|
||||
ASSERT_EQ(column.name(), foo_str);
|
||||
ASSERT_EQ(column.value(), bar_slice);
|
||||
}
|
||||
|
||||
{
|
||||
WideColumn column(foo_slice, bar_slice);
|
||||
ASSERT_EQ(column.name(), foo_slice);
|
||||
ASSERT_EQ(column.value(), bar_slice);
|
||||
}
|
||||
|
||||
{
|
||||
constexpr char foo_name[] = "foo_name";
|
||||
constexpr char bar_value[] = "bar_value";
|
||||
|
||||
WideColumn column(std::piecewise_construct,
|
||||
std::forward_as_tuple(foo_name, sizeof(foo) - 1),
|
||||
std::forward_as_tuple(bar_value, sizeof(bar) - 1));
|
||||
ASSERT_EQ(column.name(), foo);
|
||||
ASSERT_EQ(column.value(), bar);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(WideColumnSerializationTest, SerializeDeserialize) {
|
||||
WideColumns columns{{"foo", "bar"}, {"hello", "world"}};
|
||||
std::string output;
|
||||
|
||||
ASSERT_OK(WideColumnSerialization::Serialize(columns, output));
|
||||
|
||||
Slice input(output);
|
||||
WideColumns deserialized_columns;
|
||||
|
||||
ASSERT_OK(WideColumnSerialization::Deserialize(input, deserialized_columns));
|
||||
ASSERT_EQ(columns, deserialized_columns);
|
||||
|
||||
{
|
||||
const auto it = WideColumnSerialization::Find(deserialized_columns, "foo");
|
||||
ASSERT_NE(it, deserialized_columns.cend());
|
||||
ASSERT_EQ(*it, deserialized_columns.front());
|
||||
}
|
||||
|
||||
{
|
||||
const auto it =
|
||||
WideColumnSerialization::Find(deserialized_columns, "hello");
|
||||
ASSERT_NE(it, deserialized_columns.cend());
|
||||
ASSERT_EQ(*it, deserialized_columns.back());
|
||||
}
|
||||
|
||||
{
|
||||
const auto it =
|
||||
WideColumnSerialization::Find(deserialized_columns, "fubar");
|
||||
ASSERT_EQ(it, deserialized_columns.cend());
|
||||
}
|
||||
|
||||
{
|
||||
const auto it =
|
||||
WideColumnSerialization::Find(deserialized_columns, "snafu");
|
||||
ASSERT_EQ(it, deserialized_columns.cend());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(WideColumnSerializationTest, DeserializeVersionError) {
|
||||
// Can't decode version
|
||||
|
||||
std::string buf;
|
||||
|
||||
Slice input(buf);
|
||||
WideColumns columns;
|
||||
|
||||
const Status s = WideColumnSerialization::Deserialize(input, columns);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "version"));
|
||||
}
|
||||
|
||||
TEST(WideColumnSerializationTest, DeserializeUnsupportedVersion) {
|
||||
// Unsupported version
|
||||
constexpr uint32_t future_version = 1000;
|
||||
|
||||
std::string buf;
|
||||
PutVarint32(&buf, future_version);
|
||||
|
||||
Slice input(buf);
|
||||
WideColumns columns;
|
||||
|
||||
const Status s = WideColumnSerialization::Deserialize(input, columns);
|
||||
ASSERT_TRUE(s.IsNotSupported());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "version"));
|
||||
}
|
||||
|
||||
TEST(WideColumnSerializationTest, DeserializeNumberOfColumnsError) {
|
||||
// Can't decode number of columns
|
||||
|
||||
std::string buf;
|
||||
PutVarint32(&buf, WideColumnSerialization::kCurrentVersion);
|
||||
|
||||
Slice input(buf);
|
||||
WideColumns columns;
|
||||
|
||||
const Status s = WideColumnSerialization::Deserialize(input, columns);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "number"));
|
||||
}
|
||||
|
||||
TEST(WideColumnSerializationTest, DeserializeColumnsError) {
|
||||
std::string buf;
|
||||
|
||||
PutVarint32(&buf, WideColumnSerialization::kCurrentVersion);
|
||||
|
||||
constexpr uint32_t num_columns = 2;
|
||||
PutVarint32(&buf, num_columns);
|
||||
|
||||
// Can't decode the first column name
|
||||
{
|
||||
Slice input(buf);
|
||||
WideColumns columns;
|
||||
|
||||
const Status s = WideColumnSerialization::Deserialize(input, columns);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "name"));
|
||||
}
|
||||
|
||||
constexpr char first_column_name[] = "foo";
|
||||
PutLengthPrefixedSlice(&buf, first_column_name);
|
||||
|
||||
// Can't decode the size of the first column value
|
||||
{
|
||||
Slice input(buf);
|
||||
WideColumns columns;
|
||||
|
||||
const Status s = WideColumnSerialization::Deserialize(input, columns);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "value size"));
|
||||
}
|
||||
|
||||
constexpr uint32_t first_value_size = 16;
|
||||
PutVarint32(&buf, first_value_size);
|
||||
|
||||
// Can't decode the second column name
|
||||
{
|
||||
Slice input(buf);
|
||||
WideColumns columns;
|
||||
|
||||
const Status s = WideColumnSerialization::Deserialize(input, columns);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "name"));
|
||||
}
|
||||
|
||||
constexpr char second_column_name[] = "hello";
|
||||
PutLengthPrefixedSlice(&buf, second_column_name);
|
||||
|
||||
// Can't decode the size of the second column value
|
||||
{
|
||||
Slice input(buf);
|
||||
WideColumns columns;
|
||||
|
||||
const Status s = WideColumnSerialization::Deserialize(input, columns);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "value size"));
|
||||
}
|
||||
|
||||
constexpr uint32_t second_value_size = 64;
|
||||
PutVarint32(&buf, second_value_size);
|
||||
|
||||
// Can't decode the payload of the first column
|
||||
{
|
||||
Slice input(buf);
|
||||
WideColumns columns;
|
||||
|
||||
const Status s = WideColumnSerialization::Deserialize(input, columns);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "payload"));
|
||||
}
|
||||
|
||||
buf.append(first_value_size, '0');
|
||||
|
||||
// Can't decode the payload of the second column
|
||||
{
|
||||
Slice input(buf);
|
||||
WideColumns columns;
|
||||
|
||||
const Status s = WideColumnSerialization::Deserialize(input, columns);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "payload"));
|
||||
}
|
||||
|
||||
buf.append(second_value_size, 'x');
|
||||
|
||||
// Success
|
||||
{
|
||||
Slice input(buf);
|
||||
WideColumns columns;
|
||||
|
||||
ASSERT_OK(WideColumnSerialization::Deserialize(input, columns));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(WideColumnSerializationTest, DeserializeColumnsOutOfOrder) {
|
||||
std::string buf;
|
||||
|
||||
PutVarint32(&buf, WideColumnSerialization::kCurrentVersion);
|
||||
|
||||
constexpr uint32_t num_columns = 2;
|
||||
PutVarint32(&buf, num_columns);
|
||||
|
||||
constexpr char first_column_name[] = "b";
|
||||
PutLengthPrefixedSlice(&buf, first_column_name);
|
||||
|
||||
constexpr uint32_t first_value_size = 16;
|
||||
PutVarint32(&buf, first_value_size);
|
||||
|
||||
constexpr char second_column_name[] = "a";
|
||||
PutLengthPrefixedSlice(&buf, second_column_name);
|
||||
|
||||
Slice input(buf);
|
||||
WideColumns columns;
|
||||
|
||||
const Status s = WideColumnSerialization::Deserialize(input, columns);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "order"));
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
+130
-10
@@ -1491,6 +1491,94 @@ Status WriteBatch::UpdateTimestamps(
|
||||
return s;
|
||||
}
|
||||
|
||||
Status WriteBatch::VerifyChecksum() const {
|
||||
if (prot_info_ == nullptr) {
|
||||
return Status::OK();
|
||||
}
|
||||
Slice input(rep_.data() + WriteBatchInternal::kHeader,
|
||||
rep_.size() - WriteBatchInternal::kHeader);
|
||||
Slice key, value, blob, xid;
|
||||
char tag = 0;
|
||||
uint32_t column_family = 0; // default
|
||||
Status s;
|
||||
size_t prot_info_idx = 0;
|
||||
bool checksum_protected = true;
|
||||
while (!input.empty() && prot_info_idx < prot_info_->entries_.size()) {
|
||||
// In case key/value/column_family are not updated by
|
||||
// ReadRecordFromWriteBatch
|
||||
key.clear();
|
||||
value.clear();
|
||||
column_family = 0;
|
||||
s = ReadRecordFromWriteBatch(&input, &tag, &column_family, &key, &value,
|
||||
&blob, &xid);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
checksum_protected = true;
|
||||
// Write batch checksum uses op_type without ColumnFamily (e.g., if op_type
|
||||
// in the write batch is kTypeColumnFamilyValue, kTypeValue is used to
|
||||
// compute the checksum), and encodes column family id separately. See
|
||||
// comment in first `WriteBatchInternal::Put()` for more detail.
|
||||
switch (tag) {
|
||||
case kTypeColumnFamilyValue:
|
||||
case kTypeValue:
|
||||
tag = kTypeValue;
|
||||
break;
|
||||
case kTypeColumnFamilyDeletion:
|
||||
case kTypeDeletion:
|
||||
tag = kTypeDeletion;
|
||||
break;
|
||||
case kTypeColumnFamilySingleDeletion:
|
||||
case kTypeSingleDeletion:
|
||||
tag = kTypeSingleDeletion;
|
||||
break;
|
||||
case kTypeColumnFamilyRangeDeletion:
|
||||
case kTypeRangeDeletion:
|
||||
tag = kTypeRangeDeletion;
|
||||
break;
|
||||
case kTypeColumnFamilyMerge:
|
||||
case kTypeMerge:
|
||||
tag = kTypeMerge;
|
||||
break;
|
||||
case kTypeColumnFamilyBlobIndex:
|
||||
case kTypeBlobIndex:
|
||||
tag = kTypeBlobIndex;
|
||||
break;
|
||||
case kTypeLogData:
|
||||
case kTypeBeginPrepareXID:
|
||||
case kTypeEndPrepareXID:
|
||||
case kTypeCommitXID:
|
||||
case kTypeRollbackXID:
|
||||
case kTypeNoop:
|
||||
case kTypeBeginPersistedPrepareXID:
|
||||
case kTypeBeginUnprepareXID:
|
||||
case kTypeDeletionWithTimestamp:
|
||||
case kTypeCommitXIDAndTimestamp:
|
||||
checksum_protected = false;
|
||||
break;
|
||||
default:
|
||||
return Status::Corruption(
|
||||
"unknown WriteBatch tag",
|
||||
std::to_string(static_cast<unsigned int>(tag)));
|
||||
}
|
||||
if (checksum_protected) {
|
||||
s = prot_info_->entries_[prot_info_idx++]
|
||||
.StripC(column_family)
|
||||
.StripKVO(key, value, static_cast<ValueType>(tag))
|
||||
.GetStatus();
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (prot_info_idx != WriteBatchInternal::Count(this)) {
|
||||
return Status::Corruption("WriteBatch has wrong count");
|
||||
}
|
||||
assert(WriteBatchInternal::Count(this) == prot_info_->entries_.size());
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class MemTableInserter : public WriteBatch::Handler {
|
||||
@@ -2756,16 +2844,10 @@ class ProtectionInfoUpdater : public WriteBatch::Handler {
|
||||
|
||||
Status WriteBatchInternal::SetContents(WriteBatch* b, const Slice& contents) {
|
||||
assert(contents.size() >= WriteBatchInternal::kHeader);
|
||||
assert(b->prot_info_ == nullptr);
|
||||
|
||||
b->rep_.assign(contents.data(), contents.size());
|
||||
b->content_flags_.store(ContentFlags::DEFERRED, std::memory_order_relaxed);
|
||||
|
||||
// If we have a prot_info_, update protection info entries for the batch.
|
||||
if (b->prot_info_) {
|
||||
ProtectionInfoUpdater prot_info_updater(b->prot_info_.get());
|
||||
return b->Iterate(&prot_info_updater);
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -2773,6 +2855,14 @@ Status WriteBatchInternal::Append(WriteBatch* dst, const WriteBatch* src,
|
||||
const bool wal_only) {
|
||||
assert(dst->Count() == 0 ||
|
||||
(dst->prot_info_ == nullptr) == (src->prot_info_ == nullptr));
|
||||
if ((src->prot_info_ != nullptr &&
|
||||
src->prot_info_->entries_.size() != src->Count()) ||
|
||||
(dst->prot_info_ != nullptr &&
|
||||
dst->prot_info_->entries_.size() != dst->Count())) {
|
||||
return Status::Corruption(
|
||||
"Write batch has inconsistent count and number of checksums");
|
||||
}
|
||||
|
||||
size_t src_len;
|
||||
int src_count;
|
||||
uint32_t src_flags;
|
||||
@@ -2789,12 +2879,18 @@ Status WriteBatchInternal::Append(WriteBatch* dst, const WriteBatch* src,
|
||||
src_flags = src->content_flags_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
if (dst->prot_info_ != nullptr) {
|
||||
if (src->prot_info_ != nullptr) {
|
||||
if (dst->prot_info_ == nullptr) {
|
||||
dst->prot_info_.reset(new WriteBatch::ProtectionInfo());
|
||||
}
|
||||
std::copy(src->prot_info_->entries_.begin(),
|
||||
src->prot_info_->entries_.begin() + src_count,
|
||||
std::back_inserter(dst->prot_info_->entries_));
|
||||
} else if (src->prot_info_ != nullptr) {
|
||||
dst->prot_info_.reset(new WriteBatch::ProtectionInfo(*src->prot_info_));
|
||||
} else if (dst->prot_info_ != nullptr) {
|
||||
// dst has empty prot_info->entries
|
||||
// In this special case, we allow write batch without prot_info to
|
||||
// be appende to write batch with empty prot_info
|
||||
dst->prot_info_ = nullptr;
|
||||
}
|
||||
SetCount(dst, Count(dst) + src_count);
|
||||
assert(src->rep_.size() >= WriteBatchInternal::kHeader);
|
||||
@@ -2814,4 +2910,28 @@ size_t WriteBatchInternal::AppendedByteSize(size_t leftByteSize,
|
||||
}
|
||||
}
|
||||
|
||||
Status WriteBatchInternal::UpdateProtectionInfo(WriteBatch* wb,
|
||||
size_t bytes_per_key) {
|
||||
if (bytes_per_key == 0) {
|
||||
if (wb->prot_info_ != nullptr) {
|
||||
wb->prot_info_.reset();
|
||||
return Status::OK();
|
||||
} else {
|
||||
// Already not protected.
|
||||
return Status::OK();
|
||||
}
|
||||
} else if (bytes_per_key == 8) {
|
||||
if (wb->prot_info_ == nullptr) {
|
||||
wb->prot_info_.reset(new WriteBatch::ProtectionInfo());
|
||||
ProtectionInfoUpdater prot_info_updater(wb->prot_info_.get());
|
||||
return wb->Iterate(&prot_info_updater);
|
||||
} else {
|
||||
// Already protected.
|
||||
return Status::OK();
|
||||
}
|
||||
}
|
||||
return Status::NotSupported(
|
||||
"WriteBatch protection info must be zero or eight bytes/key");
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -206,6 +206,10 @@ class WriteBatchInternal {
|
||||
bool batch_per_txn = true,
|
||||
bool hint_per_batch = false);
|
||||
|
||||
// Appends src write batch to dst write batch and updates count in dst
|
||||
// write batch. Returns OK if the append is successful. Checks number of
|
||||
// checksum against count in dst and src write batches, and returns Corruption
|
||||
// if the count is inconsistent.
|
||||
static Status Append(WriteBatch* dst, const WriteBatch* src,
|
||||
const bool WAL_only = false);
|
||||
|
||||
@@ -232,6 +236,8 @@ class WriteBatchInternal {
|
||||
static bool HasKeyWithTimestamp(const WriteBatch& wb) {
|
||||
return wb.has_key_with_ts_;
|
||||
}
|
||||
|
||||
static Status UpdateProtectionInfo(WriteBatch* wb, size_t bytes_per_key);
|
||||
};
|
||||
|
||||
// LocalSavePoint is similar to a scope guard
|
||||
|
||||
@@ -307,6 +307,10 @@ TEST_P(WriteCallbackPTest, WriteWithCallbackTest) {
|
||||
WriteOptions woptions;
|
||||
woptions.disableWAL = !enable_WAL_;
|
||||
woptions.sync = enable_WAL_;
|
||||
if (woptions.protection_bytes_per_key > 0) {
|
||||
ASSERT_OK(WriteBatchInternal::UpdateProtectionInfo(
|
||||
&write_op.write_batch_, woptions.protection_bytes_per_key));
|
||||
}
|
||||
Status s;
|
||||
if (seq_per_batch_) {
|
||||
class PublishSeqCallback : public PreReleaseCallback {
|
||||
|
||||
Loaded 100 of 301 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user