mirror of
https://github.com/facebook/rocksdb.git
synced 2026-07-08 07:05:23 +08:00
Compare commits
105 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| abd4b1ff15 | |||
| 5893d5e2a1 | |||
| fbed72f03c | |||
| 1420cbf09d | |||
| 6002cce223 | |||
| c9e00cce65 | |||
| ca8082762f | |||
| 12c87b16c6 | |||
| b39b6d7711 | |||
| ceee8ad97d | |||
| c43be4f30c | |||
| d15cc91241 | |||
| eea9a027f6 | |||
| 4b879eeb9d | |||
| cac1fc4dfb | |||
| a5a5be4b4c | |||
| 46923e5ca4 | |||
| 518431d78e | |||
| 5bd1258381 | |||
| 1aae41786a | |||
| 9aa1b1dc19 | |||
| e300ce211d | |||
| 60af964372 | |||
| bbbb5a280d | |||
| 76ef894f9f | |||
| bb69b4ce7f | |||
| cf9d8e45c0 | |||
| 0dc437d65c | |||
| 2400cd69e3 | |||
| ec346da98c | |||
| c442f6809f | |||
| 8b6b6aeb1a | |||
| c57f914482 | |||
| bcba372352 | |||
| 18aee7db7e | |||
| 16d103d35b | |||
| 202605143b | |||
| 9f1c84ca47 | |||
| c3911f1a72 | |||
| 5e794b0841 | |||
| 8b8a2e9f05 | |||
| 1e40696dd1 | |||
| 1ce105d0ea | |||
| cdc7ba3a32 | |||
| da42eceabc | |||
| 4c2aef04bd | |||
| 81543369e5 | |||
| 1f627210ca | |||
| c9c9709a1a | |||
| b6d8e36741 | |||
| fde0cd7ced | |||
| 06a92fcf5c | |||
| 746909ceda | |||
| 0b94468bba | |||
| 881e0dcc09 | |||
| c992eb118b | |||
| 1adbceb581 | |||
| a388c8cc6b | |||
| 30beecef8c | |||
| 6773901f76 | |||
| 7eb2824e3f | |||
| 394210f280 | |||
| 99a0305bb8 | |||
| ea347d80df | |||
| 2404f8b9ec | |||
| 248d10fb96 | |||
| 793e9b7f5b | |||
| 9a690a74e1 | |||
| 6c2c0635c9 | |||
| 0e2e67562f | |||
| f35f7f2704 | |||
| 6134ce6444 | |||
| a7a04b6898 | |||
| 25d54c799c | |||
| 6595267980 | |||
| 1b224324b5 | |||
| a16d1b2fd3 | |||
| b1cdb8cc86 | |||
| d8ec0a760a | |||
| 1c78e4b235 | |||
| eef27d0048 | |||
| 5227b315ec | |||
| 73dbe10bbf | |||
| 1eda625eab | |||
| fc9b416013 | |||
| b4cd51d847 | |||
| 00751e4292 | |||
| 0ea7db768e | |||
| ed90e2a450 | |||
| f4ade82ad2 | |||
| e8cb32ed67 | |||
| fa2a8cda7b | |||
| a8c89cc969 | |||
| ccbf468cb1 | |||
| 1a83f5a8ac | |||
| 05fba96927 | |||
| 850cc0dbed | |||
| 6528ecc800 | |||
| b99fe1ab74 | |||
| db87afbcb3 | |||
| 7b65666cf1 | |||
| c87c3a48af | |||
| 4a6840bd00 | |||
| 3dc823212d | |||
| 16bff5370d |
+144
-10
@@ -17,6 +17,13 @@ commands:
|
||||
command: |
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install pyenv
|
||||
|
||||
install-cmake-on-macos:
|
||||
steps:
|
||||
- run:
|
||||
name: Install cmake on macos
|
||||
command: |
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install cmake
|
||||
|
||||
increase-max-open-files-on-macos:
|
||||
steps:
|
||||
- run:
|
||||
@@ -27,10 +34,14 @@ commands:
|
||||
sudo launchctl limit maxfiles 1048576
|
||||
|
||||
pre-steps:
|
||||
parameters:
|
||||
python-version:
|
||||
default: "3.5.9"
|
||||
type: string
|
||||
steps:
|
||||
- checkout
|
||||
- run: pyenv install --skip-existing 3.5.9
|
||||
- run: pyenv global 3.5.9
|
||||
- run: pyenv install --skip-existing <<parameters.python-version>>
|
||||
- run: pyenv global <<parameters.python-version>>
|
||||
- run:
|
||||
name: Setup Environment Variables
|
||||
command: |
|
||||
@@ -39,6 +50,11 @@ commands:
|
||||
echo "export SKIP_FORMAT_BUCK_CHECKS=1" >> $BASH_ENV
|
||||
echo "export PRINT_PARALLEL_OUTPUTS=1" >> $BASH_ENV
|
||||
|
||||
pre-steps-macos:
|
||||
steps:
|
||||
- pre-steps:
|
||||
python-version: "3.6.0"
|
||||
|
||||
post-steps:
|
||||
steps:
|
||||
- slack/status: *notify-on-master-failure
|
||||
@@ -89,15 +105,27 @@ executors:
|
||||
jobs:
|
||||
build-macos:
|
||||
macos:
|
||||
xcode: 11.3.0
|
||||
xcode: 9.4.1
|
||||
steps:
|
||||
- increase-max-open-files-on-macos
|
||||
- install-pyenv-on-macos
|
||||
- pre-steps
|
||||
- install-gflags-on-macos
|
||||
- pre-steps-macos
|
||||
- run: ulimit -S -n 1048576 && OPT=-DCIRCLECI make V=1 J=32 -j32 check | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-macos-cmake:
|
||||
macos:
|
||||
xcode: 9.4.1
|
||||
steps:
|
||||
- increase-max-open-files-on-macos
|
||||
- install-pyenv-on-macos
|
||||
- install-cmake-on-macos
|
||||
- install-gflags-on-macos
|
||||
- pre-steps-macos
|
||||
- run: ulimit -S -n 1048576 && (mkdir build && cd build && cmake -DWITH_GFLAGS=0 .. && make V=1 -j32) | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
@@ -108,6 +136,26 @@ jobs:
|
||||
- run: make V=1 J=32 -j32 check | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-mem-env:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- run: MEM_ENV=1 make V=1 J=32 -j32 check | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-encrypted-env:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- run: ENCRYPTED_ENV=1 make V=1 J=32 -j32 check | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-shared_lib-alt_namespace-status_checked:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
@@ -115,7 +163,7 @@ jobs:
|
||||
steps:
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- run: ASSERT_STATUS_CHECKED=1 TEST_UINT128_COMPAT=1 LIB_MODE=shared OPT="-DROCKSDB_NAMESPACE=alternative_rocksdb_ns" make V=1 -j32 all check_some | .circleci/cat_ignore_eagain
|
||||
- run: ASSERT_STATUS_CHECKED=1 TEST_UINT128_COMPAT=1 ROCKSDB_MODIFY_NPHASH=1 LIB_MODE=shared OPT="-DROCKSDB_NAMESPACE=alternative_rocksdb_ns" make V=1 -j32 all check_some | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-release:
|
||||
@@ -316,19 +364,96 @@ jobs:
|
||||
build-linux-java:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
resource_class: 2xlarge
|
||||
resource_class: large
|
||||
environment:
|
||||
JAVA_HOME: /usr/lib/jvm/java-1.8.0-openjdk-amd64
|
||||
steps:
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- run:
|
||||
name: "Build RocksDBJava"
|
||||
name: "Set Java Environment"
|
||||
command: |
|
||||
export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-amd64
|
||||
export PATH=$JAVA_HOME/bin:$PATH
|
||||
echo "JAVA_HOME=${JAVA_HOME}"
|
||||
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> $BASH_ENV
|
||||
which java && java -version
|
||||
which javac && javac -version
|
||||
make V=1 J=32 -j32 rocksdbjava jtest | .circleci/cat_ignore_eagain
|
||||
- run:
|
||||
name: "Build RocksDBJava Shared Library"
|
||||
command: make V=1 J=8 -j8 rocksdbjava | .circleci/cat_ignore_eagain
|
||||
- run:
|
||||
name: "Test RocksDBJava"
|
||||
command: make V=1 J=8 -j8 jtest | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-java-static:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
resource_class: large
|
||||
environment:
|
||||
JAVA_HOME: /usr/lib/jvm/java-1.8.0-openjdk-amd64
|
||||
steps:
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- run:
|
||||
name: "Set Java Environment"
|
||||
command: |
|
||||
echo "JAVA_HOME=${JAVA_HOME}"
|
||||
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> $BASH_ENV
|
||||
which java && java -version
|
||||
which javac && javac -version
|
||||
- run:
|
||||
name: "Build RocksDBJava Static Library"
|
||||
command: make V=1 J=8 -j8 rocksdbjavastatic | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-macos-java:
|
||||
macos:
|
||||
xcode: 9.4.1
|
||||
resource_class: medium
|
||||
environment:
|
||||
JAVA_HOME: /Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home
|
||||
steps:
|
||||
- increase-max-open-files-on-macos
|
||||
- install-pyenv-on-macos
|
||||
- install-gflags-on-macos
|
||||
- pre-steps-macos
|
||||
- run:
|
||||
name: "Set Java Environment"
|
||||
command: |
|
||||
echo "JAVA_HOME=${JAVA_HOME}"
|
||||
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> $BASH_ENV
|
||||
which java && java -version
|
||||
which javac && javac -version
|
||||
- run:
|
||||
name: "Build RocksDBJava Shared Library"
|
||||
command: make V=1 J=8 -j8 rocksdbjava | .circleci/cat_ignore_eagain
|
||||
- run:
|
||||
name: "Test RocksDBJava"
|
||||
command: make V=1 J=8 -j8 jtest | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-macos-java-static:
|
||||
macos:
|
||||
xcode: 9.4.1
|
||||
resource_class: medium
|
||||
environment:
|
||||
JAVA_HOME: /Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home
|
||||
steps:
|
||||
- increase-max-open-files-on-macos
|
||||
- install-pyenv-on-macos
|
||||
- install-gflags-on-macos
|
||||
- install-cmake-on-macos
|
||||
- pre-steps-macos
|
||||
- run:
|
||||
name: "Set Java Environment"
|
||||
command: |
|
||||
echo "JAVA_HOME=${JAVA_HOME}"
|
||||
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> $BASH_ENV
|
||||
which java && java -version
|
||||
which javac && javac -version
|
||||
- run:
|
||||
name: "Build RocksDBJava Static Library"
|
||||
command: make V=1 J=8 -j8 rocksdbjavastatic | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-examples:
|
||||
@@ -378,6 +503,12 @@ workflows:
|
||||
build-linux:
|
||||
jobs:
|
||||
- build-linux
|
||||
build-linux-mem-env:
|
||||
jobs:
|
||||
- build-linux-mem-env
|
||||
build-linux-encrypted-env:
|
||||
jobs:
|
||||
- build-linux-encrypted-env
|
||||
build-linux-shared_lib-alt_namespace-status_checked:
|
||||
jobs:
|
||||
- build-linux-shared_lib-alt_namespace-status_checked
|
||||
@@ -434,6 +565,9 @@ workflows:
|
||||
build-java:
|
||||
jobs:
|
||||
- build-linux-java
|
||||
- build-linux-java-static
|
||||
- build-macos-java
|
||||
- build-macos-java-static
|
||||
build-examples:
|
||||
jobs:
|
||||
- build-examples
|
||||
|
||||
@@ -10,7 +10,7 @@ $process = Start-Process "${PWD}\vs_installer.exe" -ArgumentList $VS_INSTALL_ARG
|
||||
Remove-Item -Path vs_installer.exe -Force
|
||||
$exitCode = $process.ExitCode
|
||||
if (($exitCode -ne 0) -and ($exitCode -ne 3010)) {
|
||||
echo "VS 2017 installer exited with code $exitCode, which should be one of [0, 3010]."
|
||||
echo "VS 2015 installer exited with code $exitCode, which should be one of [0, 3010]."
|
||||
curl.exe --retry 3 -kL $COLLECT_DOWNLOAD_LINK --output Collect.exe
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
echo "Download of the VS Collect tool failed."
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
- name: Download clang-format-diff.py
|
||||
uses: wei/wget@v1
|
||||
with:
|
||||
args: https://raw.githubusercontent.com/llvm-mirror/clang/master/tools/clang-format/clang-format-diff.py
|
||||
args: https://raw.githubusercontent.com/llvm/llvm-project/master/clang/tools/clang-format/clang-format-diff.py
|
||||
|
||||
- name: Check format
|
||||
run: VERBOSE_CHECK=1 make check-format
|
||||
|
||||
@@ -8,6 +8,7 @@ rocksdb.pc
|
||||
*.gcda
|
||||
*.gcno
|
||||
*.o
|
||||
*.o.tmp
|
||||
*.so
|
||||
*.so.*
|
||||
*_test
|
||||
|
||||
+10
-4
@@ -571,6 +571,7 @@ set(SOURCES
|
||||
db/arena_wrapped_db_iter.cc
|
||||
db/blob/blob_file_addition.cc
|
||||
db/blob/blob_file_builder.cc
|
||||
db/blob/blob_file_cache.cc
|
||||
db/blob/blob_file_garbage.cc
|
||||
db/blob/blob_file_meta.cc
|
||||
db/blob/blob_file_reader.cc
|
||||
@@ -686,6 +687,7 @@ set(SOURCES
|
||||
monitoring/thread_status_util_debug.cc
|
||||
options/cf_options.cc
|
||||
options/configurable.cc
|
||||
options/customizable.cc
|
||||
options/db_options.cc
|
||||
options/options.cc
|
||||
options/options_helper.cc
|
||||
@@ -809,8 +811,9 @@ set(SOURCES
|
||||
utilities/simulator_cache/sim_cache.cc
|
||||
utilities/table_properties_collectors/compact_on_deletion_collector.cc
|
||||
utilities/trace/file_trace_reader_writer.cc
|
||||
utilities/transactions/lock/lock_tracker.cc
|
||||
utilities/transactions/lock/point_lock_tracker.cc
|
||||
utilities/transactions/lock/lock_manager.cc
|
||||
utilities/transactions/lock/point/point_lock_tracker.cc
|
||||
utilities/transactions/lock/point/point_lock_manager.cc
|
||||
utilities/transactions/optimistic_transaction_db_impl.cc
|
||||
utilities/transactions/optimistic_transaction.cc
|
||||
utilities/transactions/pessimistic_transaction.cc
|
||||
@@ -818,7 +821,6 @@ set(SOURCES
|
||||
utilities/transactions/snapshot_checker.cc
|
||||
utilities/transactions/transaction_base.cc
|
||||
utilities/transactions/transaction_db_mutex_impl.cc
|
||||
utilities/transactions/transaction_lock_mgr.cc
|
||||
utilities/transactions/transaction_util.cc
|
||||
utilities/transactions/write_prepared_txn.cc
|
||||
utilities/transactions/write_prepared_txn_db.cc
|
||||
@@ -1044,8 +1046,10 @@ if(WITH_TESTS)
|
||||
cache/lru_cache_test.cc
|
||||
db/blob/blob_file_addition_test.cc
|
||||
db/blob/blob_file_builder_test.cc
|
||||
db/blob/blob_file_cache_test.cc
|
||||
db/blob/blob_file_garbage_test.cc
|
||||
db/blob/blob_file_reader_test.cc
|
||||
db/blob/db_blob_basic_test.cc
|
||||
db/blob/db_blob_index_test.cc
|
||||
db/column_family_test.cc
|
||||
db/compact_files_test.cc
|
||||
@@ -1139,6 +1143,7 @@ if(WITH_TESTS)
|
||||
monitoring/statistics_test.cc
|
||||
monitoring/stats_history_test.cc
|
||||
options/configurable_test.cc
|
||||
options/customizable_test.cc
|
||||
options/options_settable_test.cc
|
||||
options/options_test.cc
|
||||
table/block_based/block_based_filter_block_test.cc
|
||||
@@ -1175,6 +1180,7 @@ if(WITH_TESTS)
|
||||
util/random_test.cc
|
||||
util/rate_limiter_test.cc
|
||||
util/repeatable_thread_test.cc
|
||||
util/ribbon_test.cc
|
||||
util/slice_test.cc
|
||||
util/slice_transform_test.cc
|
||||
util/timer_queue_test.cc
|
||||
@@ -1201,7 +1207,7 @@ if(WITH_TESTS)
|
||||
utilities/table_properties_collectors/compact_on_deletion_collector_test.cc
|
||||
utilities/transactions/optimistic_transaction_test.cc
|
||||
utilities/transactions/transaction_test.cc
|
||||
utilities/transactions/transaction_lock_mgr_test.cc
|
||||
utilities/transactions/lock/point/point_lock_manager_test.cc
|
||||
utilities/transactions/write_prepared_transaction_test.cc
|
||||
utilities/transactions/write_unprepared_transaction_test.cc
|
||||
utilities/ttl/ttl_test.cc
|
||||
|
||||
+46
-1
@@ -1,13 +1,56 @@
|
||||
# Rocksdb Change Log
|
||||
## Unreleased
|
||||
## 6.15.5 (02/05/2021)
|
||||
### Bug Fixes
|
||||
* Since 6.15.0, `TransactionDB` returns error `Status`es from calls to `DeleteRange()` and calls to `Write()` where the `WriteBatch` contains a range deletion. Previously such operations may have succeeded while not providing the expected transactional guarantees. There are certain cases where range deletion can still be used on such DBs; see the API doc on `TransactionDB::DeleteRange()` for details.
|
||||
* `OptimisticTransactionDB` now returns error `Status`es from calls to `DeleteRange()` and calls to `Write()` where the `WriteBatch` contains a range deletion. Previously such operations may have succeeded while not providing the expected transactional guarantees.
|
||||
|
||||
## 6.15.4 (01/21/2021)
|
||||
### Bug Fixes
|
||||
* Fix a race condition between DB startups and shutdowns in managing the periodic background worker threads. One effect of this race condition could be the process being terminated.
|
||||
|
||||
## 6.15.3 (01/07/2021)
|
||||
### Bug Fixes
|
||||
* For Java builds, fix errors due to missing compression library includes.
|
||||
|
||||
## 6.15.2 (12/22/2020)
|
||||
### Bug Fixes
|
||||
* Fix failing RocksJava test compilation and add CI jobs
|
||||
* Fix jemalloc compilation issue on macOS
|
||||
* Fix build issues - compatibility with older gcc, older jemalloc libraries, docker warning when building i686 binaries
|
||||
|
||||
## 6.15.1 (12/01/2020)
|
||||
### Bug Fixes
|
||||
* Truncated WALs ending in incomplete records can no longer produce gaps in the recovered data when `WALRecoveryMode::kPointInTimeRecovery` is used. Gaps are still possible when WALs are truncated exactly on record boundaries.
|
||||
* Fix a bug where compressed blocks read by MultiGet are not inserted into the compressed block cache when use_direct_reads = true.
|
||||
|
||||
## 6.15.0 (11/13/2020)
|
||||
### Bug Fixes
|
||||
* Fixed a bug in the following combination of features: indexes with user keys (`format_version >= 3`), indexes are partitioned (`index_type == kTwoLevelIndexSearch`), and some index partitions are pinned in memory (`BlockBasedTableOptions::pin_l0_filter_and_index_blocks_in_cache`). The bug could cause keys to be truncated when read from the index leading to wrong read results or other unexpected behavior.
|
||||
* Fixed a bug when indexes are partitioned (`index_type == kTwoLevelIndexSearch`), some index partitions are pinned in memory (`BlockBasedTableOptions::pin_l0_filter_and_index_blocks_in_cache`), and partitions reads could be mixed between block cache and directly from the file (e.g., with `enable_index_compression == 1` and `mmap_read == 1`, partitions that were stored uncompressed due to poor compression ratio would be read directly from the file via mmap, while partitions that were stored compressed would be read from block cache). The bug could cause index partitions to be mistakenly considered empty during reads leading to wrong read results.
|
||||
* Since 6.12, memtable lookup should report unrecognized value_type as corruption (#7121).
|
||||
* Since 6.14, fix false positive flush/compaction `Status::Corruption` failure when `paranoid_file_checks == true` and range tombstones were written to the compaction output files.
|
||||
* Since 6.14, fix a bug that could cause a stalled write to crash with mixed of slowdown and no_slowdown writes (`WriteOptions.no_slowdown=true`).
|
||||
* Fixed a bug which causes hang in closing DB when refit level is set in opt build. It was because ContinueBackgroundWork() was called in assert statement which is a no op. It was introduced in 6.14.
|
||||
* Fixed a bug which causes Get() to return incorrect result when a key's merge operand is applied twice. This can occur if the thread performing Get() runs concurrently with a background flush thread and another thread writing to the MANIFEST file (PR6069).
|
||||
* Reverted a behavior change silently introduced in 6.14.2, in which the effects of the `ignore_unknown_options` flag (used in option parsing/loading functions) changed.
|
||||
* Reverted a behavior change silently introduced in 6.14, in which options parsing/loading functions began returning `NotFound` instead of `InvalidArgument` for option names not available in the present version.
|
||||
* Fixed MultiGet bugs it doesn't return valid data with user defined timestamp.
|
||||
* Fixed a potential bug caused by evaluating `TableBuilder::NeedCompact()` before `TableBuilder::Finish()` in compaction job. For example, the `NeedCompact()` method of `CompactOnDeletionCollector` returned by built-in `CompactOnDeletionCollectorFactory` requires `BlockBasedTable::Finish()` to return the correct result. The bug can cause a compaction-generated file not to be marked for future compaction based on deletion ratio.
|
||||
* Fixed a seek issue with prefix extractor and timestamp.
|
||||
* Fixed a bug of encoding and parsing BlockBasedTableOptions::read_amp_bytes_per_bit as a 64-bit integer.
|
||||
* Fixed the logic of populating native data structure for `read_amp_bytes_per_bit` during OPTIONS file parsing on big-endian architecture. Without this fix, original code introduced in PR7659, when running on big-endian machine, can mistakenly store read_amp_bytes_per_bit (an uint32) in little endian format. Future access to `read_amp_bytes_per_bit` will give wrong values. Little endian architecture is not affected.
|
||||
|
||||
|
||||
### Public API Change
|
||||
* Deprecate `BlockBasedTableOptions::pin_l0_filter_and_index_blocks_in_cache` and `BlockBasedTableOptions::pin_top_level_index_and_filter`. These options still take effect until users migrate to the replacement APIs in `BlockBasedTableOptions::metadata_cache_options`. Migration guidance can be found in the API comments on the deprecated options.
|
||||
* Add new API `DB::VerifyFileChecksums` to verify SST file checksum with corresponding entries in the MANIFEST if present. Current implementation requires scanning and recomputing file checksums.
|
||||
|
||||
### Behavior Changes
|
||||
* The dictionary compression settings specified in `ColumnFamilyOptions::compression_opts` now additionally affect files generated by flush and compaction to non-bottommost level. Previously those settings at most affected files generated by compaction to bottommost level, depending on whether `ColumnFamilyOptions::bottommost_compression_opts` overrode them. Users who relied on dictionary compression settings in `ColumnFamilyOptions::compression_opts` affecting only the bottommost level can keep the behavior by moving their dictionary settings to `ColumnFamilyOptions::bottommost_compression_opts` and setting its `enabled` flag.
|
||||
* When the `enabled` flag is set in `ColumnFamilyOptions::bottommost_compression_opts`, those compression options now take effect regardless of the value in `ColumnFamilyOptions::bottommost_compression`. Previously, those compression options only took effect when `ColumnFamilyOptions::bottommost_compression != kDisableCompressionOption`. Now, they additionally take effect when `ColumnFamilyOptions::bottommost_compression == kDisableCompressionOption` (such a setting causes bottommost compression type to fall back to `ColumnFamilyOptions::compression_per_level` if configured, and otherwise fall back to `ColumnFamilyOptions::compression`).
|
||||
|
||||
### New Features
|
||||
* An EXPERIMENTAL new Bloom alternative that saves about 30% space compared to Bloom filters, with about 3-4x construction time and similar query times is available using NewExperimentalRibbonFilterPolicy.
|
||||
|
||||
## 6.14 (10/09/2020)
|
||||
### Bug fixes
|
||||
@@ -29,6 +72,8 @@
|
||||
* Introduce options.check_flush_compaction_key_order with default value to be true. With this option, during flush and compaction, key order will be checked when writing to each SST file. If the order is violated, the flush or compaction will fail.
|
||||
* Added is_full_compaction to CompactionJobStats, so that the information is available through the EventListener interface.
|
||||
* Add more stats for MultiGet in Histogram to get number of data blocks, index blocks, filter blocks and sst files read from file system per level.
|
||||
* SST files have a new table property called db_host_id, which is set to the hostname by default. A new option in DBOptions, db_host_id, allows the property value to be overridden with a user specified string, or disable it completely by making the option string empty.
|
||||
* Methods to create customizable extensions -- such as TableFactory -- are exposed directly through the Customizable base class (from which these objects inherit). This change will allow these Customizable classes to be loaded and configured in a standard way (via CreateFromString). More information on how to write and use Customizable classes is in the customizable.h header file.
|
||||
|
||||
## 6.13 (09/12/2020)
|
||||
### Bug fixes
|
||||
|
||||
@@ -99,14 +99,14 @@ endif
|
||||
|
||||
ifneq ($(findstring rocksdbjava, $(MAKECMDGOALS)),)
|
||||
LIB_MODE=shared
|
||||
ifneq ($(findstring rocksdbjavastatic, $(MAKECMDGOALS)),)
|
||||
ifneq ($(findstring rocksdbjavastatic, $(MAKECMDGOALS)),)
|
||||
OBJ_DIR=jls
|
||||
ifneq ($(DEBUG_LEVEL),2)
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
ifeq ($(MAKECMDGOALS),rocksdbjavastaticpublish)
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
ifneq ($(DEBUG_LEVEL),2)
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
ifeq ($(MAKECMDGOALS),rocksdbjavastaticpublish)
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
else
|
||||
OBJ_DIR=jl
|
||||
endif
|
||||
@@ -414,6 +414,10 @@ ifdef TEST_UINT128_COMPAT
|
||||
PLATFORM_CCFLAGS += -DTEST_UINT128_COMPAT=1
|
||||
PLATFORM_CXXFLAGS += -DTEST_UINT128_COMPAT=1
|
||||
endif
|
||||
ifdef ROCKSDB_MODIFY_NPHASH
|
||||
PLATFORM_CCFLAGS += -DROCKSDB_MODIFY_NPHASH=1
|
||||
PLATFORM_CXXFLAGS += -DROCKSDB_MODIFY_NPHASH=1
|
||||
endif
|
||||
|
||||
# This (the first rule) must depend on "all".
|
||||
default: all
|
||||
@@ -553,7 +557,7 @@ PARALLEL_TEST = \
|
||||
persistent_cache_test \
|
||||
table_test \
|
||||
transaction_test \
|
||||
transaction_lock_mgr_test \
|
||||
point_lock_manager_test \
|
||||
write_prepared_transaction_test \
|
||||
write_unprepared_transaction_test \
|
||||
|
||||
@@ -577,6 +581,7 @@ ifdef ASSERT_STATUS_CHECKED
|
||||
lru_cache_test \
|
||||
blob_file_addition_test \
|
||||
blob_file_builder_test \
|
||||
blob_file_cache_test \
|
||||
blob_file_garbage_test \
|
||||
blob_file_reader_test \
|
||||
bloom_test \
|
||||
@@ -588,6 +593,7 @@ ifdef ASSERT_STATUS_CHECKED
|
||||
crc32c_test \
|
||||
dbformat_test \
|
||||
db_basic_test \
|
||||
db_blob_basic_test \
|
||||
db_flush_test \
|
||||
db_with_timestamp_basic_test \
|
||||
db_with_timestamp_compaction_test \
|
||||
@@ -619,8 +625,10 @@ ifdef ASSERT_STATUS_CHECKED
|
||||
mock_env_test \
|
||||
object_registry_test \
|
||||
prefix_test \
|
||||
plain_table_db_test \
|
||||
repair_test \
|
||||
configurable_test \
|
||||
customizable_test \
|
||||
options_settable_test \
|
||||
options_test \
|
||||
random_test \
|
||||
@@ -628,6 +636,7 @@ ifdef ASSERT_STATUS_CHECKED
|
||||
sst_file_reader_test \
|
||||
range_tombstone_fragmenter_test \
|
||||
repeatable_thread_test \
|
||||
ribbon_test \
|
||||
skiplist_test \
|
||||
slice_test \
|
||||
sst_dump_test \
|
||||
@@ -686,6 +695,7 @@ endif
|
||||
# Not necessarily well thought out or up-to-date, but matches old list
|
||||
TESTS_PLATFORM_DEPENDENT := \
|
||||
db_basic_test \
|
||||
db_blob_basic_test \
|
||||
db_with_timestamp_basic_test \
|
||||
db_encryption_test \
|
||||
db_test2 \
|
||||
@@ -704,6 +714,7 @@ TESTS_PLATFORM_DEPENDENT := \
|
||||
io_posix_test \
|
||||
hash_test \
|
||||
random_test \
|
||||
ribbon_test \
|
||||
thread_local_test \
|
||||
work_queue_test \
|
||||
rate_limiter_test \
|
||||
@@ -1416,6 +1427,9 @@ hash_test: $(OBJ_DIR)/util/hash_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
random_test: $(OBJ_DIR)/util/random_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
ribbon_test: $(OBJ_DIR)/util/ribbon_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
option_change_migration_test: $(OBJ_DIR)/utilities/option_change_migration/option_change_migration_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1461,6 +1475,9 @@ slice_transform_test: $(OBJ_DIR)/util/slice_transform_test.o $(TEST_LIBRARY) $(L
|
||||
db_basic_test: $(OBJ_DIR)/db/db_basic_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
db_blob_basic_test: $(OBJ_DIR)/db/blob/db_blob_basic_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
db_with_timestamp_basic_test: $(OBJ_DIR)/db/db_with_timestamp_basic_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1784,6 +1801,9 @@ compact_files_test: $(OBJ_DIR)/db/compact_files_test.o $(TEST_LIBRARY) $(LIBRARY
|
||||
configurable_test: options/configurable_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
customizable_test: options/customizable_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
options_test: $(OBJ_DIR)/options/options_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1835,7 +1855,7 @@ write_callback_test: $(OBJ_DIR)/db/write_callback_test.o $(TEST_LIBRARY) $(LIBRA
|
||||
heap_test: $(OBJ_DIR)/util/heap_test.o $(GTEST)
|
||||
$(AM_LINK)
|
||||
|
||||
transaction_lock_mgr_test: utilities/transactions/transaction_lock_mgr_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
point_lock_manager_test: utilities/transactions/lock/point/point_lock_manager_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
transaction_test: $(OBJ_DIR)/utilities/transactions/transaction_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
@@ -1913,6 +1933,9 @@ blob_file_addition_test: $(OBJ_DIR)/db/blob/blob_file_addition_test.o $(TEST_LIB
|
||||
blob_file_builder_test: $(OBJ_DIR)/db/blob/blob_file_builder_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
blob_file_cache_test: $(OBJ_DIR)/db/blob/blob_file_cache_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
blob_file_garbage_test: $(OBJ_DIR)/db/blob/blob_file_garbage_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -2050,8 +2073,8 @@ SNAPPY_DOWNLOAD_BASE ?= https://github.com/google/snappy/archive
|
||||
LZ4_VER ?= 1.9.2
|
||||
LZ4_SHA256 ?= 658ba6191fa44c92280d4aa2c271b0f4fbc0e34d249578dd05e50e76d0e5efcc
|
||||
LZ4_DOWNLOAD_BASE ?= https://github.com/lz4/lz4/archive
|
||||
ZSTD_VER ?= 1.4.4
|
||||
ZSTD_SHA256 ?= a364f5162c7d1a455cc915e8e3cf5f4bd8b75d09bc0f53965b0c9ca1383c52c8
|
||||
ZSTD_VER ?= 1.4.5
|
||||
ZSTD_SHA256 ?= 734d1f565c42f691f8420c8d06783ad818060fc390dee43ae0a89f86d0a4f8c2
|
||||
ZSTD_DOWNLOAD_BASE ?= https://github.com/facebook/zstd/archive
|
||||
CURL_SSL_OPTS ?= --tlsv1
|
||||
|
||||
@@ -2083,78 +2106,78 @@ ifeq ($(PLATFORM), OS_AIX)
|
||||
SNAPPY_MAKE_TARGET = libsnappy.la
|
||||
endif
|
||||
ifeq ($(PLATFORM), OS_OPENBSD)
|
||||
JAVA_INCLUDE = -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/openbsd
|
||||
JAVA_INCLUDE = -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/openbsd
|
||||
ROCKSDBJNILIB = librocksdbjni-openbsd$(ARCH).so
|
||||
ROCKSDB_JAR = rocksdbjni-$(ROCKSDB_JAVA_VERSION)-openbsd$(ARCH).jar
|
||||
ROCKSDB_JAR = rocksdbjni-$(ROCKSDB_JAVA_VERSION)-openbsd$(ARCH).jar
|
||||
endif
|
||||
|
||||
libz.a:
|
||||
-rm -rf zlib-$(ZLIB_VER)
|
||||
ifeq (,$(wildcard ./zlib-$(ZLIB_VER).tar.gz))
|
||||
zlib-$(ZLIB_VER).tar.gz:
|
||||
curl --fail --output zlib-$(ZLIB_VER).tar.gz --location ${ZLIB_DOWNLOAD_BASE}/zlib-$(ZLIB_VER).tar.gz
|
||||
endif
|
||||
ZLIB_SHA256_ACTUAL=`$(SHA256_CMD) zlib-$(ZLIB_VER).tar.gz | cut -d ' ' -f 1`; \
|
||||
if [ "$(ZLIB_SHA256)" != "$$ZLIB_SHA256_ACTUAL" ]; then \
|
||||
echo zlib-$(ZLIB_VER).tar.gz checksum mismatch, expected=\"$(ZLIB_SHA256)\" actual=\"$$ZLIB_SHA256_ACTUAL\"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
libz.a: zlib-$(ZLIB_VER).tar.gz
|
||||
-rm -rf zlib-$(ZLIB_VER)
|
||||
tar xvzf zlib-$(ZLIB_VER).tar.gz
|
||||
cd zlib-$(ZLIB_VER) && CFLAGS='-fPIC ${EXTRA_CFLAGS}' LDFLAGS='${EXTRA_LDFLAGS}' ./configure --static && $(MAKE)
|
||||
cp zlib-$(ZLIB_VER)/libz.a .
|
||||
|
||||
libbz2.a:
|
||||
-rm -rf bzip2-$(BZIP2_VER)
|
||||
ifeq (,$(wildcard ./bzip2-$(BZIP2_VER).tar.gz))
|
||||
bzip2-$(BZIP2_VER).tar.gz:
|
||||
curl --fail --output bzip2-$(BZIP2_VER).tar.gz --location ${CURL_SSL_OPTS} ${BZIP2_DOWNLOAD_BASE}/bzip2-$(BZIP2_VER).tar.gz
|
||||
endif
|
||||
BZIP2_SHA256_ACTUAL=`$(SHA256_CMD) bzip2-$(BZIP2_VER).tar.gz | cut -d ' ' -f 1`; \
|
||||
if [ "$(BZIP2_SHA256)" != "$$BZIP2_SHA256_ACTUAL" ]; then \
|
||||
echo bzip2-$(BZIP2_VER).tar.gz checksum mismatch, expected=\"$(BZIP2_SHA256)\" actual=\"$$BZIP2_SHA256_ACTUAL\"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
libbz2.a: bzip2-$(BZIP2_VER).tar.gz
|
||||
-rm -rf bzip2-$(BZIP2_VER)
|
||||
tar xvzf bzip2-$(BZIP2_VER).tar.gz
|
||||
cd bzip2-$(BZIP2_VER) && $(MAKE) CFLAGS='-fPIC -O2 -g -D_FILE_OFFSET_BITS=64 ${EXTRA_CFLAGS}' AR='ar ${EXTRA_ARFLAGS}'
|
||||
cp bzip2-$(BZIP2_VER)/libbz2.a .
|
||||
|
||||
libsnappy.a:
|
||||
-rm -rf snappy-$(SNAPPY_VER)
|
||||
ifeq (,$(wildcard ./snappy-$(SNAPPY_VER).tar.gz))
|
||||
snappy-$(SNAPPY_VER).tar.gz:
|
||||
curl --fail --output snappy-$(SNAPPY_VER).tar.gz --location ${CURL_SSL_OPTS} ${SNAPPY_DOWNLOAD_BASE}/$(SNAPPY_VER).tar.gz
|
||||
endif
|
||||
SNAPPY_SHA256_ACTUAL=`$(SHA256_CMD) snappy-$(SNAPPY_VER).tar.gz | cut -d ' ' -f 1`; \
|
||||
if [ "$(SNAPPY_SHA256)" != "$$SNAPPY_SHA256_ACTUAL" ]; then \
|
||||
echo snappy-$(SNAPPY_VER).tar.gz checksum mismatch, expected=\"$(SNAPPY_SHA256)\" actual=\"$$SNAPPY_SHA256_ACTUAL\"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
libsnappy.a: snappy-$(SNAPPY_VER).tar.gz
|
||||
-rm -rf snappy-$(SNAPPY_VER)
|
||||
tar xvzf snappy-$(SNAPPY_VER).tar.gz
|
||||
mkdir snappy-$(SNAPPY_VER)/build
|
||||
cd snappy-$(SNAPPY_VER)/build && CFLAGS='${EXTRA_CFLAGS}' CXXFLAGS='${EXTRA_CXXFLAGS}' LDFLAGS='${EXTRA_LDFLAGS}' cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON .. && $(MAKE) ${SNAPPY_MAKE_TARGET}
|
||||
cp snappy-$(SNAPPY_VER)/build/libsnappy.a .
|
||||
|
||||
liblz4.a:
|
||||
-rm -rf lz4-$(LZ4_VER)
|
||||
ifeq (,$(wildcard ./lz4-$(LZ4_VER).tar.gz))
|
||||
lz4-$(LZ4_VER).tar.gz:
|
||||
curl --fail --output lz4-$(LZ4_VER).tar.gz --location ${CURL_SSL_OPTS} ${LZ4_DOWNLOAD_BASE}/v$(LZ4_VER).tar.gz
|
||||
endif
|
||||
LZ4_SHA256_ACTUAL=`$(SHA256_CMD) lz4-$(LZ4_VER).tar.gz | cut -d ' ' -f 1`; \
|
||||
if [ "$(LZ4_SHA256)" != "$$LZ4_SHA256_ACTUAL" ]; then \
|
||||
echo lz4-$(LZ4_VER).tar.gz checksum mismatch, expected=\"$(LZ4_SHA256)\" actual=\"$$LZ4_SHA256_ACTUAL\"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
liblz4.a: lz4-$(LZ4_VER).tar.gz
|
||||
-rm -rf lz4-$(LZ4_VER)
|
||||
tar xvzf lz4-$(LZ4_VER).tar.gz
|
||||
cd lz4-$(LZ4_VER)/lib && $(MAKE) CFLAGS='-fPIC -O2 ${EXTRA_CFLAGS}' all
|
||||
cp lz4-$(LZ4_VER)/lib/liblz4.a .
|
||||
|
||||
libzstd.a:
|
||||
-rm -rf zstd-$(ZSTD_VER)
|
||||
ifeq (,$(wildcard ./zstd-$(ZSTD_VER).tar.gz))
|
||||
zstd-$(ZSTD_VER).tar.gz:
|
||||
curl --fail --output zstd-$(ZSTD_VER).tar.gz --location ${CURL_SSL_OPTS} ${ZSTD_DOWNLOAD_BASE}/v$(ZSTD_VER).tar.gz
|
||||
endif
|
||||
ZSTD_SHA256_ACTUAL=`$(SHA256_CMD) zstd-$(ZSTD_VER).tar.gz | cut -d ' ' -f 1`; \
|
||||
if [ "$(ZSTD_SHA256)" != "$$ZSTD_SHA256_ACTUAL" ]; then \
|
||||
echo zstd-$(ZSTD_VER).tar.gz checksum mismatch, expected=\"$(ZSTD_SHA256)\" actual=\"$$ZSTD_SHA256_ACTUAL\"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
libzstd.a: zstd-$(ZSTD_VER).tar.gz
|
||||
-rm -rf zstd-$(ZSTD_VER)
|
||||
tar xvzf zstd-$(ZSTD_VER).tar.gz
|
||||
cd zstd-$(ZSTD_VER)/lib && DESTDIR=. PREFIX= $(MAKE) CFLAGS='-fPIC -O2 ${EXTRA_CFLAGS}' install
|
||||
cp zstd-$(ZSTD_VER)/lib/libzstd.a .
|
||||
@@ -2165,14 +2188,23 @@ JAVA_COMPRESSIONS = libz.a libbz2.a libsnappy.a liblz4.a libzstd.a
|
||||
endif
|
||||
|
||||
JAVA_STATIC_FLAGS = -DZLIB -DBZIP2 -DSNAPPY -DLZ4 -DZSTD
|
||||
JAVA_STATIC_INCLUDES = -I./zlib-$(ZLIB_VER) -I./bzip2-$(BZIP2_VER) -I./snappy-$(SNAPPY_VER) -I./lz4-$(LZ4_VER)/lib -I./zstd-$(ZSTD_VER)/lib/include
|
||||
ifneq ($(findstring rocksdbjavastatic, $(MAKECMDGOALS)),)
|
||||
JAVA_STATIC_INCLUDES = -I./zlib-$(ZLIB_VER) -I./bzip2-$(BZIP2_VER) -I./snappy-$(SNAPPY_VER) -I./snappy-$(SNAPPY_VER)/build -I./lz4-$(LZ4_VER)/lib -I./zstd-$(ZSTD_VER)/lib -I./zstd-$(ZSTD_VER)/lib/dictBuilder
|
||||
|
||||
ifneq ($(findstring rocksdbjavastatic, $(filter-out rocksdbjavastatic_deps, $(MAKECMDGOALS))),)
|
||||
CXXFLAGS += $(JAVA_STATIC_FLAGS) $(JAVA_STATIC_INCLUDES)
|
||||
CFLAGS += $(JAVA_STATIC_FLAGS) $(JAVA_STATIC_INCLUDES)
|
||||
CFLAGS += $(JAVA_STATIC_FLAGS) $(JAVA_STATIC_INCLUDES)
|
||||
endif
|
||||
rocksdbjavastatic: $(LIB_OBJECTS) $(JAVA_COMPRESSIONS)
|
||||
cd java;$(MAKE) javalib;
|
||||
rm -f ./java/target/$(ROCKSDBJNILIB)
|
||||
rocksdbjavastatic:
|
||||
ifeq ($(JAVA_HOME),)
|
||||
$(error JAVA_HOME is not set)
|
||||
endif
|
||||
$(MAKE) rocksdbjavastatic_deps
|
||||
$(MAKE) rocksdbjavastatic_libobjects
|
||||
$(MAKE) rocksdbjavastatic_javalib
|
||||
|
||||
rocksdbjavastatic_javalib:
|
||||
cd java;$(MAKE) javalib
|
||||
rm -f java/target/$(ROCKSDBJNILIB)
|
||||
$(CXX) $(CXXFLAGS) -I./java/. $(JAVA_INCLUDE) -shared -fPIC \
|
||||
-o ./java/target/$(ROCKSDBJNILIB) $(JNI_NATIVE_SOURCES) \
|
||||
$(LIB_OBJECTS) $(COVERAGEFLAGS) \
|
||||
@@ -2189,6 +2221,10 @@ rocksdbjavastatic: $(LIB_OBJECTS) $(JAVA_COMPRESSIONS)
|
||||
openssl sha1 java/target/$(ROCKSDB_JAVADOCS_JAR) | sed 's/.*= \([0-9a-f]*\)/\1/' > java/target/$(ROCKSDB_JAVADOCS_JAR).sha1
|
||||
openssl sha1 java/target/$(ROCKSDB_SOURCES_JAR) | sed 's/.*= \([0-9a-f]*\)/\1/' > java/target/$(ROCKSDB_SOURCES_JAR).sha1
|
||||
|
||||
rocksdbjavastatic_deps: $(JAVA_COMPRESSIONS)
|
||||
|
||||
rocksdbjavastatic_libobjects: $(LIB_OBJECTS)
|
||||
|
||||
rocksdbjavastaticrelease: rocksdbjavastatic
|
||||
cd java/crossbuild && (vagrant destroy -f || true) && vagrant up linux32 && vagrant halt linux32 && vagrant up linux64 && vagrant halt linux64 && vagrant up linux64-musl && vagrant halt linux64-musl
|
||||
cd java;jar -cf target/$(ROCKSDB_JAR_ALL) HISTORY*.md
|
||||
@@ -2204,7 +2240,7 @@ rocksdbjavastaticreleasedocker: rocksdbjavastatic rocksdbjavastaticdockerx86 roc
|
||||
|
||||
rocksdbjavastaticdockerx86:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_x86-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:centos6_x86-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
|
||||
docker run --rm --name rocksdb_linux_x86-be --platform linux/386 --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:centos6_x86-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
|
||||
|
||||
rocksdbjavastaticdockerx86_64:
|
||||
mkdir -p java/target
|
||||
@@ -2220,7 +2256,7 @@ rocksdbjavastaticdockerarm64v8:
|
||||
|
||||
rocksdbjavastaticdockerx86musl:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_x86-musl-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:alpine3_x86-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
|
||||
docker run --rm --name rocksdb_linux_x86-musl-be --platform linux/386 --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:alpine3_x86-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
|
||||
|
||||
rocksdbjavastaticdockerx86_64musl:
|
||||
mkdir -p java/target
|
||||
@@ -2258,6 +2294,9 @@ jl/%.o: %.cc
|
||||
$(AM_V_CC)mkdir -p $(@D) && $(CXX) $(CXXFLAGS) -fPIC -c $< -o $@ $(COVERAGEFLAGS)
|
||||
|
||||
rocksdbjava: $(LIB_OBJECTS)
|
||||
ifeq ($(JAVA_HOME),)
|
||||
$(error JAVA_HOME is not set)
|
||||
endif
|
||||
$(AM_V_GEN)cd java;$(MAKE) javalib;
|
||||
$(AM_V_at)rm -f ./java/target/$(ROCKSDBJNILIB)
|
||||
$(AM_V_at)$(CXX) $(CXXFLAGS) -I./java/. $(JAVA_INCLUDE) -shared -fPIC -o ./java/target/$(ROCKSDBJNILIB) $(JNI_NATIVE_SOURCES) $(LIB_OBJECTS) $(JAVA_LDFLAGS) $(COVERAGEFLAGS)
|
||||
@@ -2388,6 +2427,8 @@ ifneq ($(MAKECMDGOALS),clean)
|
||||
ifneq ($(MAKECMDGOALS),format)
|
||||
ifneq ($(MAKECMDGOALS),jclean)
|
||||
ifneq ($(MAKECMDGOALS),jtest)
|
||||
ifneq ($(MAKECMDGOALS),rocksdbjavastatic)
|
||||
ifneq ($(MAKECMDGOALS),rocksdbjavastatic_deps)
|
||||
ifneq ($(MAKECMDGOALS),package)
|
||||
ifneq ($(MAKECMDGOALS),analyze)
|
||||
-include $(DEPFILES)
|
||||
@@ -2397,3 +2438,5 @@ endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
[](https://circleci.com/gh/facebook/rocksdb)
|
||||
[](https://travis-ci.org/facebook/rocksdb)
|
||||
[](https://ci.appveyor.com/project/Facebook/rocksdb/branch/master)
|
||||
[](http://140.211.168.68:8080/job/Rocksdb)
|
||||
[](http://140-211-168-68-openstack.osuosl.org:8080/job/rocksdb)
|
||||
|
||||
RocksDB is developed and maintained by Facebook Database Engineering Team.
|
||||
It is built on earlier work on [LevelDB](https://github.com/google/leveldb) by Sanjay Ghemawat (sanjay@google.com)
|
||||
|
||||
@@ -135,6 +135,7 @@ cpp_library(
|
||||
"db/arena_wrapped_db_iter.cc",
|
||||
"db/blob/blob_file_addition.cc",
|
||||
"db/blob/blob_file_builder.cc",
|
||||
"db/blob/blob_file_cache.cc",
|
||||
"db/blob/blob_file_garbage.cc",
|
||||
"db/blob/blob_file_meta.cc",
|
||||
"db/blob/blob_file_reader.cc",
|
||||
@@ -254,6 +255,7 @@ cpp_library(
|
||||
"monitoring/thread_status_util_debug.cc",
|
||||
"options/cf_options.cc",
|
||||
"options/configurable.cc",
|
||||
"options/customizable.cc",
|
||||
"options/db_options.cc",
|
||||
"options/options.cc",
|
||||
"options/options_helper.cc",
|
||||
@@ -383,8 +385,9 @@ cpp_library(
|
||||
"utilities/simulator_cache/sim_cache.cc",
|
||||
"utilities/table_properties_collectors/compact_on_deletion_collector.cc",
|
||||
"utilities/trace/file_trace_reader_writer.cc",
|
||||
"utilities/transactions/lock/lock_tracker.cc",
|
||||
"utilities/transactions/lock/point_lock_tracker.cc",
|
||||
"utilities/transactions/lock/lock_manager.cc",
|
||||
"utilities/transactions/lock/point/point_lock_manager.cc",
|
||||
"utilities/transactions/lock/point/point_lock_tracker.cc",
|
||||
"utilities/transactions/optimistic_transaction.cc",
|
||||
"utilities/transactions/optimistic_transaction_db_impl.cc",
|
||||
"utilities/transactions/pessimistic_transaction.cc",
|
||||
@@ -392,7 +395,6 @@ cpp_library(
|
||||
"utilities/transactions/snapshot_checker.cc",
|
||||
"utilities/transactions/transaction_base.cc",
|
||||
"utilities/transactions/transaction_db_mutex_impl.cc",
|
||||
"utilities/transactions/transaction_lock_mgr.cc",
|
||||
"utilities/transactions/transaction_util.cc",
|
||||
"utilities/transactions/write_prepared_txn.cc",
|
||||
"utilities/transactions/write_prepared_txn_db.cc",
|
||||
@@ -423,6 +425,7 @@ cpp_library(
|
||||
"db/arena_wrapped_db_iter.cc",
|
||||
"db/blob/blob_file_addition.cc",
|
||||
"db/blob/blob_file_builder.cc",
|
||||
"db/blob/blob_file_cache.cc",
|
||||
"db/blob/blob_file_garbage.cc",
|
||||
"db/blob/blob_file_meta.cc",
|
||||
"db/blob/blob_file_reader.cc",
|
||||
@@ -542,6 +545,7 @@ cpp_library(
|
||||
"monitoring/thread_status_util_debug.cc",
|
||||
"options/cf_options.cc",
|
||||
"options/configurable.cc",
|
||||
"options/customizable.cc",
|
||||
"options/db_options.cc",
|
||||
"options/options.cc",
|
||||
"options/options_helper.cc",
|
||||
@@ -671,8 +675,9 @@ cpp_library(
|
||||
"utilities/simulator_cache/sim_cache.cc",
|
||||
"utilities/table_properties_collectors/compact_on_deletion_collector.cc",
|
||||
"utilities/trace/file_trace_reader_writer.cc",
|
||||
"utilities/transactions/lock/lock_tracker.cc",
|
||||
"utilities/transactions/lock/point_lock_tracker.cc",
|
||||
"utilities/transactions/lock/lock_manager.cc",
|
||||
"utilities/transactions/lock/point/point_lock_manager.cc",
|
||||
"utilities/transactions/lock/point/point_lock_tracker.cc",
|
||||
"utilities/transactions/optimistic_transaction.cc",
|
||||
"utilities/transactions/optimistic_transaction_db_impl.cc",
|
||||
"utilities/transactions/pessimistic_transaction.cc",
|
||||
@@ -680,7 +685,6 @@ cpp_library(
|
||||
"utilities/transactions/snapshot_checker.cc",
|
||||
"utilities/transactions/transaction_base.cc",
|
||||
"utilities/transactions/transaction_db_mutex_impl.cc",
|
||||
"utilities/transactions/transaction_lock_mgr.cc",
|
||||
"utilities/transactions/transaction_util.cc",
|
||||
"utilities/transactions/write_prepared_txn.cc",
|
||||
"utilities/transactions/write_prepared_txn_db.cc",
|
||||
@@ -771,26 +775,24 @@ cpp_library(
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
)
|
||||
|
||||
if not is_opt_mode:
|
||||
cpp_binary(
|
||||
name = "c_test_bin",
|
||||
srcs = ["db/c_test.c"],
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
deps = [":rocksdb_test_lib"],
|
||||
)
|
||||
cpp_binary(
|
||||
name = "c_test_bin",
|
||||
srcs = ["db/c_test.c"],
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
deps = [":rocksdb_test_lib"],
|
||||
) if not is_opt_mode else None
|
||||
|
||||
if not is_opt_mode:
|
||||
custom_unittest(
|
||||
"c_test",
|
||||
command = [
|
||||
native.package_name() + "/buckifier/rocks_test_runner.sh",
|
||||
"$(location :{})".format("c_test_bin"),
|
||||
],
|
||||
type = "simple",
|
||||
)
|
||||
custom_unittest(
|
||||
"c_test",
|
||||
command = [
|
||||
native.package_name() + "/buckifier/rocks_test_runner.sh",
|
||||
"$(location :{})".format("c_test_bin"),
|
||||
],
|
||||
type = "simple",
|
||||
) if not is_opt_mode else None
|
||||
|
||||
cpp_library(
|
||||
name = "env_basic_test_lib",
|
||||
@@ -857,6 +859,13 @@ ROCKS_TESTS = [
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"blob_file_cache_test",
|
||||
"db/blob/blob_file_cache_test.cc",
|
||||
"serial",
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"blob_file_garbage_test",
|
||||
"db/blob/blob_file_garbage_test.cc",
|
||||
@@ -1081,6 +1090,13 @@ ROCKS_TESTS = [
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"customizable_test",
|
||||
"options/customizable_test.cc",
|
||||
"serial",
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"data_block_hash_index_test",
|
||||
"table/block_based/data_block_hash_index_test.cc",
|
||||
@@ -1095,6 +1111,13 @@ ROCKS_TESTS = [
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"db_blob_basic_test",
|
||||
"db/blob/db_blob_basic_test.cc",
|
||||
"serial",
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"db_blob_index_test",
|
||||
"db/blob/db_blob_index_test.cc",
|
||||
@@ -1711,6 +1734,13 @@ ROCKS_TESTS = [
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"point_lock_manager_test",
|
||||
"utilities/transactions/lock/point/point_lock_manager_test.cc",
|
||||
"parallel",
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"prefetch_test",
|
||||
"file/prefetch_test.cc",
|
||||
@@ -1781,6 +1811,13 @@ ROCKS_TESTS = [
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"ribbon_test",
|
||||
"util/ribbon_test.cc",
|
||||
"serial",
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"sim_cache_test",
|
||||
"utilities/simulator_cache/sim_cache_test.cc",
|
||||
@@ -1900,13 +1937,6 @@ ROCKS_TESTS = [
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"transaction_lock_mgr_test",
|
||||
"utilities/transactions/transaction_lock_mgr_test.cc",
|
||||
"parallel",
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"transaction_test",
|
||||
"utilities/transactions/transaction_test.cc",
|
||||
|
||||
@@ -79,26 +79,24 @@ class TARGETSBuilder(object):
|
||||
|
||||
def add_c_test(self):
|
||||
self.targets_file.write(b"""
|
||||
if not is_opt_mode:
|
||||
cpp_binary(
|
||||
name = "c_test_bin",
|
||||
srcs = ["db/c_test.c"],
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
deps = [":rocksdb_test_lib"],
|
||||
)
|
||||
cpp_binary(
|
||||
name = "c_test_bin",
|
||||
srcs = ["db/c_test.c"],
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
deps = [":rocksdb_test_lib"],
|
||||
) if not is_opt_mode else None
|
||||
|
||||
if not is_opt_mode:
|
||||
custom_unittest(
|
||||
"c_test",
|
||||
command = [
|
||||
native.package_name() + "/buckifier/rocks_test_runner.sh",
|
||||
"$(location :{})".format("c_test_bin"),
|
||||
],
|
||||
type = "simple",
|
||||
)
|
||||
custom_unittest(
|
||||
"c_test",
|
||||
command = [
|
||||
native.package_name() + "/buckifier/rocks_test_runner.sh",
|
||||
"$(location :{})".format("c_test_bin"),
|
||||
],
|
||||
type = "simple",
|
||||
) if not is_opt_mode else None
|
||||
""")
|
||||
|
||||
def register_test(self,
|
||||
|
||||
+10
-12
@@ -52,15 +52,15 @@ else
|
||||
else
|
||||
echo "You didn't have clang-format-diff.py and/or clang-format available in your computer!"
|
||||
echo "You can download clang-format-diff.py by running: "
|
||||
echo " curl --location http://goo.gl/iUW1u2 -o ${CLANG_FORMAT_DIFF}"
|
||||
echo " curl --location https://tinyurl.com/y2kvokof -o ${REPO_ROOT}/clang-format-diff.py"
|
||||
echo "You can download clang-format by running:"
|
||||
echo " brew install clang-format"
|
||||
echo " Or"
|
||||
echo " apt install clang-format"
|
||||
echo " This might work too:"
|
||||
echo " yum install git-clang-format"
|
||||
echo "Then, move both files (i.e. ${CLANG_FORMAT_DIFF} and clang-format) to some directory within PATH=${PATH}"
|
||||
echo "and make sure ${CLANG_FORMAT_DIFF} is executable."
|
||||
echo "Then make sure clang-format is available and executable from \$PATH:"
|
||||
echo " clang-format --version"
|
||||
exit 128
|
||||
fi
|
||||
# Check argparse pre-req on interpreter, or it will fail
|
||||
@@ -75,17 +75,15 @@ else
|
||||
exit 129
|
||||
fi
|
||||
# Unfortunately, some machines have a Python2 clang-format-diff.py
|
||||
# installed but only a Python3 interpreter installed. Rather than trying
|
||||
# different Python versions that might be installed, we can try migrating
|
||||
# the code to Python3 if it looks like Python2
|
||||
# installed but only a Python3 interpreter installed. Unfortunately,
|
||||
# automatic 2to3 migration is insufficient, so suggest downloading latest.
|
||||
if grep -q "print '" "$CFD_PATH" && \
|
||||
${PYTHON:-python3} --version | grep -q 'ython 3'; then
|
||||
if [ ! -f "$REPO_ROOT/.py3/clang-format-diff.py" ]; then
|
||||
echo "Migrating $CFD_PATH to Python3 in a hidden file"
|
||||
mkdir -p "$REPO_ROOT/.py3"
|
||||
${PYTHON:-python3} -m lib2to3 -w -n -o "$REPO_ROOT/.py3" "$CFD_PATH" > /dev/null || exit 128
|
||||
fi
|
||||
CFD_PATH="$REPO_ROOT/.py3/clang-format-diff.py"
|
||||
echo "You have clang-format-diff.py for Python 2 but are using a Python 3"
|
||||
echo "interpreter (${PYTHON:-python3})."
|
||||
echo "You can download clang-format-diff.py for Python 3 by running: "
|
||||
echo " curl --location https://tinyurl.com/y2kvokof -o ${REPO_ROOT}/clang-format-diff.py"
|
||||
exit 130
|
||||
fi
|
||||
CLANG_FORMAT_DIFF="${PYTHON:-python3} $CFD_PATH"
|
||||
# This had better work after all those checks
|
||||
|
||||
Vendored
+114
@@ -0,0 +1,114 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// Returns the cached value given a cache handle.
|
||||
template <typename T>
|
||||
T* GetFromCacheHandle(Cache* cache, Cache::Handle* handle) {
|
||||
assert(cache);
|
||||
assert(handle);
|
||||
|
||||
return static_cast<T*>(cache->Value(handle));
|
||||
}
|
||||
|
||||
// Simple generic deleter for Cache (to be used with Cache::Insert).
|
||||
template <typename T>
|
||||
void DeleteCacheEntry(const Slice& /* key */, void* value) {
|
||||
delete static_cast<T*>(value);
|
||||
}
|
||||
|
||||
// Turns a T* into a Slice so it can be used as a key with Cache.
|
||||
template <typename T>
|
||||
Slice GetSlice(const T* t) {
|
||||
return Slice(reinterpret_cast<const char*>(t), sizeof(T));
|
||||
}
|
||||
|
||||
// Generic resource management object for cache handles that releases the handle
|
||||
// when destroyed. Has unique ownership of the handle, so copying it is not
|
||||
// allowed, while moving it transfers ownership.
|
||||
template <typename T>
|
||||
class CacheHandleGuard {
|
||||
public:
|
||||
CacheHandleGuard() = default;
|
||||
|
||||
CacheHandleGuard(Cache* cache, Cache::Handle* handle)
|
||||
: cache_(cache),
|
||||
handle_(handle),
|
||||
value_(GetFromCacheHandle<T>(cache, handle)) {
|
||||
assert(cache_ && handle_ && value_);
|
||||
}
|
||||
|
||||
CacheHandleGuard(const CacheHandleGuard&) = delete;
|
||||
CacheHandleGuard& operator=(const CacheHandleGuard&) = delete;
|
||||
|
||||
CacheHandleGuard(CacheHandleGuard&& rhs) noexcept
|
||||
: cache_(rhs.cache_), handle_(rhs.handle_), value_(rhs.value_) {
|
||||
assert((!cache_ && !handle_ && !value_) || (cache_ && handle_ && value_));
|
||||
|
||||
rhs.ResetFields();
|
||||
}
|
||||
|
||||
CacheHandleGuard& operator=(CacheHandleGuard&& rhs) noexcept {
|
||||
if (this == &rhs) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
ReleaseHandle();
|
||||
|
||||
cache_ = rhs.cache_;
|
||||
handle_ = rhs.handle_;
|
||||
value_ = rhs.value_;
|
||||
|
||||
assert((!cache_ && !handle_ && !value_) || (cache_ && handle_ && value_));
|
||||
|
||||
rhs.ResetFields();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
~CacheHandleGuard() { ReleaseHandle(); }
|
||||
|
||||
bool IsEmpty() const { return !handle_; }
|
||||
|
||||
Cache* GetCache() const { return cache_; }
|
||||
Cache::Handle* GetCacheHandle() const { return handle_; }
|
||||
T* GetValue() const { return value_; }
|
||||
|
||||
void Reset() {
|
||||
ReleaseHandle();
|
||||
ResetFields();
|
||||
}
|
||||
|
||||
private:
|
||||
void ReleaseHandle() {
|
||||
if (IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(cache_);
|
||||
cache_->Release(handle_);
|
||||
}
|
||||
|
||||
void ResetFields() {
|
||||
cache_ = nullptr;
|
||||
handle_ = nullptr;
|
||||
value_ = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
Cache* cache_ = nullptr;
|
||||
Cache::Handle* handle_ = nullptr;
|
||||
T* value_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
-1
@@ -11,7 +11,6 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
|
||||
#include "util/mutexlock.h"
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/blob/blob_file_cache.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
|
||||
#include "db/blob/blob_file_reader.h"
|
||||
#include "options/cf_options.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/hash.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
BlobFileCache::BlobFileCache(Cache* cache,
|
||||
const ImmutableCFOptions* immutable_cf_options,
|
||||
const FileOptions* file_options,
|
||||
uint32_t column_family_id,
|
||||
HistogramImpl* blob_file_read_hist)
|
||||
: cache_(cache),
|
||||
mutex_(kNumberOfMutexStripes, GetSliceNPHash64),
|
||||
immutable_cf_options_(immutable_cf_options),
|
||||
file_options_(file_options),
|
||||
column_family_id_(column_family_id),
|
||||
blob_file_read_hist_(blob_file_read_hist) {
|
||||
assert(cache_);
|
||||
assert(immutable_cf_options_);
|
||||
assert(file_options_);
|
||||
}
|
||||
|
||||
Status BlobFileCache::GetBlobFileReader(
|
||||
uint64_t blob_file_number,
|
||||
CacheHandleGuard<BlobFileReader>* blob_file_reader) {
|
||||
assert(blob_file_reader);
|
||||
assert(blob_file_reader->IsEmpty());
|
||||
|
||||
const Slice key = GetSlice(&blob_file_number);
|
||||
|
||||
assert(cache_);
|
||||
|
||||
Cache::Handle* handle = cache_->Lookup(key);
|
||||
if (handle) {
|
||||
*blob_file_reader = CacheHandleGuard<BlobFileReader>(cache_, handle);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT("BlobFileCache::GetBlobFileReader:DoubleCheck");
|
||||
|
||||
// Check again while holding mutex
|
||||
MutexLock lock(mutex_.get(key));
|
||||
|
||||
handle = cache_->Lookup(key);
|
||||
if (handle) {
|
||||
*blob_file_reader = CacheHandleGuard<BlobFileReader>(cache_, handle);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
assert(immutable_cf_options_);
|
||||
Statistics* const statistics = immutable_cf_options_->statistics;
|
||||
|
||||
RecordTick(statistics, NO_FILE_OPENS);
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
{
|
||||
assert(file_options_);
|
||||
const Status s = BlobFileReader::Create(
|
||||
*immutable_cf_options_, *file_options_, column_family_id_,
|
||||
blob_file_read_hist_, blob_file_number, &reader);
|
||||
if (!s.ok()) {
|
||||
RecordTick(statistics, NO_FILE_ERRORS);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
constexpr size_t charge = 1;
|
||||
|
||||
const Status s = cache_->Insert(key, reader.get(), charge,
|
||||
&DeleteCacheEntry<BlobFileReader>, &handle);
|
||||
if (!s.ok()) {
|
||||
RecordTick(statistics, NO_FILE_ERRORS);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
reader.release();
|
||||
|
||||
*blob_file_reader = CacheHandleGuard<BlobFileReader>(cache_, handle);
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
#include "cache/cache_helpers.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
#include "util/mutexlock.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class Cache;
|
||||
struct ImmutableCFOptions;
|
||||
struct FileOptions;
|
||||
class HistogramImpl;
|
||||
class Status;
|
||||
class BlobFileReader;
|
||||
class Slice;
|
||||
|
||||
class BlobFileCache {
|
||||
public:
|
||||
BlobFileCache(Cache* cache, const ImmutableCFOptions* immutable_cf_options,
|
||||
const FileOptions* file_options, uint32_t column_family_id,
|
||||
HistogramImpl* blob_file_read_hist);
|
||||
|
||||
BlobFileCache(const BlobFileCache&) = delete;
|
||||
BlobFileCache& operator=(const BlobFileCache&) = delete;
|
||||
|
||||
Status GetBlobFileReader(uint64_t blob_file_number,
|
||||
CacheHandleGuard<BlobFileReader>* blob_file_reader);
|
||||
|
||||
private:
|
||||
Cache* cache_;
|
||||
// Note: mutex_ below is used to guard against multiple threads racing to open
|
||||
// the same file.
|
||||
Striped<port::Mutex, Slice> mutex_;
|
||||
const ImmutableCFOptions* immutable_cf_options_;
|
||||
const FileOptions* file_options_;
|
||||
uint32_t column_family_id_;
|
||||
HistogramImpl* blob_file_read_hist_;
|
||||
|
||||
static constexpr size_t kNumberOfMutexStripes = 1 << 7;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -0,0 +1,268 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/blob/blob_file_cache.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "db/blob/blob_log_writer.h"
|
||||
#include "env/mock_env.h"
|
||||
#include "file/filename.h"
|
||||
#include "file/read_write_util.h"
|
||||
#include "file/writable_file_writer.h"
|
||||
#include "options/cf_options.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/file_system.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "test_util/testharness.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
namespace {
|
||||
|
||||
// Creates a test blob file with a single blob in it.
|
||||
void WriteBlobFile(uint32_t column_family_id,
|
||||
const ImmutableCFOptions& immutable_cf_options,
|
||||
uint64_t blob_file_number) {
|
||||
assert(!immutable_cf_options.cf_paths.empty());
|
||||
|
||||
const std::string blob_file_path = BlobFileName(
|
||||
immutable_cf_options.cf_paths.front().path, blob_file_number);
|
||||
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
ASSERT_OK(NewWritableFile(immutable_cf_options.fs, blob_file_path, &file,
|
||||
FileOptions()));
|
||||
|
||||
std::unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(file), blob_file_path, FileOptions(),
|
||||
immutable_cf_options.env));
|
||||
|
||||
constexpr Statistics* statistics = nullptr;
|
||||
constexpr bool use_fsync = false;
|
||||
|
||||
BlobLogWriter blob_log_writer(std::move(file_writer),
|
||||
immutable_cf_options.env, statistics,
|
||||
blob_file_number, use_fsync);
|
||||
|
||||
constexpr bool has_ttl = false;
|
||||
constexpr ExpirationRange expiration_range;
|
||||
|
||||
BlobLogHeader header(column_family_id, kNoCompression, has_ttl,
|
||||
expiration_range);
|
||||
|
||||
ASSERT_OK(blob_log_writer.WriteHeader(header));
|
||||
|
||||
constexpr char key[] = "key";
|
||||
constexpr char blob[] = "blob";
|
||||
|
||||
std::string compressed_blob;
|
||||
Slice blob_to_write;
|
||||
|
||||
uint64_t key_offset = 0;
|
||||
uint64_t blob_offset = 0;
|
||||
|
||||
ASSERT_OK(blob_log_writer.AddRecord(key, blob, &key_offset, &blob_offset));
|
||||
|
||||
BlobLogFooter footer;
|
||||
footer.blob_count = 1;
|
||||
footer.expiration_range = expiration_range;
|
||||
|
||||
std::string checksum_method;
|
||||
std::string checksum_value;
|
||||
|
||||
ASSERT_OK(
|
||||
blob_log_writer.AppendFooter(footer, &checksum_method, &checksum_value));
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
class BlobFileCacheTest : public testing::Test {
|
||||
protected:
|
||||
BlobFileCacheTest() : mock_env_(Env::Default()) {}
|
||||
|
||||
MockEnv mock_env_;
|
||||
};
|
||||
|
||||
TEST_F(BlobFileCacheTest, GetBlobFileReader) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileCacheTest_GetBlobFileReader"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
ImmutableCFOptions immutable_cf_options(options);
|
||||
constexpr uint64_t blob_file_number = 123;
|
||||
|
||||
WriteBlobFile(column_family_id, immutable_cf_options, blob_file_number);
|
||||
|
||||
constexpr size_t capacity = 10;
|
||||
std::shared_ptr<Cache> backing_cache = NewLRUCache(capacity);
|
||||
|
||||
FileOptions file_options;
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_cf_options,
|
||||
&file_options, column_family_id,
|
||||
blob_file_read_hist);
|
||||
|
||||
// First try: reader should be opened and put in cache
|
||||
CacheHandleGuard<BlobFileReader> first;
|
||||
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(blob_file_number, &first));
|
||||
ASSERT_NE(first.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
|
||||
|
||||
// Second try: reader should be served from cache
|
||||
CacheHandleGuard<BlobFileReader> second;
|
||||
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(blob_file_number, &second));
|
||||
ASSERT_NE(second.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
|
||||
|
||||
ASSERT_EQ(first.GetValue(), second.GetValue());
|
||||
}
|
||||
|
||||
TEST_F(BlobFileCacheTest, GetBlobFileReader_Race) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_,
|
||||
"BlobFileCacheTest_GetBlobFileReader_Race"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
ImmutableCFOptions immutable_cf_options(options);
|
||||
constexpr uint64_t blob_file_number = 123;
|
||||
|
||||
WriteBlobFile(column_family_id, immutable_cf_options, blob_file_number);
|
||||
|
||||
constexpr size_t capacity = 10;
|
||||
std::shared_ptr<Cache> backing_cache = NewLRUCache(capacity);
|
||||
|
||||
FileOptions file_options;
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_cf_options,
|
||||
&file_options, column_family_id,
|
||||
blob_file_read_hist);
|
||||
|
||||
CacheHandleGuard<BlobFileReader> first;
|
||||
CacheHandleGuard<BlobFileReader> second;
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlobFileCache::GetBlobFileReader:DoubleCheck", [&](void* /* arg */) {
|
||||
// Disabling sync points to prevent infinite recursion
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(blob_file_number, &second));
|
||||
ASSERT_NE(second.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(blob_file_number, &first));
|
||||
ASSERT_NE(first.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
|
||||
|
||||
ASSERT_EQ(first.GetValue(), second.GetValue());
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(BlobFileCacheTest, GetBlobFileReader_IOError) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_,
|
||||
"BlobFileCacheTest_GetBlobFileReader_IOError"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
constexpr size_t capacity = 10;
|
||||
std::shared_ptr<Cache> backing_cache = NewLRUCache(capacity);
|
||||
|
||||
ImmutableCFOptions immutable_cf_options(options);
|
||||
FileOptions file_options;
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_cf_options,
|
||||
&file_options, column_family_id,
|
||||
blob_file_read_hist);
|
||||
|
||||
// Note: there is no blob file with the below number
|
||||
constexpr uint64_t blob_file_number = 123;
|
||||
|
||||
CacheHandleGuard<BlobFileReader> reader;
|
||||
|
||||
ASSERT_TRUE(
|
||||
blob_file_cache.GetBlobFileReader(blob_file_number, &reader).IsIOError());
|
||||
ASSERT_EQ(reader.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 1);
|
||||
}
|
||||
|
||||
TEST_F(BlobFileCacheTest, GetBlobFileReader_CacheFull) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_,
|
||||
"BlobFileCacheTest_GetBlobFileReader_CacheFull"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
ImmutableCFOptions immutable_cf_options(options);
|
||||
constexpr uint64_t blob_file_number = 123;
|
||||
|
||||
WriteBlobFile(column_family_id, immutable_cf_options, blob_file_number);
|
||||
|
||||
constexpr size_t capacity = 0;
|
||||
constexpr int num_shard_bits = -1; // determined automatically
|
||||
constexpr bool strict_capacity_limit = true;
|
||||
std::shared_ptr<Cache> backing_cache =
|
||||
NewLRUCache(capacity, num_shard_bits, strict_capacity_limit);
|
||||
|
||||
FileOptions file_options;
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_cf_options,
|
||||
&file_options, column_family_id,
|
||||
blob_file_read_hist);
|
||||
|
||||
// Insert into cache should fail since it has zero capacity and
|
||||
// strict_capacity_limit is set
|
||||
CacheHandleGuard<BlobFileReader> reader;
|
||||
|
||||
ASSERT_TRUE(blob_file_cache.GetBlobFileReader(blob_file_number, &reader)
|
||||
.IsIncomplete());
|
||||
ASSERT_EQ(reader.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 1);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/file_system.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "util/compression.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
|
||||
@@ -95,10 +95,6 @@ Status BlobLogFooter::DecodeFrom(Slice src) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
uint64_t BlobLogRecord::CalculateAdjustmentForRecordHeader(uint64_t key_size) {
|
||||
return key_size + kHeaderSize;
|
||||
}
|
||||
|
||||
void BlobLogRecord::EncodeHeaderTo(std::string* dst) {
|
||||
assert(dst != nullptr);
|
||||
dst->clear();
|
||||
|
||||
@@ -107,7 +107,9 @@ struct BlobLogRecord {
|
||||
// Note that the offset field of BlobIndex actually points to the blob value
|
||||
// as opposed to the start of the blob record. The following method can
|
||||
// be used to calculate the adjustment needed to read the blob record header.
|
||||
static uint64_t CalculateAdjustmentForRecordHeader(uint64_t key_size);
|
||||
static uint64_t CalculateAdjustmentForRecordHeader(uint64_t key_size) {
|
||||
return key_size + kHeaderSize;
|
||||
}
|
||||
|
||||
uint64_t key_size = 0;
|
||||
uint64_t value_size = 0;
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
|
||||
#include "db/blob/blob_log_sequential_reader.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "file/random_access_file_reader.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "util/stop_watch.h"
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class DBBlobBasicTest : public DBTestBase {
|
||||
protected:
|
||||
DBBlobBasicTest()
|
||||
: DBTestBase("/db_blob_basic_test", /* env_do_fsync */ false) {}
|
||||
};
|
||||
|
||||
TEST_F(DBBlobBasicTest, GetBlob) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
constexpr char key[] = "key";
|
||||
constexpr char blob_value[] = "blob_value";
|
||||
|
||||
ASSERT_OK(Put(key, blob_value));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_EQ(Get(key), blob_value);
|
||||
|
||||
// Try again with no I/O allowed. The table and the necessary blocks should
|
||||
// already be in their respective caches; however, the blob itself can only be
|
||||
// read from the blob file, so the read should return Incomplete.
|
||||
ReadOptions read_options;
|
||||
read_options.read_tier = kBlockCacheTier;
|
||||
|
||||
PinnableSlice result;
|
||||
ASSERT_TRUE(db_->Get(read_options, db_->DefaultColumnFamily(), key, &result)
|
||||
.IsIncomplete());
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, GetBlob_CorruptIndex) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
constexpr char key[] = "key";
|
||||
|
||||
// Fake a corrupt blob index.
|
||||
const std::string blob_index("foobar");
|
||||
|
||||
WriteBatch batch;
|
||||
ASSERT_OK(WriteBatchInternal::PutBlobIndex(&batch, 0, key, blob_index));
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
PinnableSlice result;
|
||||
ASSERT_TRUE(db_->Get(ReadOptions(), db_->DefaultColumnFamily(), key, &result)
|
||||
.IsCorruption());
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, GetBlob_InlinedTTLIndex) {
|
||||
constexpr uint64_t min_blob_size = 10;
|
||||
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = min_blob_size;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
constexpr char key[] = "key";
|
||||
constexpr char blob[] = "short";
|
||||
static_assert(sizeof(short) - 1 < min_blob_size,
|
||||
"Blob too long to be inlined");
|
||||
|
||||
// Fake an inlined TTL blob index.
|
||||
std::string blob_index;
|
||||
|
||||
constexpr uint64_t expiration = 1234567890;
|
||||
|
||||
BlobIndex::EncodeInlinedTTL(&blob_index, expiration, blob);
|
||||
|
||||
WriteBatch batch;
|
||||
ASSERT_OK(WriteBatchInternal::PutBlobIndex(&batch, 0, key, blob_index));
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
PinnableSlice result;
|
||||
ASSERT_TRUE(db_->Get(ReadOptions(), db_->DefaultColumnFamily(), key, &result)
|
||||
.IsCorruption());
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, GetBlob_IndexWithInvalidFileNumber) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
constexpr char key[] = "key";
|
||||
|
||||
// Fake a blob index referencing a non-existent blob file.
|
||||
std::string blob_index;
|
||||
|
||||
constexpr uint64_t blob_file_number = 1000;
|
||||
constexpr uint64_t offset = 1234;
|
||||
constexpr uint64_t size = 5678;
|
||||
|
||||
BlobIndex::EncodeBlob(&blob_index, blob_file_number, offset, size,
|
||||
kNoCompression);
|
||||
|
||||
WriteBatch batch;
|
||||
ASSERT_OK(WriteBatchInternal::PutBlobIndex(&batch, 0, key, blob_index));
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
PinnableSlice result;
|
||||
ASSERT_TRUE(db_->Get(ReadOptions(), db_->DefaultColumnFamily(), key, &result)
|
||||
.IsCorruption());
|
||||
}
|
||||
|
||||
class DBBlobBasicIOErrorTest : public DBBlobBasicTest,
|
||||
public testing::WithParamInterface<std::string> {
|
||||
protected:
|
||||
DBBlobBasicIOErrorTest() : sync_point_(GetParam()) {
|
||||
fault_injection_env_.reset(new FaultInjectionTestEnv(env_));
|
||||
}
|
||||
~DBBlobBasicIOErrorTest() { Close(); }
|
||||
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_injection_env_;
|
||||
std::string sync_point_;
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBBlobBasicTest, DBBlobBasicIOErrorTest,
|
||||
::testing::ValuesIn(std::vector<std::string>{
|
||||
"BlobFileReader::OpenFile:NewRandomAccessFile",
|
||||
"BlobFileReader::GetBlob:ReadFromFile"}));
|
||||
|
||||
TEST_P(DBBlobBasicIOErrorTest, GetBlob_IOError) {
|
||||
Options options;
|
||||
options.env = fault_injection_env_.get();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
constexpr char key[] = "key";
|
||||
constexpr char blob_value[] = "blob_value";
|
||||
|
||||
ASSERT_OK(Put(key, blob_value));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(sync_point_, [this](void* /* arg */) {
|
||||
fault_injection_env_->SetFilesystemActive(false,
|
||||
Status::IOError(sync_point_));
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
PinnableSlice result;
|
||||
ASSERT_TRUE(db_->Get(ReadOptions(), db_->DefaultColumnFamily(), key, &result)
|
||||
.IsIOError());
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -73,6 +73,9 @@ class DBBlobIndexTest : public DBTestBase {
|
||||
if (s.IsNotFound()) {
|
||||
return "NOT_FOUND";
|
||||
}
|
||||
if (s.IsCorruption()) {
|
||||
return "CORRUPTION";
|
||||
}
|
||||
if (s.IsNotSupported()) {
|
||||
return "NOT_SUPPORTED";
|
||||
}
|
||||
@@ -100,6 +103,7 @@ class DBBlobIndexTest : public DBTestBase {
|
||||
|
||||
Options GetTestOptions() {
|
||||
Options options;
|
||||
options.env = CurrentOptions().env;
|
||||
options.create_if_missing = true;
|
||||
options.num_levels = 2;
|
||||
options.disable_auto_compactions = true;
|
||||
@@ -153,8 +157,13 @@ TEST_F(DBBlobIndexTest, Write) {
|
||||
}
|
||||
}
|
||||
|
||||
// Get should be able to return blob index if is_blob_index is provided,
|
||||
// otherwise return Status::NotSupported status.
|
||||
// Note: the following test case pertains to the StackableDB-based BlobDB
|
||||
// implementation. Get should be able to return blob index if is_blob_index is
|
||||
// provided, otherwise it should return Status::NotSupported (when reading from
|
||||
// memtable) or Status::Corruption (when reading from SST). Reading from SST
|
||||
// returns Corruption because we can't differentiate between the application
|
||||
// accidentally opening the base DB of a stacked BlobDB and actual corruption
|
||||
// when using the integrated BlobDB.
|
||||
TEST_F(DBBlobIndexTest, Get) {
|
||||
for (auto tier : kAllTiers) {
|
||||
DestroyAndReopen(GetTestOptions());
|
||||
@@ -171,15 +180,22 @@ TEST_F(DBBlobIndexTest, Get) {
|
||||
ASSERT_EQ("value", GetImpl("key", &is_blob_index));
|
||||
ASSERT_FALSE(is_blob_index);
|
||||
// Verify blob index
|
||||
ASSERT_TRUE(Get("blob_key", &value).IsNotSupported());
|
||||
ASSERT_EQ("NOT_SUPPORTED", GetImpl("blob_key"));
|
||||
if (tier <= kImmutableMemtables) {
|
||||
ASSERT_TRUE(Get("blob_key", &value).IsNotSupported());
|
||||
ASSERT_EQ("NOT_SUPPORTED", GetImpl("blob_key"));
|
||||
} else {
|
||||
ASSERT_TRUE(Get("blob_key", &value).IsCorruption());
|
||||
ASSERT_EQ("CORRUPTION", GetImpl("blob_key"));
|
||||
}
|
||||
ASSERT_EQ("blob_index", GetImpl("blob_key", &is_blob_index));
|
||||
ASSERT_TRUE(is_blob_index);
|
||||
}
|
||||
}
|
||||
|
||||
// Get should NOT return Status::NotSupported if blob index is updated with
|
||||
// a normal value.
|
||||
// Note: the following test case pertains to the StackableDB-based BlobDB
|
||||
// implementation. Get should NOT return Status::NotSupported/Status::Corruption
|
||||
// if blob index is updated with a normal value. See the test case above for
|
||||
// more details.
|
||||
TEST_F(DBBlobIndexTest, Updated) {
|
||||
for (auto tier : kAllTiers) {
|
||||
DestroyAndReopen(GetTestOptions());
|
||||
@@ -206,7 +222,11 @@ TEST_F(DBBlobIndexTest, Updated) {
|
||||
ASSERT_EQ("blob_index", GetBlobIndex("key" + ToString(i), snapshot));
|
||||
}
|
||||
ASSERT_EQ("new_value", Get("key1"));
|
||||
ASSERT_EQ("NOT_SUPPORTED", GetImpl("key2"));
|
||||
if (tier <= kImmutableMemtables) {
|
||||
ASSERT_EQ("NOT_SUPPORTED", GetImpl("key2"));
|
||||
} else {
|
||||
ASSERT_EQ("CORRUPTION", GetImpl("key2"));
|
||||
}
|
||||
ASSERT_EQ("NOT_FOUND", Get("key3"));
|
||||
ASSERT_EQ("NOT_FOUND", Get("key4"));
|
||||
ASSERT_EQ("a,b,c", GetImpl("key5"));
|
||||
|
||||
+24
-16
@@ -69,8 +69,8 @@ TableBuilder* NewTableBuilder(
|
||||
}
|
||||
|
||||
Status BuildTable(
|
||||
const std::string& dbname, VersionSet* versions, Env* env, FileSystem* fs,
|
||||
const ImmutableCFOptions& ioptions,
|
||||
const std::string& dbname, VersionSet* versions,
|
||||
const ImmutableDBOptions& db_options, const ImmutableCFOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options, const FileOptions& file_options,
|
||||
TableCache* table_cache, InternalIterator* iter,
|
||||
std::vector<std::unique_ptr<FragmentedRangeTombstoneIterator>>
|
||||
@@ -91,7 +91,7 @@ Status BuildTable(
|
||||
TableProperties* table_properties, int level, const uint64_t creation_time,
|
||||
const uint64_t oldest_key_time, Env::WriteLifeTimeHint write_hint,
|
||||
const uint64_t file_creation_time, const std::string& db_id,
|
||||
const std::string& db_session_id) {
|
||||
const std::string& db_session_id, const std::string* full_history_ts_low) {
|
||||
assert((column_family_id ==
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily) ==
|
||||
column_family_name.empty());
|
||||
@@ -120,15 +120,14 @@ Status BuildTable(
|
||||
EventHelpers::NotifyTableFileCreationStarted(
|
||||
ioptions.listeners, dbname, column_family_name, fname, job_id, reason);
|
||||
#endif // !ROCKSDB_LITE
|
||||
Env* env = db_options.env;
|
||||
assert(env);
|
||||
FileSystem* fs = db_options.fs.get();
|
||||
assert(fs);
|
||||
TableProperties tp;
|
||||
if (iter->Valid() || !range_del_agg->IsEmpty()) {
|
||||
TableBuilder* builder;
|
||||
std::unique_ptr<WritableFileWriter> file_writer;
|
||||
// Currently we only enable dictionary compression during compaction to the
|
||||
// bottommost level.
|
||||
CompressionOptions compression_opts_for_flush(compression_opts);
|
||||
compression_opts_for_flush.max_dict_bytes = 0;
|
||||
compression_opts_for_flush.zstd_max_train_bytes = 0;
|
||||
{
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
#ifndef NDEBUG
|
||||
@@ -160,7 +159,7 @@ Status BuildTable(
|
||||
ioptions, mutable_cf_options, internal_comparator,
|
||||
int_tbl_prop_collector_factories, column_family_id,
|
||||
column_family_name, file_writer.get(), compression,
|
||||
sample_for_compression, compression_opts_for_flush, level,
|
||||
sample_for_compression, compression_opts, level,
|
||||
false /* skip_filters */, creation_time, oldest_key_time,
|
||||
0 /*target_file_size*/, file_creation_time, db_id, db_session_id);
|
||||
}
|
||||
@@ -185,7 +184,11 @@ Status BuildTable(
|
||||
&snapshots, earliest_write_conflict_snapshot, snapshot_checker, env,
|
||||
ShouldReportDetailedTime(env, ioptions.statistics),
|
||||
true /* internal key corruption is not ok */, range_del_agg.get(),
|
||||
blob_file_builder.get(), ioptions.allow_data_in_errors);
|
||||
blob_file_builder.get(), ioptions.allow_data_in_errors,
|
||||
/*compaction=*/nullptr,
|
||||
/*compaction_filter=*/nullptr, /*shutting_down=*/nullptr,
|
||||
/*preserve_deletes_seqnum=*/0, /*manual_compaction_paused=*/nullptr,
|
||||
db_options.info_log, full_history_ts_low);
|
||||
|
||||
c_iter.SeekToFirst();
|
||||
for (; c_iter.Valid(); c_iter.Next()) {
|
||||
@@ -212,6 +215,7 @@ Status BuildTable(
|
||||
} else if (!c_iter.status().ok()) {
|
||||
s = c_iter.status();
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
auto range_del_it = range_del_agg->NewIterator();
|
||||
for (range_del_it->SeekToFirst(); range_del_it->Valid();
|
||||
@@ -222,10 +226,6 @@ Status BuildTable(
|
||||
meta->UpdateBoundariesForRange(kv.first, tombstone.SerializeEndKey(),
|
||||
tombstone.seq_, internal_comparator);
|
||||
}
|
||||
|
||||
if (blob_file_builder) {
|
||||
s = blob_file_builder->Finish();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT("BuildTable:BeforeFinishBuildTable");
|
||||
@@ -273,6 +273,14 @@ Status BuildTable(
|
||||
s = *io_status;
|
||||
}
|
||||
|
||||
if (blob_file_builder) {
|
||||
if (s.ok()) {
|
||||
s = blob_file_builder->Finish();
|
||||
}
|
||||
|
||||
blob_file_builder.reset();
|
||||
}
|
||||
|
||||
// TODO Also check the IO status when create the Iterator.
|
||||
|
||||
if (s.ok() && !empty) {
|
||||
@@ -318,6 +326,8 @@ Status BuildTable(
|
||||
}
|
||||
|
||||
if (!s.ok() || meta->fd.GetFileSize() == 0) {
|
||||
TEST_SYNC_POINT("BuildTable:BeforeDeleteFile");
|
||||
|
||||
constexpr IODebugContext* dbg = nullptr;
|
||||
|
||||
Status ignored = fs->DeleteFile(fname, IOOptions(), dbg);
|
||||
@@ -330,8 +340,6 @@ Status BuildTable(
|
||||
ignored = fs->DeleteFile(blob_file_path, IOOptions(), dbg);
|
||||
ignored.PermitUncheckedError();
|
||||
}
|
||||
|
||||
blob_file_additions->clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -65,8 +65,8 @@ TableBuilder* NewTableBuilder(
|
||||
// @param column_family_name Name of the column family that is also identified
|
||||
// by column_family_id, or empty string if unknown.
|
||||
extern Status BuildTable(
|
||||
const std::string& dbname, VersionSet* versions, Env* env, FileSystem* fs,
|
||||
const ImmutableCFOptions& options,
|
||||
const std::string& dbname, VersionSet* versions,
|
||||
const ImmutableDBOptions& db_options, const ImmutableCFOptions& options,
|
||||
const MutableCFOptions& mutable_cf_options, const FileOptions& file_options,
|
||||
TableCache* table_cache, InternalIterator* iter,
|
||||
std::vector<std::unique_ptr<FragmentedRangeTombstoneIterator>>
|
||||
@@ -89,6 +89,7 @@ extern Status BuildTable(
|
||||
const uint64_t creation_time = 0, const uint64_t oldest_key_time = 0,
|
||||
Env::WriteLifeTimeHint write_hint = Env::WLTH_NOT_SET,
|
||||
const uint64_t file_creation_time = 0, const std::string& db_id = "",
|
||||
const std::string& db_session_id = "");
|
||||
const std::string& db_session_id = "",
|
||||
const std::string* full_history_ts_low = nullptr);
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -60,7 +60,6 @@ using ROCKSDB_NAMESPACE::ColumnFamilyDescriptor;
|
||||
using ROCKSDB_NAMESPACE::ColumnFamilyHandle;
|
||||
using ROCKSDB_NAMESPACE::ColumnFamilyOptions;
|
||||
using ROCKSDB_NAMESPACE::CompactionFilter;
|
||||
using ROCKSDB_NAMESPACE::CompactionFilterContext;
|
||||
using ROCKSDB_NAMESPACE::CompactionFilterFactory;
|
||||
using ROCKSDB_NAMESPACE::CompactionOptionsFIFO;
|
||||
using ROCKSDB_NAMESPACE::CompactRangeOptions;
|
||||
@@ -82,7 +81,6 @@ using ROCKSDB_NAMESPACE::LiveFileMetaData;
|
||||
using ROCKSDB_NAMESPACE::Logger;
|
||||
using ROCKSDB_NAMESPACE::MemoryUtil;
|
||||
using ROCKSDB_NAMESPACE::MergeOperator;
|
||||
using ROCKSDB_NAMESPACE::MergeOperators;
|
||||
using ROCKSDB_NAMESPACE::NewBloomFilterPolicy;
|
||||
using ROCKSDB_NAMESPACE::NewGenericRateLimiter;
|
||||
using ROCKSDB_NAMESPACE::NewLRUCache;
|
||||
@@ -115,10 +113,8 @@ using ROCKSDB_NAMESPACE::WriteBatch;
|
||||
using ROCKSDB_NAMESPACE::WriteBatchWithIndex;
|
||||
using ROCKSDB_NAMESPACE::WriteOptions;
|
||||
|
||||
using std::shared_ptr;
|
||||
using std::vector;
|
||||
using std::unordered_set;
|
||||
using std::map;
|
||||
|
||||
extern "C" {
|
||||
|
||||
@@ -181,6 +177,9 @@ struct rocksdb_transaction_options_t {
|
||||
struct rocksdb_transaction_t {
|
||||
Transaction* rep;
|
||||
};
|
||||
struct rocksdb_backupable_db_options_t {
|
||||
BackupableDBOptions rep;
|
||||
};
|
||||
struct rocksdb_checkpoint_t {
|
||||
Checkpoint* rep;
|
||||
};
|
||||
@@ -549,6 +548,18 @@ rocksdb_backup_engine_t* rocksdb_backup_engine_open(
|
||||
return result;
|
||||
}
|
||||
|
||||
rocksdb_backup_engine_t* rocksdb_backup_engine_open_opts(
|
||||
const rocksdb_backupable_db_options_t* options, rocksdb_env_t* env,
|
||||
char** errptr) {
|
||||
BackupEngine* be;
|
||||
if (SaveError(errptr, BackupEngine::Open(options->rep, env->rep, &be))) {
|
||||
return nullptr;
|
||||
}
|
||||
rocksdb_backup_engine_t* result = new rocksdb_backup_engine_t;
|
||||
result->rep = be;
|
||||
return result;
|
||||
}
|
||||
|
||||
void rocksdb_backup_engine_create_new_backup(rocksdb_backup_engine_t* be,
|
||||
rocksdb_t* db,
|
||||
char** errptr) {
|
||||
@@ -645,6 +656,128 @@ void rocksdb_backup_engine_close(rocksdb_backup_engine_t* be) {
|
||||
delete be;
|
||||
}
|
||||
|
||||
rocksdb_backupable_db_options_t* rocksdb_backupable_db_options_create(
|
||||
const char* backup_dir) {
|
||||
return new rocksdb_backupable_db_options_t{
|
||||
BackupableDBOptions(std::string(backup_dir))};
|
||||
}
|
||||
|
||||
void rocksdb_backupable_db_options_set_backup_dir(
|
||||
rocksdb_backupable_db_options_t* options, const char* backup_dir) {
|
||||
options->rep.backup_dir = std::string(backup_dir);
|
||||
}
|
||||
|
||||
void rocksdb_backupable_db_options_set_env(
|
||||
rocksdb_backupable_db_options_t* options, rocksdb_env_t* env) {
|
||||
options->rep.backup_env = (env ? env->rep : nullptr);
|
||||
}
|
||||
|
||||
void rocksdb_backupable_db_options_set_share_table_files(
|
||||
rocksdb_backupable_db_options_t* options, unsigned char val) {
|
||||
options->rep.share_table_files = val;
|
||||
}
|
||||
|
||||
unsigned char rocksdb_backupable_db_options_get_share_table_files(
|
||||
rocksdb_backupable_db_options_t* options) {
|
||||
return options->rep.share_table_files;
|
||||
}
|
||||
|
||||
void rocksdb_backupable_db_options_set_sync(
|
||||
rocksdb_backupable_db_options_t* options, unsigned char val) {
|
||||
options->rep.sync = val;
|
||||
}
|
||||
|
||||
unsigned char rocksdb_backupable_db_options_get_sync(
|
||||
rocksdb_backupable_db_options_t* options) {
|
||||
return options->rep.sync;
|
||||
}
|
||||
|
||||
void rocksdb_backupable_db_options_set_destroy_old_data(
|
||||
rocksdb_backupable_db_options_t* options, unsigned char val) {
|
||||
options->rep.destroy_old_data = val;
|
||||
}
|
||||
|
||||
unsigned char rocksdb_backupable_db_options_get_destroy_old_data(
|
||||
rocksdb_backupable_db_options_t* options) {
|
||||
return options->rep.destroy_old_data;
|
||||
}
|
||||
|
||||
void rocksdb_backupable_db_options_set_backup_log_files(
|
||||
rocksdb_backupable_db_options_t* options, unsigned char val) {
|
||||
options->rep.backup_log_files = val;
|
||||
}
|
||||
|
||||
unsigned char rocksdb_backupable_db_options_get_backup_log_files(
|
||||
rocksdb_backupable_db_options_t* options) {
|
||||
return options->rep.backup_log_files;
|
||||
}
|
||||
|
||||
void rocksdb_backupable_db_options_set_backup_rate_limit(
|
||||
rocksdb_backupable_db_options_t* options, uint64_t limit) {
|
||||
options->rep.backup_rate_limit = limit;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_backupable_db_options_get_backup_rate_limit(
|
||||
rocksdb_backupable_db_options_t* options) {
|
||||
return options->rep.backup_rate_limit;
|
||||
}
|
||||
|
||||
void rocksdb_backupable_db_options_set_restore_rate_limit(
|
||||
rocksdb_backupable_db_options_t* options, uint64_t limit) {
|
||||
options->rep.restore_rate_limit = limit;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_backupable_db_options_get_restore_rate_limit(
|
||||
rocksdb_backupable_db_options_t* options) {
|
||||
return options->rep.restore_rate_limit;
|
||||
}
|
||||
|
||||
void rocksdb_backupable_db_options_set_max_background_operations(
|
||||
rocksdb_backupable_db_options_t* options, int val) {
|
||||
options->rep.max_background_operations = val;
|
||||
}
|
||||
|
||||
int rocksdb_backupable_db_options_get_max_background_operations(
|
||||
rocksdb_backupable_db_options_t* options) {
|
||||
return options->rep.max_background_operations;
|
||||
}
|
||||
|
||||
void rocksdb_backupable_db_options_set_callback_trigger_interval_size(
|
||||
rocksdb_backupable_db_options_t* options, uint64_t size) {
|
||||
options->rep.callback_trigger_interval_size = size;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_backupable_db_options_get_callback_trigger_interval_size(
|
||||
rocksdb_backupable_db_options_t* options) {
|
||||
return options->rep.callback_trigger_interval_size;
|
||||
}
|
||||
|
||||
void rocksdb_backupable_db_options_set_max_valid_backups_to_open(
|
||||
rocksdb_backupable_db_options_t* options, int val) {
|
||||
options->rep.max_valid_backups_to_open = val;
|
||||
}
|
||||
|
||||
int rocksdb_backupable_db_options_get_max_valid_backups_to_open(
|
||||
rocksdb_backupable_db_options_t* options) {
|
||||
return options->rep.max_valid_backups_to_open;
|
||||
}
|
||||
|
||||
void rocksdb_backupable_db_options_set_share_files_with_checksum_naming(
|
||||
rocksdb_backupable_db_options_t* options, int val) {
|
||||
options->rep.share_files_with_checksum_naming =
|
||||
static_cast<BackupableDBOptions::ShareFilesNaming>(val);
|
||||
}
|
||||
|
||||
int rocksdb_backupable_db_options_get_share_files_with_checksum_naming(
|
||||
rocksdb_backupable_db_options_t* options) {
|
||||
return static_cast<int>(options->rep.share_files_with_checksum_naming);
|
||||
}
|
||||
|
||||
void rocksdb_backupable_db_options_destroy(
|
||||
rocksdb_backupable_db_options_t* options) {
|
||||
delete options;
|
||||
}
|
||||
|
||||
rocksdb_checkpoint_t* rocksdb_checkpoint_object_create(rocksdb_t* db,
|
||||
char** errptr) {
|
||||
Checkpoint* checkpoint;
|
||||
@@ -3985,20 +4118,36 @@ void rocksdb_env_set_background_threads(rocksdb_env_t* env, int n) {
|
||||
env->rep->SetBackgroundThreads(n);
|
||||
}
|
||||
|
||||
int rocksdb_env_get_background_threads(rocksdb_env_t* env) {
|
||||
return env->rep->GetBackgroundThreads();
|
||||
}
|
||||
|
||||
void rocksdb_env_set_bottom_priority_background_threads(rocksdb_env_t* env,
|
||||
int n) {
|
||||
env->rep->SetBackgroundThreads(n, Env::BOTTOM);
|
||||
}
|
||||
|
||||
int rocksdb_env_get_bottom_priority_background_threads(rocksdb_env_t* env) {
|
||||
return env->rep->GetBackgroundThreads(Env::BOTTOM);
|
||||
}
|
||||
|
||||
void rocksdb_env_set_high_priority_background_threads(rocksdb_env_t* env, int n) {
|
||||
env->rep->SetBackgroundThreads(n, Env::HIGH);
|
||||
}
|
||||
|
||||
int rocksdb_env_get_high_priority_background_threads(rocksdb_env_t* env) {
|
||||
return env->rep->GetBackgroundThreads(Env::HIGH);
|
||||
}
|
||||
|
||||
void rocksdb_env_set_low_priority_background_threads(rocksdb_env_t* env,
|
||||
int n) {
|
||||
env->rep->SetBackgroundThreads(n, Env::LOW);
|
||||
}
|
||||
|
||||
int rocksdb_env_get_low_priority_background_threads(rocksdb_env_t* env) {
|
||||
return env->rep->GetBackgroundThreads(Env::LOW);
|
||||
}
|
||||
|
||||
void rocksdb_env_join_all_threads(rocksdb_env_t* env) {
|
||||
env->rep->WaitForJoin();
|
||||
}
|
||||
@@ -4226,32 +4375,62 @@ void rocksdb_universal_compaction_options_set_size_ratio(
|
||||
uco->rep->size_ratio = ratio;
|
||||
}
|
||||
|
||||
int rocksdb_universal_compaction_options_get_size_ratio(
|
||||
rocksdb_universal_compaction_options_t* uco) {
|
||||
return uco->rep->size_ratio;
|
||||
}
|
||||
|
||||
void rocksdb_universal_compaction_options_set_min_merge_width(
|
||||
rocksdb_universal_compaction_options_t* uco, int w) {
|
||||
uco->rep->min_merge_width = w;
|
||||
}
|
||||
|
||||
int rocksdb_universal_compaction_options_get_min_merge_width(
|
||||
rocksdb_universal_compaction_options_t* uco) {
|
||||
return uco->rep->min_merge_width;
|
||||
}
|
||||
|
||||
void rocksdb_universal_compaction_options_set_max_merge_width(
|
||||
rocksdb_universal_compaction_options_t* uco, int w) {
|
||||
uco->rep->max_merge_width = w;
|
||||
}
|
||||
|
||||
int rocksdb_universal_compaction_options_get_max_merge_width(
|
||||
rocksdb_universal_compaction_options_t* uco) {
|
||||
return uco->rep->max_merge_width;
|
||||
}
|
||||
|
||||
void rocksdb_universal_compaction_options_set_max_size_amplification_percent(
|
||||
rocksdb_universal_compaction_options_t* uco, int p) {
|
||||
uco->rep->max_size_amplification_percent = p;
|
||||
}
|
||||
|
||||
int rocksdb_universal_compaction_options_get_max_size_amplification_percent(
|
||||
rocksdb_universal_compaction_options_t* uco) {
|
||||
return uco->rep->max_size_amplification_percent;
|
||||
}
|
||||
|
||||
void rocksdb_universal_compaction_options_set_compression_size_percent(
|
||||
rocksdb_universal_compaction_options_t* uco, int p) {
|
||||
uco->rep->compression_size_percent = p;
|
||||
}
|
||||
|
||||
int rocksdb_universal_compaction_options_get_compression_size_percent(
|
||||
rocksdb_universal_compaction_options_t* uco) {
|
||||
return uco->rep->compression_size_percent;
|
||||
}
|
||||
|
||||
void rocksdb_universal_compaction_options_set_stop_style(
|
||||
rocksdb_universal_compaction_options_t* uco, int style) {
|
||||
uco->rep->stop_style =
|
||||
static_cast<ROCKSDB_NAMESPACE::CompactionStopStyle>(style);
|
||||
}
|
||||
|
||||
int rocksdb_universal_compaction_options_get_stop_style(
|
||||
rocksdb_universal_compaction_options_t* uco) {
|
||||
return static_cast<int>(uco->rep->stop_style);
|
||||
}
|
||||
|
||||
void rocksdb_universal_compaction_options_destroy(
|
||||
rocksdb_universal_compaction_options_t* uco) {
|
||||
delete uco->rep;
|
||||
@@ -4269,6 +4448,11 @@ void rocksdb_fifo_compaction_options_set_max_table_files_size(
|
||||
fifo_opts->rep.max_table_files_size = size;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_fifo_compaction_options_get_max_table_files_size(
|
||||
rocksdb_fifo_compaction_options_t* fifo_opts) {
|
||||
return fifo_opts->rep.max_table_files_size;
|
||||
}
|
||||
|
||||
void rocksdb_fifo_compaction_options_destroy(
|
||||
rocksdb_fifo_compaction_options_t* fifo_opts) {
|
||||
delete fifo_opts;
|
||||
|
||||
+118
@@ -2355,6 +2355,124 @@ int main(int argc, char** argv) {
|
||||
rocksdb_cache_destroy(co);
|
||||
}
|
||||
|
||||
StartPhase("env");
|
||||
{
|
||||
rocksdb_env_t* e;
|
||||
e = rocksdb_create_default_env();
|
||||
|
||||
rocksdb_env_set_background_threads(e, 10);
|
||||
CheckCondition(10 == rocksdb_env_get_background_threads(e));
|
||||
|
||||
rocksdb_env_set_high_priority_background_threads(e, 20);
|
||||
CheckCondition(20 == rocksdb_env_get_high_priority_background_threads(e));
|
||||
|
||||
rocksdb_env_set_low_priority_background_threads(e, 30);
|
||||
CheckCondition(30 == rocksdb_env_get_low_priority_background_threads(e));
|
||||
|
||||
rocksdb_env_set_bottom_priority_background_threads(e, 40);
|
||||
CheckCondition(40 == rocksdb_env_get_bottom_priority_background_threads(e));
|
||||
|
||||
rocksdb_env_destroy(e);
|
||||
}
|
||||
|
||||
StartPhase("universal_compaction_options");
|
||||
{
|
||||
rocksdb_universal_compaction_options_t* uco;
|
||||
uco = rocksdb_universal_compaction_options_create();
|
||||
|
||||
rocksdb_universal_compaction_options_set_size_ratio(uco, 5);
|
||||
CheckCondition(5 ==
|
||||
rocksdb_universal_compaction_options_get_size_ratio(uco));
|
||||
|
||||
rocksdb_universal_compaction_options_set_min_merge_width(uco, 15);
|
||||
CheckCondition(
|
||||
15 == rocksdb_universal_compaction_options_get_min_merge_width(uco));
|
||||
|
||||
rocksdb_universal_compaction_options_set_max_merge_width(uco, 25);
|
||||
CheckCondition(
|
||||
25 == rocksdb_universal_compaction_options_get_max_merge_width(uco));
|
||||
|
||||
rocksdb_universal_compaction_options_set_max_size_amplification_percent(uco,
|
||||
35);
|
||||
CheckCondition(
|
||||
35 ==
|
||||
rocksdb_universal_compaction_options_get_max_size_amplification_percent(
|
||||
uco));
|
||||
|
||||
rocksdb_universal_compaction_options_set_compression_size_percent(uco, 45);
|
||||
CheckCondition(
|
||||
45 ==
|
||||
rocksdb_universal_compaction_options_get_compression_size_percent(uco));
|
||||
|
||||
rocksdb_universal_compaction_options_set_stop_style(uco, 1);
|
||||
CheckCondition(1 ==
|
||||
rocksdb_universal_compaction_options_get_stop_style(uco));
|
||||
|
||||
rocksdb_universal_compaction_options_destroy(uco);
|
||||
}
|
||||
|
||||
StartPhase("fifo_compaction_options");
|
||||
{
|
||||
rocksdb_fifo_compaction_options_t* fco;
|
||||
fco = rocksdb_fifo_compaction_options_create();
|
||||
|
||||
rocksdb_fifo_compaction_options_set_max_table_files_size(fco, 100000);
|
||||
CheckCondition(
|
||||
100000 ==
|
||||
rocksdb_fifo_compaction_options_get_max_table_files_size(fco));
|
||||
|
||||
rocksdb_fifo_compaction_options_destroy(fco);
|
||||
}
|
||||
|
||||
StartPhase("backupable_db_option");
|
||||
{
|
||||
rocksdb_backupable_db_options_t* bdo;
|
||||
bdo = rocksdb_backupable_db_options_create("path");
|
||||
|
||||
rocksdb_backupable_db_options_set_share_table_files(bdo, 1);
|
||||
CheckCondition(1 ==
|
||||
rocksdb_backupable_db_options_get_share_table_files(bdo));
|
||||
|
||||
rocksdb_backupable_db_options_set_sync(bdo, 1);
|
||||
CheckCondition(1 == rocksdb_backupable_db_options_get_sync(bdo));
|
||||
|
||||
rocksdb_backupable_db_options_set_destroy_old_data(bdo, 1);
|
||||
CheckCondition(1 ==
|
||||
rocksdb_backupable_db_options_get_destroy_old_data(bdo));
|
||||
|
||||
rocksdb_backupable_db_options_set_backup_log_files(bdo, 1);
|
||||
CheckCondition(1 ==
|
||||
rocksdb_backupable_db_options_get_backup_log_files(bdo));
|
||||
|
||||
rocksdb_backupable_db_options_set_backup_rate_limit(bdo, 123);
|
||||
CheckCondition(123 ==
|
||||
rocksdb_backupable_db_options_get_backup_rate_limit(bdo));
|
||||
|
||||
rocksdb_backupable_db_options_set_restore_rate_limit(bdo, 37);
|
||||
CheckCondition(37 ==
|
||||
rocksdb_backupable_db_options_get_restore_rate_limit(bdo));
|
||||
|
||||
rocksdb_backupable_db_options_set_max_background_operations(bdo, 20);
|
||||
CheckCondition(
|
||||
20 == rocksdb_backupable_db_options_get_max_background_operations(bdo));
|
||||
|
||||
rocksdb_backupable_db_options_set_callback_trigger_interval_size(bdo, 9000);
|
||||
CheckCondition(
|
||||
9000 ==
|
||||
rocksdb_backupable_db_options_get_callback_trigger_interval_size(bdo));
|
||||
|
||||
rocksdb_backupable_db_options_set_max_valid_backups_to_open(bdo, 40);
|
||||
CheckCondition(
|
||||
40 == rocksdb_backupable_db_options_get_max_valid_backups_to_open(bdo));
|
||||
|
||||
rocksdb_backupable_db_options_set_share_files_with_checksum_naming(bdo, 2);
|
||||
CheckCondition(
|
||||
2 == rocksdb_backupable_db_options_get_share_files_with_checksum_naming(
|
||||
bdo));
|
||||
|
||||
rocksdb_backupable_db_options_destroy(bdo);
|
||||
}
|
||||
|
||||
StartPhase("iterate_upper_bound");
|
||||
{
|
||||
// Create new empty database
|
||||
|
||||
+11
-7
@@ -16,6 +16,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/blob/blob_file_cache.h"
|
||||
#include "db/compaction/compaction_picker.h"
|
||||
#include "db/compaction/compaction_picker_fifo.h"
|
||||
#include "db/compaction/compaction_picker_level.h"
|
||||
@@ -559,6 +560,10 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
new InternalStats(ioptions_.num_levels, db_options.env, this));
|
||||
table_cache_.reset(new TableCache(ioptions_, file_options, _table_cache,
|
||||
block_cache_tracer, io_tracer));
|
||||
blob_file_cache_.reset(
|
||||
new BlobFileCache(_table_cache, ioptions(), soptions(), id_,
|
||||
internal_stats_->GetBlobFileReadHist()));
|
||||
|
||||
if (ioptions_.compaction_style == kCompactionStyleLevel) {
|
||||
compaction_picker_.reset(
|
||||
new LevelCompactionPicker(ioptions_, &internal_comparator_));
|
||||
@@ -1083,7 +1088,7 @@ bool ColumnFamilyData::RangeOverlapWithCompaction(
|
||||
|
||||
Status ColumnFamilyData::RangesOverlapWithMemtables(
|
||||
const autovector<Range>& ranges, SuperVersion* super_version,
|
||||
bool* overlap) {
|
||||
bool allow_data_in_errors, bool* overlap) {
|
||||
assert(overlap != nullptr);
|
||||
*overlap = false;
|
||||
// Create an InternalIterator over all unflushed memtables
|
||||
@@ -1116,13 +1121,12 @@ Status ColumnFamilyData::RangesOverlapWithMemtables(
|
||||
memtable_iter->Seek(range_start.Encode());
|
||||
status = memtable_iter->status();
|
||||
ParsedInternalKey seek_result;
|
||||
if (status.ok()) {
|
||||
if (memtable_iter->Valid() &&
|
||||
ParseInternalKey(memtable_iter->key(), &seek_result) !=
|
||||
Status::OK()) {
|
||||
status = Status::Corruption("DB have corrupted keys");
|
||||
}
|
||||
|
||||
if (status.ok() && memtable_iter->Valid()) {
|
||||
status = ParseInternalKey(memtable_iter->key(), &seek_result,
|
||||
allow_data_in_errors);
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
if (memtable_iter->Valid() &&
|
||||
ucmp->Compare(seek_result.user_key, ranges[i].limit) <= 0) {
|
||||
|
||||
+5
-1
@@ -44,6 +44,7 @@ class LogBuffer;
|
||||
class InstrumentedMutex;
|
||||
class InstrumentedMutexLock;
|
||||
struct SuperVersionContext;
|
||||
class BlobFileCache;
|
||||
|
||||
extern const double kIncSlowdownRatio;
|
||||
// This file contains a list of data structures for managing column family
|
||||
@@ -381,6 +382,7 @@ class ColumnFamilyData {
|
||||
SequenceNumber earliest_seq);
|
||||
|
||||
TableCache* table_cache() const { return table_cache_.get(); }
|
||||
BlobFileCache* blob_file_cache() const { return blob_file_cache_.get(); }
|
||||
|
||||
// See documentation in compaction_picker.h
|
||||
// REQUIRES: DB mutex held
|
||||
@@ -404,7 +406,8 @@ class ColumnFamilyData {
|
||||
//
|
||||
// Thread-safe
|
||||
Status RangesOverlapWithMemtables(const autovector<Range>& ranges,
|
||||
SuperVersion* super_version, bool* overlap);
|
||||
SuperVersion* super_version,
|
||||
bool allow_data_in_errors, bool* overlap);
|
||||
|
||||
// A flag to tell a manual compaction is to compact all levels together
|
||||
// instead of a specific level.
|
||||
@@ -543,6 +546,7 @@ class ColumnFamilyData {
|
||||
const bool is_delete_range_supported_;
|
||||
|
||||
std::unique_ptr<TableCache> table_cache_;
|
||||
std::unique_ptr<BlobFileCache> blob_file_cache_;
|
||||
|
||||
std::unique_ptr<InternalStats> internal_stats_;
|
||||
|
||||
|
||||
@@ -35,10 +35,10 @@ namespace ROCKSDB_NAMESPACE {
|
||||
static const int kValueSize = 1000;
|
||||
|
||||
// counts how many operations were performed
|
||||
class EnvCounter : public EnvWrapper {
|
||||
class EnvCounter : public SpecialEnv {
|
||||
public:
|
||||
explicit EnvCounter(Env* base)
|
||||
: EnvWrapper(base), num_new_writable_file_(0) {}
|
||||
: SpecialEnv(base), num_new_writable_file_(0) {}
|
||||
int GetNumberOfNewWritableFileCalls() {
|
||||
return num_new_writable_file_;
|
||||
}
|
||||
@@ -68,6 +68,7 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
#endif // !ROCKSDB_LITE
|
||||
EXPECT_NE(nullptr, base_env);
|
||||
env_ = new EnvCounter(base_env);
|
||||
env_->skip_fsync_ = true;
|
||||
dbname_ = test::PerThreadDBPath("column_family_test");
|
||||
db_options_.create_if_missing = true;
|
||||
db_options_.fail_if_options_file_error = true;
|
||||
@@ -87,7 +88,6 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
#endif // ROCKSDB_LITE
|
||||
column_families.push_back(cfdescriptor);
|
||||
}
|
||||
Close();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
Destroy(column_families);
|
||||
delete env_;
|
||||
@@ -3271,7 +3271,7 @@ TEST_P(ColumnFamilyTest, DISABLED_LogTruncationTest) {
|
||||
FileType type;
|
||||
if (!(ParseFileName(filenames[i], &number, &type))) continue;
|
||||
|
||||
if (type != kLogFile) continue;
|
||||
if (type != kWalFile) continue;
|
||||
|
||||
logfs.push_back(filenames[i]);
|
||||
}
|
||||
|
||||
@@ -248,12 +248,6 @@ Compaction::Compaction(VersionStorageInfo* vstorage,
|
||||
if (max_subcompactions_ == 0) {
|
||||
max_subcompactions_ = _mutable_db_options.max_subcompactions;
|
||||
}
|
||||
if (!bottommost_level_) {
|
||||
// Currently we only enable dictionary compression during compaction to the
|
||||
// bottommost level.
|
||||
output_compression_opts_.max_dict_bytes = 0;
|
||||
output_compression_opts_.zstd_max_train_bytes = 0;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
for (size_t i = 1; i < inputs_.size(); ++i) {
|
||||
@@ -383,7 +377,13 @@ bool Compaction::KeyNotExistsBeyondOutputLevel(
|
||||
auto* f = files[level_ptrs->at(lvl)];
|
||||
if (user_cmp->Compare(user_key, f->largest.user_key()) <= 0) {
|
||||
// We've advanced far enough
|
||||
if (user_cmp->Compare(user_key, f->smallest.user_key()) >= 0) {
|
||||
// In the presence of user-defined timestamp, we may need to handle
|
||||
// the case in which f->smallest.user_key() (including ts) has the
|
||||
// same user key, but the ts part is smaller. If so,
|
||||
// Compare(user_key, f->smallest.user_key()) returns -1.
|
||||
// That's why we need CompareWithoutTimestamp().
|
||||
if (user_cmp->CompareWithoutTimestamp(user_key,
|
||||
f->smallest.user_key()) >= 0) {
|
||||
// Key falls in this file's range, so it may
|
||||
// exist beyond output level
|
||||
return false;
|
||||
|
||||
@@ -44,16 +44,17 @@ CompactionIterator::CompactionIterator(
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const SequenceNumber preserve_deletes_seqnum,
|
||||
const std::atomic<int>* manual_compaction_paused,
|
||||
const std::shared_ptr<Logger> info_log)
|
||||
const std::shared_ptr<Logger> info_log,
|
||||
const std::string* full_history_ts_low)
|
||||
: CompactionIterator(
|
||||
input, cmp, merge_helper, last_sequence, snapshots,
|
||||
earliest_write_conflict_snapshot, snapshot_checker, env,
|
||||
report_detailed_time, expect_valid_internal_key, range_del_agg,
|
||||
blob_file_builder, allow_data_in_errors,
|
||||
std::unique_ptr<CompactionProxy>(
|
||||
compaction ? new CompactionProxy(compaction) : nullptr),
|
||||
compaction ? new RealCompaction(compaction) : nullptr),
|
||||
compaction_filter, shutting_down, preserve_deletes_seqnum,
|
||||
manual_compaction_paused, info_log) {}
|
||||
manual_compaction_paused, info_log, full_history_ts_low) {}
|
||||
|
||||
CompactionIterator::CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
@@ -68,7 +69,8 @@ CompactionIterator::CompactionIterator(
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const SequenceNumber preserve_deletes_seqnum,
|
||||
const std::atomic<int>* manual_compaction_paused,
|
||||
const std::shared_ptr<Logger> info_log)
|
||||
const std::shared_ptr<Logger> info_log,
|
||||
const std::string* full_history_ts_low)
|
||||
: input_(input),
|
||||
cmp_(cmp),
|
||||
merge_helper_(merge_helper),
|
||||
@@ -85,12 +87,15 @@ CompactionIterator::CompactionIterator(
|
||||
shutting_down_(shutting_down),
|
||||
manual_compaction_paused_(manual_compaction_paused),
|
||||
preserve_deletes_seqnum_(preserve_deletes_seqnum),
|
||||
info_log_(info_log),
|
||||
allow_data_in_errors_(allow_data_in_errors),
|
||||
timestamp_size_(cmp_ ? cmp_->timestamp_size() : 0),
|
||||
full_history_ts_low_(full_history_ts_low),
|
||||
current_user_key_sequence_(0),
|
||||
current_user_key_snapshot_(0),
|
||||
merge_out_iter_(merge_helper_),
|
||||
current_key_committed_(false),
|
||||
info_log_(info_log),
|
||||
allow_data_in_errors_(allow_data_in_errors) {
|
||||
cmp_with_history_ts_low_(0) {
|
||||
assert(compaction_filter_ == nullptr || compaction_ != nullptr);
|
||||
assert(snapshots_ != nullptr);
|
||||
bottommost_level_ = compaction_ == nullptr
|
||||
@@ -117,6 +122,8 @@ CompactionIterator::CompactionIterator(
|
||||
for (size_t i = 1; i < snapshots_->size(); ++i) {
|
||||
assert(snapshots_->at(i - 1) < snapshots_->at(i));
|
||||
}
|
||||
assert(timestamp_size_ == 0 || !full_history_ts_low_ ||
|
||||
timestamp_size_ == full_history_ts_low_->size());
|
||||
#endif
|
||||
input_->SetPinnedItersMgr(&pinned_iters_mgr_);
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionIterator:AfterInit", compaction_.get());
|
||||
@@ -151,13 +158,13 @@ void CompactionIterator::Next() {
|
||||
if (merge_out_iter_.Valid()) {
|
||||
key_ = merge_out_iter_.key();
|
||||
value_ = merge_out_iter_.value();
|
||||
Status s = ParseInternalKey(key_, &ikey_);
|
||||
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 (%s) in compaction",
|
||||
key_.ToString(true).c_str());
|
||||
ROCKS_LOG_FATAL(info_log_, "Invalid key in compaction. %s",
|
||||
s.getState());
|
||||
}
|
||||
|
||||
// Keep current_key_ in sync.
|
||||
@@ -270,22 +277,14 @@ void CompactionIterator::NextFromInput() {
|
||||
value_ = input_->value();
|
||||
iter_stats_.num_input_records++;
|
||||
|
||||
Status pikStatus = ParseInternalKey(key_, &ikey_);
|
||||
if (!pikStatus.ok()) {
|
||||
Status pik_status = ParseInternalKey(key_, &ikey_, allow_data_in_errors_);
|
||||
if (!pik_status.ok()) {
|
||||
iter_stats_.num_input_corrupt_records++;
|
||||
|
||||
// If `expect_valid_internal_key_` is false, return the corrupted key
|
||||
// and let the caller decide what to do with it.
|
||||
// TODO(noetzli): We should have a more elegant solution for this.
|
||||
if (expect_valid_internal_key_) {
|
||||
std::string msg("Corrupted internal key not expected.");
|
||||
if (allow_data_in_errors_) {
|
||||
msg.append(" Corrupt key: " + ikey_.user_key.ToString(/*hex=*/true) +
|
||||
". ");
|
||||
msg.append("key type: " + std::to_string(ikey_.type) + ".");
|
||||
msg.append("seq: " + std::to_string(ikey_.sequence) + ".");
|
||||
}
|
||||
status_ = Status::Corruption(msg.c_str());
|
||||
status_ = pik_status;
|
||||
return;
|
||||
}
|
||||
key_ = current_key_.SetInternalKey(key_);
|
||||
@@ -298,7 +297,8 @@ void CompactionIterator::NextFromInput() {
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionIterator:ProcessKV", &ikey_);
|
||||
|
||||
// Update input statistics
|
||||
if (ikey_.type == kTypeDeletion || ikey_.type == kTypeSingleDeletion) {
|
||||
if (ikey_.type == kTypeDeletion || ikey_.type == kTypeSingleDeletion ||
|
||||
ikey_.type == kTypeDeletionWithTimestamp) {
|
||||
iter_stats_.num_input_deletion_records++;
|
||||
}
|
||||
iter_stats_.total_input_raw_key_bytes += key_.size();
|
||||
@@ -311,19 +311,56 @@ void CompactionIterator::NextFromInput() {
|
||||
// merge_helper_->compaction_filter_skip_until_.
|
||||
Slice skip_until;
|
||||
|
||||
int cmp_user_key_without_ts = 0;
|
||||
int cmp_ts = 0;
|
||||
if (has_current_user_key_) {
|
||||
cmp_user_key_without_ts =
|
||||
timestamp_size_
|
||||
? cmp_->CompareWithoutTimestamp(ikey_.user_key, current_user_key_)
|
||||
: cmp_->Compare(ikey_.user_key, current_user_key_);
|
||||
// if timestamp_size_ > 0, then curr_ts_ has been initialized by a
|
||||
// previous key.
|
||||
cmp_ts = timestamp_size_ ? cmp_->CompareTimestamp(
|
||||
ExtractTimestampFromUserKey(
|
||||
ikey_.user_key, timestamp_size_),
|
||||
curr_ts_)
|
||||
: 0;
|
||||
}
|
||||
|
||||
// Check whether the user key changed. After this if statement current_key_
|
||||
// is a copy of the current input key (maybe converted to a delete by the
|
||||
// compaction filter). ikey_.user_key is pointing to the copy.
|
||||
if (!has_current_user_key_ ||
|
||||
!cmp_->Equal(ikey_.user_key, current_user_key_)) {
|
||||
if (!has_current_user_key_ || cmp_user_key_without_ts != 0 || cmp_ts != 0) {
|
||||
// First occurrence of this user key
|
||||
// Copy key for output
|
||||
key_ = current_key_.SetInternalKey(key_, &ikey_);
|
||||
|
||||
// If timestamp_size_ > 0, then copy from ikey_ to curr_ts_ for the use
|
||||
// in next iteration to compare with the timestamp of next key.
|
||||
UpdateTimestampAndCompareWithFullHistoryLow();
|
||||
|
||||
// If
|
||||
// (1) !has_current_user_key_, OR
|
||||
// (2) timestamp is disabled, OR
|
||||
// (3) all history will be preserved, OR
|
||||
// (4) user key (excluding timestamp) is different from previous key, OR
|
||||
// (5) timestamp is NO older than *full_history_ts_low_
|
||||
// then current_user_key_ must be treated as a different user key.
|
||||
// This means, if a user key (excluding ts) is the same as the previous
|
||||
// user key, and its ts is older than *full_history_ts_low_, then we
|
||||
// consider this key for GC, e.g. it may be dropped if certain conditions
|
||||
// match.
|
||||
if (!has_current_user_key_ || !timestamp_size_ || !full_history_ts_low_ ||
|
||||
0 != cmp_user_key_without_ts || cmp_with_history_ts_low_ >= 0) {
|
||||
// Initialize for future comparison for rule (A) and etc.
|
||||
current_user_key_sequence_ = kMaxSequenceNumber;
|
||||
current_user_key_snapshot_ = 0;
|
||||
has_current_user_key_ = true;
|
||||
}
|
||||
current_user_key_ = ikey_.user_key;
|
||||
has_current_user_key_ = true;
|
||||
|
||||
has_outputted_key_ = false;
|
||||
current_user_key_sequence_ = kMaxSequenceNumber;
|
||||
current_user_key_snapshot_ = 0;
|
||||
|
||||
current_key_committed_ = KeyCommitted(ikey_.sequence);
|
||||
|
||||
// Apply the compaction filter to the first committed version of the user
|
||||
@@ -439,7 +476,8 @@ void CompactionIterator::NextFromInput() {
|
||||
// Check whether the next key exists, is not corrupt, and is the same key
|
||||
// as the single delete.
|
||||
if (input_->Valid() &&
|
||||
ParseInternalKey(input_->key(), &next_ikey) == Status::OK() &&
|
||||
ParseInternalKey(input_->key(), &next_ikey, allow_data_in_errors_)
|
||||
.ok() &&
|
||||
cmp_->Equal(ikey_.user_key, next_ikey.user_key)) {
|
||||
// Check whether the next key belongs to the same snapshot as the
|
||||
// SingleDelete.
|
||||
@@ -543,9 +581,12 @@ void CompactionIterator::NextFromInput() {
|
||||
last_sequence, current_user_key_sequence_);
|
||||
}
|
||||
|
||||
++iter_stats_.num_record_drop_hidden; // (A)
|
||||
++iter_stats_.num_record_drop_hidden; // rule (A)
|
||||
input_->Next();
|
||||
} else if (compaction_ != nullptr && ikey_.type == kTypeDeletion &&
|
||||
} else if (compaction_ != nullptr &&
|
||||
(ikey_.type == kTypeDeletion ||
|
||||
(ikey_.type == kTypeDeletionWithTimestamp &&
|
||||
cmp_with_history_ts_low_ < 0)) &&
|
||||
IN_EARLIEST_SNAPSHOT(ikey_.sequence) &&
|
||||
ikeyNotNeededForIncrementalSnapshot() &&
|
||||
compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key,
|
||||
@@ -569,13 +610,19 @@ void CompactionIterator::NextFromInput() {
|
||||
// given that:
|
||||
// (1) The deletion is earlier than earliest_write_conflict_snapshot, and
|
||||
// (2) No value exist earlier than the deletion.
|
||||
//
|
||||
// Note also that a deletion marker of type kTypeDeletionWithTimestamp
|
||||
// will be treated as a different user key unless the timestamp is older
|
||||
// than *full_history_ts_low_.
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
if (!bottommost_level_) {
|
||||
++iter_stats_.num_optimized_del_drop_obsolete;
|
||||
}
|
||||
input_->Next();
|
||||
} else if ((ikey_.type == kTypeDeletion) && bottommost_level_ &&
|
||||
ikeyNotNeededForIncrementalSnapshot()) {
|
||||
} else if ((ikey_.type == kTypeDeletion ||
|
||||
(ikey_.type == kTypeDeletionWithTimestamp &&
|
||||
cmp_with_history_ts_low_ < 0)) &&
|
||||
bottommost_level_ && ikeyNotNeededForIncrementalSnapshot()) {
|
||||
// Handle the case where we have a delete key at the bottom most level
|
||||
// We can skip outputting the key iff there are no subsequent puts for this
|
||||
// key
|
||||
@@ -583,12 +630,18 @@ void CompactionIterator::NextFromInput() {
|
||||
ikey_.user_key, &level_ptrs_));
|
||||
ParsedInternalKey next_ikey;
|
||||
input_->Next();
|
||||
// Skip over all versions of this key that happen to occur in the same snapshot
|
||||
// range as the delete
|
||||
// Skip over all versions of this key that happen to occur in the same
|
||||
// snapshot range as the delete.
|
||||
//
|
||||
// Note that a deletion marker of type kTypeDeletionWithTimestamp will be
|
||||
// considered to have a different user key unless the timestamp is older
|
||||
// than *full_history_ts_low_.
|
||||
while (!IsPausingManualCompaction() && !IsShuttingDown() &&
|
||||
input_->Valid() &&
|
||||
(ParseInternalKey(input_->key(), &next_ikey) == Status::OK()) &&
|
||||
cmp_->Equal(ikey_.user_key, next_ikey.user_key) &&
|
||||
(ParseInternalKey(input_->key(), &next_ikey, allow_data_in_errors_)
|
||||
.ok()) &&
|
||||
0 == cmp_->CompareWithoutTimestamp(ikey_.user_key,
|
||||
next_ikey.user_key) &&
|
||||
(prev_snapshot == 0 ||
|
||||
DEFINITELY_NOT_IN_SNAPSHOT(next_ikey.sequence, prev_snapshot))) {
|
||||
input_->Next();
|
||||
@@ -596,8 +649,10 @@ void CompactionIterator::NextFromInput() {
|
||||
// If you find you still need to output a row with this key, we need to output the
|
||||
// delete too
|
||||
if (input_->Valid() &&
|
||||
(ParseInternalKey(input_->key(), &next_ikey) == Status::OK()) &&
|
||||
cmp_->Equal(ikey_.user_key, next_ikey.user_key)) {
|
||||
(ParseInternalKey(input_->key(), &next_ikey, allow_data_in_errors_)
|
||||
.ok()) &&
|
||||
0 == cmp_->CompareWithoutTimestamp(ikey_.user_key,
|
||||
next_ikey.user_key)) {
|
||||
valid_ = true;
|
||||
at_next_ = true;
|
||||
}
|
||||
@@ -613,8 +668,9 @@ void CompactionIterator::NextFromInput() {
|
||||
// have hit (A)
|
||||
// We encapsulate the merge related state machine in a different
|
||||
// object to minimize change to the existing flow.
|
||||
Status s = merge_helper_->MergeUntil(input_, range_del_agg_,
|
||||
prev_snapshot, bottommost_level_);
|
||||
Status s =
|
||||
merge_helper_->MergeUntil(input_, range_del_agg_, prev_snapshot,
|
||||
bottommost_level_, allow_data_in_errors_);
|
||||
merge_out_iter_.SeekToFirst();
|
||||
|
||||
if (!s.ok() && !s.IsMergeInProgress()) {
|
||||
@@ -625,13 +681,13 @@ void CompactionIterator::NextFromInput() {
|
||||
// These will be correctly set below.
|
||||
key_ = merge_out_iter_.key();
|
||||
value_ = merge_out_iter_.value();
|
||||
pikStatus = ParseInternalKey(key_, &ikey_);
|
||||
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(pikStatus.ok());
|
||||
if (!pikStatus.ok()) {
|
||||
ROCKS_LOG_FATAL(info_log_, "Invalid key (%s) in compaction",
|
||||
key_.ToString(true).c_str());
|
||||
assert(pik_status.ok());
|
||||
if (!pik_status.ok()) {
|
||||
ROCKS_LOG_FATAL(info_log_, "Invalid key in compaction. %s",
|
||||
pik_status.getState());
|
||||
}
|
||||
// Keep current_key_ in sync.
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
|
||||
@@ -735,7 +791,18 @@ void CompactionIterator::PrepareOutput() {
|
||||
ikey_.type);
|
||||
}
|
||||
ikey_.sequence = 0;
|
||||
current_key_.UpdateInternalKey(0, ikey_.type);
|
||||
if (!timestamp_size_) {
|
||||
current_key_.UpdateInternalKey(0, ikey_.type);
|
||||
} else if (full_history_ts_low_ && cmp_with_history_ts_low_ < 0) {
|
||||
// We can also zero out timestamp for better compression.
|
||||
// For the same user key (excluding timestamp), the timestamp-based
|
||||
// history can be collapsed to save some space if the timestamp is
|
||||
// older than *full_history_ts_low_.
|
||||
const std::string kTsMin(timestamp_size_, static_cast<char>(0));
|
||||
const Slice ts_slice = kTsMin;
|
||||
ikey_.SetTimestamp(ts_slice);
|
||||
current_key_.UpdateInternalKey(0, ikey_.type, &ts_slice);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,34 +29,57 @@ class CompactionIterator {
|
||||
// CompactionIterator uses. Tests can override it.
|
||||
class CompactionProxy {
|
||||
public:
|
||||
explicit CompactionProxy(const Compaction* compaction)
|
||||
: compaction_(compaction) {}
|
||||
|
||||
virtual ~CompactionProxy() = default;
|
||||
virtual int level(size_t /*compaction_input_level*/ = 0) const {
|
||||
return compaction_->level();
|
||||
}
|
||||
|
||||
virtual int level() const = 0;
|
||||
|
||||
virtual bool KeyNotExistsBeyondOutputLevel(
|
||||
const Slice& user_key, std::vector<size_t>* level_ptrs) const {
|
||||
const Slice& user_key, std::vector<size_t>* level_ptrs) const = 0;
|
||||
|
||||
virtual bool bottommost_level() const = 0;
|
||||
|
||||
virtual int number_levels() const = 0;
|
||||
|
||||
virtual Slice GetLargestUserKey() const = 0;
|
||||
|
||||
virtual bool allow_ingest_behind() const = 0;
|
||||
|
||||
virtual bool preserve_deletes() const = 0;
|
||||
};
|
||||
|
||||
class RealCompaction : public CompactionProxy {
|
||||
public:
|
||||
explicit RealCompaction(const Compaction* compaction)
|
||||
: compaction_(compaction) {
|
||||
assert(compaction_);
|
||||
assert(compaction_->immutable_cf_options());
|
||||
}
|
||||
|
||||
int level() const override { return compaction_->level(); }
|
||||
|
||||
bool KeyNotExistsBeyondOutputLevel(
|
||||
const Slice& user_key, std::vector<size_t>* level_ptrs) const override {
|
||||
return compaction_->KeyNotExistsBeyondOutputLevel(user_key, level_ptrs);
|
||||
}
|
||||
virtual bool bottommost_level() const {
|
||||
|
||||
bool bottommost_level() const override {
|
||||
return compaction_->bottommost_level();
|
||||
}
|
||||
virtual int number_levels() const { return compaction_->number_levels(); }
|
||||
virtual Slice GetLargestUserKey() const {
|
||||
|
||||
int number_levels() const override { return compaction_->number_levels(); }
|
||||
|
||||
Slice GetLargestUserKey() const override {
|
||||
return compaction_->GetLargestUserKey();
|
||||
}
|
||||
virtual bool allow_ingest_behind() const {
|
||||
|
||||
bool allow_ingest_behind() const override {
|
||||
return compaction_->immutable_cf_options()->allow_ingest_behind;
|
||||
}
|
||||
virtual bool preserve_deletes() const {
|
||||
|
||||
bool preserve_deletes() const override {
|
||||
return compaction_->immutable_cf_options()->preserve_deletes;
|
||||
}
|
||||
|
||||
protected:
|
||||
CompactionProxy() = default;
|
||||
|
||||
private:
|
||||
const Compaction* compaction_;
|
||||
};
|
||||
@@ -75,7 +98,8 @@ class CompactionIterator {
|
||||
const std::atomic<bool>* shutting_down = nullptr,
|
||||
const SequenceNumber preserve_deletes_seqnum = 0,
|
||||
const std::atomic<int>* manual_compaction_paused = nullptr,
|
||||
const std::shared_ptr<Logger> info_log = 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,
|
||||
@@ -92,7 +116,8 @@ class CompactionIterator {
|
||||
const std::atomic<bool>* shutting_down = nullptr,
|
||||
const SequenceNumber preserve_deletes_seqnum = 0,
|
||||
const std::atomic<int>* manual_compaction_paused = nullptr,
|
||||
const std::shared_ptr<Logger> info_log = nullptr);
|
||||
const std::shared_ptr<Logger> info_log = nullptr,
|
||||
const std::string* full_history_ts_low = nullptr);
|
||||
|
||||
~CompactionIterator();
|
||||
|
||||
@@ -152,6 +177,20 @@ class CompactionIterator {
|
||||
|
||||
bool IsInEarliestSnapshot(SequenceNumber sequence);
|
||||
|
||||
// Extract user-defined timestamp from user key if possible and compare it
|
||||
// with *full_history_ts_low_ if applicable.
|
||||
inline void UpdateTimestampAndCompareWithFullHistoryLow() {
|
||||
if (!timestamp_size_) {
|
||||
return;
|
||||
}
|
||||
Slice ts = ExtractTimestampFromUserKey(ikey_.user_key, timestamp_size_);
|
||||
curr_ts_.assign(ts.data(), ts.size());
|
||||
if (full_history_ts_low_) {
|
||||
cmp_with_history_ts_low_ =
|
||||
cmp_->CompareTimestamp(ts, *full_history_ts_low_);
|
||||
}
|
||||
}
|
||||
|
||||
InternalIterator* input_;
|
||||
const Comparator* cmp_;
|
||||
MergeHelper* merge_helper_;
|
||||
@@ -181,6 +220,20 @@ class CompactionIterator {
|
||||
SequenceNumber earliest_snapshot_;
|
||||
SequenceNumber latest_snapshot_;
|
||||
|
||||
std::shared_ptr<Logger> info_log_;
|
||||
|
||||
bool allow_data_in_errors_;
|
||||
|
||||
// Comes from comparator.
|
||||
const size_t timestamp_size_;
|
||||
|
||||
// Lower bound timestamp to retain full history in terms of user-defined
|
||||
// timestamp. If a key's timestamp is older than full_history_ts_low_, then
|
||||
// the key *may* be eligible for garbage collection (GC). The skipping logic
|
||||
// is in `NextFromInput()` and `PrepareOutput()`.
|
||||
// If nullptr, NO GC will be performed and all history will be preserved.
|
||||
const std::string* const full_history_ts_low_;
|
||||
|
||||
// State
|
||||
//
|
||||
// Points to a copy of the current compaction iterator output (current_key_)
|
||||
@@ -199,11 +252,13 @@ class CompactionIterator {
|
||||
// Stores whether ikey_.user_key is valid. If set to false, the user key is
|
||||
// not compared against the current key in the underlying iterator.
|
||||
bool has_current_user_key_ = false;
|
||||
bool at_next_ = false; // If false, the iterator
|
||||
// Holds a copy of the current compaction iterator output (or current key in
|
||||
// the underlying iterator during NextFromInput()).
|
||||
// If false, the iterator holds a copy of the current compaction iterator
|
||||
// output (or current key in the underlying iterator during NextFromInput()).
|
||||
bool at_next_ = false;
|
||||
|
||||
IterKey current_key_;
|
||||
Slice current_user_key_;
|
||||
std::string curr_ts_;
|
||||
SequenceNumber current_user_key_sequence_;
|
||||
SequenceNumber current_user_key_snapshot_;
|
||||
|
||||
@@ -233,9 +288,9 @@ class CompactionIterator {
|
||||
// Used to avoid purging uncommitted values. The application can specify
|
||||
// uncommitted values by providing a SnapshotChecker object.
|
||||
bool current_key_committed_;
|
||||
std::shared_ptr<Logger> info_log_;
|
||||
|
||||
bool allow_data_in_errors_;
|
||||
// Saved result of ucmp->CompareTimestamp(current_ts_, *full_history_ts_low_)
|
||||
int cmp_with_history_ts_low_;
|
||||
|
||||
bool IsShuttingDown() {
|
||||
// This is a best-effort facility, so memory_order_relaxed is sufficient.
|
||||
|
||||
@@ -156,19 +156,22 @@ class LoggingForwardVectorIterator : public InternalIterator {
|
||||
|
||||
class FakeCompaction : public CompactionIterator::CompactionProxy {
|
||||
public:
|
||||
FakeCompaction() = default;
|
||||
int level() const override { return 0; }
|
||||
|
||||
int level(size_t /*compaction_input_level*/) const override { return 0; }
|
||||
bool KeyNotExistsBeyondOutputLevel(
|
||||
const Slice& /*user_key*/,
|
||||
std::vector<size_t>* /*level_ptrs*/) const override {
|
||||
return is_bottommost_level || key_not_exists_beyond_output_level;
|
||||
}
|
||||
|
||||
bool bottommost_level() const override { return is_bottommost_level; }
|
||||
|
||||
int number_levels() const override { return 1; }
|
||||
|
||||
Slice GetLargestUserKey() const override {
|
||||
return "\xff\xff\xff\xff\xff\xff\xff\xff\xff";
|
||||
}
|
||||
|
||||
bool allow_ingest_behind() const override { return is_allow_ingest_behind; }
|
||||
|
||||
bool preserve_deletes() const override { return false; }
|
||||
@@ -216,6 +219,9 @@ class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
CompactionIteratorTest()
|
||||
: cmp_(BytewiseComparator()), icmp_(cmp_), snapshots_({}) {}
|
||||
|
||||
explicit CompactionIteratorTest(const Comparator* ucmp)
|
||||
: cmp_(ucmp), icmp_(cmp_), snapshots_({}) {}
|
||||
|
||||
void InitIterators(
|
||||
const std::vector<std::string>& ks, const std::vector<std::string>& vs,
|
||||
const std::vector<std::string>& range_del_ks,
|
||||
@@ -224,7 +230,9 @@ class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
SequenceNumber last_committed_sequence = kMaxSequenceNumber,
|
||||
MergeOperator* merge_op = nullptr, CompactionFilter* filter = nullptr,
|
||||
bool bottommost_level = false,
|
||||
SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber) {
|
||||
SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber,
|
||||
bool key_not_exists_beyond_output_level = false,
|
||||
const std::string* full_history_ts_low = nullptr) {
|
||||
std::unique_ptr<InternalIterator> unfragmented_range_del_iter(
|
||||
new test::VectorIterator(range_del_ks, range_del_vs));
|
||||
auto tombstone_list = std::make_shared<FragmentedRangeTombstoneList>(
|
||||
@@ -236,10 +244,12 @@ class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
range_del_agg_->AddTombstones(std::move(range_del_iter));
|
||||
|
||||
std::unique_ptr<CompactionIterator::CompactionProxy> compaction;
|
||||
if (filter || bottommost_level) {
|
||||
if (filter || bottommost_level || key_not_exists_beyond_output_level) {
|
||||
compaction_proxy_ = new FakeCompaction();
|
||||
compaction_proxy_->is_bottommost_level = bottommost_level;
|
||||
compaction_proxy_->is_allow_ingest_behind = AllowIngestBehind();
|
||||
compaction_proxy_->key_not_exists_beyond_output_level =
|
||||
key_not_exists_beyond_output_level;
|
||||
compaction.reset(compaction_proxy_);
|
||||
}
|
||||
bool use_snapshot_checker = UseSnapshotChecker() || GetParam();
|
||||
@@ -252,6 +262,11 @@ class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
0 /*latest_snapshot*/, snapshot_checker_.get(),
|
||||
0 /*level*/, nullptr /*statistics*/, &shutting_down_));
|
||||
|
||||
if (c_iter_) {
|
||||
// Since iter_ is still used in ~CompactionIterator(), we call
|
||||
// ~CompactionIterator() first.
|
||||
c_iter_.reset();
|
||||
}
|
||||
iter_.reset(new LoggingForwardVectorIterator(ks, vs));
|
||||
iter_->SeekToFirst();
|
||||
c_iter_.reset(new CompactionIterator(
|
||||
@@ -259,8 +274,10 @@ class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
earliest_write_conflict_snapshot, snapshot_checker_.get(),
|
||||
Env::Default(), false /* report_detailed_time */, false,
|
||||
range_del_agg_.get(), nullptr /* blob_file_builder */,
|
||||
false /*allow_data_in_errors*/, std::move(compaction), filter,
|
||||
&shutting_down_));
|
||||
true /*allow_data_in_errors*/, std::move(compaction), filter,
|
||||
&shutting_down_, /*preserve_deletes_seqnum=*/0,
|
||||
/*manual_compaction_paused=*/nullptr, /*info_log=*/nullptr,
|
||||
full_history_ts_low));
|
||||
}
|
||||
|
||||
void AddSnapshot(SequenceNumber snapshot,
|
||||
@@ -282,10 +299,13 @@ class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
MergeOperator* merge_operator = nullptr,
|
||||
CompactionFilter* compaction_filter = nullptr,
|
||||
bool bottommost_level = false,
|
||||
SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber) {
|
||||
SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber,
|
||||
bool key_not_exists_beyond_output_level = false,
|
||||
const std::string* full_history_ts_low = nullptr) {
|
||||
InitIterators(input_keys, input_values, {}, {}, kMaxSequenceNumber,
|
||||
last_committed_seq, merge_operator, compaction_filter,
|
||||
bottommost_level, earliest_write_conflict_snapshot);
|
||||
bottommost_level, earliest_write_conflict_snapshot,
|
||||
key_not_exists_beyond_output_level, full_history_ts_low);
|
||||
c_iter_->SeekToFirst();
|
||||
for (size_t i = 0; i < expected_keys.size(); i++) {
|
||||
std::string info = "i = " + ToString(i);
|
||||
@@ -299,6 +319,11 @@ class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
ASSERT_FALSE(c_iter_->Valid());
|
||||
}
|
||||
|
||||
void ClearSnapshots() {
|
||||
snapshots_.clear();
|
||||
snapshot_map_.clear();
|
||||
}
|
||||
|
||||
const Comparator* cmp_;
|
||||
const InternalKeyComparator icmp_;
|
||||
std::vector<SequenceNumber> snapshots_;
|
||||
@@ -1033,6 +1058,188 @@ INSTANTIATE_TEST_CASE_P(CompactionIteratorWithAllowIngestBehindTestInstance,
|
||||
CompactionIteratorWithAllowIngestBehindTest,
|
||||
testing::Values(true, false));
|
||||
|
||||
class CompactionIteratorTsGcTest : public CompactionIteratorTest {
|
||||
public:
|
||||
CompactionIteratorTsGcTest()
|
||||
: CompactionIteratorTest(test::ComparatorWithU64Ts()) {}
|
||||
};
|
||||
|
||||
TEST_P(CompactionIteratorTsGcTest, NoKeyEligibleForGC) {
|
||||
constexpr char user_key[][2] = {{'a', '\0'}, {'b', '\0'}};
|
||||
const std::vector<std::string> input_keys = {
|
||||
test::KeyStr(/*ts=*/103, user_key[0], /*seq=*/4, kTypeValue),
|
||||
test::KeyStr(/*ts=*/102, user_key[0], /*seq=*/3,
|
||||
kTypeDeletionWithTimestamp),
|
||||
test::KeyStr(/*ts=*/104, user_key[1], /*seq=*/5, kTypeValue)};
|
||||
const std::vector<std::string> input_values = {"a3", "", "b2"};
|
||||
std::string full_history_ts_low;
|
||||
// All keys' timestamps are newer than or equal to 102, thus none of them
|
||||
// will be eligible for GC.
|
||||
PutFixed64(&full_history_ts_low, 102);
|
||||
const std::vector<std::string>& expected_keys = input_keys;
|
||||
const std::vector<std::string>& expected_values = input_values;
|
||||
const std::vector<std::pair<bool, bool>> params = {
|
||||
{false, false}, {false, true}, {true, true}};
|
||||
for (const std::pair<bool, bool>& param : params) {
|
||||
const bool bottommost_level = param.first;
|
||||
const bool key_not_exists_beyond_output_level = param.second;
|
||||
RunTest(input_keys, input_values, expected_keys, expected_values,
|
||||
/*last_committed_seq=*/kMaxSequenceNumber,
|
||||
/*merge_operator=*/nullptr, /*compaction_filter=*/nullptr,
|
||||
bottommost_level,
|
||||
/*earliest_write_conflict_snapshot=*/kMaxSequenceNumber,
|
||||
key_not_exists_beyond_output_level, &full_history_ts_low);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CompactionIteratorTsGcTest, AllKeysOlderThanThreshold) {
|
||||
constexpr char user_key[][2] = {{'a', '\0'}, {'b', '\0'}};
|
||||
const std::vector<std::string> input_keys = {
|
||||
test::KeyStr(/*ts=*/103, user_key[0], /*seq=*/4,
|
||||
kTypeDeletionWithTimestamp),
|
||||
test::KeyStr(/*ts=*/102, user_key[0], /*seq=*/3, kTypeValue),
|
||||
test::KeyStr(/*ts=*/101, user_key[0], /*seq=*/2, kTypeValue),
|
||||
test::KeyStr(/*ts=*/104, user_key[1], /*seq=*/5, kTypeValue)};
|
||||
const std::vector<std::string> input_values = {"", "a2", "a1", "b5"};
|
||||
std::string full_history_ts_low;
|
||||
PutFixed64(&full_history_ts_low, std::numeric_limits<uint64_t>::max());
|
||||
{
|
||||
// With a snapshot at seq 3, both the deletion marker and the key at 3 must
|
||||
// be preserved.
|
||||
AddSnapshot(3);
|
||||
const std::vector<std::string> expected_keys = {
|
||||
input_keys[0], input_keys[1], input_keys[3]};
|
||||
const std::vector<std::string> expected_values = {"", "a2", "b5"};
|
||||
RunTest(input_keys, input_values, expected_keys, expected_values,
|
||||
/*last_committed_seq=*/kMaxSequenceNumber,
|
||||
/*merge_operator=*/nullptr, /*compaction_filter=*/nullptr,
|
||||
/*bottommost_level=*/false,
|
||||
/*earliest_write_conflict_snapshot=*/kMaxSequenceNumber,
|
||||
/*key_not_exists_beyond_output_level=*/false, &full_history_ts_low);
|
||||
ClearSnapshots();
|
||||
}
|
||||
{
|
||||
// No snapshot, the deletion marker should be preserved because the user
|
||||
// key may appear beyond output level.
|
||||
const std::vector<std::string> expected_keys = {input_keys[0],
|
||||
input_keys[3]};
|
||||
const std::vector<std::string> expected_values = {"", "b5"};
|
||||
RunTest(input_keys, input_values, expected_keys, expected_values,
|
||||
/*last_committed_seq=*/kMaxSequenceNumber,
|
||||
/*merge_operator=*/nullptr, /*compaction_filter=*/nullptr,
|
||||
/*bottommost_level=*/false,
|
||||
/*earliest_write_conflict_snapshot=*/kMaxSequenceNumber,
|
||||
/*key_not_exists_beyond_output_level=*/false, &full_history_ts_low);
|
||||
}
|
||||
{
|
||||
// No snapshot, the deletion marker can be dropped because the user key
|
||||
// does not appear in higher levels.
|
||||
const std::vector<std::string> expected_keys = {input_keys[3]};
|
||||
const std::vector<std::string> expected_values = {"b5"};
|
||||
RunTest(input_keys, input_values, expected_keys, expected_values,
|
||||
/*last_committed_seq=*/kMaxSequenceNumber,
|
||||
/*merge_operator=*/nullptr, /*compaction_filter=*/nullptr,
|
||||
/*bottommost_level=*/false,
|
||||
/*earliest_write_conflict_snapshot=*/kMaxSequenceNumber,
|
||||
/*key_not_exists_beyond_output_level=*/true, &full_history_ts_low);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CompactionIteratorTsGcTest, NewHidesOldSameSnapshot) {
|
||||
constexpr char user_key[] = "a";
|
||||
const std::vector<std::string> input_keys = {
|
||||
test::KeyStr(/*ts=*/103, user_key, /*seq=*/4, kTypeDeletionWithTimestamp),
|
||||
test::KeyStr(/*ts=*/102, user_key, /*seq=*/3, kTypeValue),
|
||||
test::KeyStr(/*ts=*/101, user_key, /*seq=*/2, kTypeValue),
|
||||
test::KeyStr(/*ts=*/100, user_key, /*seq=*/1, kTypeValue)};
|
||||
const std::vector<std::string> input_values = {"", "a2", "a1", "a0"};
|
||||
{
|
||||
std::string full_history_ts_low;
|
||||
// Keys whose timestamps larger than or equal to 102 will be preserved.
|
||||
PutFixed64(&full_history_ts_low, 102);
|
||||
const std::vector<std::string> expected_keys = {input_keys[0],
|
||||
input_keys[1]};
|
||||
const std::vector<std::string> expected_values = {"", "a2"};
|
||||
RunTest(input_keys, input_values, expected_keys, expected_values,
|
||||
/*last_committed_seq=*/kMaxSequenceNumber,
|
||||
/*merge_operator=*/nullptr, /*compaction_filter=*/nullptr,
|
||||
/*bottommost_level=*/false,
|
||||
/*earliest_write_conflict_snapshot=*/kMaxSequenceNumber,
|
||||
/*key_not_exists_beyond_output_level=*/false, &full_history_ts_low);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CompactionIteratorTsGcTest, DropTombstones) {
|
||||
constexpr char user_key[] = "a";
|
||||
const std::vector<std::string> input_keys = {
|
||||
test::KeyStr(/*ts=*/103, user_key, /*seq=*/4, kTypeDeletionWithTimestamp),
|
||||
test::KeyStr(/*ts=*/102, user_key, /*seq=*/3, kTypeValue),
|
||||
test::KeyStr(/*ts=*/101, user_key, /*seq=*/2, kTypeDeletionWithTimestamp),
|
||||
test::KeyStr(/*ts=*/100, user_key, /*seq=*/1, kTypeValue)};
|
||||
const std::vector<std::string> input_values = {"", "a2", "", "a0"};
|
||||
const std::vector<std::string> expected_keys = {input_keys[0], input_keys[1]};
|
||||
const std::vector<std::string> expected_values = {"", "a2"};
|
||||
|
||||
// Take a snapshot at seq 2.
|
||||
AddSnapshot(2);
|
||||
|
||||
{
|
||||
// Non-bottommost level, but key does not exist beyond output level.
|
||||
std::string full_history_ts_low;
|
||||
PutFixed64(&full_history_ts_low, 102);
|
||||
RunTest(input_keys, input_values, expected_keys, expected_values,
|
||||
/*last_committed_sequence=*/kMaxSequenceNumber,
|
||||
/*merge_op=*/nullptr, /*compaction_filter=*/nullptr,
|
||||
/*bottommost_level=*/false,
|
||||
/*earliest_write_conflict_snapshot=*/kMaxSequenceNumber,
|
||||
/*key_not_exists_beyond_output_level=*/true, &full_history_ts_low);
|
||||
}
|
||||
{
|
||||
// Bottommost level
|
||||
std::string full_history_ts_low;
|
||||
PutFixed64(&full_history_ts_low, 102);
|
||||
RunTest(input_keys, input_values, expected_keys, expected_values,
|
||||
/*last_committed_seq=*/kMaxSequenceNumber,
|
||||
/*merge_operator=*/nullptr, /*compaction_filter=*/nullptr,
|
||||
/*bottommost_level=*/true,
|
||||
/*earliest_write_conflict_snapshot=*/kMaxSequenceNumber,
|
||||
/*key_not_exists_beyond_output_level=*/false, &full_history_ts_low);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CompactionIteratorTsGcTest, RewriteTs) {
|
||||
constexpr char user_key[] = "a";
|
||||
const std::vector<std::string> input_keys = {
|
||||
test::KeyStr(/*ts=*/103, user_key, /*seq=*/4, kTypeDeletionWithTimestamp),
|
||||
test::KeyStr(/*ts=*/102, user_key, /*seq=*/3, kTypeValue),
|
||||
test::KeyStr(/*ts=*/101, user_key, /*seq=*/2, kTypeDeletionWithTimestamp),
|
||||
test::KeyStr(/*ts=*/100, user_key, /*seq=*/1, kTypeValue)};
|
||||
const std::vector<std::string> input_values = {"", "a2", "", "a0"};
|
||||
const std::vector<std::string> expected_keys = {
|
||||
input_keys[0], input_keys[1], input_keys[2],
|
||||
test::KeyStr(/*ts=*/0, user_key, /*seq=*/0, kTypeValue)};
|
||||
const std::vector<std::string> expected_values = {"", "a2", "", "a0"};
|
||||
|
||||
AddSnapshot(1);
|
||||
AddSnapshot(2);
|
||||
|
||||
{
|
||||
// Bottommost level and need to rewrite both ts and seq.
|
||||
std::string full_history_ts_low;
|
||||
PutFixed64(&full_history_ts_low, 102);
|
||||
RunTest(input_keys, input_values, expected_keys, expected_values,
|
||||
/*last_committed_seq=*/kMaxSequenceNumber,
|
||||
/*merge_operator=*/nullptr, /*compaction_filter=*/nullptr,
|
||||
/*bottommost_level=*/true,
|
||||
/*earliest_write_conflict_snapshot=*/kMaxSequenceNumber,
|
||||
/*key_not_exists_beyond_output_level=*/true, &full_history_ts_low);
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(CompactionIteratorTsGcTestInstance,
|
||||
CompactionIteratorTsGcTest,
|
||||
testing::Values(true, false));
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+154
-61
@@ -20,6 +20,8 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "db/blob/blob_file_addition.h"
|
||||
#include "db/blob/blob_file_builder.h"
|
||||
#include "db/builder.h"
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/db_iter.h"
|
||||
@@ -138,6 +140,7 @@ struct CompactionJob::SubcompactionState {
|
||||
|
||||
// State kept for output being generated
|
||||
std::vector<Output> outputs;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
std::unique_ptr<WritableFileWriter> outfile;
|
||||
std::unique_ptr<TableBuilder> builder;
|
||||
|
||||
@@ -231,21 +234,13 @@ struct CompactionJob::CompactionState {
|
||||
std::vector<CompactionJob::SubcompactionState> sub_compact_states;
|
||||
Status status;
|
||||
|
||||
uint64_t total_bytes;
|
||||
uint64_t num_output_records;
|
||||
size_t num_output_files = 0;
|
||||
uint64_t total_bytes = 0;
|
||||
size_t num_blob_output_files = 0;
|
||||
uint64_t total_blob_bytes = 0;
|
||||
uint64_t num_output_records = 0;
|
||||
|
||||
explicit CompactionState(Compaction* c)
|
||||
: compaction(c),
|
||||
total_bytes(0),
|
||||
num_output_records(0) {}
|
||||
|
||||
size_t NumOutputFiles() {
|
||||
size_t total = 0;
|
||||
for (auto& s : sub_compact_states) {
|
||||
total += s.outputs.size();
|
||||
}
|
||||
return total;
|
||||
}
|
||||
explicit CompactionState(Compaction* c) : compaction(c) {}
|
||||
|
||||
Slice SmallestUserKey() {
|
||||
for (const auto& sub_compact_state : sub_compact_states) {
|
||||
@@ -272,11 +267,29 @@ struct CompactionJob::CompactionState {
|
||||
};
|
||||
|
||||
void CompactionJob::AggregateStatistics() {
|
||||
assert(compact_);
|
||||
|
||||
for (SubcompactionState& sc : compact_->sub_compact_states) {
|
||||
auto& outputs = sc.outputs;
|
||||
|
||||
if (!outputs.empty() && !outputs.back().meta.fd.file_size) {
|
||||
// An error occurred, so ignore the last output.
|
||||
outputs.pop_back();
|
||||
}
|
||||
|
||||
compact_->num_output_files += outputs.size();
|
||||
compact_->total_bytes += sc.total_bytes;
|
||||
|
||||
const auto& blobs = sc.blob_file_additions;
|
||||
|
||||
compact_->num_blob_output_files += blobs.size();
|
||||
|
||||
for (const auto& blob : blobs) {
|
||||
compact_->total_blob_bytes += blob.GetTotalBlobBytes();
|
||||
}
|
||||
|
||||
compact_->num_output_records += sc.num_output_records;
|
||||
}
|
||||
for (SubcompactionState& sc : compact_->sub_compact_states) {
|
||||
|
||||
compaction_job_stats_->Add(sc.compaction_job_stats);
|
||||
}
|
||||
}
|
||||
@@ -286,7 +299,8 @@ CompactionJob::CompactionJob(
|
||||
const FileOptions& file_options, VersionSet* versions,
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const SequenceNumber preserve_deletes_seqnum, LogBuffer* log_buffer,
|
||||
FSDirectory* db_directory, FSDirectory* output_directory, Statistics* stats,
|
||||
FSDirectory* db_directory, FSDirectory* output_directory,
|
||||
FSDirectory* blob_output_directory, Statistics* stats,
|
||||
InstrumentedMutex* db_mutex, ErrorHandler* db_error_handler,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
@@ -295,7 +309,7 @@ CompactionJob::CompactionJob(
|
||||
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::string& db_id,
|
||||
const std::string& db_session_id)
|
||||
const std::string& db_session_id, std::string full_history_ts_low)
|
||||
: job_id_(job_id),
|
||||
compact_(new CompactionState(compaction)),
|
||||
compaction_job_stats_(compaction_job_stats),
|
||||
@@ -317,6 +331,7 @@ CompactionJob::CompactionJob(
|
||||
log_buffer_(log_buffer),
|
||||
db_directory_(db_directory),
|
||||
output_directory_(output_directory),
|
||||
blob_output_directory_(blob_output_directory),
|
||||
stats_(stats),
|
||||
db_mutex_(db_mutex),
|
||||
db_error_handler_(db_error_handler),
|
||||
@@ -329,7 +344,8 @@ CompactionJob::CompactionJob(
|
||||
paranoid_file_checks_(paranoid_file_checks),
|
||||
measure_io_stats_(measure_io_stats),
|
||||
write_hint_(Env::WLTH_NOT_SET),
|
||||
thread_pri_(thread_pri) {
|
||||
thread_pri_(thread_pri),
|
||||
full_history_ts_low_(std::move(full_history_ts_low)) {
|
||||
assert(compaction_job_stats_ != nullptr);
|
||||
assert(log_buffer_ != nullptr);
|
||||
const auto* cfd = compact_->compaction->column_family_data();
|
||||
@@ -604,18 +620,34 @@ Status CompactionJob::Run() {
|
||||
// Check if any thread encountered an error during execution
|
||||
Status status;
|
||||
IOStatus io_s;
|
||||
bool wrote_new_blob_files = false;
|
||||
|
||||
for (const auto& state : compact_->sub_compact_states) {
|
||||
if (!state.status.ok()) {
|
||||
status = state.status;
|
||||
io_s = state.io_status;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!state.blob_file_additions.empty()) {
|
||||
wrote_new_blob_files = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (io_status_.ok()) {
|
||||
io_status_ = io_s;
|
||||
}
|
||||
if (status.ok() && output_directory_) {
|
||||
io_s = output_directory_->Fsync(IOOptions(), nullptr);
|
||||
if (status.ok()) {
|
||||
constexpr IODebugContext* dbg = nullptr;
|
||||
|
||||
if (output_directory_) {
|
||||
io_s = output_directory_->Fsync(IOOptions(), dbg);
|
||||
}
|
||||
|
||||
if (io_s.ok() && wrote_new_blob_files && blob_output_directory_ &&
|
||||
blob_output_directory_ != output_directory_) {
|
||||
io_s = blob_output_directory_->Fsync(IOOptions(), dbg);
|
||||
}
|
||||
}
|
||||
if (io_status_.ok()) {
|
||||
io_status_ = io_s;
|
||||
@@ -721,6 +753,7 @@ Status CompactionJob::Run() {
|
||||
// Finish up all book-keeping to unify the subcompaction results
|
||||
AggregateStatistics();
|
||||
UpdateCompactionStats();
|
||||
|
||||
RecordCompactionIOStats();
|
||||
LogFlush(db_options_.info_log);
|
||||
TEST_SYNC_POINT("CompactionJob::Run():End");
|
||||
@@ -730,11 +763,16 @@ Status CompactionJob::Run() {
|
||||
}
|
||||
|
||||
Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) {
|
||||
assert(compact_);
|
||||
|
||||
AutoThreadOperationStageUpdater stage_updater(
|
||||
ThreadStatus::STAGE_COMPACTION_INSTALL);
|
||||
db_mutex_->AssertHeld();
|
||||
Status status = compact_->status;
|
||||
|
||||
ColumnFamilyData* cfd = compact_->compaction->column_family_data();
|
||||
assert(cfd);
|
||||
|
||||
cfd->internal_stats()->AddCompactionStats(
|
||||
compact_->compaction->output_level(), thread_pri_, compaction_stats_);
|
||||
|
||||
@@ -744,6 +782,7 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) {
|
||||
if (!versions_->io_status().ok()) {
|
||||
io_status_ = versions_->io_status();
|
||||
}
|
||||
|
||||
VersionStorageInfo::LevelSummaryStorage tmp;
|
||||
auto vstorage = cfd->current()->storage_info();
|
||||
const auto& stats = compaction_stats_;
|
||||
@@ -768,6 +807,8 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) {
|
||||
stats.bytes_written / static_cast<double>(stats.micros);
|
||||
}
|
||||
|
||||
const std::string& column_family_name = cfd->GetName();
|
||||
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer_,
|
||||
"[%s] compacted to: %s, MB/sec: %.1f rd, %.1f wr, level %d, "
|
||||
@@ -775,8 +816,9 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) {
|
||||
"MB in(%.1f, %.1f) out(%.1f), read-write-amplify(%.1f) "
|
||||
"write-amplify(%.1f) %s, records in: %" PRIu64
|
||||
", records dropped: %" PRIu64 " output_compression: %s\n",
|
||||
cfd->GetName().c_str(), vstorage->LevelSummary(&tmp), bytes_read_per_sec,
|
||||
bytes_written_per_sec, compact_->compaction->output_level(),
|
||||
column_family_name.c_str(), vstorage->LevelSummary(&tmp),
|
||||
bytes_read_per_sec, bytes_written_per_sec,
|
||||
compact_->compaction->output_level(),
|
||||
stats.num_input_files_in_non_output_levels,
|
||||
stats.num_input_files_in_output_level, stats.num_output_files,
|
||||
stats.bytes_read_non_output_levels / 1048576.0,
|
||||
@@ -787,6 +829,15 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) {
|
||||
CompressionTypeToString(compact_->compaction->output_compression())
|
||||
.c_str());
|
||||
|
||||
const auto& blob_files = vstorage->GetBlobFiles();
|
||||
if (!blob_files.empty()) {
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Blob file summary: head=%" PRIu64 ", tail=%" PRIu64
|
||||
"\n",
|
||||
column_family_name.c_str(), blob_files.begin()->first,
|
||||
blob_files.rbegin()->first);
|
||||
}
|
||||
|
||||
UpdateCompactionJobStats(stats);
|
||||
|
||||
auto stream = event_logger_->LogToBuffer(log_buffer_);
|
||||
@@ -795,11 +846,18 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) {
|
||||
<< "compaction_time_micros" << stats.micros
|
||||
<< "compaction_time_cpu_micros" << stats.cpu_micros << "output_level"
|
||||
<< compact_->compaction->output_level() << "num_output_files"
|
||||
<< compact_->NumOutputFiles() << "total_output_size"
|
||||
<< compact_->total_bytes << "num_input_records"
|
||||
<< stats.num_input_records << "num_output_records"
|
||||
<< compact_->num_output_records << "num_subcompactions"
|
||||
<< compact_->sub_compact_states.size() << "output_compression"
|
||||
<< compact_->num_output_files << "total_output_size"
|
||||
<< compact_->total_bytes;
|
||||
|
||||
if (compact_->num_blob_output_files > 0) {
|
||||
stream << "num_blob_output_files" << compact_->num_blob_output_files
|
||||
<< "total_blob_output_size" << compact_->total_blob_bytes;
|
||||
}
|
||||
|
||||
stream << "num_input_records" << stats.num_input_records
|
||||
<< "num_output_records" << compact_->num_output_records
|
||||
<< "num_subcompactions" << compact_->sub_compact_states.size()
|
||||
<< "output_compression"
|
||||
<< CompressionTypeToString(compact_->compaction->output_compression());
|
||||
|
||||
stream << "num_single_delete_mismatches"
|
||||
@@ -823,12 +881,18 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) {
|
||||
}
|
||||
stream.EndArray();
|
||||
|
||||
if (!blob_files.empty()) {
|
||||
stream << "blob_file_head" << blob_files.begin()->first;
|
||||
stream << "blob_file_tail" << blob_files.rbegin()->first;
|
||||
}
|
||||
|
||||
CleanupCompaction();
|
||||
return status;
|
||||
}
|
||||
|
||||
void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
assert(sub_compact != nullptr);
|
||||
assert(sub_compact);
|
||||
assert(sub_compact->compaction);
|
||||
|
||||
uint64_t prev_cpu_micros = env_->NowCPUNanos() / 1000;
|
||||
|
||||
@@ -899,6 +963,22 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
snapshot_checker_, compact_->compaction->level(),
|
||||
db_options_.statistics.get());
|
||||
|
||||
const MutableCFOptions* mutable_cf_options =
|
||||
sub_compact->compaction->mutable_cf_options();
|
||||
assert(mutable_cf_options);
|
||||
|
||||
std::vector<std::string> blob_file_paths;
|
||||
|
||||
std::unique_ptr<BlobFileBuilder> blob_file_builder(
|
||||
mutable_cf_options->enable_blob_files
|
||||
? new BlobFileBuilder(
|
||||
versions_, env_, fs_.get(),
|
||||
sub_compact->compaction->immutable_cf_options(),
|
||||
mutable_cf_options, &file_options_, job_id_, cfd->GetID(),
|
||||
cfd->GetName(), Env::IOPriority::IO_LOW, write_hint_,
|
||||
&blob_file_paths, &sub_compact->blob_file_additions)
|
||||
: nullptr);
|
||||
|
||||
TEST_SYNC_POINT("CompactionJob::Run():Inprogress");
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"CompactionJob::Run():PausingManualCompaction:1",
|
||||
@@ -916,15 +996,17 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
}
|
||||
|
||||
Status status;
|
||||
const std::string* const full_history_ts_low =
|
||||
full_history_ts_low_.empty() ? nullptr : &full_history_ts_low_;
|
||||
sub_compact->c_iter.reset(new CompactionIterator(
|
||||
input.get(), cfd->user_comparator(), &merge, versions_->LastSequence(),
|
||||
&existing_snapshots_, earliest_write_conflict_snapshot_,
|
||||
snapshot_checker_, env_, ShouldReportDetailedTime(env_, stats_),
|
||||
/*expect_valid_internal_key=*/true, &range_del_agg,
|
||||
/* blob_file_builder */ nullptr, db_options_.allow_data_in_errors,
|
||||
blob_file_builder.get(), db_options_.allow_data_in_errors,
|
||||
sub_compact->compaction, compaction_filter, shutting_down_,
|
||||
preserve_deletes_seqnum_, manual_compaction_paused_,
|
||||
db_options_.info_log));
|
||||
preserve_deletes_seqnum_, manual_compaction_paused_, 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) {
|
||||
@@ -1093,6 +1175,14 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
RecordDroppedKeys(range_del_out_stats, &sub_compact->compaction_job_stats);
|
||||
}
|
||||
|
||||
if (blob_file_builder) {
|
||||
if (status.ok()) {
|
||||
status = blob_file_builder->Finish();
|
||||
}
|
||||
|
||||
blob_file_builder.reset();
|
||||
}
|
||||
|
||||
sub_compact->compaction_job_stats.cpu_micros =
|
||||
env_->NowCPUNanos() / 1000 - prev_cpu_micros;
|
||||
|
||||
@@ -1352,7 +1442,6 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
ExtractInternalKeyFooter(meta->smallest.Encode()) !=
|
||||
PackSequenceAndType(0, kTypeRangeDeletion));
|
||||
}
|
||||
meta->marked_for_compaction = sub_compact->builder->NeedCompact();
|
||||
}
|
||||
const uint64_t current_entries = sub_compact->builder->NumEntries();
|
||||
if (s.ok()) {
|
||||
@@ -1367,6 +1456,7 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
const uint64_t current_bytes = sub_compact->builder->FileSize();
|
||||
if (s.ok()) {
|
||||
meta->fd.file_size = current_bytes;
|
||||
meta->marked_for_compaction = sub_compact->builder->NeedCompact();
|
||||
}
|
||||
sub_compact->current_output()->finished = true;
|
||||
sub_compact->total_bytes += current_bytes;
|
||||
@@ -1479,9 +1569,13 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
|
||||
Status CompactionJob::InstallCompactionResults(
|
||||
const MutableCFOptions& mutable_cf_options) {
|
||||
assert(compact_);
|
||||
|
||||
db_mutex_->AssertHeld();
|
||||
|
||||
auto* compaction = compact_->compaction;
|
||||
assert(compaction);
|
||||
|
||||
// paranoia: verify that the files that we started with
|
||||
// still exist in the current version and in the same original level.
|
||||
// This ensures that a concurrent compaction did not erroneously
|
||||
@@ -1497,23 +1591,32 @@ Status CompactionJob::InstallCompactionResults(
|
||||
|
||||
{
|
||||
Compaction::InputLevelSummaryBuffer inputs_summary;
|
||||
ROCKS_LOG_INFO(
|
||||
db_options_.info_log, "[%s] [JOB %d] Compacted %s => %" PRIu64 " bytes",
|
||||
compaction->column_family_data()->GetName().c_str(), job_id_,
|
||||
compaction->InputLevelSummary(&inputs_summary), compact_->total_bytes);
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
"[%s] [JOB %d] Compacted %s => %" PRIu64 " bytes",
|
||||
compaction->column_family_data()->GetName().c_str(), job_id_,
|
||||
compaction->InputLevelSummary(&inputs_summary),
|
||||
compact_->total_bytes + compact_->total_blob_bytes);
|
||||
}
|
||||
|
||||
VersionEdit* const edit = compaction->edit();
|
||||
assert(edit);
|
||||
|
||||
// Add compaction inputs
|
||||
compaction->AddInputDeletions(compact_->compaction->edit());
|
||||
compaction->AddInputDeletions(edit);
|
||||
|
||||
for (const auto& sub_compact : compact_->sub_compact_states) {
|
||||
for (const auto& out : sub_compact.outputs) {
|
||||
compaction->edit()->AddFile(compaction->output_level(), out.meta);
|
||||
edit->AddFile(compaction->output_level(), out.meta);
|
||||
}
|
||||
|
||||
for (const auto& blob : sub_compact.blob_file_additions) {
|
||||
edit->AddBlobFile(blob);
|
||||
}
|
||||
}
|
||||
|
||||
return versions_->LogAndApply(compaction->column_family_data(),
|
||||
mutable_cf_options, compaction->edit(),
|
||||
db_mutex_, db_directory_);
|
||||
mutable_cf_options, edit, db_mutex_,
|
||||
db_directory_);
|
||||
}
|
||||
|
||||
void CompactionJob::RecordCompactionIOStats() {
|
||||
@@ -1689,6 +1792,8 @@ void CopyPrefix(const Slice& src, size_t prefix_length, std::string* dst) {
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
void CompactionJob::UpdateCompactionStats() {
|
||||
assert(compact_);
|
||||
|
||||
Compaction* compaction = compact_->compaction;
|
||||
compaction_stats_.num_input_files_in_non_output_levels = 0;
|
||||
compaction_stats_.num_input_files_in_output_level = 0;
|
||||
@@ -1706,27 +1811,15 @@ void CompactionJob::UpdateCompactionStats() {
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t num_output_records = 0;
|
||||
compaction_stats_.num_output_files =
|
||||
static_cast<int>(compact_->num_output_files) +
|
||||
static_cast<int>(compact_->num_blob_output_files);
|
||||
compaction_stats_.bytes_written =
|
||||
compact_->total_bytes + compact_->total_blob_bytes;
|
||||
|
||||
for (const auto& sub_compact : compact_->sub_compact_states) {
|
||||
size_t num_output_files = sub_compact.outputs.size();
|
||||
if (sub_compact.builder != nullptr) {
|
||||
// An error occurred so ignore the last output.
|
||||
assert(num_output_files > 0);
|
||||
--num_output_files;
|
||||
}
|
||||
compaction_stats_.num_output_files += static_cast<int>(num_output_files);
|
||||
|
||||
num_output_records += sub_compact.num_output_records;
|
||||
|
||||
for (const auto& out : sub_compact.outputs) {
|
||||
compaction_stats_.bytes_written += out.meta.fd.file_size;
|
||||
}
|
||||
}
|
||||
|
||||
if (compaction_stats_.num_input_records > num_output_records) {
|
||||
if (compaction_stats_.num_input_records > compact_->num_output_records) {
|
||||
compaction_stats_.num_dropped_records =
|
||||
compaction_stats_.num_input_records - num_output_records;
|
||||
compaction_stats_.num_input_records - compact_->num_output_records;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1765,7 +1858,7 @@ void CompactionJob::UpdateCompactionJobStats(
|
||||
compaction_job_stats_->num_output_records = compact_->num_output_records;
|
||||
compaction_job_stats_->num_output_files = stats.num_output_files;
|
||||
|
||||
if (compact_->NumOutputFiles() > 0U) {
|
||||
if (stats.num_output_files > 0) {
|
||||
CopyPrefix(compact_->SmallestUserKey(),
|
||||
CompactionJobStats::kMaxPrefixLength,
|
||||
&compaction_job_stats_->smallest_output_key_prefix);
|
||||
|
||||
@@ -68,8 +68,8 @@ class CompactionJob {
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const SequenceNumber preserve_deletes_seqnum, LogBuffer* log_buffer,
|
||||
FSDirectory* db_directory, FSDirectory* output_directory,
|
||||
Statistics* stats, InstrumentedMutex* db_mutex,
|
||||
ErrorHandler* db_error_handler,
|
||||
FSDirectory* blob_output_directory, Statistics* stats,
|
||||
InstrumentedMutex* db_mutex, ErrorHandler* db_error_handler,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker,
|
||||
@@ -78,7 +78,8 @@ class CompactionJob {
|
||||
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::string& db_id = "", const std::string& db_session_id = "");
|
||||
const std::string& db_id = "", const std::string& db_session_id = "",
|
||||
std::string full_history_ts_low = "");
|
||||
|
||||
~CompactionJob();
|
||||
|
||||
@@ -169,6 +170,7 @@ class CompactionJob {
|
||||
LogBuffer* log_buffer_;
|
||||
FSDirectory* db_directory_;
|
||||
FSDirectory* output_directory_;
|
||||
FSDirectory* blob_output_directory_;
|
||||
Statistics* stats_;
|
||||
InstrumentedMutex* db_mutex_;
|
||||
ErrorHandler* db_error_handler_;
|
||||
@@ -200,6 +202,7 @@ class CompactionJob {
|
||||
Env::WriteLifeTimeHint write_hint_;
|
||||
Env::Priority thread_pri_;
|
||||
IOStatus io_status_;
|
||||
std::string full_history_ts_low_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "env/mock_env.h"
|
||||
#include "file/filename.h"
|
||||
#include "logging/logging.h"
|
||||
#include "memtable/hash_linklist_rep.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "monitoring/thread_status_util.h"
|
||||
|
||||
@@ -67,13 +67,14 @@ void VerifyInitializationOfCompactionJobStats(
|
||||
|
||||
} // namespace
|
||||
|
||||
// TODO(icanadi) Make it simpler once we mock out VersionSet
|
||||
class CompactionJobTest : public testing::Test {
|
||||
public:
|
||||
CompactionJobTest()
|
||||
class CompactionJobTestBase : public testing::Test {
|
||||
protected:
|
||||
CompactionJobTestBase(std::string dbname, const Comparator* ucmp,
|
||||
std::function<std::string(uint64_t)> encode_u64_ts)
|
||||
: env_(Env::Default()),
|
||||
fs_(std::make_shared<LegacyFileSystemWrapper>(env_)),
|
||||
dbname_(test::PerThreadDBPath("compaction_job_test")),
|
||||
dbname_(std::move(dbname)),
|
||||
ucmp_(ucmp),
|
||||
db_options_(),
|
||||
mutable_cf_options_(cf_options_),
|
||||
mutable_db_options_(),
|
||||
@@ -86,12 +87,17 @@ class CompactionJobTest : public testing::Test {
|
||||
shutting_down_(false),
|
||||
preserve_deletes_seqnum_(0),
|
||||
mock_table_factory_(new mock::MockTableFactory()),
|
||||
error_handler_(nullptr, db_options_, &mutex_) {
|
||||
error_handler_(nullptr, db_options_, &mutex_),
|
||||
encode_u64_ts_(std::move(encode_u64_ts)) {}
|
||||
|
||||
void SetUp() override {
|
||||
EXPECT_OK(env_->CreateDirIfMissing(dbname_));
|
||||
db_options_.env = env_;
|
||||
db_options_.fs = fs_;
|
||||
db_options_.db_paths.emplace_back(dbname_,
|
||||
std::numeric_limits<uint64_t>::max());
|
||||
cf_options_.comparator = ucmp_;
|
||||
cf_options_.table_factory = mock_table_factory_;
|
||||
}
|
||||
|
||||
std::string GenerateFileName(uint64_t file_number) {
|
||||
@@ -102,9 +108,10 @@ class CompactionJobTest : public testing::Test {
|
||||
return TableFileName(db_paths, meta.fd.GetNumber(), meta.fd.GetPathId());
|
||||
}
|
||||
|
||||
static std::string KeyStr(const std::string& user_key,
|
||||
const SequenceNumber seq_num, const ValueType t) {
|
||||
return InternalKey(user_key, seq_num, t).Encode().ToString();
|
||||
std::string KeyStr(const std::string& user_key, const SequenceNumber seq_num,
|
||||
const ValueType t, uint64_t ts = 0) {
|
||||
std::string user_key_with_ts = user_key + encode_u64_ts_(ts);
|
||||
return InternalKey(user_key_with_ts, seq_num, t).Encode().ToString();
|
||||
}
|
||||
|
||||
static std::string BlobStr(uint64_t blob_file_number, uint64_t offset,
|
||||
@@ -144,7 +151,8 @@ class CompactionJobTest : public testing::Test {
|
||||
std::string skey;
|
||||
std::string value;
|
||||
std::tie(skey, value) = kv;
|
||||
const Status pikStatus = ParseInternalKey(skey, &key);
|
||||
const Status pik_status =
|
||||
ParseInternalKey(skey, &key, true /* log_err_key */);
|
||||
|
||||
smallest_seqno = std::min(smallest_seqno, key.sequence);
|
||||
largest_seqno = std::max(largest_seqno, key.sequence);
|
||||
@@ -162,7 +170,7 @@ class CompactionJobTest : public testing::Test {
|
||||
|
||||
first_key = false;
|
||||
|
||||
if (pikStatus.ok() && key.type == kTypeBlobIndex) {
|
||||
if (pik_status.ok() && key.type == kTypeBlobIndex) {
|
||||
BlobIndex blob_index;
|
||||
const Status s = blob_index.DecodeFrom(value);
|
||||
if (!s.ok()) {
|
||||
@@ -207,9 +215,9 @@ class CompactionJobTest : public testing::Test {
|
||||
// returns expected result after compaction
|
||||
mock::KVVector CreateTwoFiles(bool gen_corrupted_keys) {
|
||||
stl_wrappers::KVMap expected_results;
|
||||
const int kKeysPerFile = 10000;
|
||||
const int kCorruptKeysPerFile = 200;
|
||||
const int kMatchingKeys = kKeysPerFile / 2;
|
||||
constexpr int kKeysPerFile = 10000;
|
||||
constexpr int kCorruptKeysPerFile = 200;
|
||||
constexpr int kMatchingKeys = kKeysPerFile / 2;
|
||||
SequenceNumber sequence_number = 0;
|
||||
|
||||
auto corrupt_id = [&](int id) {
|
||||
@@ -238,7 +246,7 @@ class CompactionJobTest : public testing::Test {
|
||||
{bottommost_internal_key.Encode().ToString(), value});
|
||||
}
|
||||
}
|
||||
mock::SortKVVector(&contents);
|
||||
mock::SortKVVector(&contents, ucmp_);
|
||||
|
||||
AddMockFile(contents);
|
||||
}
|
||||
@@ -254,7 +262,7 @@ class CompactionJobTest : public testing::Test {
|
||||
}
|
||||
|
||||
void NewDB() {
|
||||
DestroyDB(dbname_, Options());
|
||||
EXPECT_OK(DestroyDB(dbname_, Options()));
|
||||
EXPECT_OK(env_->CreateDirIfMissing(dbname_));
|
||||
versions_.reset(
|
||||
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
@@ -264,12 +272,6 @@ class CompactionJobTest : public testing::Test {
|
||||
SetIdentityFile(env_, dbname_);
|
||||
|
||||
VersionEdit new_db;
|
||||
if (db_options_.write_dbid_to_manifest) {
|
||||
DBImpl* impl = new DBImpl(DBOptions(), dbname_);
|
||||
std::string db_id;
|
||||
impl->GetDbIdentityFromIdentityFile(&db_id);
|
||||
new_db.SetDBId(db_id);
|
||||
}
|
||||
new_db.SetLogNumber(0);
|
||||
new_db.SetNextFile(2);
|
||||
new_db.SetLastSequence(0);
|
||||
@@ -293,13 +295,12 @@ class CompactionJobTest : public testing::Test {
|
||||
|
||||
ASSERT_OK(s);
|
||||
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
cf_options_.table_factory = mock_table_factory_;
|
||||
cf_options_.merge_operator = merge_op_;
|
||||
cf_options_.compaction_filter = compaction_filter_.get();
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
column_families.emplace_back(kDefaultColumnFamilyName, cf_options_);
|
||||
|
||||
EXPECT_OK(versions_->Recover(column_families, false));
|
||||
ASSERT_OK(versions_->Recover(column_families, false));
|
||||
cfd_ = versions_->GetColumnFamilySet()->GetDefault();
|
||||
}
|
||||
|
||||
@@ -337,19 +338,22 @@ class CompactionJobTest : public testing::Test {
|
||||
EventLogger event_logger(db_options_.info_log.get());
|
||||
// TODO(yiwu) add a mock snapshot checker and add test for it.
|
||||
SnapshotChecker* snapshot_checker = nullptr;
|
||||
ASSERT_TRUE(full_history_ts_low_.empty() ||
|
||||
ucmp_->timestamp_size() == full_history_ts_low_.size());
|
||||
CompactionJob compaction_job(
|
||||
0, &compaction, db_options_, env_options_, versions_.get(),
|
||||
&shutting_down_, preserve_deletes_seqnum_, &log_buffer, nullptr,
|
||||
nullptr, nullptr, &mutex_, &error_handler_, snapshots,
|
||||
nullptr, nullptr, nullptr, &mutex_, &error_handler_, snapshots,
|
||||
earliest_write_conflict_snapshot, snapshot_checker, table_cache_,
|
||||
&event_logger, false, false, dbname_, &compaction_job_stats_,
|
||||
Env::Priority::USER, nullptr /* IOTracer */);
|
||||
Env::Priority::USER, nullptr /* IOTracer */,
|
||||
/*manual_compaction_paused=*/nullptr, /*db_id=*/"",
|
||||
/*db_session_id=*/"", full_history_ts_low_);
|
||||
VerifyInitializationOfCompactionJobStats(compaction_job_stats_);
|
||||
|
||||
compaction_job.Prepare();
|
||||
mutex_.Unlock();
|
||||
Status s;
|
||||
s = compaction_job.Run();
|
||||
Status s = compaction_job.Run();
|
||||
ASSERT_OK(s);
|
||||
ASSERT_OK(compaction_job.io_status());
|
||||
mutex_.Lock();
|
||||
@@ -379,6 +383,7 @@ class CompactionJobTest : public testing::Test {
|
||||
Env* env_;
|
||||
std::shared_ptr<FileSystem> fs_;
|
||||
std::string dbname_;
|
||||
const Comparator* const ucmp_;
|
||||
EnvOptions env_options_;
|
||||
ImmutableDBOptions db_options_;
|
||||
ColumnFamilyOptions cf_options_;
|
||||
@@ -397,6 +402,17 @@ class CompactionJobTest : public testing::Test {
|
||||
std::unique_ptr<CompactionFilter> compaction_filter_;
|
||||
std::shared_ptr<MergeOperator> merge_op_;
|
||||
ErrorHandler error_handler_;
|
||||
std::string full_history_ts_low_;
|
||||
const std::function<std::string(uint64_t)> encode_u64_ts_;
|
||||
};
|
||||
|
||||
// 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 ""; }) {}
|
||||
};
|
||||
|
||||
TEST_F(CompactionJobTest, Simple) {
|
||||
@@ -1077,6 +1093,118 @@ TEST_F(CompactionJobTest, OldestBlobFileNumber) {
|
||||
/* expected_oldest_blob_file_number */ 19);
|
||||
}
|
||||
|
||||
class CompactionJobTimestampTest : public CompactionJobTestBase {
|
||||
public:
|
||||
CompactionJobTimestampTest()
|
||||
: CompactionJobTestBase(test::PerThreadDBPath("compaction_job_ts_test"),
|
||||
test::ComparatorWithU64Ts(), test::EncodeInt) {}
|
||||
};
|
||||
|
||||
TEST_F(CompactionJobTimestampTest, GCDisabled) {
|
||||
NewDB();
|
||||
|
||||
auto file1 =
|
||||
mock::MakeMockFile({{KeyStr("a", 10, ValueType::kTypeValue, 100), "a10"},
|
||||
{KeyStr("a", 9, ValueType::kTypeValue, 99), "a9"},
|
||||
{KeyStr("b", 8, ValueType::kTypeValue, 98), "b8"}});
|
||||
AddMockFile(file1);
|
||||
|
||||
auto file2 = mock::MakeMockFile(
|
||||
{{KeyStr("b", 7, ValueType::kTypeDeletionWithTimestamp, 97), ""},
|
||||
{KeyStr("c", 6, ValueType::kTypeDeletionWithTimestamp, 96), ""},
|
||||
{KeyStr("c", 5, ValueType::kTypeValue, 95), "c5"}});
|
||||
AddMockFile(file2);
|
||||
|
||||
SetLastSequence(10);
|
||||
|
||||
auto expected_results = mock::MakeMockFile(
|
||||
{{KeyStr("a", 10, ValueType::kTypeValue, 100), "a10"},
|
||||
{KeyStr("a", 9, ValueType::kTypeValue, 99), "a9"},
|
||||
{KeyStr("b", 8, ValueType::kTypeValue, 98), "b8"},
|
||||
{KeyStr("b", 7, ValueType::kTypeDeletionWithTimestamp, 97), ""},
|
||||
{KeyStr("c", 6, ValueType::kTypeDeletionWithTimestamp, 96), ""},
|
||||
{KeyStr("c", 5, ValueType::kTypeValue, 95), "c5"}});
|
||||
const auto& files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
RunCompaction({files}, expected_results);
|
||||
}
|
||||
|
||||
TEST_F(CompactionJobTimestampTest, NoKeyExpired) {
|
||||
NewDB();
|
||||
|
||||
auto file1 =
|
||||
mock::MakeMockFile({{KeyStr("a", 6, ValueType::kTypeValue, 100), "a6"},
|
||||
{KeyStr("b", 7, ValueType::kTypeValue, 101), "b7"},
|
||||
{KeyStr("c", 5, ValueType::kTypeValue, 99), "c5"}});
|
||||
AddMockFile(file1);
|
||||
|
||||
auto file2 =
|
||||
mock::MakeMockFile({{KeyStr("a", 4, ValueType::kTypeValue, 98), "a4"},
|
||||
{KeyStr("c", 3, ValueType::kTypeValue, 97), "c3"}});
|
||||
AddMockFile(file2);
|
||||
|
||||
SetLastSequence(101);
|
||||
|
||||
auto expected_results =
|
||||
mock::MakeMockFile({{KeyStr("a", 6, ValueType::kTypeValue, 100), "a6"},
|
||||
{KeyStr("a", 4, ValueType::kTypeValue, 98), "a4"},
|
||||
{KeyStr("b", 7, ValueType::kTypeValue, 101), "b7"},
|
||||
{KeyStr("c", 5, ValueType::kTypeValue, 99), "c5"},
|
||||
{KeyStr("c", 3, ValueType::kTypeValue, 97), "c3"}});
|
||||
const auto& files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
|
||||
full_history_ts_low_ = encode_u64_ts_(0);
|
||||
RunCompaction({files}, expected_results);
|
||||
}
|
||||
|
||||
TEST_F(CompactionJobTimestampTest, AllKeysExpired) {
|
||||
NewDB();
|
||||
|
||||
auto file1 = mock::MakeMockFile(
|
||||
{{KeyStr("a", 5, ValueType::kTypeDeletionWithTimestamp, 100), ""},
|
||||
{KeyStr("b", 6, ValueType::kTypeValue, 99), "b6"}});
|
||||
AddMockFile(file1);
|
||||
|
||||
auto file2 = mock::MakeMockFile(
|
||||
{{KeyStr("a", 4, ValueType::kTypeValue, 98), "a4"},
|
||||
{KeyStr("b", 3, ValueType::kTypeDeletionWithTimestamp, 97), ""},
|
||||
{KeyStr("b", 2, ValueType::kTypeValue, 96), "b2"}});
|
||||
AddMockFile(file2);
|
||||
|
||||
SetLastSequence(6);
|
||||
|
||||
auto expected_results =
|
||||
mock::MakeMockFile({{KeyStr("b", 0, ValueType::kTypeValue, 0), "b6"}});
|
||||
const auto& files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
|
||||
full_history_ts_low_ = encode_u64_ts_(std::numeric_limits<uint64_t>::max());
|
||||
RunCompaction({files}, expected_results);
|
||||
}
|
||||
|
||||
TEST_F(CompactionJobTimestampTest, SomeKeysExpired) {
|
||||
NewDB();
|
||||
|
||||
auto file1 =
|
||||
mock::MakeMockFile({{KeyStr("a", 5, ValueType::kTypeValue, 50), "a5"},
|
||||
{KeyStr("b", 6, ValueType::kTypeValue, 49), "b6"}});
|
||||
AddMockFile(file1);
|
||||
|
||||
auto file2 = mock::MakeMockFile(
|
||||
{{KeyStr("a", 3, ValueType::kTypeValue, 48), "a3"},
|
||||
{KeyStr("a", 2, ValueType::kTypeValue, 46), "a2"},
|
||||
{KeyStr("b", 4, ValueType::kTypeDeletionWithTimestamp, 47), ""}});
|
||||
AddMockFile(file2);
|
||||
|
||||
SetLastSequence(6);
|
||||
|
||||
auto expected_results =
|
||||
mock::MakeMockFile({{KeyStr("a", 5, ValueType::kTypeValue, 50), "a5"},
|
||||
{KeyStr("b", 6, ValueType::kTypeValue, 49), "b6"}});
|
||||
const auto& files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
|
||||
full_history_ts_low_ = encode_u64_ts_(49);
|
||||
RunCompaction({files}, expected_results);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -139,11 +139,9 @@ CompressionOptions GetCompressionOptions(const MutableCFOptions& cf_options,
|
||||
if (!enable_compression) {
|
||||
return cf_options.compression_opts;
|
||||
}
|
||||
// If bottommost_compression is set and we are compacting to the
|
||||
// bottommost level then we should use the specified compression options
|
||||
// for the bottmomost_compression.
|
||||
if (cf_options.bottommost_compression != kDisableCompressionOption &&
|
||||
level >= (vstorage->num_non_empty_levels() - 1) &&
|
||||
// If bottommost_compression_opts is enabled and we are compacting to the
|
||||
// bottommost level then we should use the specified compression options.
|
||||
if (level >= (vstorage->num_non_empty_levels() - 1) &&
|
||||
cf_options.bottommost_compression_opts.enabled) {
|
||||
return cf_options.bottommost_compression_opts;
|
||||
}
|
||||
@@ -1045,6 +1043,8 @@ void CompactionPicker::RegisterCompaction(Compaction* c) {
|
||||
level0_compactions_in_progress_.insert(c);
|
||||
}
|
||||
compactions_in_progress_.insert(c);
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionPicker::RegisterCompaction:Registered",
|
||||
c);
|
||||
}
|
||||
|
||||
void CompactionPicker::UnregisterCompaction(Compaction* c) {
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "db/compaction/compaction_picker_level.h"
|
||||
#include "db/compaction/compaction_picker_universal.h"
|
||||
|
||||
#include "logging/logging.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include "util/string_util.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
using std::unique_ptr;
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
namespace {
|
||||
|
||||
+54
-10
@@ -9,7 +9,6 @@
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
@@ -33,12 +32,13 @@
|
||||
#include "table/mock_table.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/cast_util.h"
|
||||
#include "util/random.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
static const int kValueSize = 1000;
|
||||
static constexpr int kValueSize = 1000;
|
||||
|
||||
class CorruptionTest : public testing::Test {
|
||||
public:
|
||||
@@ -69,9 +69,16 @@ class CorruptionTest : public testing::Test {
|
||||
}
|
||||
|
||||
~CorruptionTest() override {
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->LoadDependency({});
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
delete db_;
|
||||
db_ = nullptr;
|
||||
DestroyDB(dbname_, Options());
|
||||
if (getenv("KEEP_DB")) {
|
||||
fprintf(stdout, "db is still at %s\n", dbname_.c_str());
|
||||
} else {
|
||||
EXPECT_OK(DestroyDB(dbname_, Options()));
|
||||
}
|
||||
}
|
||||
|
||||
void CloseDb() {
|
||||
@@ -184,7 +191,7 @@ class CorruptionTest : public testing::Test {
|
||||
}
|
||||
ASSERT_TRUE(!fname.empty()) << filetype;
|
||||
|
||||
test::CorruptFile(fname, offset, bytes_to_corrupt);
|
||||
ASSERT_OK(test::CorruptFile(&env_, fname, offset, bytes_to_corrupt));
|
||||
}
|
||||
|
||||
// corrupts exactly one file at level `level`. if no file found at level,
|
||||
@@ -194,7 +201,8 @@ class CorruptionTest : public testing::Test {
|
||||
db_->GetLiveFilesMetaData(&metadata);
|
||||
for (const auto& m : metadata) {
|
||||
if (m.level == level) {
|
||||
test::CorruptFile(dbname_ + "/" + m.name, offset, bytes_to_corrupt);
|
||||
ASSERT_OK(test::CorruptFile(&env_, dbname_ + "/" + m.name, offset,
|
||||
bytes_to_corrupt));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -249,8 +257,8 @@ TEST_F(CorruptionTest, Recovery) {
|
||||
// is not available for WAL though.
|
||||
CloseDb();
|
||||
#endif
|
||||
Corrupt(kLogFile, 19, 1); // WriteBatch tag for first record
|
||||
Corrupt(kLogFile, log::kBlockSize + 1000, 1); // Somewhere in second block
|
||||
Corrupt(kWalFile, 19, 1); // WriteBatch tag for first record
|
||||
Corrupt(kWalFile, log::kBlockSize + 1000, 1); // Somewhere in second block
|
||||
ASSERT_TRUE(!TryReopen().ok());
|
||||
options_.paranoid_checks = false;
|
||||
Reopen(&options_);
|
||||
@@ -529,7 +537,8 @@ TEST_F(CorruptionTest, RangeDeletionCorrupted) {
|
||||
ImmutableCFOptions(options_), kRangeDelBlock, &range_del_handle));
|
||||
|
||||
ASSERT_OK(TryReopen());
|
||||
test::CorruptFile(filename, static_cast<int>(range_del_handle.offset()), 1);
|
||||
ASSERT_OK(test::CorruptFile(&env_, filename,
|
||||
static_cast<int>(range_del_handle.offset()), 1));
|
||||
ASSERT_TRUE(TryReopen().IsCorruption());
|
||||
}
|
||||
|
||||
@@ -544,7 +553,7 @@ TEST_F(CorruptionTest, FileSystemStateCorrupted) {
|
||||
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
|
||||
std::vector<LiveFileMetaData> metadata;
|
||||
dbi->GetLiveFilesMetaData(&metadata);
|
||||
ASSERT_GT(metadata.size(), size_t(0));
|
||||
ASSERT_GT(metadata.size(), 0);
|
||||
std::string filename = dbname_ + metadata[0].name;
|
||||
|
||||
delete db_;
|
||||
@@ -560,7 +569,7 @@ TEST_F(CorruptionTest, FileSystemStateCorrupted) {
|
||||
} else { // delete the file
|
||||
ASSERT_OK(env_.DeleteFile(filename));
|
||||
Status x = TryReopen(&options);
|
||||
ASSERT_TRUE(x.IsPathNotFound());
|
||||
ASSERT_TRUE(x.IsCorruption());
|
||||
}
|
||||
|
||||
ASSERT_OK(DestroyDB(dbname_, options_));
|
||||
@@ -824,6 +833,41 @@ TEST_F(CorruptionTest, DisableKeyOrderCheck) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(CorruptionTest, VerifyWholeTableChecksum) {
|
||||
CloseDb();
|
||||
Options options;
|
||||
options.env = &env_;
|
||||
ASSERT_OK(DestroyDB(dbname_, options));
|
||||
options.create_if_missing = true;
|
||||
options.file_checksum_gen_factory =
|
||||
ROCKSDB_NAMESPACE::GetFileChecksumGenCrc32cFactory();
|
||||
Reopen(&options);
|
||||
|
||||
Build(10, 5);
|
||||
|
||||
ASSERT_OK(db_->VerifyFileChecksums(ReadOptions()));
|
||||
CloseDb();
|
||||
|
||||
// Corrupt the first byte of each table file, this must be data block.
|
||||
Corrupt(kTableFile, 0, 1);
|
||||
|
||||
ASSERT_OK(TryReopen(&options));
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
int count{0};
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::VerifySstFileChecksum:mismatch", [&](void* arg) {
|
||||
auto* s = reinterpret_cast<Status*>(arg);
|
||||
assert(s);
|
||||
++count;
|
||||
ASSERT_NOK(*s);
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
ASSERT_TRUE(db_->VerifyFileChecksums(ReadOptions()).IsCorruption());
|
||||
ASSERT_EQ(1, count);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -63,6 +63,15 @@ class CuckooTableDBTest : public testing::Test {
|
||||
ASSERT_OK(DB::Open(opts, dbname_, &db_));
|
||||
}
|
||||
|
||||
void DestroyAndReopen(Options* options) {
|
||||
assert(options);
|
||||
ASSERT_OK(db_->Close());
|
||||
delete db_;
|
||||
db_ = nullptr;
|
||||
ASSERT_OK(DestroyDB(dbname_, *options));
|
||||
Reopen(options);
|
||||
}
|
||||
|
||||
Status Put(const Slice& k, const Slice& v) {
|
||||
return db_->Put(WriteOptions(), k, v);
|
||||
}
|
||||
@@ -205,7 +214,7 @@ static std::string Uint64Key(uint64_t i) {
|
||||
TEST_F(CuckooTableDBTest, Uint64Comparator) {
|
||||
Options options = CurrentOptions();
|
||||
options.comparator = test::Uint64Comparator();
|
||||
Reopen(&options);
|
||||
DestroyAndReopen(&options);
|
||||
|
||||
ASSERT_OK(Put(Uint64Key(1), "v1"));
|
||||
ASSERT_OK(Put(Uint64Key(2), "v2"));
|
||||
|
||||
+188
-8
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/flush_block_policy.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "rocksdb/utilities/debug.h"
|
||||
@@ -409,8 +410,7 @@ TEST_F(DBBasicTest, CheckLock) {
|
||||
Status s = DB::Open(options, dbname_, &localdb);
|
||||
ASSERT_NOK(s);
|
||||
#ifdef OS_LINUX
|
||||
ASSERT_TRUE(s.ToString().find("lock hold by current process") !=
|
||||
std::string::npos);
|
||||
ASSERT_TRUE(s.ToString().find("lock ") != std::string::npos);
|
||||
#endif // OS_LINUX
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
@@ -1875,6 +1875,7 @@ TEST_F(DBBasicTest, MultiGetStats) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
options.env = env_;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_size = 1;
|
||||
@@ -1884,7 +1885,7 @@ TEST_F(DBBasicTest, MultiGetStats) {
|
||||
table_options.no_block_cache = true;
|
||||
table_options.cache_index_and_filter_blocks = false;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
int total_keys = 2000;
|
||||
@@ -2168,7 +2169,7 @@ TEST_F(DBBasicTest, MultiGetIOBufferOverrun) {
|
||||
table_options.block_size = 16 * 1024;
|
||||
ASSERT_TRUE(table_options.block_size >
|
||||
BlockBasedTable::kMultiGetReadStackBufSize);
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
Reopen(options);
|
||||
|
||||
std::string zero_str(128, '\0');
|
||||
@@ -2278,6 +2279,43 @@ class TableFileListener : public EventListener {
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_F(DBBasicTest, LastSstFileNotInManifest) {
|
||||
// If the last sst file is not tracked in MANIFEST,
|
||||
// or the VersionEdit for the last sst file is not synced,
|
||||
// on recovery, the last sst file should be deleted,
|
||||
// and new sst files shouldn't reuse its file number.
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
Close();
|
||||
|
||||
// Manually add a sst file.
|
||||
constexpr uint64_t kSstFileNumber = 100;
|
||||
const std::string kSstFile = MakeTableFileName(dbname_, kSstFileNumber);
|
||||
ASSERT_OK(WriteStringToFile(env_, /* data = */ "bad sst file content",
|
||||
/* fname = */ kSstFile,
|
||||
/* should_sync = */ true));
|
||||
ASSERT_OK(env_->FileExists(kSstFile));
|
||||
|
||||
TableFileListener* listener = new TableFileListener();
|
||||
options.listeners.emplace_back(listener);
|
||||
Reopen(options);
|
||||
// kSstFile should already be deleted.
|
||||
ASSERT_TRUE(env_->FileExists(kSstFile).IsNotFound());
|
||||
|
||||
ASSERT_OK(Put("k", "v"));
|
||||
ASSERT_OK(Flush());
|
||||
// New sst file should have file number > kSstFileNumber.
|
||||
std::vector<std::string>& files =
|
||||
listener->GetFiles(kDefaultColumnFamilyName);
|
||||
ASSERT_EQ(files.size(), 1);
|
||||
const std::string fname = files[0].erase(0, (dbname_ + "/").size());
|
||||
uint64_t number = 0;
|
||||
FileType type = kTableFile;
|
||||
ASSERT_TRUE(ParseFileName(fname, &number, &type));
|
||||
ASSERT_EQ(type, kTableFile);
|
||||
ASSERT_GT(number, kSstFileNumber);
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, RecoverWithMissingFiles) {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
@@ -2405,7 +2443,7 @@ TEST_F(DBBasicTest, RecoverWithNoManifest) {
|
||||
ASSERT_OK(env_->GetChildren(dbname_, &files));
|
||||
for (const auto& file : files) {
|
||||
uint64_t number = 0;
|
||||
FileType type = kLogFile;
|
||||
FileType type = kWalFile;
|
||||
if (ParseFileName(file, &number, &type) && type == kDescriptorFile) {
|
||||
ASSERT_OK(env_->DeleteFile(dbname_ + "/" + file));
|
||||
}
|
||||
@@ -2549,12 +2587,13 @@ class DBBasicTestMultiGet : public DBTestBase {
|
||||
table_options.block_cache_compressed = compressed_cache_;
|
||||
table_options.flush_block_policy_factory.reset(
|
||||
new MyFlushBlockPolicyFactory());
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
if (!compression_enabled_) {
|
||||
options.compression = kNoCompression;
|
||||
} else {
|
||||
options.compression_opts.parallel_threads = compression_parallel_threads;
|
||||
}
|
||||
options_ = options;
|
||||
Reopen(options);
|
||||
|
||||
if (num_cfs > 1) {
|
||||
@@ -2624,6 +2663,7 @@ class DBBasicTestMultiGet : public DBTestBase {
|
||||
bool compression_enabled() { return compression_enabled_; }
|
||||
bool has_compressed_cache() { return compressed_cache_ != nullptr; }
|
||||
bool has_uncompressed_cache() { return uncompressed_cache_ != nullptr; }
|
||||
Options get_options() { return options_; }
|
||||
|
||||
static void SetUpTestCase() {}
|
||||
static void TearDownTestCase() {}
|
||||
@@ -2709,6 +2749,7 @@ class DBBasicTestMultiGet : public DBTestBase {
|
||||
|
||||
std::shared_ptr<MyBlockCache> compressed_cache_;
|
||||
std::shared_ptr<MyBlockCache> uncompressed_cache_;
|
||||
Options options_;
|
||||
bool compression_enabled_;
|
||||
std::vector<std::string> values_;
|
||||
std::vector<std::string> uncompressable_values_;
|
||||
@@ -2851,6 +2892,123 @@ TEST_P(DBBasicTestWithParallelIO, MultiGet) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_P(DBBasicTestWithParallelIO, MultiGetDirectIO) {
|
||||
class FakeDirectIOEnv : public EnvWrapper {
|
||||
class FakeDirectIOSequentialFile;
|
||||
class FakeDirectIORandomAccessFile;
|
||||
|
||||
public:
|
||||
FakeDirectIOEnv(Env* env) : EnvWrapper(env) {}
|
||||
|
||||
Status NewRandomAccessFile(const std::string& fname,
|
||||
std::unique_ptr<RandomAccessFile>* result,
|
||||
const EnvOptions& options) override {
|
||||
std::unique_ptr<RandomAccessFile> file;
|
||||
assert(options.use_direct_reads);
|
||||
EnvOptions opts = options;
|
||||
opts.use_direct_reads = false;
|
||||
Status s = target()->NewRandomAccessFile(fname, &file, opts);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
result->reset(new FakeDirectIORandomAccessFile(std::move(file)));
|
||||
return s;
|
||||
}
|
||||
|
||||
private:
|
||||
class FakeDirectIOSequentialFile : public SequentialFileWrapper {
|
||||
public:
|
||||
FakeDirectIOSequentialFile(std::unique_ptr<SequentialFile>&& file)
|
||||
: SequentialFileWrapper(file.get()), file_(std::move(file)) {}
|
||||
~FakeDirectIOSequentialFile() {}
|
||||
|
||||
bool use_direct_io() const override { return true; }
|
||||
size_t GetRequiredBufferAlignment() const override { return 1; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<SequentialFile> file_;
|
||||
};
|
||||
|
||||
class FakeDirectIORandomAccessFile : public RandomAccessFileWrapper {
|
||||
public:
|
||||
FakeDirectIORandomAccessFile(std::unique_ptr<RandomAccessFile>&& file)
|
||||
: RandomAccessFileWrapper(file.get()), file_(std::move(file)) {}
|
||||
~FakeDirectIORandomAccessFile() {}
|
||||
|
||||
bool use_direct_io() const override { return true; }
|
||||
size_t GetRequiredBufferAlignment() const override { return 1; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<RandomAccessFile> file_;
|
||||
};
|
||||
};
|
||||
|
||||
std::unique_ptr<FakeDirectIOEnv> env(new FakeDirectIOEnv(env_));
|
||||
Options opts = get_options();
|
||||
opts.env = env.get();
|
||||
opts.use_direct_reads = true;
|
||||
Reopen(opts);
|
||||
|
||||
std::vector<std::string> key_data(10);
|
||||
std::vector<Slice> keys;
|
||||
// We cannot resize a PinnableSlice vector, so just set initial size to
|
||||
// largest we think we will need
|
||||
std::vector<PinnableSlice> values(10);
|
||||
std::vector<Status> statuses;
|
||||
ReadOptions ro;
|
||||
ro.fill_cache = fill_cache();
|
||||
|
||||
// Warm up the cache first
|
||||
key_data.emplace_back(Key(0));
|
||||
keys.emplace_back(Slice(key_data.back()));
|
||||
key_data.emplace_back(Key(50));
|
||||
keys.emplace_back(Slice(key_data.back()));
|
||||
statuses.resize(keys.size());
|
||||
|
||||
dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(),
|
||||
keys.data(), values.data(), statuses.data(), true);
|
||||
ASSERT_TRUE(CheckValue(0, values[0].ToString()));
|
||||
ASSERT_TRUE(CheckValue(50, values[1].ToString()));
|
||||
|
||||
int random_reads = env_->random_read_counter_.Read();
|
||||
key_data[0] = Key(1);
|
||||
key_data[1] = Key(51);
|
||||
keys[0] = Slice(key_data[0]);
|
||||
keys[1] = Slice(key_data[1]);
|
||||
values[0].Reset();
|
||||
values[1].Reset();
|
||||
if (uncompressed_cache_) {
|
||||
uncompressed_cache_->SetCapacity(0);
|
||||
uncompressed_cache_->SetCapacity(1048576);
|
||||
}
|
||||
dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(),
|
||||
keys.data(), values.data(), statuses.data(), true);
|
||||
ASSERT_TRUE(CheckValue(1, values[0].ToString()));
|
||||
ASSERT_TRUE(CheckValue(51, values[1].ToString()));
|
||||
|
||||
bool read_from_cache = false;
|
||||
if (fill_cache()) {
|
||||
if (has_uncompressed_cache()) {
|
||||
read_from_cache = true;
|
||||
} else if (has_compressed_cache() && compression_enabled()) {
|
||||
read_from_cache = true;
|
||||
}
|
||||
}
|
||||
|
||||
int expected_reads = random_reads;
|
||||
if (!compression_enabled() || !has_compressed_cache()) {
|
||||
expected_reads += 2;
|
||||
} else {
|
||||
expected_reads += (read_from_cache ? 0 : 2);
|
||||
}
|
||||
if (env_->random_read_counter_.Read() != expected_reads) {
|
||||
ASSERT_EQ(env_->random_read_counter_.Read(), expected_reads);
|
||||
}
|
||||
Close();
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_P(DBBasicTestWithParallelIO, MultiGetWithChecksumMismatch) {
|
||||
std::vector<std::string> key_data(10);
|
||||
std::vector<Slice> keys;
|
||||
@@ -2976,7 +3134,7 @@ class DeadlineFS : public FileSystemWrapper {
|
||||
// or to simply delay but return success anyway. The latter mimics the
|
||||
// behavior of PosixFileSystem, which does not enforce any timeout
|
||||
explicit DeadlineFS(SpecialEnv* env, bool error_on_delay)
|
||||
: FileSystemWrapper(FileSystem::Default()),
|
||||
: FileSystemWrapper(env->GetFileSystem()),
|
||||
deadline_(std::chrono::microseconds::zero()),
|
||||
io_timeout_(std::chrono::microseconds::zero()),
|
||||
env_(env),
|
||||
@@ -3151,7 +3309,7 @@ TEST_F(DBBasicTestMultiGetDeadline, MultiGetDeadlineExceeded) {
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(1048576);
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_cache = cache;
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.env = env.get();
|
||||
SetTimeElapseOnlySleepOnReopen(&options);
|
||||
ReopenWithColumnFamilies(GetCFNames(), options);
|
||||
@@ -3303,6 +3461,28 @@ TEST_F(DBBasicTest, ManifestWriteFailure) {
|
||||
Reopen(options);
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBBasicTest, VerifyFileChecksums) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.env = env_;
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("a", "value"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_TRUE(db_->VerifyFileChecksums(ReadOptions()).IsInvalidArgument());
|
||||
|
||||
options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
|
||||
Reopen(options);
|
||||
ASSERT_OK(db_->VerifyFileChecksums(ReadOptions()));
|
||||
|
||||
// Write an L0 with checksum computed.
|
||||
ASSERT_OK(Put("b", "value"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(db_->VerifyFileChecksums(ReadOptions()));
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
// A test class for intercepting random reads and injecting artificial
|
||||
// delays. Used for testing the deadline/timeout feature
|
||||
class DBBasicTestDeadline
|
||||
|
||||
@@ -837,8 +837,9 @@ TEST_F(DBBlockCacheTest, CacheCompressionDict) {
|
||||
Random rnd(301);
|
||||
for (auto compression_type : compression_types) {
|
||||
Options options = CurrentOptions();
|
||||
options.compression = compression_type;
|
||||
options.compression_opts.max_dict_bytes = 4096;
|
||||
options.bottommost_compression = compression_type;
|
||||
options.bottommost_compression_opts.max_dict_bytes = 4096;
|
||||
options.bottommost_compression_opts.enabled = true;
|
||||
options.create_if_missing = true;
|
||||
options.num_levels = 2;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
@@ -991,6 +992,9 @@ TEST_P(DBBlockCachePinningTest, TwoLevelDB) {
|
||||
++expected_index_misses;
|
||||
}
|
||||
}
|
||||
if (unpartitioned_pinning_ == PinningTier::kNone) {
|
||||
++expected_compression_dict_misses;
|
||||
}
|
||||
ASSERT_EQ(expected_filter_misses,
|
||||
TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
|
||||
ASSERT_EQ(expected_index_misses,
|
||||
|
||||
@@ -514,24 +514,24 @@ INSTANTIATE_TEST_CASE_P(
|
||||
::testing::Values(
|
||||
std::make_tuple(BFP::kDeprecatedBlock, false,
|
||||
test::kDefaultFormatVersion),
|
||||
std::make_tuple(BFP::kAuto, true, test::kDefaultFormatVersion),
|
||||
std::make_tuple(BFP::kAuto, false, test::kDefaultFormatVersion)));
|
||||
std::make_tuple(BFP::kAutoBloom, true, test::kDefaultFormatVersion),
|
||||
std::make_tuple(BFP::kAutoBloom, false, test::kDefaultFormatVersion)));
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
FormatDef, DBBloomFilterTestWithParam,
|
||||
::testing::Values(
|
||||
std::make_tuple(BFP::kDeprecatedBlock, false,
|
||||
test::kDefaultFormatVersion),
|
||||
std::make_tuple(BFP::kAuto, true, test::kDefaultFormatVersion),
|
||||
std::make_tuple(BFP::kAuto, false, test::kDefaultFormatVersion)));
|
||||
std::make_tuple(BFP::kAutoBloom, true, test::kDefaultFormatVersion),
|
||||
std::make_tuple(BFP::kAutoBloom, false, test::kDefaultFormatVersion)));
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
FormatLatest, DBBloomFilterTestWithParam,
|
||||
::testing::Values(
|
||||
std::make_tuple(BFP::kDeprecatedBlock, false,
|
||||
test::kLatestFormatVersion),
|
||||
std::make_tuple(BFP::kAuto, true, test::kLatestFormatVersion),
|
||||
std::make_tuple(BFP::kAuto, false, test::kLatestFormatVersion)));
|
||||
std::make_tuple(BFP::kAutoBloom, true, test::kLatestFormatVersion),
|
||||
std::make_tuple(BFP::kAutoBloom, false, test::kLatestFormatVersion)));
|
||||
#endif // ROCKSDB_VALGRIND_RUN
|
||||
|
||||
TEST_F(DBBloomFilterTest, BloomFilterRate) {
|
||||
@@ -1730,6 +1730,7 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterUpperBound) {
|
||||
int using_full_builder = bfp_impl != BFP::kDeprecatedBlock;
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.env = CurrentOptions().env;
|
||||
options.prefix_extractor.reset(NewCappedPrefixTransform(4));
|
||||
options.disable_auto_compactions = true;
|
||||
options.statistics = CreateDBStatistics();
|
||||
@@ -1860,6 +1861,7 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterMultipleSST) {
|
||||
for (auto bfp_impl : BFP::kAllFixedImpls) {
|
||||
int using_full_builder = bfp_impl != BFP::kDeprecatedBlock;
|
||||
Options options;
|
||||
options.env = CurrentOptions().env;
|
||||
options.create_if_missing = true;
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
||||
options.disable_auto_compactions = true;
|
||||
@@ -2052,6 +2054,7 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterNewColumnFamily) {
|
||||
TEST_F(DBBloomFilterTest, DynamicBloomFilterOptions) {
|
||||
for (auto bfp_impl : BFP::kAllFixedImpls) {
|
||||
Options options;
|
||||
options.env = CurrentOptions().env;
|
||||
options.create_if_missing = true;
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
||||
options.disable_auto_compactions = true;
|
||||
|
||||
@@ -314,7 +314,7 @@ TEST_F(DBTestCompactionFilter, CompactionFilter) {
|
||||
ASSERT_OK(iter->status());
|
||||
while (iter->Valid()) {
|
||||
ParsedInternalKey ikey(Slice(), 0, kTypeValue);
|
||||
ASSERT_OK(ParseInternalKey(iter->key(), &ikey));
|
||||
ASSERT_OK(ParseInternalKey(iter->key(), &ikey, true /* log_err_key */));
|
||||
total++;
|
||||
if (ikey.sequence != 0) {
|
||||
count++;
|
||||
@@ -405,7 +405,7 @@ TEST_F(DBTestCompactionFilter, CompactionFilter) {
|
||||
ASSERT_OK(iter->status());
|
||||
while (iter->Valid()) {
|
||||
ParsedInternalKey ikey(Slice(), 0, kTypeValue);
|
||||
ASSERT_OK(ParseInternalKey(iter->key(), &ikey));
|
||||
ASSERT_OK(ParseInternalKey(iter->key(), &ikey, true /* log_err_key */));
|
||||
ASSERT_NE(ikey.sequence, (unsigned)0);
|
||||
count++;
|
||||
iter->Next();
|
||||
@@ -624,7 +624,7 @@ TEST_F(DBTestCompactionFilter, CompactionFilterContextManual) {
|
||||
ASSERT_OK(iter->status());
|
||||
while (iter->Valid()) {
|
||||
ParsedInternalKey ikey(Slice(), 0, kTypeValue);
|
||||
ASSERT_OK(ParseInternalKey(iter->key(), &ikey));
|
||||
ASSERT_OK(ParseInternalKey(iter->key(), &ikey, true /* log_err_key */));
|
||||
total++;
|
||||
if (ikey.sequence != 0) {
|
||||
count++;
|
||||
|
||||
+178
-3
@@ -3270,7 +3270,7 @@ TEST_P(DBCompactionTestWithParam, IntraL0Compaction) {
|
||||
table_options.block_cache = NewLRUCache(64 << 20); // 64MB
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
table_options.pin_l0_filter_and_index_blocks_in_cache = true;
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
@@ -5023,7 +5023,7 @@ TEST_F(DBCompactionTest, ManualCompactionFailsInReadOnlyMode) {
|
||||
// is in read-only mode. Verify it now at least returns, despite failing.
|
||||
const int kNumL0Files = 4;
|
||||
std::unique_ptr<FaultInjectionTestEnv> mock_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
new FaultInjectionTestEnv(env_));
|
||||
Options opts = CurrentOptions();
|
||||
opts.disable_auto_compactions = true;
|
||||
opts.env = mock_env.get();
|
||||
@@ -5250,7 +5250,7 @@ TEST_F(DBCompactionTest, ConsistencyFailTest2) {
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.block_size = 400; // small block size
|
||||
options.table_factory.reset(new BlockBasedTableFactory(bbto));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
@@ -5845,6 +5845,181 @@ TEST_F(DBCompactionTest, ChangeLevelErrorPathTest) {
|
||||
ASSERT_EQ("0,5", FilesPerLevel(0));
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactionWithBlob) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.disable_auto_compactions = true;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
constexpr char first_key[] = "first_key";
|
||||
constexpr char second_key[] = "second_key";
|
||||
constexpr char first_value[] = "first_value";
|
||||
constexpr char second_value[] = "second_value";
|
||||
constexpr char third_value[] = "third_value";
|
||||
|
||||
ASSERT_OK(Put(first_key, first_value));
|
||||
ASSERT_OK(Put(second_key, first_value));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(Put(first_key, second_value));
|
||||
ASSERT_OK(Put(second_key, second_value));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(Put(first_key, third_value));
|
||||
ASSERT_OK(Put(second_key, third_value));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
options.enable_blob_files = true;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
constexpr Slice* begin = nullptr;
|
||||
constexpr Slice* end = nullptr;
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), begin, end));
|
||||
|
||||
ASSERT_EQ(Get(first_key), third_value);
|
||||
ASSERT_EQ(Get(second_key), third_value);
|
||||
|
||||
VersionSet* const versions = dbfull()->TEST_GetVersionSet();
|
||||
assert(versions);
|
||||
|
||||
ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault();
|
||||
assert(cfd);
|
||||
|
||||
Version* const current = cfd->current();
|
||||
assert(current);
|
||||
|
||||
const VersionStorageInfo* const storage_info = current->storage_info();
|
||||
assert(storage_info);
|
||||
|
||||
const auto& l1_files = storage_info->LevelFiles(1);
|
||||
ASSERT_EQ(l1_files.size(), 1);
|
||||
|
||||
const FileMetaData* const table_file = l1_files[0];
|
||||
assert(table_file);
|
||||
|
||||
const auto& blob_files = storage_info->GetBlobFiles();
|
||||
ASSERT_EQ(blob_files.size(), 1);
|
||||
|
||||
const auto& blob_file = blob_files.begin()->second;
|
||||
assert(blob_file);
|
||||
|
||||
ASSERT_EQ(table_file->smallest.user_key(), first_key);
|
||||
ASSERT_EQ(table_file->largest.user_key(), second_key);
|
||||
ASSERT_EQ(table_file->oldest_blob_file_number,
|
||||
blob_file->GetBlobFileNumber());
|
||||
|
||||
ASSERT_EQ(blob_file->GetTotalBlobCount(), 2);
|
||||
|
||||
const InternalStats* const internal_stats = cfd->internal_stats();
|
||||
assert(internal_stats);
|
||||
|
||||
const uint64_t expected_bytes =
|
||||
table_file->fd.GetFileSize() + blob_file->GetTotalBlobBytes();
|
||||
|
||||
const auto& compaction_stats = internal_stats->TEST_GetCompactionStats();
|
||||
ASSERT_GE(compaction_stats.size(), 2);
|
||||
ASSERT_EQ(compaction_stats[1].bytes_written, expected_bytes);
|
||||
ASSERT_EQ(compaction_stats[1].num_output_files, 2);
|
||||
}
|
||||
|
||||
class DBCompactionTestBlobError
|
||||
: public DBCompactionTest,
|
||||
public testing::WithParamInterface<std::string> {
|
||||
public:
|
||||
DBCompactionTestBlobError()
|
||||
: fault_injection_env_(env_), sync_point_(GetParam()) {}
|
||||
~DBCompactionTestBlobError() { Close(); }
|
||||
|
||||
FaultInjectionTestEnv fault_injection_env_;
|
||||
std::string sync_point_;
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBCompactionTestBlobError, DBCompactionTestBlobError,
|
||||
::testing::ValuesIn(std::vector<std::string>{
|
||||
"BlobFileBuilder::WriteBlobToFile:AddRecord",
|
||||
"BlobFileBuilder::WriteBlobToFile:AppendFooter"}));
|
||||
|
||||
TEST_P(DBCompactionTestBlobError, CompactionError) {
|
||||
Options options;
|
||||
options.disable_auto_compactions = true;
|
||||
options.env = env_;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
constexpr char first_key[] = "first_key";
|
||||
constexpr char second_key[] = "second_key";
|
||||
constexpr char first_value[] = "first_value";
|
||||
constexpr char second_value[] = "second_value";
|
||||
constexpr char third_value[] = "third_value";
|
||||
|
||||
ASSERT_OK(Put(first_key, first_value));
|
||||
ASSERT_OK(Put(second_key, first_value));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(Put(first_key, second_value));
|
||||
ASSERT_OK(Put(second_key, second_value));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(Put(first_key, third_value));
|
||||
ASSERT_OK(Put(second_key, third_value));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
options.enable_blob_files = true;
|
||||
options.env = &fault_injection_env_;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(sync_point_, [this](void* /* arg */) {
|
||||
fault_injection_env_.SetFilesystemActive(false,
|
||||
Status::IOError(sync_point_));
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
constexpr Slice* begin = nullptr;
|
||||
constexpr Slice* end = nullptr;
|
||||
|
||||
ASSERT_TRUE(db_->CompactRange(CompactRangeOptions(), begin, end).IsIOError());
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
VersionSet* const versions = dbfull()->TEST_GetVersionSet();
|
||||
assert(versions);
|
||||
|
||||
ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault();
|
||||
assert(cfd);
|
||||
|
||||
Version* const current = cfd->current();
|
||||
assert(current);
|
||||
|
||||
const VersionStorageInfo* const storage_info = current->storage_info();
|
||||
assert(storage_info);
|
||||
|
||||
const auto& l1_files = storage_info->LevelFiles(1);
|
||||
ASSERT_TRUE(l1_files.empty());
|
||||
|
||||
const auto& blob_files = storage_info->GetBlobFiles();
|
||||
ASSERT_TRUE(blob_files.empty());
|
||||
|
||||
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);
|
||||
|
||||
if (sync_point_ == "BlobFileBuilder::WriteBlobToFile:AddRecord") {
|
||||
ASSERT_EQ(compaction_stats[1].bytes_written, 0);
|
||||
ASSERT_EQ(compaction_stats[1].num_output_files, 0);
|
||||
} else {
|
||||
// SST file writing succeeded; blob file writing failed (during Finish)
|
||||
ASSERT_GT(compaction_stats[1].bytes_written, 0);
|
||||
ASSERT_EQ(compaction_stats[1].num_output_files, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // !defined(ROCKSDB_LITE)
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -141,6 +141,7 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase2) {
|
||||
options.max_background_compactions = 2;
|
||||
options.num_levels = 5;
|
||||
options.max_compaction_bytes = 0; // Force not expanding in compactions
|
||||
options.db_host_id = ""; // Setting this messes up the file size calculation
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_size = 1024;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
@@ -18,6 +18,13 @@ class DBEncryptionTest : public DBTestBase {
|
||||
public:
|
||||
DBEncryptionTest()
|
||||
: DBTestBase("/db_encryption_test", /*env_do_fsync=*/true) {}
|
||||
Env* GetTargetEnv() {
|
||||
if (encrypted_env_ != nullptr) {
|
||||
return (static_cast<EnvWrapper*>(encrypted_env_))->target();
|
||||
} else {
|
||||
return env_;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
@@ -34,20 +41,20 @@ TEST_F(DBEncryptionTest, CheckEncrypted) {
|
||||
auto status = env_->GetChildren(dbname_, &fileNames);
|
||||
ASSERT_OK(status);
|
||||
|
||||
auto defaultEnv = Env::Default();
|
||||
Env* target = GetTargetEnv();
|
||||
int hits = 0;
|
||||
for (auto it = fileNames.begin() ; it != fileNames.end(); ++it) {
|
||||
if ((*it == "..") || (*it == ".")) {
|
||||
if ((*it == "..") || (*it == ".") || (*it == "LOCK")) {
|
||||
continue;
|
||||
}
|
||||
auto filePath = dbname_ + "/" + *it;
|
||||
std::unique_ptr<SequentialFile> seqFile;
|
||||
auto envOptions = EnvOptions(CurrentOptions());
|
||||
status = defaultEnv->NewSequentialFile(filePath, &seqFile, envOptions);
|
||||
status = target->NewSequentialFile(filePath, &seqFile, envOptions);
|
||||
ASSERT_OK(status);
|
||||
|
||||
uint64_t fileSize;
|
||||
status = defaultEnv->GetFileSize(filePath, &fileSize);
|
||||
status = target->GetFileSize(filePath, &fileSize);
|
||||
ASSERT_OK(status);
|
||||
|
||||
std::string scratch;
|
||||
@@ -85,7 +92,7 @@ TEST_F(DBEncryptionTest, CheckEncrypted) {
|
||||
}
|
||||
|
||||
TEST_F(DBEncryptionTest, ReadEmptyFile) {
|
||||
auto defaultEnv = Env::Default();
|
||||
auto defaultEnv = GetTargetEnv();
|
||||
|
||||
// create empty file for reading it back in later
|
||||
auto envOptions = EnvOptions(CurrentOptions());
|
||||
|
||||
+21
-10
@@ -450,9 +450,11 @@ TEST_F(DBFlushTest, FlushWithBlob) {
|
||||
constexpr uint64_t min_blob_size = 10;
|
||||
|
||||
Options options;
|
||||
options.env = CurrentOptions().env;
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = min_blob_size;
|
||||
options.disable_auto_compactions = true;
|
||||
options.env = env_;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
@@ -470,9 +472,7 @@ TEST_F(DBFlushTest, FlushWithBlob) {
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_EQ(Get("key1"), short_value);
|
||||
|
||||
// TODO: enable once Get support is implemented for blobs
|
||||
// ASSERT_EQ(Get("key2"), long_value);
|
||||
ASSERT_EQ(Get("key2"), long_value);
|
||||
|
||||
VersionSet* const versions = dbfull()->TEST_GetVersionSet();
|
||||
assert(versions);
|
||||
@@ -527,10 +527,12 @@ TEST_F(DBFlushTest, FlushWithBlob) {
|
||||
class DBFlushTestBlobError : public DBFlushTest,
|
||||
public testing::WithParamInterface<std::string> {
|
||||
public:
|
||||
DBFlushTestBlobError() : fault_injection_env_(env_) {}
|
||||
DBFlushTestBlobError()
|
||||
: fault_injection_env_(env_), sync_point_(GetParam()) {}
|
||||
~DBFlushTestBlobError() { Close(); }
|
||||
|
||||
FaultInjectionTestEnv fault_injection_env_;
|
||||
std::string sync_point_;
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBFlushTestBlobError, DBFlushTestBlobError,
|
||||
@@ -548,11 +550,12 @@ TEST_P(DBFlushTestBlobError, FlushError) {
|
||||
|
||||
ASSERT_OK(Put("key", "blob"));
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(GetParam(), [this](void* /* arg */) {
|
||||
fault_injection_env_.SetFilesystemActive(false, Status::IOError());
|
||||
SyncPoint::GetInstance()->SetCallBack(sync_point_, [this](void* /* arg */) {
|
||||
fault_injection_env_.SetFilesystemActive(false,
|
||||
Status::IOError(sync_point_));
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BuildTable:BeforeFinishBuildTable", [this](void* /* arg */) {
|
||||
"BuildTable:BeforeDeleteFile", [this](void* /* arg */) {
|
||||
fault_injection_env_.SetFilesystemActive(true);
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
@@ -601,11 +604,19 @@ TEST_P(DBFlushTestBlobError, FlushError) {
|
||||
|
||||
const auto& compaction_stats = internal_stats->TEST_GetCompactionStats();
|
||||
ASSERT_FALSE(compaction_stats.empty());
|
||||
ASSERT_EQ(compaction_stats[0].bytes_written, 0);
|
||||
ASSERT_EQ(compaction_stats[0].num_output_files, 0);
|
||||
|
||||
if (sync_point_ == "BlobFileBuilder::WriteBlobToFile:AddRecord") {
|
||||
ASSERT_EQ(compaction_stats[0].bytes_written, 0);
|
||||
ASSERT_EQ(compaction_stats[0].num_output_files, 0);
|
||||
} else {
|
||||
// SST file writing succeeded; blob file writing failed (during Finish)
|
||||
ASSERT_GT(compaction_stats[0].bytes_written, 0);
|
||||
ASSERT_EQ(compaction_stats[0].num_output_files, 1);
|
||||
}
|
||||
|
||||
const uint64_t* const cf_stats_value = internal_stats->TEST_GetCFStatsValue();
|
||||
ASSERT_EQ(cf_stats_value[InternalStats::BYTES_FLUSHED], 0);
|
||||
ASSERT_EQ(cf_stats_value[InternalStats::BYTES_FLUSHED],
|
||||
compaction_stats[0].bytes_written);
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
|
||||
|
||||
+111
-21
@@ -18,6 +18,7 @@
|
||||
#include <cstdio>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
@@ -637,7 +638,7 @@ Status DBImpl::CloseHelper() {
|
||||
|
||||
if (immutable_db_options_.info_log && own_info_log_) {
|
||||
Status s = immutable_db_options_.info_log->Close();
|
||||
if (!s.ok() && ret.ok()) {
|
||||
if (!s.ok() && !s.IsNotSupported() && ret.ok()) {
|
||||
ret = s;
|
||||
}
|
||||
}
|
||||
@@ -976,6 +977,7 @@ Status DBImpl::SetOptions(
|
||||
MutableCFOptions new_options;
|
||||
Status s;
|
||||
Status persist_options_status;
|
||||
persist_options_status.PermitUncheckedError(); // Allow uninitialized access
|
||||
SuperVersionContext sv_context(/* create_superversion */ true);
|
||||
{
|
||||
auto db_options = GetDBOptions();
|
||||
@@ -1281,7 +1283,11 @@ Status DBImpl::SyncWAL() {
|
||||
TEST_SYNC_POINT("DBImpl::SyncWAL:BeforeMarkLogsSynced:1");
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
MarkLogsSynced(current_log_number, need_log_dir_sync, status);
|
||||
if (status.ok()) {
|
||||
status = MarkLogsSynced(current_log_number, need_log_dir_sync);
|
||||
} else {
|
||||
MarkLogsNotSynced(current_log_number);
|
||||
}
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::SyncWAL:BeforeMarkLogsSynced:2");
|
||||
|
||||
@@ -1307,27 +1313,53 @@ Status DBImpl::UnlockWAL() {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void DBImpl::MarkLogsSynced(uint64_t up_to, bool synced_dir,
|
||||
const Status& status) {
|
||||
Status DBImpl::MarkLogsSynced(uint64_t up_to, bool synced_dir) {
|
||||
mutex_.AssertHeld();
|
||||
if (synced_dir && logfile_number_ == up_to && status.ok()) {
|
||||
if (synced_dir && logfile_number_ == up_to) {
|
||||
log_dir_synced_ = true;
|
||||
}
|
||||
VersionEdit synced_wals;
|
||||
for (auto it = logs_.begin(); it != logs_.end() && it->number <= up_to;) {
|
||||
auto& log = *it;
|
||||
assert(log.getting_synced);
|
||||
if (status.ok() && logs_.size() > 1) {
|
||||
logs_to_free_.push_back(log.ReleaseWriter());
|
||||
auto& wal = *it;
|
||||
assert(wal.getting_synced);
|
||||
if (logs_.size() > 1) {
|
||||
if (immutable_db_options_.track_and_verify_wals_in_manifest) {
|
||||
synced_wals.AddWal(wal.number,
|
||||
WalMetadata(wal.writer->file()->GetFileSize()));
|
||||
}
|
||||
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 {
|
||||
log.getting_synced = false;
|
||||
wal.getting_synced = false;
|
||||
++it;
|
||||
}
|
||||
}
|
||||
assert(!status.ok() || logs_.empty() || logs_[0].number > up_to ||
|
||||
assert(logs_.empty() || logs_[0].number > up_to ||
|
||||
(logs_.size() == 1 && !logs_[0].getting_synced));
|
||||
|
||||
Status s;
|
||||
if (synced_wals.IsWalAddition()) {
|
||||
// not empty, write to MANIFEST.
|
||||
s = versions_->LogAndApplyToDefaultColumnFamily(&synced_wals, &mutex_);
|
||||
if (!s.ok() && versions_->io_status().IsIOError()) {
|
||||
s = error_handler_.SetBGError(versions_->io_status(),
|
||||
BackgroundErrorReason::kManifestWrite);
|
||||
}
|
||||
}
|
||||
log_sync_cv_.SignalAll();
|
||||
return s;
|
||||
}
|
||||
|
||||
void DBImpl::MarkLogsNotSynced(uint64_t up_to) {
|
||||
mutex_.AssertHeld();
|
||||
for (auto it = logs_.begin(); it != logs_.end() && it->number <= up_to;
|
||||
++it) {
|
||||
auto& wal = *it;
|
||||
assert(wal.getting_synced);
|
||||
wal.getting_synced = false;
|
||||
}
|
||||
log_sync_cv_.SignalAll();
|
||||
}
|
||||
|
||||
@@ -3429,14 +3461,14 @@ Status DBImpl::DeleteFile(std::string name) {
|
||||
FileType type;
|
||||
WalFileType log_type;
|
||||
if (!ParseFileName(name, &number, &type, &log_type) ||
|
||||
(type != kTableFile && type != kLogFile)) {
|
||||
(type != kTableFile && type != kWalFile)) {
|
||||
ROCKS_LOG_ERROR(immutable_db_options_.info_log, "DeleteFile %s failed.\n",
|
||||
name.c_str());
|
||||
return Status::InvalidArgument("Invalid file name");
|
||||
}
|
||||
|
||||
Status status;
|
||||
if (type == kLogFile) {
|
||||
if (type == kWalFile) {
|
||||
// Only allow deleting archived log files
|
||||
if (log_type != kArchivedLogFile) {
|
||||
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
|
||||
@@ -3861,7 +3893,7 @@ Status DestroyDB(const std::string& dbname, const Options& options,
|
||||
std::string path_to_delete = dbname + "/" + fname;
|
||||
if (type == kMetaDatabase) {
|
||||
del = DestroyDB(path_to_delete, options);
|
||||
} else if (type == kTableFile || type == kLogFile) {
|
||||
} else if (type == kTableFile || type == kWalFile) {
|
||||
del = DeleteDBFile(&soptions, path_to_delete, dbname,
|
||||
/*force_bg=*/false, /*force_fg=*/!wal_in_db_path);
|
||||
} else {
|
||||
@@ -3915,7 +3947,7 @@ Status DestroyDB(const std::string& dbname, const Options& options,
|
||||
if (env->GetChildren(archivedir, &archiveFiles).ok()) {
|
||||
// Delete archival files.
|
||||
for (const auto& file : archiveFiles) {
|
||||
if (ParseFileName(file, &number, &type) && type == kLogFile) {
|
||||
if (ParseFileName(file, &number, &type) && type == kWalFile) {
|
||||
Status del =
|
||||
DeleteDBFile(&soptions, archivedir + "/" + file, archivedir,
|
||||
/*force_bg=*/false, /*force_fg=*/!wal_in_db_path);
|
||||
@@ -3931,7 +3963,7 @@ Status DestroyDB(const std::string& dbname, const Options& options,
|
||||
// Delete log files in the WAL dir
|
||||
if (wal_dir_exists) {
|
||||
for (const auto& file : walDirFiles) {
|
||||
if (ParseFileName(file, &number, &type) && type == kLogFile) {
|
||||
if (ParseFileName(file, &number, &type) && type == kWalFile) {
|
||||
Status del =
|
||||
DeleteDBFile(&soptions, LogFileName(soptions.wal_dir, number),
|
||||
soptions.wal_dir, /*force_bg=*/false,
|
||||
@@ -4719,14 +4751,36 @@ Status DBImpl::CreateColumnFamilyWithImport(
|
||||
temp_s.ToString().c_str());
|
||||
}
|
||||
// Always returns Status::OK()
|
||||
assert(DestroyColumnFamilyHandle(*handle).ok());
|
||||
temp_s = DestroyColumnFamilyHandle(*handle);
|
||||
assert(temp_s.ok());
|
||||
*handle = nullptr;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Status DBImpl::VerifyFileChecksums(const ReadOptions& read_options) {
|
||||
return VerifyChecksumInternal(read_options, /*use_file_checksum=*/true);
|
||||
}
|
||||
|
||||
Status DBImpl::VerifyChecksum(const ReadOptions& read_options) {
|
||||
return VerifyChecksumInternal(read_options, /*use_file_checksum=*/false);
|
||||
}
|
||||
|
||||
Status DBImpl::VerifyChecksumInternal(const ReadOptions& read_options,
|
||||
bool use_file_checksum) {
|
||||
Status s;
|
||||
|
||||
if (use_file_checksum) {
|
||||
FileChecksumGenFactory* const file_checksum_gen_factory =
|
||||
immutable_db_options_.file_checksum_gen_factory.get();
|
||||
if (!file_checksum_gen_factory) {
|
||||
s = Status::InvalidArgument(
|
||||
"Cannot verify file checksum if options.file_checksum_gen_factory is "
|
||||
"null");
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ColumnFamilyData*> cfd_list;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
@@ -4741,11 +4795,12 @@ Status DBImpl::VerifyChecksum(const ReadOptions& read_options) {
|
||||
for (auto cfd : cfd_list) {
|
||||
sv_list.push_back(cfd->GetReferencedSuperVersion(this));
|
||||
}
|
||||
|
||||
for (auto& sv : sv_list) {
|
||||
VersionStorageInfo* vstorage = sv->current->storage_info();
|
||||
ColumnFamilyData* cfd = sv->current->cfd();
|
||||
Options opts;
|
||||
{
|
||||
if (!use_file_checksum) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
opts = Options(BuildDBOptions(immutable_db_options_, mutable_db_options_),
|
||||
cfd->GetLatestCFOptions());
|
||||
@@ -4753,11 +4808,18 @@ Status DBImpl::VerifyChecksum(const ReadOptions& read_options) {
|
||||
for (int i = 0; i < vstorage->num_non_empty_levels() && s.ok(); i++) {
|
||||
for (size_t j = 0; j < vstorage->LevelFilesBrief(i).num_files && s.ok();
|
||||
j++) {
|
||||
const auto& fd = vstorage->LevelFilesBrief(i).files[j].fd;
|
||||
const auto& fd_with_krange = vstorage->LevelFilesBrief(i).files[j];
|
||||
const auto& fd = fd_with_krange.fd;
|
||||
const FileMetaData* fmeta = fd_with_krange.file_metadata;
|
||||
assert(fmeta);
|
||||
std::string fname = TableFileName(cfd->ioptions()->cf_paths,
|
||||
fd.GetNumber(), fd.GetPathId());
|
||||
s = ROCKSDB_NAMESPACE::VerifySstFileChecksum(opts, file_options_,
|
||||
read_options, fname);
|
||||
if (use_file_checksum) {
|
||||
s = VerifySstFileChecksum(*fmeta, fname, read_options);
|
||||
} else {
|
||||
s = ROCKSDB_NAMESPACE::VerifySstFileChecksum(opts, file_options_,
|
||||
read_options, fname);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!s.ok()) {
|
||||
@@ -4788,6 +4850,34 @@ Status DBImpl::VerifyChecksum(const ReadOptions& read_options) {
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::VerifySstFileChecksum(const FileMetaData& fmeta,
|
||||
const std::string& fname,
|
||||
const ReadOptions& read_options) {
|
||||
Status s;
|
||||
if (fmeta.file_checksum == kUnknownFileChecksum) {
|
||||
return s;
|
||||
}
|
||||
std::string file_checksum;
|
||||
std::string func_name;
|
||||
s = ROCKSDB_NAMESPACE::GenerateOneFileChecksum(
|
||||
fs_.get(), fname, immutable_db_options_.file_checksum_gen_factory.get(),
|
||||
fmeta.file_checksum_func_name, &file_checksum, &func_name,
|
||||
read_options.readahead_size, immutable_db_options_.allow_mmap_reads,
|
||||
io_tracer_);
|
||||
if (s.ok()) {
|
||||
assert(fmeta.file_checksum_func_name == func_name);
|
||||
if (file_checksum != fmeta.file_checksum) {
|
||||
std::ostringstream oss;
|
||||
oss << fname << " file checksum mismatch, ";
|
||||
oss << "expecting " << Slice(fmeta.file_checksum).ToString(/*hex=*/true);
|
||||
oss << ", but actual " << Slice(file_checksum).ToString(/*hex=*/true);
|
||||
s = Status::Corruption(oss.str());
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::VerifySstFileChecksum:mismatch", &s);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void DBImpl::NotifyOnExternalFileIngested(
|
||||
ColumnFamilyData* cfd, const ExternalSstFileIngestionJob& ingestion_job) {
|
||||
if (immutable_db_options_.listeners.empty()) {
|
||||
|
||||
+42
-6
@@ -431,8 +431,28 @@ class DBImpl : public DB {
|
||||
const ExportImportFilesMetaData& metadata,
|
||||
ColumnFamilyHandle** handle) override;
|
||||
|
||||
using DB::VerifyFileChecksums;
|
||||
Status VerifyFileChecksums(const ReadOptions& read_options) override;
|
||||
|
||||
using DB::VerifyChecksum;
|
||||
virtual Status VerifyChecksum(const ReadOptions& /*read_options*/) override;
|
||||
// Verify the checksums of files in db. Currently only tables are checked.
|
||||
//
|
||||
// read_options: controls file I/O behavior, e.g. read ahead size while
|
||||
// reading all the live table files.
|
||||
//
|
||||
// use_file_checksum: if false, verify the block checksums of all live table
|
||||
// in db. Otherwise, obtain the file checksums and compare
|
||||
// with the MANIFEST. Currently, file checksums are
|
||||
// recomputed by reading all table files.
|
||||
//
|
||||
// Returns: OK if there is no file whose file or block checksum mismatches.
|
||||
Status VerifyChecksumInternal(const ReadOptions& read_options,
|
||||
bool use_file_checksum);
|
||||
|
||||
Status VerifySstFileChecksum(const FileMetaData& fmeta,
|
||||
const std::string& fpath,
|
||||
const ReadOptions& read_options);
|
||||
|
||||
using DB::StartTrace;
|
||||
virtual Status StartTrace(
|
||||
@@ -1195,14 +1215,22 @@ class DBImpl : public DB {
|
||||
|
||||
virtual bool OwnTablesAndLogs() const { return true; }
|
||||
|
||||
// Set DB identity file, and write DB ID to manifest if necessary.
|
||||
Status SetDBId();
|
||||
|
||||
// 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
|
||||
// returns.
|
||||
// After best-efforts recovery, there may be SST files in db/cf paths that are
|
||||
// not referenced in the MANIFEST. We delete these SST files. In the
|
||||
// After recovery, there may be SST files in db/cf paths that are
|
||||
// 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
|
||||
// still not synced to MANIFEST during recovery.)
|
||||
// We delete these SST files. 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 FinishBestEffortsRecovery();
|
||||
Status DeleteUnreferencedSstFiles();
|
||||
|
||||
// SetDbSessionId() should be called in the constuctor DBImpl()
|
||||
// to ensure that db_session_id_ gets updated every time the DB is opened
|
||||
@@ -1682,7 +1710,9 @@ class DBImpl : public DB {
|
||||
std::unique_ptr<TaskLimiterToken>* token, LogBuffer* log_buffer);
|
||||
|
||||
// helper function to call after some of the logs_ were synced
|
||||
void MarkLogsSynced(uint64_t up_to, bool synced_dir, const Status& status);
|
||||
Status MarkLogsSynced(uint64_t up_to, bool synced_dir);
|
||||
// WALs with log number up to up_to are not synced successfully.
|
||||
void MarkLogsNotSynced(uint64_t up_to);
|
||||
|
||||
SnapshotImpl* GetSnapshotImpl(bool is_write_conflict_boundary,
|
||||
bool lock = true);
|
||||
@@ -2184,12 +2214,18 @@ extern CompressionType GetCompressionFlush(
|
||||
// `memtables_to_flush`) will be flushed and thus will not depend on any WAL
|
||||
// file.
|
||||
// The function is only applicable to 2pc mode.
|
||||
extern uint64_t PrecomputeMinLogNumberToKeep(
|
||||
extern uint64_t PrecomputeMinLogNumberToKeep2PC(
|
||||
VersionSet* vset, const ColumnFamilyData& cfd_to_flush,
|
||||
autovector<VersionEdit*> edit_list,
|
||||
const autovector<VersionEdit*>& edit_list,
|
||||
const autovector<MemTable*>& memtables_to_flush,
|
||||
LogsWithPrepTracker* prep_tracker);
|
||||
|
||||
// In non-2PC mode, WALs with log number < the returned number can be
|
||||
// deleted after the cfd_to_flush column family is flushed successfully.
|
||||
extern uint64_t PrecomputeMinLogNumberToKeepNon2PC(
|
||||
VersionSet* vset, const ColumnFamilyData& cfd_to_flush,
|
||||
const autovector<VersionEdit*>& edit_list);
|
||||
|
||||
// `cfd_to_flush` is the column family whose memtable will be flushed and thus
|
||||
// will not depend on any WAL file. nullptr means no memtable is being flushed.
|
||||
// The function is only applicable to 2pc mode.
|
||||
|
||||
@@ -123,7 +123,11 @@ IOStatus DBImpl::SyncClosedLogs(JobContext* job_context) {
|
||||
|
||||
// "number <= current_log_number - 1" is equivalent to
|
||||
// "number < current_log_number".
|
||||
MarkLogsSynced(current_log_number - 1, true, io_s);
|
||||
if (io_s.ok()) {
|
||||
io_s = status_to_io_status(MarkLogsSynced(current_log_number - 1, true));
|
||||
} else {
|
||||
MarkLogsNotSynced(current_log_number - 1);
|
||||
}
|
||||
if (!io_s.ok()) {
|
||||
if (total_log_size_ > 0) {
|
||||
error_handler_.SetBGError(io_s, BackgroundErrorReason::kFlush)
|
||||
@@ -792,7 +796,9 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
|
||||
// changes to RangesOverlapWithMemtables.
|
||||
Range range(*begin, *end);
|
||||
SuperVersion* super_version = cfd->GetReferencedSuperVersion(this);
|
||||
cfd->RangesOverlapWithMemtables({range}, super_version, &flush_needed);
|
||||
cfd->RangesOverlapWithMemtables({range}, super_version,
|
||||
immutable_db_options_.allow_data_in_errors,
|
||||
&flush_needed);
|
||||
CleanupSuperVersion(super_version);
|
||||
}
|
||||
|
||||
@@ -950,7 +956,8 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
|
||||
s = ReFitLevel(cfd, final_output_level, options.target_level);
|
||||
TEST_SYNC_POINT("DBImpl::CompactRange:PostRefitLevel");
|
||||
// ContinueBackgroundWork always return Status::OK().
|
||||
assert(ContinueBackgroundWork().ok());
|
||||
Status temp_s = ContinueBackgroundWork();
|
||||
assert(temp_s.ok());
|
||||
}
|
||||
EnableManualCompaction();
|
||||
}
|
||||
@@ -1146,9 +1153,10 @@ Status DBImpl::CompactFilesImpl(
|
||||
job_context->job_id, c.get(), immutable_db_options_,
|
||||
file_options_for_compaction_, versions_.get(), &shutting_down_,
|
||||
preserve_deletes_seqnum_.load(), log_buffer, directories_.GetDbDir(),
|
||||
GetDataDir(c->column_family_data(), c->output_path_id()), stats_, &mutex_,
|
||||
&error_handler_, snapshot_seqs, earliest_write_conflict_snapshot,
|
||||
snapshot_checker, table_cache_, &event_logger_,
|
||||
GetDataDir(c->column_family_data(), c->output_path_id()),
|
||||
GetDataDir(c->column_family_data(), 0), stats_, &mutex_, &error_handler_,
|
||||
snapshot_seqs, earliest_write_conflict_snapshot, snapshot_checker,
|
||||
table_cache_, &event_logger_,
|
||||
c->mutable_cf_options()->paranoid_file_checks,
|
||||
c->mutable_cf_options()->report_bg_io_stats, dbname_,
|
||||
&compaction_job_stats, Env::Priority::USER, io_tracer_,
|
||||
@@ -2953,10 +2961,11 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
job_context->job_id, c.get(), immutable_db_options_,
|
||||
file_options_for_compaction_, versions_.get(), &shutting_down_,
|
||||
preserve_deletes_seqnum_.load(), log_buffer, directories_.GetDbDir(),
|
||||
GetDataDir(c->column_family_data(), c->output_path_id()), stats_,
|
||||
&mutex_, &error_handler_, snapshot_seqs,
|
||||
earliest_write_conflict_snapshot, snapshot_checker, table_cache_,
|
||||
&event_logger_, c->mutable_cf_options()->paranoid_file_checks,
|
||||
GetDataDir(c->column_family_data(), c->output_path_id()),
|
||||
GetDataDir(c->column_family_data(), 0), stats_, &mutex_,
|
||||
&error_handler_, snapshot_seqs, earliest_write_conflict_snapshot,
|
||||
snapshot_checker, table_cache_, &event_logger_,
|
||||
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, db_id_,
|
||||
|
||||
+59
-15
@@ -319,7 +319,7 @@ void DBImpl::DeleteObsoleteFileImpl(int job_id, const std::string& fname,
|
||||
const_cast<std::string*>(&fname));
|
||||
|
||||
Status file_deletion_status;
|
||||
if (type == kTableFile || type == kBlobFile || type == kLogFile) {
|
||||
if (type == kTableFile || type == kBlobFile || type == kWalFile) {
|
||||
file_deletion_status =
|
||||
DeleteDBFile(&immutable_db_options_, fname, path_to_sync,
|
||||
/*force_bg=*/false, /*force_fg=*/!wal_in_db_path_);
|
||||
@@ -466,7 +466,7 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
|
||||
bool keep = true;
|
||||
switch (type) {
|
||||
case kLogFile:
|
||||
case kWalFile:
|
||||
keep = ((number >= state.log_number) ||
|
||||
(number == state.prev_log_number) ||
|
||||
(log_recycle_files_set.find(number) !=
|
||||
@@ -546,7 +546,7 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
dir_to_sync = candidate_file.file_path;
|
||||
} else {
|
||||
dir_to_sync =
|
||||
(type == kLogFile) ? immutable_db_options_.wal_dir : dbname_;
|
||||
(type == kWalFile) ? immutable_db_options_.wal_dir : dbname_;
|
||||
fname = dir_to_sync +
|
||||
((!dir_to_sync.empty() && dir_to_sync.back() == '/') ||
|
||||
(!to_delete.empty() && to_delete.front() == '/')
|
||||
@@ -556,7 +556,7 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (type == kLogFile && (immutable_db_options_.wal_ttl_seconds > 0 ||
|
||||
if (type == kWalFile && (immutable_db_options_.wal_ttl_seconds > 0 ||
|
||||
immutable_db_options_.wal_size_limit_mb > 0)) {
|
||||
wal_manager_.ArchiveWALFile(fname, number);
|
||||
continue;
|
||||
@@ -680,16 +680,10 @@ uint64_t FindMinPrepLogReferencedByMemTable(
|
||||
return min_log;
|
||||
}
|
||||
|
||||
uint64_t PrecomputeMinLogNumberToKeep(
|
||||
uint64_t PrecomputeMinLogNumberToKeepNon2PC(
|
||||
VersionSet* vset, const ColumnFamilyData& cfd_to_flush,
|
||||
autovector<VersionEdit*> edit_list,
|
||||
const autovector<MemTable*>& memtables_to_flush,
|
||||
LogsWithPrepTracker* prep_tracker) {
|
||||
const autovector<VersionEdit*>& edit_list) {
|
||||
assert(vset != nullptr);
|
||||
assert(prep_tracker != nullptr);
|
||||
// Calculate updated min_log_number_to_keep
|
||||
// Since the function should only be called in 2pc mode, log number in
|
||||
// the version edit should be sufficient.
|
||||
|
||||
// Precompute the min log number containing unflushed data for the column
|
||||
// family being flushed (`cfd_to_flush`).
|
||||
@@ -713,6 +707,22 @@ uint64_t PrecomputeMinLogNumberToKeep(
|
||||
min_log_number_to_keep =
|
||||
std::min(cf_min_log_number_to_keep, min_log_number_to_keep);
|
||||
}
|
||||
return min_log_number_to_keep;
|
||||
}
|
||||
|
||||
uint64_t PrecomputeMinLogNumberToKeep2PC(
|
||||
VersionSet* vset, const ColumnFamilyData& cfd_to_flush,
|
||||
const autovector<VersionEdit*>& edit_list,
|
||||
const autovector<MemTable*>& memtables_to_flush,
|
||||
LogsWithPrepTracker* prep_tracker) {
|
||||
assert(vset != nullptr);
|
||||
assert(prep_tracker != nullptr);
|
||||
// Calculate updated min_log_number_to_keep
|
||||
// Since the function should only be called in 2pc mode, log number in
|
||||
// the version edit should be sufficient.
|
||||
|
||||
uint64_t min_log_number_to_keep =
|
||||
PrecomputeMinLogNumberToKeepNon2PC(vset, cfd_to_flush, edit_list);
|
||||
|
||||
// if are 2pc we must consider logs containing prepared
|
||||
// sections of outstanding transactions.
|
||||
@@ -741,7 +751,43 @@ uint64_t PrecomputeMinLogNumberToKeep(
|
||||
return min_log_number_to_keep;
|
||||
}
|
||||
|
||||
Status DBImpl::FinishBestEffortsRecovery() {
|
||||
Status DBImpl::SetDBId() {
|
||||
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()) {
|
||||
s = SetIdentityFile(env_, dbname_);
|
||||
if (!s.ok()) {
|
||||
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 {
|
||||
s = SetIdentityFile(env_, dbname_, db_id_);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::DeleteUnreferencedSstFiles() {
|
||||
mutex_.AssertHeld();
|
||||
std::vector<std::string> paths;
|
||||
paths.push_back(NormalizePath(dbname_ + std::string(1, kFilePathSeparator)));
|
||||
@@ -797,8 +843,6 @@ Status DBImpl::FinishBestEffortsRecovery() {
|
||||
assert(versions_->GetColumnFamilySet());
|
||||
ColumnFamilyData* default_cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
assert(default_cfd);
|
||||
// Even if new_descriptor_log is false, we will still switch to a new
|
||||
// MANIFEST and update CURRENT file, since this is in recovery.
|
||||
s = versions_->LogAndApply(
|
||||
default_cfd, *default_cfd->GetLatestMutableCFOptions(), &edit, &mutex_,
|
||||
directories_.GetDbDir(), /*new_descriptor_log*/ false);
|
||||
|
||||
+110
-103
@@ -401,7 +401,7 @@ Status DBImpl::Recover(
|
||||
}
|
||||
for (const std::string& file : files_in_dbname) {
|
||||
uint64_t number = 0;
|
||||
FileType type = kLogFile; // initialize
|
||||
FileType type = kWalFile; // initialize
|
||||
if (ParseFileName(file, &number, &type) && type == kDescriptorFile) {
|
||||
// Found MANIFEST (descriptor log), thus best-efforts recovery does
|
||||
// not have to treat the db as empty.
|
||||
@@ -479,42 +479,14 @@ Status DBImpl::Recover(
|
||||
// TryRecover may delete previous column_family_set_.
|
||||
column_family_memtables_.reset(
|
||||
new ColumnFamilyMemTablesImpl(versions_->GetColumnFamilySet()));
|
||||
s = FinishBestEffortsRecovery();
|
||||
}
|
||||
}
|
||||
if (!s.ok()) {
|
||||
return 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()) {
|
||||
s = SetIdentityFile(env_, dbname_);
|
||||
if (!s.ok()) {
|
||||
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,
|
||||
false);
|
||||
}
|
||||
} else {
|
||||
s = SetIdentityFile(env_, dbname_, db_id_);
|
||||
s = SetDBId();
|
||||
if (s.ok() && !read_only) {
|
||||
s = DeleteUnreferencedSstFiles();
|
||||
}
|
||||
|
||||
if (immutable_db_options_.paranoid_checks && s.ok()) {
|
||||
@@ -536,7 +508,7 @@ Status DBImpl::Recover(
|
||||
|
||||
std::vector<std::string> files_in_wal_dir;
|
||||
if (s.ok()) {
|
||||
// Initial max_total_in_memory_state_ before recovery logs. Log recovery
|
||||
// Initial max_total_in_memory_state_ before recovery wals. Log recovery
|
||||
// may check this value to decide whether to flush.
|
||||
max_total_in_memory_state_ = 0;
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
@@ -571,32 +543,53 @@ Status DBImpl::Recover(
|
||||
return s;
|
||||
}
|
||||
|
||||
std::vector<uint64_t> logs;
|
||||
std::unordered_map<uint64_t, std::string> wal_files;
|
||||
for (const auto& file : files_in_wal_dir) {
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
if (ParseFileName(file, &number, &type) && type == kLogFile) {
|
||||
if (ParseFileName(file, &number, &type) && type == kWalFile) {
|
||||
if (is_new_db) {
|
||||
return Status::Corruption(
|
||||
"While creating a new Db, wal_dir contains "
|
||||
"existing log file: ",
|
||||
file);
|
||||
} else {
|
||||
logs.push_back(number);
|
||||
wal_files[number] =
|
||||
LogFileName(immutable_db_options_.wal_dir, number);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (logs.size() > 0) {
|
||||
if (immutable_db_options_.track_and_verify_wals_in_manifest) {
|
||||
if (!immutable_db_options_.best_efforts_recovery) {
|
||||
// Verify WALs in MANIFEST.
|
||||
s = versions_->GetWalSet().CheckWals(env_, wal_files);
|
||||
} // else since best effort recovery does not recover from WALs, no need
|
||||
// to check WALs.
|
||||
} else if (!versions_->GetWalSet().GetWals().empty()) {
|
||||
// Tracking is disabled, clear previously tracked WALs from MANIFEST,
|
||||
// otherwise, in the future, if WAL tracking is enabled again,
|
||||
// since the WALs deleted when WAL tracking is disabled are not persisted
|
||||
// into MANIFEST, WAL check may fail.
|
||||
VersionEdit edit;
|
||||
WalNumber max_wal_number =
|
||||
versions_->GetWalSet().GetWals().rbegin()->first;
|
||||
edit.DeleteWalsBefore(max_wal_number + 1);
|
||||
s = versions_->LogAndApplyToDefaultColumnFamily(&edit, &mutex_);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
if (!wal_files.empty()) {
|
||||
if (error_if_wal_file_exists) {
|
||||
return Status::Corruption(
|
||||
"The db was opened in readonly mode with error_if_wal_file_exists"
|
||||
"flag but a WAL file already exists");
|
||||
} else if (error_if_data_exists_in_wals) {
|
||||
for (auto& log : logs) {
|
||||
std::string fname = LogFileName(immutable_db_options_.wal_dir, log);
|
||||
for (auto& wal_file : wal_files) {
|
||||
uint64_t bytes;
|
||||
s = env_->GetFileSize(fname, &bytes);
|
||||
s = env_->GetFileSize(wal_file.second, &bytes);
|
||||
if (s.ok()) {
|
||||
if (bytes > 0) {
|
||||
return Status::Corruption(
|
||||
@@ -608,13 +601,19 @@ Status DBImpl::Recover(
|
||||
}
|
||||
}
|
||||
|
||||
if (!logs.empty()) {
|
||||
// Recover in the order in which the logs were generated
|
||||
std::sort(logs.begin(), logs.end());
|
||||
bool corrupted_log_found = false;
|
||||
s = RecoverLogFiles(logs, &next_sequence, read_only,
|
||||
&corrupted_log_found);
|
||||
if (corrupted_log_found && recovered_seq != nullptr) {
|
||||
if (!wal_files.empty()) {
|
||||
// Recover in the order in which the wals were generated
|
||||
std::vector<uint64_t> wals;
|
||||
wals.reserve(wal_files.size());
|
||||
for (const auto& wal_file : wal_files) {
|
||||
wals.push_back(wal_file.first);
|
||||
}
|
||||
std::sort(wals.begin(), wals.end());
|
||||
|
||||
bool corrupted_wal_found = false;
|
||||
s = RecoverLogFiles(wals, &next_sequence, read_only,
|
||||
&corrupted_wal_found);
|
||||
if (corrupted_wal_found && recovered_seq != nullptr) {
|
||||
*recovered_seq = next_sequence;
|
||||
}
|
||||
if (!s.ok()) {
|
||||
@@ -767,10 +766,10 @@ Status DBImpl::InitPersistStatsColumnFamily() {
|
||||
return s;
|
||||
}
|
||||
|
||||
// REQUIRES: log_numbers are sorted in ascending order
|
||||
Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
// 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_log_found) {
|
||||
bool* corrupted_wal_found) {
|
||||
struct LogReporter : public log::Reader::Reporter {
|
||||
Env* env;
|
||||
Logger* info_log;
|
||||
@@ -800,10 +799,10 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
auto stream = event_logger_.Log();
|
||||
stream << "job" << job_id << "event"
|
||||
<< "recovery_started";
|
||||
stream << "log_files";
|
||||
stream << "wal_files";
|
||||
stream.StartArray();
|
||||
for (auto log_number : log_numbers) {
|
||||
stream << log_number;
|
||||
for (auto wal_number : wal_numbers) {
|
||||
stream << wal_number;
|
||||
}
|
||||
stream.EndArray();
|
||||
}
|
||||
@@ -826,25 +825,25 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
bool stop_replay_by_wal_filter = false;
|
||||
bool stop_replay_for_corruption = false;
|
||||
bool flushed = false;
|
||||
uint64_t corrupted_log_number = kMaxSequenceNumber;
|
||||
uint64_t min_log_number = MinLogNumberToKeep();
|
||||
for (auto log_number : log_numbers) {
|
||||
if (log_number < min_log_number) {
|
||||
uint64_t corrupted_wal_number = kMaxSequenceNumber;
|
||||
uint64_t min_wal_number = MinLogNumberToKeep();
|
||||
for (auto wal_number : wal_numbers) {
|
||||
if (wal_number < min_wal_number) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Skipping log #%" PRIu64
|
||||
" since it is older than min log to keep #%" PRIu64,
|
||||
log_number, min_log_number);
|
||||
wal_number, min_wal_number);
|
||||
continue;
|
||||
}
|
||||
// The previous incarnation may not have written any MANIFEST
|
||||
// records after allocating this log number. So we manually
|
||||
// update the file number allocation counter in VersionSet.
|
||||
versions_->MarkFileNumberUsed(log_number);
|
||||
versions_->MarkFileNumberUsed(wal_number);
|
||||
// Open the log file
|
||||
std::string fname = LogFileName(immutable_db_options_.wal_dir, log_number);
|
||||
std::string fname = LogFileName(immutable_db_options_.wal_dir, wal_number);
|
||||
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Recovering log #%" PRIu64 " mode %d", log_number,
|
||||
"Recovering log #%" PRIu64 " mode %d", wal_number,
|
||||
static_cast<int>(immutable_db_options_.wal_recovery_mode));
|
||||
auto logFileDropped = [this, &fname]() {
|
||||
uint64_t bytes;
|
||||
@@ -897,7 +896,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
// to be skipped instead of propagating bad information (like overly
|
||||
// large sequence numbers).
|
||||
log::Reader reader(immutable_db_options_.info_log, std::move(file_reader),
|
||||
&reporter, true /*checksum*/, log_number);
|
||||
&reporter, true /*checksum*/, wal_number);
|
||||
|
||||
// Determine if we should tolerate incomplete records at the tail end of the
|
||||
// Read all the records and add to a memtable
|
||||
@@ -945,7 +944,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
|
||||
WalFilter::WalProcessingOption wal_processing_option =
|
||||
immutable_db_options_.wal_filter->LogRecordFound(
|
||||
log_number, fname, batch, &new_batch, &batch_changed);
|
||||
wal_number, fname, batch, &new_batch, &batch_changed);
|
||||
|
||||
switch (wal_processing_option) {
|
||||
case WalFilter::WalProcessingOption::kContinueProcessing:
|
||||
@@ -997,7 +996,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
" mode %d log filter %s returned "
|
||||
"more records (%d) than original (%d) which is not allowed. "
|
||||
"Aborting recovery.",
|
||||
log_number,
|
||||
wal_number,
|
||||
static_cast<int>(immutable_db_options_.wal_recovery_mode),
|
||||
immutable_db_options_.wal_filter->Name(), new_count,
|
||||
original_count);
|
||||
@@ -1024,7 +1023,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
bool has_valid_writes = false;
|
||||
status = WriteBatchInternal::InsertInto(
|
||||
&batch, column_family_memtables_.get(), &flush_scheduler_,
|
||||
&trim_history_scheduler_, true, log_number, this,
|
||||
&trim_history_scheduler_, true, wal_number, this,
|
||||
false /* concurrent_memtable_writes */, next_sequence,
|
||||
&has_valid_writes, seq_per_batch_, batch_per_txn_);
|
||||
MaybeIgnoreError(&status);
|
||||
@@ -1044,7 +1043,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
cfd->UnrefAndTryDelete();
|
||||
// If this asserts, it means that InsertInto failed in
|
||||
// filtering updates to already-flushed column families
|
||||
assert(cfd->GetLogNumber() <= log_number);
|
||||
assert(cfd->GetLogNumber() <= wal_number);
|
||||
auto iter = version_edits.find(cfd->GetID());
|
||||
assert(iter != version_edits.end());
|
||||
VersionEdit* edit = &iter->second;
|
||||
@@ -1081,21 +1080,21 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
" seq #%" PRIu64
|
||||
". %s. This likely mean loss of synced WAL, "
|
||||
"thus recovery fails.",
|
||||
log_number, *next_sequence,
|
||||
wal_number, *next_sequence,
|
||||
status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
// We should ignore the error but not continue replaying
|
||||
status = Status::OK();
|
||||
stop_replay_for_corruption = true;
|
||||
corrupted_log_number = log_number;
|
||||
if (corrupted_log_found != nullptr) {
|
||||
*corrupted_log_found = true;
|
||||
corrupted_wal_number = wal_number;
|
||||
if (corrupted_wal_found != nullptr) {
|
||||
*corrupted_wal_found = true;
|
||||
}
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Point in time recovered to log #%" PRIu64
|
||||
" seq #%" PRIu64,
|
||||
log_number, *next_sequence);
|
||||
wal_number, *next_sequence);
|
||||
} else {
|
||||
assert(immutable_db_options_.wal_recovery_mode ==
|
||||
WALRecoveryMode::kTolerateCorruptedTailRecords ||
|
||||
@@ -1121,7 +1120,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
// corruption. This could during PIT recovery when the WAL is corrupted and
|
||||
// some (but not all) CFs are flushed
|
||||
// Exclude the PIT case where no log is dropped after the corruption point.
|
||||
// This is to cover the case for empty logs after corrupted log, in which we
|
||||
// This is to cover the case for empty wals after corrupted log, in which we
|
||||
// don't reset stop_replay_for_corruption.
|
||||
if (stop_replay_for_corruption == true &&
|
||||
(immutable_db_options_.wal_recovery_mode ==
|
||||
@@ -1129,7 +1128,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
immutable_db_options_.wal_recovery_mode ==
|
||||
WALRecoveryMode::kTolerateCorruptedTailRecords)) {
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (cfd->GetLogNumber() > corrupted_log_number) {
|
||||
if (cfd->GetLogNumber() > corrupted_wal_number) {
|
||||
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
|
||||
"Column family inconsistency: SST file contains data"
|
||||
" beyond the point of corruption.");
|
||||
@@ -1144,16 +1143,16 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
if (!read_only) {
|
||||
// no need to refcount since client still doesn't have access
|
||||
// to the DB and can not drop column families while we iterate
|
||||
auto max_log_number = log_numbers.back();
|
||||
const WalNumber max_wal_number = wal_numbers.back();
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
auto iter = version_edits.find(cfd->GetID());
|
||||
assert(iter != version_edits.end());
|
||||
VersionEdit* edit = &iter->second;
|
||||
|
||||
if (cfd->GetLogNumber() > max_log_number) {
|
||||
if (cfd->GetLogNumber() > max_wal_number) {
|
||||
// Column family cfd has already flushed the data
|
||||
// from all logs. Memtable has to be empty because
|
||||
// we filter the updates based on log_number
|
||||
// from all wals. Memtable has to be empty because
|
||||
// we filter the updates based on wal_number
|
||||
// (in WriteBatch::InsertInto)
|
||||
assert(cfd->mem()->GetFirstSequenceNumber() == 0);
|
||||
assert(edit->NumEntries() == 0);
|
||||
@@ -1185,13 +1184,13 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
// Update the log number info in the version edit corresponding to this
|
||||
// column family. Note that the version edits will be written to MANIFEST
|
||||
// together later.
|
||||
// writing log_number in the manifest means that any log file
|
||||
// with number strongly less than (log_number + 1) is already
|
||||
// writing wal_number in the manifest means that any log file
|
||||
// with number strongly less than (wal_number + 1) is already
|
||||
// recovered and should be ignored on next reincarnation.
|
||||
// Since we already recovered max_log_number, we want all logs
|
||||
// with numbers `<= max_log_number` (includes this one) to be ignored
|
||||
// Since we already recovered max_wal_number, we want all wals
|
||||
// with numbers `<= max_wal_number` (includes this one) to be ignored
|
||||
if (flushed || cfd->mem()->GetFirstSequenceNumber() == 0) {
|
||||
edit->SetLogNumber(max_log_number + 1);
|
||||
edit->SetLogNumber(max_wal_number + 1);
|
||||
}
|
||||
}
|
||||
if (status.ok()) {
|
||||
@@ -1199,7 +1198,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
// not actually used. that is because VersionSet assumes
|
||||
// VersionSet::next_file_number_ always to be strictly greater than any
|
||||
// log number
|
||||
versions_->MarkFileNumberUsed(max_log_number + 1);
|
||||
versions_->MarkFileNumberUsed(max_wal_number + 1);
|
||||
|
||||
autovector<ColumnFamilyData*> cfds;
|
||||
autovector<const MutableCFOptions*> cf_opts;
|
||||
@@ -1211,6 +1210,14 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
assert(iter != version_edits.end());
|
||||
edit_lists.push_back({&iter->second});
|
||||
}
|
||||
|
||||
std::unique_ptr<VersionEdit> wal_deletion;
|
||||
if (immutable_db_options_.track_and_verify_wals_in_manifest) {
|
||||
wal_deletion.reset(new VersionEdit);
|
||||
wal_deletion->DeleteWalsBefore(max_wal_number + 1);
|
||||
edit_lists.back().push_back(wal_deletion.get());
|
||||
}
|
||||
|
||||
// write MANIFEST with update
|
||||
status = versions_->LogAndApply(cfds, cf_opts, edit_lists, &mutex_,
|
||||
directories_.GetDbDir(),
|
||||
@@ -1219,7 +1226,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
}
|
||||
|
||||
if (status.ok() && data_seen && !flushed) {
|
||||
status = RestoreAliveLogFiles(log_numbers);
|
||||
status = RestoreAliveLogFiles(wal_numbers);
|
||||
}
|
||||
|
||||
event_logger_.Log() << "job" << job_id << "event"
|
||||
@@ -1228,8 +1235,8 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
return status;
|
||||
}
|
||||
|
||||
Status DBImpl::RestoreAliveLogFiles(const std::vector<uint64_t>& log_numbers) {
|
||||
if (log_numbers.empty()) {
|
||||
Status DBImpl::RestoreAliveLogFiles(const std::vector<uint64_t>& wal_numbers) {
|
||||
if (wal_numbers.empty()) {
|
||||
return Status::OK();
|
||||
}
|
||||
Status s;
|
||||
@@ -1242,20 +1249,20 @@ Status DBImpl::RestoreAliveLogFiles(const std::vector<uint64_t>& log_numbers) {
|
||||
// FindObsoleteFiles()
|
||||
total_log_size_ = 0;
|
||||
log_empty_ = false;
|
||||
for (auto log_number : log_numbers) {
|
||||
LogFileNumberSize log(log_number);
|
||||
std::string fname = LogFileName(immutable_db_options_.wal_dir, log_number);
|
||||
// This gets the appear size of the logs, not including preallocated space.
|
||||
for (auto wal_number : wal_numbers) {
|
||||
LogFileNumberSize log(wal_number);
|
||||
std::string fname = LogFileName(immutable_db_options_.wal_dir, wal_number);
|
||||
// This gets the appear size of the wals, not including preallocated space.
|
||||
s = env_->GetFileSize(fname, &log.size);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
total_log_size_ += log.size;
|
||||
alive_log_files_.push_back(log);
|
||||
// We preallocate space for logs, but then after a crash and restart, those
|
||||
// We preallocate space for wals, but then after a crash and restart, those
|
||||
// preallocated space are not needed anymore. It is likely only the last
|
||||
// log has such preallocated space, so we only truncate for the last log.
|
||||
if (log_number == log_numbers.back()) {
|
||||
if (wal_number == wal_numbers.back()) {
|
||||
std::unique_ptr<FSWritableFile> last_log;
|
||||
Status truncate_status = fs_->ReopenWritableFile(
|
||||
fname,
|
||||
@@ -1272,7 +1279,7 @@ Status DBImpl::RestoreAliveLogFiles(const std::vector<uint64_t>& log_numbers) {
|
||||
// Not a critical error if fail to truncate.
|
||||
if (!truncate_status.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Failed to truncate log #%" PRIu64 ": %s", log_number,
|
||||
"Failed to truncate log #%" PRIu64 ": %s", wal_number,
|
||||
truncate_status.ToString().c_str());
|
||||
}
|
||||
}
|
||||
@@ -1340,7 +1347,7 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
|
||||
IOStatus io_s;
|
||||
s = BuildTable(
|
||||
dbname_, versions_.get(), env_, fs_.get(), *cfd->ioptions(),
|
||||
dbname_, versions_.get(), immutable_db_options_, *cfd->ioptions(),
|
||||
mutable_cf_options, file_options_for_compaction_, cfd->table_cache(),
|
||||
iter.get(), std::move(range_del_iters), &meta, &blob_file_additions,
|
||||
cfd->internal_comparator(), cfd->int_tbl_prop_collector_factories(),
|
||||
@@ -1368,7 +1375,6 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
// Note that if file_size is zero, the file has been deleted and
|
||||
// should not be added to the manifest.
|
||||
const bool has_output = meta.fd.GetFileSize() > 0;
|
||||
assert(has_output || blob_file_additions.empty());
|
||||
|
||||
constexpr int level = 0;
|
||||
|
||||
@@ -1388,15 +1394,16 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
|
||||
if (has_output) {
|
||||
stats.bytes_written = meta.fd.GetFileSize();
|
||||
|
||||
const auto& blobs = edit->GetBlobFileAdditions();
|
||||
for (const auto& blob : blobs) {
|
||||
stats.bytes_written += blob.GetTotalBlobBytes();
|
||||
}
|
||||
|
||||
stats.num_output_files = static_cast<int>(blobs.size()) + 1;
|
||||
stats.num_output_files = 1;
|
||||
}
|
||||
|
||||
const auto& blobs = edit->GetBlobFileAdditions();
|
||||
for (const auto& blob : blobs) {
|
||||
stats.bytes_written += blob.GetTotalBlobBytes();
|
||||
}
|
||||
|
||||
stats.num_output_files += static_cast<int>(blobs.size());
|
||||
|
||||
cfd->internal_stats()->AddCompactionStats(level, Env::Priority::USER, stats);
|
||||
cfd->internal_stats()->AddCFStats(InternalStats::BYTES_FLUSHED,
|
||||
stats.bytes_written);
|
||||
@@ -1610,7 +1617,7 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
// In WritePrepared there could be gap in sequence numbers. This breaks
|
||||
// the trick we use in kPointInTimeRecovery which assumes the first seq in
|
||||
// the log right after the corrupted log is one larger than the last seq
|
||||
// we read from the logs. To let this trick keep working, we add a dummy
|
||||
// we read from the wals. To let this trick keep working, we add a dummy
|
||||
// entry with the expected sequence to the first log right after recovery.
|
||||
// In non-WritePrepared case also the new log after recovery could be
|
||||
// empty, and thus missing the consecutive seq hint to distinguish
|
||||
|
||||
@@ -112,7 +112,7 @@ Status DBImplSecondary::FindNewLogNumbers(std::vector<uint64_t>* logs) {
|
||||
for (size_t i = 0; i < filenames.size(); i++) {
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
if (ParseFileName(filenames[i], &number, &type) && type == kLogFile &&
|
||||
if (ParseFileName(filenames[i], &number, &type) && type == kWalFile &&
|
||||
number >= log_number_min) {
|
||||
logs->push_back(number);
|
||||
}
|
||||
|
||||
@@ -426,7 +426,11 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
|
||||
if (need_log_sync) {
|
||||
mutex_.Lock();
|
||||
MarkLogsSynced(logfile_number_, need_log_dir_sync, status);
|
||||
if (status.ok()) {
|
||||
status = MarkLogsSynced(logfile_number_, need_log_dir_sync);
|
||||
} else {
|
||||
MarkLogsNotSynced(logfile_number_);
|
||||
}
|
||||
mutex_.Unlock();
|
||||
// Requesting sync with two_write_queues_ is expected to be very rare. We
|
||||
// hence provide a simple implementation that is not necessarily efficient.
|
||||
@@ -551,7 +555,11 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
|
||||
if (need_log_sync) {
|
||||
mutex_.Lock();
|
||||
MarkLogsSynced(logfile_number_, need_log_dir_sync, w.status);
|
||||
if (w.status.ok()) {
|
||||
w.status = MarkLogsSynced(logfile_number_, need_log_dir_sync);
|
||||
} else {
|
||||
MarkLogsNotSynced(logfile_number_);
|
||||
}
|
||||
mutex_.Unlock();
|
||||
}
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ void DBSecondaryTest::CheckFileTypeCounts(const std::string& dir,
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
if (ParseFileName(file, &number, &type)) {
|
||||
log_cnt += (type == kLogFile);
|
||||
log_cnt += (type == kWalFile);
|
||||
sst_cnt += (type == kTableFile);
|
||||
manifest_cnt += (type == kDescriptorFile);
|
||||
}
|
||||
@@ -883,6 +883,7 @@ TEST_F(DBSecondaryTest, StartFromInconsistent) {
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Options options1;
|
||||
options1.env = env_;
|
||||
Status s = TryOpenSecondary(options1);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
}
|
||||
@@ -894,6 +895,7 @@ TEST_F(DBSecondaryTest, InconsistencyDuringCatchUp) {
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
Options options1;
|
||||
options1.env = env_;
|
||||
OpenSecondary(options1);
|
||||
|
||||
{
|
||||
|
||||
@@ -62,7 +62,7 @@ void DumpDBFileSummary(const ImmutableDBOptions& options,
|
||||
dbname.c_str(), file.c_str());
|
||||
}
|
||||
break;
|
||||
case kLogFile:
|
||||
case kWalFile:
|
||||
if (env->GetFileSize(dbname + "/" + file, &file_size).ok()) {
|
||||
char str[16];
|
||||
snprintf(str, sizeof(str), "%" PRIu64, file_size);
|
||||
@@ -118,7 +118,7 @@ void DumpDBFileSummary(const ImmutableDBOptions& options,
|
||||
wal_info.clear();
|
||||
for (const std::string& file : files) {
|
||||
if (ParseFileName(file, &number, &type)) {
|
||||
if (type == kLogFile) {
|
||||
if (type == kWalFile) {
|
||||
if (env->GetFileSize(options.wal_dir + "/" + file, &file_size).ok()) {
|
||||
char str[16];
|
||||
snprintf(str, sizeof(str), "%" PRIu64, file_size);
|
||||
|
||||
+16
-8
@@ -107,11 +107,12 @@ Status DBIter::GetProperty(std::string prop_name, std::string* prop) {
|
||||
}
|
||||
|
||||
bool DBIter::ParseKey(ParsedInternalKey* ikey) {
|
||||
if (ParseInternalKey(iter_.key(), ikey) != Status::OK()) {
|
||||
status_ = Status::Corruption("corrupted internal key in DBIter");
|
||||
Status s =
|
||||
ParseInternalKey(iter_.key(), ikey, false /* log_err_key */); // TODO
|
||||
if (!s.ok()) {
|
||||
status_ = Status::Corruption("In DBIter: ", s.getState());
|
||||
valid_ = false;
|
||||
ROCKS_LOG_ERROR(logger_, "corrupted internal key in DBIter: %s",
|
||||
iter_.key().ToString(true).c_str());
|
||||
ROCKS_LOG_ERROR(logger_, "In DBIter: %s", status_.getState());
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
@@ -436,11 +437,11 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
&last_key,
|
||||
ParsedInternalKey(saved_key_.GetUserKey(), 0, kTypeDeletion));
|
||||
} else {
|
||||
std::string min_ts(timestamp_size_, static_cast<char>(0));
|
||||
const std::string kTsMin(timestamp_size_, static_cast<char>(0));
|
||||
AppendInternalKeyWithDifferentTimestamp(
|
||||
&last_key,
|
||||
ParsedInternalKey(saved_key_.GetUserKey(), 0, kTypeDeletion),
|
||||
min_ts);
|
||||
kTsMin);
|
||||
}
|
||||
// Don't set skipping_saved_key = false because we may still see more
|
||||
// user-keys equal to saved_key_.
|
||||
@@ -810,12 +811,19 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
ikey, RangeDelPositioningMode::kBackwardTraversal)) {
|
||||
last_key_entry_type = kTypeRangeDeletion;
|
||||
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
|
||||
} else {
|
||||
assert(iter_.iter()->IsValuePinned());
|
||||
} else if (iter_.iter()->IsValuePinned()) {
|
||||
pinned_value_ = iter_.value();
|
||||
} else {
|
||||
valid_ = false;
|
||||
status_ = Status::NotSupported(
|
||||
"Backward iteration not supported if underlying iterator's value "
|
||||
"cannot be pinned.");
|
||||
}
|
||||
merge_context_.Clear();
|
||||
last_not_merge_type = last_key_entry_type;
|
||||
if (!status_.ok()) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case kTypeDeletion:
|
||||
case kTypeSingleDeletion:
|
||||
|
||||
+5
-4
@@ -99,10 +99,11 @@ class TestIterator : public InternalIterator {
|
||||
}
|
||||
for (auto it = data_.begin(); it != data_.end(); ++it) {
|
||||
ParsedInternalKey ikey;
|
||||
Status pikStatus = ParseInternalKey(it->first, &ikey);
|
||||
pikStatus.PermitUncheckedError();
|
||||
assert(pikStatus.ok());
|
||||
if (!pikStatus.ok() || ikey.user_key != _key) {
|
||||
Status pik_status =
|
||||
ParseInternalKey(it->first, &ikey, true /* log_err_key */);
|
||||
pik_status.PermitUncheckedError();
|
||||
assert(pik_status.ok());
|
||||
if (!pik_status.ok() || ikey.user_key != _key) {
|
||||
continue;
|
||||
}
|
||||
if (valid_ && data_.begin() + iter_ > it) {
|
||||
|
||||
@@ -3021,6 +3021,44 @@ TEST_F(DBIteratorWithReadCallbackTest, ReadCallback) {
|
||||
delete iter;
|
||||
}
|
||||
|
||||
TEST_F(DBIteratorTest, BackwardIterationOnInplaceUpdateMemtable) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.inplace_update_support = false;
|
||||
options.env = env_;
|
||||
DestroyAndReopen(options);
|
||||
constexpr int kNumKeys = 10;
|
||||
|
||||
// Write kNumKeys to WAL.
|
||||
for (int i = 0; i < kNumKeys; ++i) {
|
||||
ASSERT_OK(Put(Key(i), "val"));
|
||||
}
|
||||
ReadOptions read_opts;
|
||||
read_opts.total_order_seek = true;
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_opts));
|
||||
int count = 0;
|
||||
for (iter->SeekToLast(); iter->Valid(); iter->Prev()) {
|
||||
++count;
|
||||
}
|
||||
ASSERT_EQ(kNumKeys, count);
|
||||
}
|
||||
|
||||
// Reopen and rebuild the memtable from WAL.
|
||||
options.create_if_missing = false;
|
||||
options.avoid_flush_during_recovery = true;
|
||||
options.inplace_update_support = true;
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
Reopen(options);
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_opts));
|
||||
iter->SeekToLast();
|
||||
// Backward iteration not supported due to inplace_update_support = true.
|
||||
ASSERT_TRUE(iter->status().IsNotSupported());
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -316,6 +316,7 @@ TEST_F(DBMemTableTest, InsertWithHint) {
|
||||
TEST_F(DBMemTableTest, ColumnFamilyId) {
|
||||
// Verifies MemTableRepFactory is told the right column family id.
|
||||
Options options;
|
||||
options.env = CurrentOptions().env;
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
options.create_if_missing = true;
|
||||
options.memtable_factory.reset(new MockMemTableRepFactory());
|
||||
|
||||
@@ -37,7 +37,7 @@ class TestReadCallback : public ReadCallback {
|
||||
class DBMergeOperatorTest : public DBTestBase {
|
||||
public:
|
||||
DBMergeOperatorTest()
|
||||
: DBTestBase("/db_merge_operator_test", /*env_do_fsync=*/true) {}
|
||||
: DBTestBase("/db_merge_operator_test", /*env_do_fsync=*/false) {}
|
||||
|
||||
std::string GetWithReadCallback(SnapshotChecker* snapshot_checker,
|
||||
const Slice& key,
|
||||
|
||||
@@ -81,6 +81,7 @@ class DBOptionsTest : public DBTestBase {
|
||||
|
||||
TEST_F(DBOptionsTest, ImmutableTrackAndVerifyWalsInManifest) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.track_and_verify_wals_in_manifest = true;
|
||||
|
||||
ImmutableDBOptions db_options(options);
|
||||
@@ -621,6 +622,7 @@ TEST_F(DBOptionsTest, MaxOpenFilesChange) {
|
||||
|
||||
TEST_F(DBOptionsTest, SanitizeDelayedWriteRate) {
|
||||
Options options;
|
||||
options.env = CurrentOptions().env;
|
||||
options.delayed_write_rate = 0;
|
||||
Reopen(options);
|
||||
ASSERT_EQ(16 * 1024 * 1024, dbfull()->GetDBOptions().delayed_write_rate);
|
||||
@@ -632,6 +634,7 @@ TEST_F(DBOptionsTest, SanitizeDelayedWriteRate) {
|
||||
|
||||
TEST_F(DBOptionsTest, SanitizeUniversalTTLCompaction) {
|
||||
Options options;
|
||||
options.env = CurrentOptions().env;
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
|
||||
options.ttl = 0;
|
||||
@@ -661,6 +664,7 @@ TEST_F(DBOptionsTest, SanitizeUniversalTTLCompaction) {
|
||||
|
||||
TEST_F(DBOptionsTest, SanitizeTtlDefault) {
|
||||
Options options;
|
||||
options.env = CurrentOptions().env;
|
||||
Reopen(options);
|
||||
ASSERT_EQ(30 * 24 * 60 * 60, dbfull()->GetOptions().ttl);
|
||||
|
||||
@@ -677,6 +681,7 @@ TEST_F(DBOptionsTest, SanitizeTtlDefault) {
|
||||
TEST_F(DBOptionsTest, SanitizeFIFOPeriodicCompaction) {
|
||||
Options options;
|
||||
options.compaction_style = kCompactionStyleFIFO;
|
||||
options.env = CurrentOptions().env;
|
||||
options.ttl = 0;
|
||||
Reopen(options);
|
||||
ASSERT_EQ(30 * 24 * 60 * 60, dbfull()->GetOptions().ttl);
|
||||
@@ -702,6 +707,7 @@ TEST_F(DBOptionsTest, SanitizeFIFOPeriodicCompaction) {
|
||||
|
||||
TEST_F(DBOptionsTest, SetFIFOCompactionOptions) {
|
||||
Options options;
|
||||
options.env = CurrentOptions().env;
|
||||
options.compaction_style = kCompactionStyleFIFO;
|
||||
options.write_buffer_size = 10 << 10; // 10KB
|
||||
options.arena_block_size = 4096;
|
||||
@@ -841,6 +847,7 @@ TEST_F(DBOptionsTest, FIFOTtlBackwardCompatible) {
|
||||
options.compaction_style = kCompactionStyleFIFO;
|
||||
options.write_buffer_size = 10 << 10; // 10KB
|
||||
options.create_if_missing = true;
|
||||
options.env = CurrentOptions().env;
|
||||
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
@@ -894,6 +901,7 @@ TEST_F(DBOptionsTest, ChangeCompression) {
|
||||
options.bottommost_compression = CompressionType::kNoCompression;
|
||||
options.bottommost_compression_opts.level = 2;
|
||||
options.bottommost_compression_opts.parallel_threads = 1;
|
||||
options.env = CurrentOptions().env;
|
||||
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
@@ -944,6 +952,66 @@ TEST_F(DBOptionsTest, ChangeCompression) {
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBOptionsTest, BottommostCompressionOptsWithFallbackType) {
|
||||
// Verify the bottommost compression options still take effect even when the
|
||||
// bottommost compression type is left at its default value. Verify for both
|
||||
// automatic and manual compaction.
|
||||
if (!Snappy_Supported() || !LZ4_Supported()) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr int kUpperCompressionLevel = 1;
|
||||
constexpr int kBottommostCompressionLevel = 2;
|
||||
constexpr int kNumL0Files = 2;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.level0_file_num_compaction_trigger = kNumL0Files;
|
||||
options.compression = CompressionType::kLZ4Compression;
|
||||
options.compression_opts.level = kUpperCompressionLevel;
|
||||
options.bottommost_compression_opts.level = kBottommostCompressionLevel;
|
||||
options.bottommost_compression_opts.enabled = true;
|
||||
Reopen(options);
|
||||
|
||||
CompressionType compression_used = CompressionType::kDisableCompressionOption;
|
||||
CompressionOptions compression_opt_used;
|
||||
bool compacted = false;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionPicker::RegisterCompaction:Registered", [&](void* arg) {
|
||||
Compaction* c = static_cast<Compaction*>(arg);
|
||||
compression_used = c->output_compression();
|
||||
compression_opt_used = c->output_compression_opts();
|
||||
compacted = true;
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// First, verify for automatic compaction.
|
||||
for (int i = 0; i < kNumL0Files; ++i) {
|
||||
ASSERT_OK(Put("foo", "foofoofoo"));
|
||||
ASSERT_OK(Put("bar", "foofoofoo"));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
ASSERT_TRUE(compacted);
|
||||
ASSERT_EQ(CompressionType::kLZ4Compression, compression_used);
|
||||
ASSERT_EQ(kBottommostCompressionLevel, compression_opt_used.level);
|
||||
|
||||
// Second, verify for manual compaction.
|
||||
compacted = false;
|
||||
compression_used = CompressionType::kDisableCompressionOption;
|
||||
compression_opt_used = CompressionOptions();
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForceOptimized;
|
||||
ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr));
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
ASSERT_TRUE(compacted);
|
||||
ASSERT_EQ(CompressionType::kLZ4Compression, compression_used);
|
||||
ASSERT_EQ(kBottommostCompressionLevel, compression_opt_used.level);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -336,7 +336,7 @@ TEST_F(DBPropertiesTest, AggregatedTableProperties) {
|
||||
table_options.filter_policy.reset(
|
||||
NewBloomFilterPolicy(kBloomBitsPerKey, false));
|
||||
table_options.block_size = 1024;
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
@@ -536,7 +536,7 @@ TEST_F(DBPropertiesTest, AggregatedTablePropertiesAtLevel) {
|
||||
table_options.filter_policy.reset(
|
||||
NewBloomFilterPolicy(kBloomBitsPerKey, false));
|
||||
table_options.block_size = 1024;
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
@@ -1414,7 +1414,7 @@ TEST_F(DBPropertiesTest, NeedCompactHintPersistentTest) {
|
||||
}
|
||||
|
||||
TEST_F(DBPropertiesTest, EstimateNumKeysUnderflow) {
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_OK(Delete("foo"));
|
||||
@@ -1524,6 +1524,7 @@ TEST_F(DBPropertiesTest, SstFilesSize) {
|
||||
std::shared_ptr<TestListener> listener = std::make_shared<TestListener>();
|
||||
|
||||
Options options;
|
||||
options.env = CurrentOptions().env;
|
||||
options.disable_auto_compactions = true;
|
||||
options.listeners.push_back(listener);
|
||||
Reopen(options);
|
||||
@@ -1608,6 +1609,8 @@ TEST_F(DBPropertiesTest, BlockCacheProperties) {
|
||||
Options options;
|
||||
uint64_t value;
|
||||
|
||||
options.env = CurrentOptions().env;
|
||||
|
||||
// Block cache properties are not available for tables other than
|
||||
// block-based table.
|
||||
options.table_factory.reset(NewPlainTableFactory());
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class DBRangeDelTest : public DBTestBase {
|
||||
public:
|
||||
DBRangeDelTest() : DBTestBase("/db_range_del_test", /*env_do_fsync=*/true) {}
|
||||
DBRangeDelTest() : DBTestBase("/db_range_del_test", /*env_do_fsync=*/false) {}
|
||||
|
||||
std::string GetNumericStr(int key) {
|
||||
uint64_t uint64_key = static_cast<uint64_t>(key);
|
||||
@@ -159,7 +159,7 @@ TEST_F(DBRangeDelTest, MaxCompactionBytesCutsOutputFiles) {
|
||||
// Want max_compaction_bytes to trigger the end of compaction output file, not
|
||||
// target_file_size_base, so make the latter much bigger
|
||||
opts.target_file_size_base = 100 * opts.max_compaction_bytes;
|
||||
Reopen(opts);
|
||||
DestroyAndReopen(opts);
|
||||
|
||||
// snapshot protects range tombstone from dropping due to becoming obsolete.
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
@@ -269,7 +269,7 @@ TEST_F(DBRangeDelTest, FlushRemovesCoveredKeys) {
|
||||
const int kNum = 300, kRangeBegin = 50, kRangeEnd = 250;
|
||||
Options opts = CurrentOptions();
|
||||
opts.comparator = test::Uint64Comparator();
|
||||
Reopen(opts);
|
||||
DestroyAndReopen(opts);
|
||||
|
||||
// Write a third before snapshot, a third between snapshot and tombstone, and
|
||||
// a third after the tombstone. Keys older than snapshot or newer than the
|
||||
@@ -309,7 +309,7 @@ TEST_F(DBRangeDelTest, CompactionRemovesCoveredKeys) {
|
||||
opts.memtable_factory.reset(new SpecialSkipListFactory(kNumPerFile));
|
||||
opts.num_levels = 2;
|
||||
opts.statistics = CreateDBStatistics();
|
||||
Reopen(opts);
|
||||
DestroyAndReopen(opts);
|
||||
|
||||
for (int i = 0; i < kNumFiles; ++i) {
|
||||
if (i > 0) {
|
||||
@@ -603,7 +603,7 @@ TEST_F(DBRangeDelTest, TableEvictedDuringScan) {
|
||||
bbto.cache_index_and_filter_blocks = true;
|
||||
bbto.block_cache = NewLRUCache(8 << 20);
|
||||
opts.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
Reopen(opts);
|
||||
DestroyAndReopen(opts);
|
||||
|
||||
// Hold a snapshot so range deletions can't become obsolete during compaction
|
||||
// to bottommost level (i.e., L1).
|
||||
@@ -761,7 +761,7 @@ TEST_F(DBRangeDelTest, IteratorRemovesCoveredKeys) {
|
||||
Options opts = CurrentOptions();
|
||||
opts.comparator = test::Uint64Comparator();
|
||||
opts.memtable_factory.reset(new SpecialSkipListFactory(kNumPerFile));
|
||||
Reopen(opts);
|
||||
DestroyAndReopen(opts);
|
||||
|
||||
// Write half of the keys before the tombstone and half after the tombstone.
|
||||
// Only covered keys (i.e., within the range and older than the tombstone)
|
||||
@@ -794,7 +794,7 @@ TEST_F(DBRangeDelTest, IteratorOverUserSnapshot) {
|
||||
Options opts = CurrentOptions();
|
||||
opts.comparator = test::Uint64Comparator();
|
||||
opts.memtable_factory.reset(new SpecialSkipListFactory(kNumPerFile));
|
||||
Reopen(opts);
|
||||
DestroyAndReopen(opts);
|
||||
|
||||
const Snapshot* snapshot = nullptr;
|
||||
// Put a snapshot before the range tombstone, verify an iterator using that
|
||||
|
||||
+2
-1
@@ -141,6 +141,7 @@ TEST_F(DBSSTTest, SkipCheckingSSTFileSizesOnDBOpen) {
|
||||
|
||||
// Just open the DB with the option set to true and check that we don't crash.
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.skip_checking_sst_file_sizes_on_db_open = true;
|
||||
Reopen(options);
|
||||
|
||||
@@ -519,7 +520,7 @@ TEST_P(DBWALTestWithParam, WALTrashCleanupOnOpen) {
|
||||
bool fake_log_delete;
|
||||
};
|
||||
|
||||
std::unique_ptr<MyEnv> env(new MyEnv(Env::Default()));
|
||||
std::unique_ptr<MyEnv> env(new MyEnv(env_));
|
||||
Destroy(last_options_);
|
||||
|
||||
env->set_fake_log_delete(true);
|
||||
|
||||
@@ -11,9 +11,11 @@
|
||||
#include <vector>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/port.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/utilities/table_properties_collectors.h"
|
||||
#include "table/format.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/random.h"
|
||||
@@ -50,7 +52,7 @@ class DBTablePropertiesTest : public DBTestBase,
|
||||
public testing::WithParamInterface<std::string> {
|
||||
public:
|
||||
DBTablePropertiesTest()
|
||||
: DBTestBase("/db_table_properties_test", /*env_do_fsync=*/true) {}
|
||||
: DBTestBase("/db_table_properties_test", /*env_do_fsync=*/false) {}
|
||||
TablePropertiesCollection TestGetPropertiesOfTablesInRange(
|
||||
std::vector<Range> ranges, std::size_t* num_properties = nullptr,
|
||||
std::size_t* num_files = nullptr);
|
||||
@@ -274,6 +276,55 @@ TEST_F(DBTablePropertiesTest, GetDbIdentifiersProperty) {
|
||||
}
|
||||
}
|
||||
|
||||
class DBTableHostnamePropertyTest
|
||||
: public DBTestBase,
|
||||
public ::testing::WithParamInterface<std::tuple<int, std::string>> {
|
||||
public:
|
||||
DBTableHostnamePropertyTest()
|
||||
: DBTestBase("/db_table_hostname_property_test",
|
||||
/*env_do_fsync=*/false) {}
|
||||
};
|
||||
|
||||
TEST_P(DBTableHostnamePropertyTest, DbHostLocationProperty) {
|
||||
option_config_ = std::get<0>(GetParam());
|
||||
Options opts = CurrentOptions();
|
||||
std::string expected_host_id = std::get<1>(GetParam());
|
||||
;
|
||||
if (expected_host_id == kHostnameForDbHostId) {
|
||||
ASSERT_OK(env_->GetHostNameString(&expected_host_id));
|
||||
} else {
|
||||
opts.db_host_id = expected_host_id;
|
||||
}
|
||||
CreateAndReopenWithCF({"goku"}, opts);
|
||||
|
||||
for (uint32_t cf = 0; cf < 2; ++cf) {
|
||||
Put(cf, "key", "val");
|
||||
Put(cf, "foo", "bar");
|
||||
Flush(cf);
|
||||
|
||||
TablePropertiesCollection fname_to_props;
|
||||
ASSERT_OK(db_->GetPropertiesOfAllTables(handles_[cf], &fname_to_props));
|
||||
ASSERT_EQ(1U, fname_to_props.size());
|
||||
|
||||
ASSERT_EQ(fname_to_props.begin()->second->db_host_id, expected_host_id);
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
DBTableHostnamePropertyTest, DBTableHostnamePropertyTest,
|
||||
::testing::Values(
|
||||
// OptionConfig, override db_host_location
|
||||
std::make_tuple(DBTestBase::OptionConfig::kDefault,
|
||||
kHostnameForDbHostId),
|
||||
std::make_tuple(DBTestBase::OptionConfig::kDefault, "foobar"),
|
||||
std::make_tuple(DBTestBase::OptionConfig::kDefault, ""),
|
||||
std::make_tuple(DBTestBase::OptionConfig::kPlainTableFirstBytePrefix,
|
||||
kHostnameForDbHostId),
|
||||
std::make_tuple(DBTestBase::OptionConfig::kPlainTableFirstBytePrefix,
|
||||
"foobar"),
|
||||
std::make_tuple(DBTestBase::OptionConfig::kPlainTableFirstBytePrefix,
|
||||
"")));
|
||||
|
||||
class DeletionTriggeredCompactionTestListener : public EventListener {
|
||||
public:
|
||||
void OnCompactionBegin(DB* , const CompactionJobInfo& ci) override {
|
||||
@@ -370,6 +421,54 @@ TEST_P(DBTablePropertiesTest, DeletionTriggeredCompactionMarking) {
|
||||
ASSERT_LT(0, opts.statistics->getTickerCount(COMPACT_READ_BYTES_MARKED));
|
||||
}
|
||||
|
||||
TEST_P(DBTablePropertiesTest, RatioBasedDeletionTriggeredCompactionMarking) {
|
||||
constexpr int kNumKeys = 1000;
|
||||
constexpr int kWindowSize = 0;
|
||||
constexpr int kNumDelsTrigger = 0;
|
||||
constexpr double kDeletionRatio = 0.1;
|
||||
std::shared_ptr<TablePropertiesCollectorFactory> compact_on_del =
|
||||
NewCompactOnDeletionCollectorFactory(kWindowSize, kNumDelsTrigger,
|
||||
kDeletionRatio);
|
||||
|
||||
Options opts = CurrentOptions();
|
||||
opts.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
opts.table_properties_collector_factories.emplace_back(compact_on_del);
|
||||
|
||||
Reopen(opts);
|
||||
|
||||
// Add an L2 file to prevent tombstones from dropping due to obsolescence
|
||||
// during flush
|
||||
Put(Key(0), "val");
|
||||
Flush();
|
||||
MoveFilesToLevel(2);
|
||||
|
||||
auto* listener = new DeletionTriggeredCompactionTestListener();
|
||||
opts.listeners.emplace_back(listener);
|
||||
Reopen(opts);
|
||||
|
||||
// Generate one L0 with kNumKeys Put.
|
||||
for (int i = 0; i < kNumKeys; ++i) {
|
||||
ASSERT_OK(Put(Key(i), "not important"));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Generate another L0 with kNumKeys Delete.
|
||||
// This file, due to deletion ratio, will trigger compaction: 2@0 files to L1.
|
||||
// The resulting L1 file has only one tombstone for user key 'Key(0)'.
|
||||
// Again, due to deletion ratio, a compaction will be triggered: 1@1 + 1@2
|
||||
// files to L2. However, the resulting file is empty because the tombstone
|
||||
// and value are both dropped.
|
||||
for (int i = 0; i < kNumKeys; ++i) {
|
||||
ASSERT_OK(Delete(Key(i)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(i));
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
DBTablePropertiesTest,
|
||||
DBTablePropertiesTest,
|
||||
|
||||
@@ -2391,6 +2391,7 @@ TEST_F(DBTest, PurgeInfoLogs) {
|
||||
Options options = CurrentOptions();
|
||||
options.keep_log_file_num = 5;
|
||||
options.create_if_missing = true;
|
||||
options.env = env_;
|
||||
for (int mode = 0; mode <= 1; mode++) {
|
||||
if (mode == 1) {
|
||||
options.db_log_dir = dbname_ + "_logs";
|
||||
@@ -3947,6 +3948,7 @@ TEST_F(DBTest, WriteSingleThreadEntry) {
|
||||
TEST_F(DBTest, ConcurrentFlushWAL) {
|
||||
const size_t cnt = 100;
|
||||
Options options;
|
||||
options.env = env_;
|
||||
WriteOptions wopt;
|
||||
ReadOptions ropt;
|
||||
for (bool two_write_queues : {false, true}) {
|
||||
@@ -4615,6 +4617,7 @@ TEST_F(DBTest, DynamicLevelCompressionPerLevel) {
|
||||
|
||||
Random rnd(301);
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.db_write_buffer_size = 20480;
|
||||
options.write_buffer_size = 20480;
|
||||
@@ -5017,6 +5020,7 @@ TEST_F(DBTest, DynamicFIFOCompactionOptions) {
|
||||
Options options;
|
||||
options.ttl = 0;
|
||||
options.create_if_missing = true;
|
||||
options.env = env_;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Initial defaults
|
||||
@@ -5078,6 +5082,7 @@ TEST_F(DBTest, DynamicFIFOCompactionOptions) {
|
||||
TEST_F(DBTest, DynamicUniversalCompactionOptions) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.env = env_;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Initial defaults
|
||||
|
||||
+223
-28
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/read_callback.h"
|
||||
#include "options/options_helper.h"
|
||||
#include "port/port.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/persistent_cache.h"
|
||||
@@ -104,6 +105,7 @@ class TestReadOnlyWithCompressedCache
|
||||
|
||||
TEST_P(TestReadOnlyWithCompressedCache, ReadOnlyWithCompressedCache) {
|
||||
if (use_mmap_ && !IsMemoryMappedAccessSupported()) {
|
||||
ROCKSDB_GTEST_SKIP("Test requires MMAP support");
|
||||
return;
|
||||
}
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
@@ -291,7 +293,7 @@ TEST_F(DBTest2, CacheIndexAndFilterWithDBRestart) {
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(20));
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
Put(1, "a", "begin");
|
||||
@@ -1344,7 +1346,7 @@ TEST_F(DBTest2, PresetCompressionDictLocality) {
|
||||
options.target_file_size_base = kNumEntriesPerFile * kNumBytesPerEntry;
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
Reopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
@@ -1389,6 +1391,178 @@ TEST_F(DBTest2, PresetCompressionDictLocality) {
|
||||
}
|
||||
}
|
||||
|
||||
class PresetCompressionDictTest
|
||||
: public DBTestBase,
|
||||
public testing::WithParamInterface<std::tuple<CompressionType, bool>> {
|
||||
public:
|
||||
PresetCompressionDictTest()
|
||||
: DBTestBase("/db_test2", false /* env_do_fsync */),
|
||||
compression_type_(std::get<0>(GetParam())),
|
||||
bottommost_(std::get<1>(GetParam())) {}
|
||||
|
||||
protected:
|
||||
const CompressionType compression_type_;
|
||||
const bool bottommost_;
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
DBTest2, PresetCompressionDictTest,
|
||||
::testing::Combine(::testing::ValuesIn(GetSupportedDictCompressions()),
|
||||
::testing::Bool()));
|
||||
|
||||
TEST_P(PresetCompressionDictTest, Flush) {
|
||||
// Verifies that dictionary is generated and written during flush only when
|
||||
// `ColumnFamilyOptions::compression` enables dictionary.
|
||||
const size_t kValueLen = 256;
|
||||
const size_t kKeysPerFile = 1 << 10;
|
||||
const size_t kDictLen = 4 << 10;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
if (bottommost_) {
|
||||
options.bottommost_compression = compression_type_;
|
||||
options.bottommost_compression_opts.enabled = true;
|
||||
options.bottommost_compression_opts.max_dict_bytes = kDictLen;
|
||||
} else {
|
||||
options.compression = compression_type_;
|
||||
options.compression_opts.max_dict_bytes = kDictLen;
|
||||
}
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kKeysPerFile));
|
||||
options.statistics = CreateDBStatistics();
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.cache_index_and_filter_blocks = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
Reopen(options);
|
||||
|
||||
uint64_t prev_compression_dict_misses =
|
||||
TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_MISS);
|
||||
Random rnd(301);
|
||||
for (size_t i = 0; i <= kKeysPerFile; ++i) {
|
||||
ASSERT_OK(Put(Key(static_cast<int>(i)), rnd.RandomString(kValueLen)));
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
|
||||
// If there's a compression dictionary, it should have been loaded when the
|
||||
// flush finished, incurring a cache miss.
|
||||
uint64_t expected_compression_dict_misses;
|
||||
if (bottommost_) {
|
||||
expected_compression_dict_misses = prev_compression_dict_misses;
|
||||
} else {
|
||||
expected_compression_dict_misses = prev_compression_dict_misses + 1;
|
||||
}
|
||||
ASSERT_EQ(expected_compression_dict_misses,
|
||||
TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_MISS));
|
||||
}
|
||||
|
||||
TEST_P(PresetCompressionDictTest, CompactNonBottommost) {
|
||||
// Verifies that dictionary is generated and written during compaction to
|
||||
// non-bottommost level only when `ColumnFamilyOptions::compression` enables
|
||||
// dictionary.
|
||||
const size_t kValueLen = 256;
|
||||
const size_t kKeysPerFile = 1 << 10;
|
||||
const size_t kDictLen = 4 << 10;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
if (bottommost_) {
|
||||
options.bottommost_compression = compression_type_;
|
||||
options.bottommost_compression_opts.enabled = true;
|
||||
options.bottommost_compression_opts.max_dict_bytes = kDictLen;
|
||||
} else {
|
||||
options.compression = compression_type_;
|
||||
options.compression_opts.max_dict_bytes = kDictLen;
|
||||
}
|
||||
options.disable_auto_compactions = true;
|
||||
options.statistics = CreateDBStatistics();
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.cache_index_and_filter_blocks = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
Reopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
for (size_t j = 0; j <= kKeysPerFile; ++j) {
|
||||
ASSERT_OK(Put(Key(static_cast<int>(j)), rnd.RandomString(kValueLen)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(2);
|
||||
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
for (size_t j = 0; j <= kKeysPerFile; ++j) {
|
||||
ASSERT_OK(Put(Key(static_cast<int>(j)), rnd.RandomString(kValueLen)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
#ifndef ROCKSDB_LITE
|
||||
ASSERT_EQ("2,0,1", FilesPerLevel(0));
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
uint64_t prev_compression_dict_misses =
|
||||
TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_MISS);
|
||||
// This L0->L1 compaction merges the two L0 files into L1. The produced L1
|
||||
// file is not bottommost due to the existing L2 file covering the same key-
|
||||
// range.
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr));
|
||||
#ifndef ROCKSDB_LITE
|
||||
ASSERT_EQ("0,1,1", FilesPerLevel(0));
|
||||
#endif // ROCKSDB_LITE
|
||||
// If there's a compression dictionary, it should have been loaded when the
|
||||
// compaction finished, incurring a cache miss.
|
||||
uint64_t expected_compression_dict_misses;
|
||||
if (bottommost_) {
|
||||
expected_compression_dict_misses = prev_compression_dict_misses;
|
||||
} else {
|
||||
expected_compression_dict_misses = prev_compression_dict_misses + 1;
|
||||
}
|
||||
ASSERT_EQ(expected_compression_dict_misses,
|
||||
TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_MISS));
|
||||
}
|
||||
|
||||
TEST_P(PresetCompressionDictTest, CompactBottommost) {
|
||||
// Verifies that dictionary is generated and written during compaction to
|
||||
// non-bottommost level only when either `ColumnFamilyOptions::compression` or
|
||||
// `ColumnFamilyOptions::bottommost_compression` enables dictionary.
|
||||
const size_t kValueLen = 256;
|
||||
const size_t kKeysPerFile = 1 << 10;
|
||||
const size_t kDictLen = 4 << 10;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
if (bottommost_) {
|
||||
options.bottommost_compression = compression_type_;
|
||||
options.bottommost_compression_opts.enabled = true;
|
||||
options.bottommost_compression_opts.max_dict_bytes = kDictLen;
|
||||
} else {
|
||||
options.compression = compression_type_;
|
||||
options.compression_opts.max_dict_bytes = kDictLen;
|
||||
}
|
||||
options.disable_auto_compactions = true;
|
||||
options.statistics = CreateDBStatistics();
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.cache_index_and_filter_blocks = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
Reopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
for (size_t j = 0; j <= kKeysPerFile; ++j) {
|
||||
ASSERT_OK(Put(Key(static_cast<int>(j)), rnd.RandomString(kValueLen)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
#ifndef ROCKSDB_LITE
|
||||
ASSERT_EQ("2", FilesPerLevel(0));
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
uint64_t prev_compression_dict_misses =
|
||||
TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_MISS);
|
||||
CompactRangeOptions cro;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
#ifndef ROCKSDB_LITE
|
||||
ASSERT_EQ("0,1", FilesPerLevel(0));
|
||||
#endif // ROCKSDB_LITE
|
||||
// If there's a compression dictionary, it should have been loaded when the
|
||||
// compaction finished, incurring a cache miss.
|
||||
ASSERT_EQ(prev_compression_dict_misses + 1,
|
||||
TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_MISS));
|
||||
}
|
||||
|
||||
class CompactionCompressionListener : public EventListener {
|
||||
public:
|
||||
explicit CompactionCompressionListener(Options* db_options)
|
||||
@@ -1470,7 +1644,7 @@ TEST_P(CompressionFailuresTest, CompressionFailures) {
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_size = 512;
|
||||
table_options.verify_compression = true;
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
options.compression = compression_type_;
|
||||
options.compression_opts.parallel_threads = compression_parallel_threads_;
|
||||
@@ -1808,7 +1982,7 @@ class PinL0IndexAndFilterBlocksTest
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
table_options.pin_l0_filter_and_index_blocks_in_cache = true;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(20));
|
||||
options->table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
options->table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
CreateAndReopenWithCF({"pikachu"}, *options);
|
||||
|
||||
Put(1, "a", "begin");
|
||||
@@ -1848,7 +2022,7 @@ TEST_P(PinL0IndexAndFilterBlocksTest,
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
table_options.pin_l0_filter_and_index_blocks_in_cache = true;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(20));
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
ASSERT_OK(Put(1, "key", "val"));
|
||||
@@ -2485,26 +2659,30 @@ TEST_F(DBTest2, ReadAmpBitmapLiveInCacheAfterDBClose) {
|
||||
{
|
||||
const int kIdBufLen = 100;
|
||||
char id_buf[kIdBufLen];
|
||||
Status s = Status::NotSupported();
|
||||
#ifndef OS_WIN
|
||||
// You can't open a directory on windows using random access file
|
||||
std::unique_ptr<RandomAccessFile> file;
|
||||
ASSERT_OK(env_->NewRandomAccessFile(dbname_, &file, EnvOptions()));
|
||||
if (file->GetUniqueId(id_buf, kIdBufLen) == 0) {
|
||||
// fs holding db directory doesn't support getting a unique file id,
|
||||
// this means that running this test will fail because lru_cache will load
|
||||
// the blocks again regardless of them being already in the cache
|
||||
return;
|
||||
}
|
||||
#else
|
||||
std::unique_ptr<Directory> dir;
|
||||
ASSERT_OK(env_->NewDirectory(dbname_, &dir));
|
||||
if (dir->GetUniqueId(id_buf, kIdBufLen) == 0) {
|
||||
// fs holding db directory doesn't support getting a unique file id,
|
||||
// this means that running this test will fail because lru_cache will load
|
||||
// the blocks again regardless of them being already in the cache
|
||||
return;
|
||||
s = env_->NewRandomAccessFile(dbname_, &file, EnvOptions());
|
||||
if (s.ok()) {
|
||||
if (file->GetUniqueId(id_buf, kIdBufLen) == 0) {
|
||||
// fs holding db directory doesn't support getting a unique file id,
|
||||
// this means that running this test will fail because lru_cache will
|
||||
// load the blocks again regardless of them being already in the cache
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (!s.ok()) {
|
||||
std::unique_ptr<Directory> dir;
|
||||
ASSERT_OK(env_->NewDirectory(dbname_, &dir));
|
||||
if (dir->GetUniqueId(id_buf, kIdBufLen) == 0) {
|
||||
// fs holding db directory doesn't support getting a unique file id,
|
||||
// this means that running this test will fail because lru_cache will
|
||||
// load the blocks again regardless of them being already in the cache
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
uint32_t bytes_per_bit[2] = {1, 16};
|
||||
for (size_t k = 0; k < 2; k++) {
|
||||
@@ -3297,7 +3475,7 @@ TEST_F(DBTest2, RateLimitedCompactionReads) {
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.block_size = 16384;
|
||||
bbto.no_block_cache = true;
|
||||
options.table_factory.reset(new BlockBasedTableFactory(bbto));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
for (int i = 0; i < kNumL0Files; ++i) {
|
||||
@@ -3342,6 +3520,7 @@ TEST_F(DBTest2, RateLimitedCompactionReads) {
|
||||
// is on levels higher than the new num_levels.
|
||||
TEST_F(DBTest2, ReduceLevel) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.disable_auto_compactions = true;
|
||||
options.num_levels = 7;
|
||||
Reopen(options);
|
||||
@@ -3370,6 +3549,7 @@ TEST_F(DBTest2, ReadCallbackTest) {
|
||||
Options options;
|
||||
options.disable_auto_compactions = true;
|
||||
options.num_levels = 7;
|
||||
options.env = env_;
|
||||
Reopen(options);
|
||||
std::vector<const Snapshot*> snapshots;
|
||||
// Try to create a db with multiple layers and a memtable
|
||||
@@ -3629,7 +3809,9 @@ TEST_F(DBTest2, TraceAndReplay) {
|
||||
column_families.push_back(
|
||||
ColumnFamilyDescriptor("pikachu", ColumnFamilyOptions()));
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
ASSERT_OK(DB::Open(DBOptions(), dbname2, column_families, &handles, &db2));
|
||||
DBOptions db_opts;
|
||||
db_opts.env = env_;
|
||||
ASSERT_OK(DB::Open(db_opts, dbname2, column_families, &handles, &db2));
|
||||
|
||||
env_->SleepForMicroseconds(100);
|
||||
// Verify that the keys don't already exist
|
||||
@@ -3704,7 +3886,9 @@ TEST_F(DBTest2, TraceWithLimit) {
|
||||
column_families.push_back(
|
||||
ColumnFamilyDescriptor("pikachu", ColumnFamilyOptions()));
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
ASSERT_OK(DB::Open(DBOptions(), dbname2, column_families, &handles, &db2));
|
||||
DBOptions db_opts;
|
||||
db_opts.env = env_;
|
||||
ASSERT_OK(DB::Open(db_opts, dbname2, column_families, &handles, &db2));
|
||||
|
||||
env_->SleepForMicroseconds(100);
|
||||
// Verify that the keys don't already exist
|
||||
@@ -3772,7 +3956,9 @@ TEST_F(DBTest2, TraceWithSampling) {
|
||||
column_families.push_back(
|
||||
ColumnFamilyDescriptor("pikachu", ColumnFamilyOptions()));
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
ASSERT_OK(DB::Open(DBOptions(), dbname2, column_families, &handles, &db2));
|
||||
DBOptions db_opts;
|
||||
db_opts.env = env_;
|
||||
ASSERT_OK(DB::Open(db_opts, dbname2, column_families, &handles, &db2));
|
||||
|
||||
env_->SleepForMicroseconds(100);
|
||||
ASSERT_TRUE(db2->Get(ro, handles[0], "a", &value).IsNotFound());
|
||||
@@ -3872,7 +4058,9 @@ TEST_F(DBTest2, TraceWithFilter) {
|
||||
column_families.push_back(
|
||||
ColumnFamilyDescriptor("pikachu", ColumnFamilyOptions()));
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
ASSERT_OK(DB::Open(DBOptions(), dbname2, column_families, &handles, &db2));
|
||||
DBOptions db_opts;
|
||||
db_opts.env = env_;
|
||||
ASSERT_OK(DB::Open(db_opts, dbname2, column_families, &handles, &db2));
|
||||
|
||||
env_->SleepForMicroseconds(100);
|
||||
// Verify that the keys don't already exist
|
||||
@@ -3918,7 +4106,7 @@ TEST_F(DBTest2, TraceWithFilter) {
|
||||
handles.clear();
|
||||
|
||||
DB* db3 = nullptr;
|
||||
ASSERT_OK(DB::Open(DBOptions(), dbname3, column_families, &handles, &db3));
|
||||
ASSERT_OK(DB::Open(db_opts, dbname3, column_families, &handles, &db3));
|
||||
|
||||
env_->SleepForMicroseconds(100);
|
||||
// Verify that the keys don't already exist
|
||||
@@ -3974,6 +4162,11 @@ TEST_F(DBTest2, TraceWithFilter) {
|
||||
|
||||
TEST_F(DBTest2, PinnableSliceAndMmapReads) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
if (!IsMemoryMappedAccessSupported()) {
|
||||
ROCKSDB_GTEST_SKIP("Test requires default environment");
|
||||
return;
|
||||
}
|
||||
options.allow_mmap_reads = true;
|
||||
options.max_open_files = 100;
|
||||
options.compression = kNoCompression;
|
||||
@@ -4026,7 +4219,7 @@ TEST_F(DBTest2, DISABLED_IteratorPinnedMemory) {
|
||||
bbto.cache_index_and_filter_blocks = false;
|
||||
bbto.block_cache = NewLRUCache(100000);
|
||||
bbto.block_size = 400; // small block size
|
||||
options.table_factory.reset(new BlockBasedTableFactory(bbto));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
Reopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
@@ -4252,6 +4445,7 @@ TEST_F(DBTest2, TestCompactFiles) {
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.num_levels = 2;
|
||||
options.disable_auto_compactions = true;
|
||||
Reopen(options);
|
||||
@@ -4569,7 +4763,7 @@ TEST_F(DBTest2, CrashInRecoveryMultipleCF) {
|
||||
for (const auto& f : filenames) {
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
if (ParseFileName(f, &number, &type) && type == FileType::kLogFile) {
|
||||
if (ParseFileName(f, &number, &type) && type == FileType::kWalFile) {
|
||||
std::string fname = dbname_ + "/" + f;
|
||||
std::string file_content;
|
||||
ASSERT_OK(ReadFileToString(env_, fname, &file_content));
|
||||
@@ -4807,6 +5001,7 @@ TEST_F(DBTest2, BlockBasedTablePrefixIndexSeekForPrev) {
|
||||
|
||||
TEST_F(DBTest2, PartitionedIndexPrefetchFailure) {
|
||||
Options options = last_options_;
|
||||
options.env = env_;
|
||||
options.max_open_files = 20;
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.index_type = BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch;
|
||||
|
||||
+16
-11
@@ -331,7 +331,7 @@ Options DBTestBase::CurrentOptions(
|
||||
return GetOptions(option_config_, default_options, options_override);
|
||||
}
|
||||
|
||||
Options DBTestBase::GetDefaultOptions() {
|
||||
Options DBTestBase::GetDefaultOptions() const {
|
||||
Options options;
|
||||
options.write_buffer_size = 4090 * 4096;
|
||||
options.target_file_size_base = 2 * 1024 * 1024;
|
||||
@@ -339,6 +339,10 @@ Options DBTestBase::GetDefaultOptions() {
|
||||
options.max_open_files = 5000;
|
||||
options.wal_recovery_mode = WALRecoveryMode::kTolerateCorruptedTailRecords;
|
||||
options.compaction_pri = CompactionPri::kByCompensatedSize;
|
||||
options.env = env_;
|
||||
if (!env_->skip_fsync_) {
|
||||
options.track_and_verify_wals_in_manifest = true;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -367,28 +371,28 @@ Options DBTestBase::GetOptions(
|
||||
options.unordered_write = false;
|
||||
break;
|
||||
case kPlainTableFirstBytePrefix:
|
||||
options.table_factory.reset(new PlainTableFactory());
|
||||
options.table_factory.reset(NewPlainTableFactory());
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
||||
options.allow_mmap_reads = can_allow_mmap;
|
||||
options.max_sequential_skip_in_iterations = 999999;
|
||||
set_block_based_table_factory = false;
|
||||
break;
|
||||
case kPlainTableCappedPrefix:
|
||||
options.table_factory.reset(new PlainTableFactory());
|
||||
options.table_factory.reset(NewPlainTableFactory());
|
||||
options.prefix_extractor.reset(NewCappedPrefixTransform(8));
|
||||
options.allow_mmap_reads = can_allow_mmap;
|
||||
options.max_sequential_skip_in_iterations = 999999;
|
||||
set_block_based_table_factory = false;
|
||||
break;
|
||||
case kPlainTableCappedPrefixNonMmap:
|
||||
options.table_factory.reset(new PlainTableFactory());
|
||||
options.table_factory.reset(NewPlainTableFactory());
|
||||
options.prefix_extractor.reset(NewCappedPrefixTransform(8));
|
||||
options.allow_mmap_reads = false;
|
||||
options.max_sequential_skip_in_iterations = 999999;
|
||||
set_block_based_table_factory = false;
|
||||
break;
|
||||
case kPlainTableAllBytesPrefix:
|
||||
options.table_factory.reset(new PlainTableFactory());
|
||||
options.table_factory.reset(NewPlainTableFactory());
|
||||
options.prefix_extractor.reset(NewNoopTransform());
|
||||
options.allow_mmap_reads = can_allow_mmap;
|
||||
options.max_sequential_skip_in_iterations = 999999;
|
||||
@@ -704,9 +708,9 @@ Status DBTestBase::TryReopen(const Options& options) {
|
||||
// Note: operator= is an unsafe approach here since it destructs
|
||||
// std::shared_ptr in the same order of their creation, in contrast to
|
||||
// destructors which destructs them in the opposite order of creation. One
|
||||
// particular problme is that the cache destructor might invoke callback
|
||||
// particular problem is that the cache destructor might invoke callback
|
||||
// functions that use Option members such as statistics. To work around this
|
||||
// problem, we manually call destructor of table_facotry which eventually
|
||||
// problem, we manually call destructor of table_factory which eventually
|
||||
// clears the block cache.
|
||||
last_options_ = options;
|
||||
MaybeInstallTimeElapseOnlySleep(options);
|
||||
@@ -963,7 +967,8 @@ std::string DBTestBase::AllEntriesFor(const Slice& user_key, int cf) {
|
||||
bool first = true;
|
||||
while (iter->Valid()) {
|
||||
ParsedInternalKey ikey(Slice(), 0, kTypeValue);
|
||||
if (ParseInternalKey(iter->key(), &ikey) != Status::OK()) {
|
||||
if (ParseInternalKey(iter->key(), &ikey, true /* log_err_key */) !=
|
||||
Status::OK()) {
|
||||
result += "CORRUPTED";
|
||||
} else {
|
||||
if (!last_options_.comparator->Equal(ikey.user_key, user_key)) {
|
||||
@@ -1370,12 +1375,12 @@ void DBTestBase::validateNumberOfEntries(int numValues, int cf) {
|
||||
kMaxSequenceNumber));
|
||||
}
|
||||
iter->SeekToFirst();
|
||||
ASSERT_EQ(iter->status().ok(), true);
|
||||
ASSERT_OK(iter->status());
|
||||
int seq = numValues;
|
||||
while (iter->Valid()) {
|
||||
ParsedInternalKey ikey;
|
||||
ikey.clear();
|
||||
ASSERT_OK(ParseInternalKey(iter->key(), &ikey));
|
||||
ASSERT_OK(ParseInternalKey(iter->key(), &ikey, true /* log_err_key */));
|
||||
|
||||
// checks sequence number for updates
|
||||
ASSERT_EQ(ikey.sequence, (unsigned)seq--);
|
||||
@@ -1580,7 +1585,7 @@ void DBTestBase::VerifyDBInternal(
|
||||
for (auto p : true_data) {
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ParsedInternalKey ikey;
|
||||
ASSERT_OK(ParseInternalKey(iter->key(), &ikey));
|
||||
ASSERT_OK(ParseInternalKey(iter->key(), &ikey, true /* log_err_key */));
|
||||
ASSERT_EQ(p.first, ikey.user_key);
|
||||
ASSERT_EQ(p.second, iter->value());
|
||||
iter->Next();
|
||||
|
||||
+6
-3
@@ -949,10 +949,13 @@ class DBTestBase : public testing::Test {
|
||||
const anon::OptionsOverride& options_override =
|
||||
anon::OptionsOverride()) const;
|
||||
|
||||
static Options GetDefaultOptions();
|
||||
Options GetDefaultOptions() const;
|
||||
|
||||
Options GetOptions(int option_config,
|
||||
const Options& default_options = GetDefaultOptions(),
|
||||
Options GetOptions(int option_config) const {
|
||||
return GetOptions(option_config, GetDefaultOptions());
|
||||
}
|
||||
|
||||
Options GetOptions(int option_config, const Options& default_options,
|
||||
const anon::OptionsOverride& options_override =
|
||||
anon::OptionsOverride()) const;
|
||||
|
||||
|
||||
@@ -2129,7 +2129,7 @@ TEST_F(DBTestUniversalCompaction2, IngestBehind) {
|
||||
TEST_F(DBTestUniversalCompaction2, PeriodicCompactionDefault) {
|
||||
Options options;
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
|
||||
options.env = env_;
|
||||
KeepFilterFactory* filter = new KeepFilterFactory(true);
|
||||
options.compaction_filter_factory.reset(filter);
|
||||
Reopen(options);
|
||||
|
||||
+37
-44
@@ -47,8 +47,8 @@ class EnrichedSpecialEnv : public SpecialEnv {
|
||||
InstrumentedMutexLock l(&env_mutex_);
|
||||
if (f == skipped_wal) {
|
||||
deleted_wal_reopened = true;
|
||||
if (IsWAL(f) && largetest_deleted_wal.size() != 0 &&
|
||||
f.compare(largetest_deleted_wal) <= 0) {
|
||||
if (IsWAL(f) && largest_deleted_wal.size() != 0 &&
|
||||
f.compare(largest_deleted_wal) <= 0) {
|
||||
gap_in_wals = true;
|
||||
}
|
||||
}
|
||||
@@ -62,9 +62,9 @@ class EnrichedSpecialEnv : public SpecialEnv {
|
||||
// remember its name partly because the application might attempt to
|
||||
// delete the file again.
|
||||
if (skipped_wal.size() != 0 && skipped_wal != fname) {
|
||||
if (largetest_deleted_wal.size() == 0 ||
|
||||
largetest_deleted_wal.compare(fname) < 0) {
|
||||
largetest_deleted_wal = fname;
|
||||
if (largest_deleted_wal.size() == 0 ||
|
||||
largest_deleted_wal.compare(fname) < 0) {
|
||||
largest_deleted_wal = fname;
|
||||
}
|
||||
} else {
|
||||
skipped_wal = fname;
|
||||
@@ -82,7 +82,7 @@ class EnrichedSpecialEnv : public SpecialEnv {
|
||||
// the wal whose actual delete was skipped by the env
|
||||
std::string skipped_wal = "";
|
||||
// the largest WAL that was requested to be deleted
|
||||
std::string largetest_deleted_wal = "";
|
||||
std::string largest_deleted_wal = "";
|
||||
// number of WALs that were successfully deleted
|
||||
std::atomic<size_t> deleted_wal_cnt = {0};
|
||||
// the WAL whose delete from fs was skipped is reopened during recovery
|
||||
@@ -380,13 +380,12 @@ TEST_F(DBWALTest, RecoverWithBlob) {
|
||||
options.min_blob_size = min_blob_size;
|
||||
options.avoid_flush_during_recovery = false;
|
||||
options.disable_auto_compactions = true;
|
||||
options.env = env_;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_EQ(Get("key1"), short_value);
|
||||
|
||||
// TODO: enable once Get support is implemented for blobs
|
||||
// ASSERT_EQ(Get("key2"), long_value);
|
||||
ASSERT_EQ(Get("key2"), long_value);
|
||||
|
||||
VersionSet* const versions = dbfull()->TEST_GetVersionSet();
|
||||
assert(versions);
|
||||
@@ -442,10 +441,12 @@ class DBRecoveryTestBlobError
|
||||
: public DBWALTest,
|
||||
public testing::WithParamInterface<std::string> {
|
||||
public:
|
||||
DBRecoveryTestBlobError() : fault_injection_env_(env_) {}
|
||||
DBRecoveryTestBlobError()
|
||||
: fault_injection_env_(env_), sync_point_(GetParam()) {}
|
||||
~DBRecoveryTestBlobError() { Close(); }
|
||||
|
||||
FaultInjectionTestEnv fault_injection_env_;
|
||||
std::string sync_point_;
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBRecoveryTestBlobError, DBRecoveryTestBlobError,
|
||||
@@ -459,11 +460,12 @@ TEST_P(DBRecoveryTestBlobError, RecoverWithBlobError) {
|
||||
|
||||
// Reopen with blob files enabled but make blob file writing fail during
|
||||
// recovery.
|
||||
SyncPoint::GetInstance()->SetCallBack(GetParam(), [this](void* /* arg */) {
|
||||
fault_injection_env_.SetFilesystemActive(false, Status::IOError());
|
||||
SyncPoint::GetInstance()->SetCallBack(sync_point_, [this](void* /* arg */) {
|
||||
fault_injection_env_.SetFilesystemActive(false,
|
||||
Status::IOError(sync_point_));
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BuildTable:BeforeFinishBuildTable", [this](void* /* arg */) {
|
||||
"BuildTable:BeforeDeleteFile", [this](void* /* arg */) {
|
||||
fault_injection_env_.SetFilesystemActive(true);
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
@@ -1179,32 +1181,13 @@ class RecoveryTestHelper {
|
||||
test->Close();
|
||||
#endif
|
||||
if (trunc) {
|
||||
ASSERT_EQ(0, truncate(fname.c_str(), static_cast<int64_t>(size * off)));
|
||||
ASSERT_OK(
|
||||
test::TruncateFile(env, fname, static_cast<uint64_t>(size * off)));
|
||||
} else {
|
||||
InduceCorruption(fname, static_cast<size_t>(size * off + 8),
|
||||
static_cast<size_t>(size * len));
|
||||
ASSERT_OK(test::CorruptFile(env, fname, static_cast<int>(size * off + 8),
|
||||
static_cast<int>(size * len), false));
|
||||
}
|
||||
}
|
||||
|
||||
// Overwrite data with 'a' from offset for length len
|
||||
static void InduceCorruption(const std::string& filename, size_t offset,
|
||||
size_t len) {
|
||||
ASSERT_GT(len, 0U);
|
||||
|
||||
int fd = open(filename.c_str(), O_RDWR);
|
||||
|
||||
// On windows long is 32-bit
|
||||
ASSERT_LE(offset, std::numeric_limits<long>::max());
|
||||
|
||||
ASSERT_GT(fd, 0);
|
||||
ASSERT_EQ(offset, lseek(fd, static_cast<long>(offset), SEEK_SET));
|
||||
|
||||
void* buf = alloca(len);
|
||||
memset(buf, 'b', len);
|
||||
ASSERT_EQ(len, write(fd, buf, static_cast<unsigned int>(len)));
|
||||
|
||||
close(fd);
|
||||
}
|
||||
};
|
||||
|
||||
class DBWALTestWithParams
|
||||
@@ -1326,8 +1309,7 @@ TEST_F(DBWALTest, kPointInTimeRecoveryCFConsistency) {
|
||||
|
||||
ASSERT_OK(Put(1, "key3", "val3"));
|
||||
// Corrupt WAL at location of key3
|
||||
RecoveryTestHelper::InduceCorruption(
|
||||
fname, static_cast<size_t>(offset_to_corrupt), static_cast<size_t>(4));
|
||||
test::CorruptFile(env, fname, static_cast<int>(offset_to_corrupt), 4, false);
|
||||
ASSERT_OK(Put(2, "key4", "val4"));
|
||||
ASSERT_OK(Put(1, "key5", "val5"));
|
||||
Flush(2);
|
||||
@@ -1366,13 +1348,19 @@ TEST_P(DBWALTestWithParams, kPointInTimeRecovery) {
|
||||
size_t recovered_row_count = RecoveryTestHelper::GetData(this);
|
||||
ASSERT_LT(recovered_row_count, row_count);
|
||||
|
||||
bool expect_data = true;
|
||||
for (size_t k = 0; k < maxkeys; ++k) {
|
||||
bool found = Get("key" + ToString(corrupt_offset)) != "NOT_FOUND";
|
||||
if (expect_data && !found) {
|
||||
expect_data = false;
|
||||
// Verify a prefix of keys were recovered. But not in the case of full WAL
|
||||
// truncation, because we have no way to know there was a corruption when
|
||||
// truncation happened on record boundaries (preventing recovery holes in
|
||||
// that case requires using `track_and_verify_wals_in_manifest`).
|
||||
if (!trunc || corrupt_offset != 0) {
|
||||
bool expect_data = true;
|
||||
for (size_t k = 0; k < maxkeys; ++k) {
|
||||
bool found = Get("key" + ToString(k)) != "NOT_FOUND";
|
||||
if (expect_data && !found) {
|
||||
expect_data = false;
|
||||
}
|
||||
ASSERT_EQ(found, expect_data);
|
||||
}
|
||||
ASSERT_EQ(found, expect_data);
|
||||
}
|
||||
|
||||
const size_t min = RecoveryTestHelper::kKeysPerWALFile *
|
||||
@@ -1714,7 +1702,12 @@ TEST_F(DBWALTest, RestoreTotalLogSizeAfterRecoverWithoutFlush) {
|
||||
TEST_F(DBWALTest, TruncateLastLogAfterRecoverWithoutFlush) {
|
||||
constexpr size_t kKB = 1024;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.avoid_flush_during_recovery = true;
|
||||
if (mem_env_) {
|
||||
ROCKSDB_GTEST_SKIP("Test requires non-mem environment");
|
||||
return;
|
||||
}
|
||||
// Test fallocate support of running file system.
|
||||
// Skip this test if fallocate is not supported.
|
||||
std::string fname_test_fallocate = dbname_ + "/preallocate_testfile";
|
||||
|
||||
@@ -141,7 +141,8 @@ class DBBasicTestWithTimestampBase : public DBTestBase {
|
||||
ukey_and_ts.assign(expected_ukey.data(), expected_ukey.size());
|
||||
ukey_and_ts.append(expected_ts.data(), expected_ts.size());
|
||||
ParsedInternalKey parsed_ikey;
|
||||
ASSERT_OK(ParseInternalKey(it->key(), &parsed_ikey));
|
||||
ASSERT_OK(
|
||||
ParseInternalKey(it->key(), &parsed_ikey, true /* log_err_key */));
|
||||
ASSERT_EQ(ukey_and_ts, parsed_ikey.user_key);
|
||||
ASSERT_EQ(expected_val_type, parsed_ikey.type);
|
||||
ASSERT_EQ(expected_seq, parsed_ikey.sequence);
|
||||
@@ -161,7 +162,8 @@ class DBBasicTestWithTimestampBase : public DBTestBase {
|
||||
ukey_and_ts.append(expected_ts.data(), expected_ts.size());
|
||||
|
||||
ParsedInternalKey parsed_ikey;
|
||||
ASSERT_OK(ParseInternalKey(it->key(), &parsed_ikey));
|
||||
ASSERT_OK(
|
||||
ParseInternalKey(it->key(), &parsed_ikey, true /* log_err_key */));
|
||||
ASSERT_EQ(expected_val_type, parsed_ikey.type);
|
||||
ASSERT_EQ(Slice(ukey_and_ts), parsed_ikey.user_key);
|
||||
if (expected_val_type == kTypeValue) {
|
||||
@@ -242,6 +244,103 @@ TEST_F(DBBasicTestWithTimestamp, SimpleForwardIterate) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, SeekWithPrefixLessThanKey) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(3));
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
bbto.cache_index_and_filter_blocks = true;
|
||||
bbto.whole_key_filtering = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
WriteOptions write_opts;
|
||||
std::string ts_str = Timestamp(1, 0);
|
||||
Slice ts = ts_str;
|
||||
write_opts.timestamp = &ts;
|
||||
|
||||
ASSERT_OK(db_->Put(write_opts, "foo1", "bar"));
|
||||
Flush();
|
||||
|
||||
ASSERT_OK(db_->Put(write_opts, "foo2", "bar"));
|
||||
Flush();
|
||||
|
||||
// Move sst file to next level
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
|
||||
ASSERT_OK(db_->Put(write_opts, "foo3", "bar"));
|
||||
Flush();
|
||||
|
||||
ReadOptions read_opts;
|
||||
std::string read_ts = Timestamp(1, 0);
|
||||
ts = read_ts;
|
||||
read_opts.timestamp = &ts;
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_opts));
|
||||
iter->Seek("foo");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
iter->Seek("bbb");
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, SeekWithPrefixLargerThanKey) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(20));
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
bbto.cache_index_and_filter_blocks = true;
|
||||
bbto.whole_key_filtering = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
WriteOptions write_opts;
|
||||
std::string ts_str = Timestamp(1, 0);
|
||||
Slice ts = ts_str;
|
||||
write_opts.timestamp = &ts;
|
||||
|
||||
ASSERT_OK(db_->Put(write_opts, "foo1", "bar"));
|
||||
Flush();
|
||||
|
||||
ASSERT_OK(db_->Put(write_opts, "foo2", "bar"));
|
||||
Flush();
|
||||
|
||||
// Move sst file to next level
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
|
||||
ASSERT_OK(db_->Put(write_opts, "foo3", "bar"));
|
||||
Flush();
|
||||
|
||||
ReadOptions read_opts;
|
||||
std::string read_ts = Timestamp(2, 0);
|
||||
ts = read_ts;
|
||||
read_opts.timestamp = &ts;
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_opts));
|
||||
// Make sure the prefix extractor doesn't include timestamp, otherwise it
|
||||
// may return invalid result.
|
||||
iter->Seek("foo");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, SimpleForwardIterateLowerTsBound) {
|
||||
constexpr int kNumKeysPerFile = 128;
|
||||
constexpr uint64_t kMaxKey = 1024;
|
||||
@@ -490,6 +589,228 @@ TEST_F(DBBasicTestWithTimestamp, ReseekToNextUserKey) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, MultiGetWithFastLocalBloom) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
bbto.cache_index_and_filter_blocks = true;
|
||||
bbto.whole_key_filtering = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Write any value
|
||||
WriteOptions write_opts;
|
||||
std::string ts_str = Timestamp(1, 0);
|
||||
Slice ts = ts_str;
|
||||
write_opts.timestamp = &ts;
|
||||
|
||||
ASSERT_OK(db_->Put(write_opts, "foo", "bar"));
|
||||
|
||||
Flush();
|
||||
|
||||
// Read with MultiGet
|
||||
ReadOptions read_opts;
|
||||
read_opts.timestamp = &ts;
|
||||
size_t batch_size = 1;
|
||||
std::vector<Slice> keys(batch_size);
|
||||
std::vector<PinnableSlice> values(batch_size);
|
||||
std::vector<Status> statuses(batch_size);
|
||||
keys[0] = "foo";
|
||||
ColumnFamilyHandle* cfh = db_->DefaultColumnFamily();
|
||||
db_->MultiGet(read_opts, cfh, batch_size, keys.data(), values.data(),
|
||||
statuses.data());
|
||||
|
||||
ASSERT_OK(statuses[0]);
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, MultiGetWithPrefix) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.prefix_extractor.reset(NewCappedPrefixTransform(5));
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
bbto.cache_index_and_filter_blocks = true;
|
||||
bbto.whole_key_filtering = false;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Write any value
|
||||
WriteOptions write_opts;
|
||||
std::string ts_str = Timestamp(1, 0);
|
||||
Slice ts = ts_str;
|
||||
write_opts.timestamp = &ts;
|
||||
|
||||
ASSERT_OK(db_->Put(write_opts, "foo", "bar"));
|
||||
|
||||
Flush();
|
||||
|
||||
// Read with MultiGet
|
||||
ReadOptions read_opts;
|
||||
read_opts.timestamp = &ts;
|
||||
size_t batch_size = 1;
|
||||
std::vector<Slice> keys(batch_size);
|
||||
std::vector<PinnableSlice> values(batch_size);
|
||||
std::vector<Status> statuses(batch_size);
|
||||
keys[0] = "foo";
|
||||
ColumnFamilyHandle* cfh = db_->DefaultColumnFamily();
|
||||
db_->MultiGet(read_opts, cfh, batch_size, keys.data(), values.data(),
|
||||
statuses.data());
|
||||
|
||||
ASSERT_OK(statuses[0]);
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, MultiGetWithMemBloomFilter) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.prefix_extractor.reset(NewCappedPrefixTransform(5));
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
bbto.cache_index_and_filter_blocks = true;
|
||||
bbto.whole_key_filtering = false;
|
||||
options.memtable_prefix_bloom_size_ratio = 0.1;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Write any value
|
||||
WriteOptions write_opts;
|
||||
std::string ts_str = Timestamp(1, 0);
|
||||
Slice ts = ts_str;
|
||||
write_opts.timestamp = &ts;
|
||||
|
||||
ASSERT_OK(db_->Put(write_opts, "foo", "bar"));
|
||||
|
||||
// Read with MultiGet
|
||||
ts_str = Timestamp(2, 0);
|
||||
ts = ts_str;
|
||||
ReadOptions read_opts;
|
||||
read_opts.timestamp = &ts;
|
||||
size_t batch_size = 1;
|
||||
std::vector<Slice> keys(batch_size);
|
||||
std::vector<PinnableSlice> values(batch_size);
|
||||
std::vector<Status> statuses(batch_size);
|
||||
keys[0] = "foo";
|
||||
ColumnFamilyHandle* cfh = db_->DefaultColumnFamily();
|
||||
db_->MultiGet(read_opts, cfh, batch_size, keys.data(), values.data(),
|
||||
statuses.data());
|
||||
|
||||
ASSERT_OK(statuses[0]);
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, MultiGetRangeFiltering) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
bbto.cache_index_and_filter_blocks = true;
|
||||
bbto.whole_key_filtering = false;
|
||||
options.memtable_prefix_bloom_size_ratio = 0.1;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Write any value
|
||||
WriteOptions write_opts;
|
||||
std::string ts_str = Timestamp(1, 0);
|
||||
Slice ts = ts_str;
|
||||
write_opts.timestamp = &ts;
|
||||
|
||||
// random data
|
||||
for (int i = 0; i < 3; i++) {
|
||||
auto key = ToString(i * 10);
|
||||
auto value = ToString(i * 10);
|
||||
Slice key_slice = key;
|
||||
Slice value_slice = value;
|
||||
ASSERT_OK(db_->Put(write_opts, key_slice, value_slice));
|
||||
Flush();
|
||||
}
|
||||
|
||||
// Make num_levels to 2 to do key range filtering of sst files
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
|
||||
ASSERT_OK(db_->Put(write_opts, "foo", "bar"));
|
||||
|
||||
Flush();
|
||||
|
||||
// Read with MultiGet
|
||||
ts_str = Timestamp(2, 0);
|
||||
ts = ts_str;
|
||||
ReadOptions read_opts;
|
||||
read_opts.timestamp = &ts;
|
||||
size_t batch_size = 1;
|
||||
std::vector<Slice> keys(batch_size);
|
||||
std::vector<PinnableSlice> values(batch_size);
|
||||
std::vector<Status> statuses(batch_size);
|
||||
keys[0] = "foo";
|
||||
ColumnFamilyHandle* cfh = db_->DefaultColumnFamily();
|
||||
db_->MultiGet(read_opts, cfh, batch_size, keys.data(), values.data(),
|
||||
statuses.data());
|
||||
|
||||
ASSERT_OK(statuses[0]);
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, MultiGetPrefixFilter) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.prefix_extractor.reset(NewCappedPrefixTransform(5));
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
bbto.cache_index_and_filter_blocks = true;
|
||||
bbto.whole_key_filtering = false;
|
||||
options.memtable_prefix_bloom_size_ratio = 0.1;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
WriteOptions write_opts;
|
||||
std::string ts_str = Timestamp(1, 0);
|
||||
Slice ts = ts_str;
|
||||
write_opts.timestamp = &ts;
|
||||
|
||||
ASSERT_OK(db_->Put(write_opts, "foo", "bar"));
|
||||
|
||||
Flush();
|
||||
// Read with MultiGet
|
||||
ts_str = Timestamp(2, 0);
|
||||
ts = ts_str;
|
||||
ReadOptions read_opts;
|
||||
read_opts.timestamp = &ts;
|
||||
size_t batch_size = 1;
|
||||
std::vector<Slice> keys(batch_size);
|
||||
std::vector<std::string> values(batch_size);
|
||||
std::vector<std::string> timestamps(batch_size);
|
||||
keys[0] = "foo";
|
||||
ColumnFamilyHandle* cfh = db_->DefaultColumnFamily();
|
||||
std::vector<ColumnFamilyHandle*> cfhs(keys.size(), cfh);
|
||||
std::vector<Status> statuses =
|
||||
db_->MultiGet(read_opts, cfhs, keys, &values, ×tamps);
|
||||
|
||||
ASSERT_OK(statuses[0]);
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, MaxKeysSkipped) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
|
||||
+3
-3
@@ -260,7 +260,7 @@ TEST_P(DBWriteTest, WriteThreadHangOnWriteStall) {
|
||||
TEST_P(DBWriteTest, IOErrorOnWALWritePropagateToWriteThreadFollower) {
|
||||
constexpr int kNumThreads = 5;
|
||||
std::unique_ptr<FaultInjectionTestEnv> mock_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
new FaultInjectionTestEnv(env_));
|
||||
Options options = GetOptions();
|
||||
options.env = mock_env.get();
|
||||
Reopen(options);
|
||||
@@ -329,7 +329,7 @@ TEST_P(DBWriteTest, ManualWalFlushInEffect) {
|
||||
|
||||
TEST_P(DBWriteTest, IOErrorOnWALWriteTriggersReadOnlyMode) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> mock_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
new FaultInjectionTestEnv(env_));
|
||||
Options options = GetOptions();
|
||||
options.env = mock_env.get();
|
||||
Reopen(options);
|
||||
@@ -361,7 +361,7 @@ TEST_P(DBWriteTest, IOErrorOnWALWriteTriggersReadOnlyMode) {
|
||||
TEST_P(DBWriteTest, IOErrorOnSwitchMemtable) {
|
||||
Random rnd(301);
|
||||
std::unique_ptr<FaultInjectionTestEnv> mock_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
new FaultInjectionTestEnv(env_));
|
||||
Options options = GetOptions();
|
||||
options.env = mock_env.get();
|
||||
options.writable_file_max_buffer_size = 4 * 1024 * 1024;
|
||||
|
||||
+13
-6
@@ -49,7 +49,8 @@ EntryType GetEntryType(ValueType value_type) {
|
||||
|
||||
bool ParseFullKey(const Slice& internal_key, FullKey* fkey) {
|
||||
ParsedInternalKey ikey;
|
||||
if (ParseInternalKey(internal_key, &ikey) != Status::OK()) {
|
||||
if (!ParseInternalKey(internal_key, &ikey, false /*log_err_key */)
|
||||
.ok()) { // TODO
|
||||
return false;
|
||||
}
|
||||
fkey->user_key = ikey.user_key;
|
||||
@@ -77,12 +78,18 @@ void AppendInternalKeyFooter(std::string* result, SequenceNumber s,
|
||||
PutFixed64(result, PackSequenceAndType(s, t));
|
||||
}
|
||||
|
||||
std::string ParsedInternalKey::DebugString(bool hex) const {
|
||||
std::string ParsedInternalKey::DebugString(bool log_err_key, bool hex) const {
|
||||
std::string result = "'";
|
||||
if (log_err_key) {
|
||||
result += user_key.ToString(hex);
|
||||
} else {
|
||||
result += "<redacted>";
|
||||
}
|
||||
|
||||
char buf[50];
|
||||
snprintf(buf, sizeof(buf), "' seq:%" PRIu64 ", type:%d", sequence,
|
||||
static_cast<int>(type));
|
||||
std::string result = "'";
|
||||
result += user_key.ToString(hex);
|
||||
|
||||
result += buf;
|
||||
return result;
|
||||
}
|
||||
@@ -90,8 +97,8 @@ std::string ParsedInternalKey::DebugString(bool hex) const {
|
||||
std::string InternalKey::DebugString(bool hex) const {
|
||||
std::string result;
|
||||
ParsedInternalKey parsed;
|
||||
if (ParseInternalKey(rep_, &parsed) == Status::OK()) {
|
||||
result = parsed.DebugString(hex);
|
||||
if (ParseInternalKey(rep_, &parsed, false /* log_err_key */).ok()) {
|
||||
result = parsed.DebugString(true /* log_err_key */, hex); // TODO
|
||||
} else {
|
||||
result = "(bad)";
|
||||
result.append(EscapeString(rep_));
|
||||
|
||||
+29
-11
@@ -112,13 +112,19 @@ struct ParsedInternalKey {
|
||||
// u contains timestamp if user timestamp feature is enabled.
|
||||
ParsedInternalKey(const Slice& u, const SequenceNumber& seq, ValueType t)
|
||||
: user_key(u), sequence(seq), type(t) {}
|
||||
std::string DebugString(bool hex = false) const;
|
||||
std::string DebugString(bool log_err_key, bool hex) const;
|
||||
|
||||
void clear() {
|
||||
user_key.clear();
|
||||
sequence = 0;
|
||||
type = kTypeDeletion;
|
||||
}
|
||||
|
||||
void SetTimestamp(const Slice& ts) {
|
||||
assert(ts.size() <= user_key.size());
|
||||
const char* addr = user_key.data() - ts.size();
|
||||
memcpy(const_cast<char*>(addr), ts.data(), ts.size());
|
||||
}
|
||||
};
|
||||
|
||||
// Return the length of the encoding of "key".
|
||||
@@ -166,7 +172,7 @@ extern void AppendInternalKeyFooter(std::string* result, SequenceNumber s,
|
||||
//
|
||||
// On error, returns false, leaves "*result" in an undefined state.
|
||||
extern Status ParseInternalKey(const Slice& internal_key,
|
||||
ParsedInternalKey* result);
|
||||
ParsedInternalKey* result, bool log_err_key);
|
||||
|
||||
// Returns the user key portion of an internal key.
|
||||
inline Slice ExtractUserKey(const Slice& internal_key) {
|
||||
@@ -285,8 +291,8 @@ class InternalKey {
|
||||
|
||||
bool Valid() const {
|
||||
ParsedInternalKey parsed;
|
||||
return (ParseInternalKey(Slice(rep_), &parsed) == Status::OK()) ? true
|
||||
: false;
|
||||
return (ParseInternalKey(Slice(rep_), &parsed, false /* log_err_key */)
|
||||
.ok()); // TODO
|
||||
}
|
||||
|
||||
void DecodeFrom(const Slice& s) { rep_.assign(s.data(), s.size()); }
|
||||
@@ -319,7 +325,7 @@ class InternalKey {
|
||||
AppendInternalKeyFooter(&rep_, s, t);
|
||||
}
|
||||
|
||||
std::string DebugString(bool hex = false) const;
|
||||
std::string DebugString(bool hex) const;
|
||||
};
|
||||
|
||||
inline int InternalKeyComparator::Compare(const InternalKey& a,
|
||||
@@ -328,20 +334,27 @@ inline int InternalKeyComparator::Compare(const InternalKey& a,
|
||||
}
|
||||
|
||||
inline Status ParseInternalKey(const Slice& internal_key,
|
||||
ParsedInternalKey* result) {
|
||||
ParsedInternalKey* result, bool log_err_key) {
|
||||
const size_t n = internal_key.size();
|
||||
|
||||
if (n < kNumInternalBytes) {
|
||||
return Status::Corruption("Internal Key too small");
|
||||
return Status::Corruption("Corrupted Key: Internal Key too small. Size=" +
|
||||
std::to_string(n) + ". ");
|
||||
}
|
||||
|
||||
uint64_t num = DecodeFixed64(internal_key.data() + n - kNumInternalBytes);
|
||||
unsigned char c = num & 0xff;
|
||||
result->sequence = num >> 8;
|
||||
result->type = static_cast<ValueType>(c);
|
||||
assert(result->type <= ValueType::kMaxValue);
|
||||
result->user_key = Slice(internal_key.data(), n - kNumInternalBytes);
|
||||
return IsExtendedValueType(result->type)
|
||||
? Status::OK()
|
||||
: Status::Corruption("Invalid Key Type");
|
||||
|
||||
if (IsExtendedValueType(result->type)) {
|
||||
return Status::OK();
|
||||
} else {
|
||||
return Status::Corruption("Corrupted Key",
|
||||
result->DebugString(log_err_key, true));
|
||||
}
|
||||
}
|
||||
|
||||
// Update the sequence number in the internal key.
|
||||
@@ -475,9 +488,14 @@ class IterKey {
|
||||
|
||||
// Update the sequence number in the internal key. Guarantees not to
|
||||
// invalidate slices to the key (and the user key).
|
||||
void UpdateInternalKey(uint64_t seq, ValueType t) {
|
||||
void UpdateInternalKey(uint64_t seq, ValueType t, const Slice* ts = nullptr) {
|
||||
assert(!IsKeyPinned());
|
||||
assert(key_size_ >= kNumInternalBytes);
|
||||
if (ts) {
|
||||
assert(key_size_ >= kNumInternalBytes + ts->size());
|
||||
memcpy(&buf_[key_size_ - kNumInternalBytes - ts->size()], ts->data(),
|
||||
ts->size());
|
||||
}
|
||||
uint64_t newval = (seq << 8) | t;
|
||||
EncodeFixed64(&buf_[key_size_ - kNumInternalBytes], newval);
|
||||
}
|
||||
|
||||
+3
-4
@@ -8,7 +8,6 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "logging/logging.h"
|
||||
#include "test_util/testharness.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -41,12 +40,12 @@ static void TestKey(const std::string& key,
|
||||
Slice in(encoded);
|
||||
ParsedInternalKey decoded("", 0, kTypeValue);
|
||||
|
||||
ASSERT_OK(ParseInternalKey(in, &decoded));
|
||||
ASSERT_OK(ParseInternalKey(in, &decoded, true /* log_err_key */));
|
||||
ASSERT_EQ(key, decoded.user_key.ToString());
|
||||
ASSERT_EQ(seq, decoded.sequence);
|
||||
ASSERT_EQ(vt, decoded.type);
|
||||
|
||||
ASSERT_NOK(ParseInternalKey(Slice("bar"), &decoded));
|
||||
ASSERT_NOK(ParseInternalKey(Slice("bar"), &decoded, true /* log_err_key */));
|
||||
}
|
||||
|
||||
class FormatTest : public testing::Test {};
|
||||
@@ -186,7 +185,7 @@ TEST_F(FormatTest, UpdateInternalKey) {
|
||||
|
||||
Slice in(ikey);
|
||||
ParsedInternalKey decoded;
|
||||
ASSERT_OK(ParseInternalKey(in, &decoded));
|
||||
ASSERT_OK(ParseInternalKey(in, &decoded, true /* log_err_key */));
|
||||
ASSERT_EQ(user_key, decoded.user_key.ToString());
|
||||
ASSERT_EQ(new_seq, decoded.sequence);
|
||||
ASSERT_EQ(new_val_type, decoded.type);
|
||||
|
||||
@@ -112,7 +112,7 @@ class DeleteFileTest : public DBTestBase {
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
if (ParseFileName(file, &number, &type)) {
|
||||
log_cnt += (type == kLogFile);
|
||||
log_cnt += (type == kWalFile);
|
||||
sst_cnt += (type == kTableFile);
|
||||
manifest_cnt += (type == kDescriptorFile);
|
||||
}
|
||||
|
||||
+155
-242
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,7 @@ class ExternalSSTFileBasicTest
|
||||
ExternalSSTFileBasicTest()
|
||||
: DBTestBase("/external_sst_file_basic_test", /*env_do_fsync=*/true) {
|
||||
sst_files_dir_ = dbname_ + "/sst_files/";
|
||||
fault_injection_test_env_.reset(new FaultInjectionTestEnv(Env::Default()));
|
||||
fault_injection_test_env_.reset(new FaultInjectionTestEnv(env_));
|
||||
DestroyAndRecreateExternalSSTFilesDir();
|
||||
}
|
||||
|
||||
@@ -1109,6 +1109,7 @@ TEST_F(ExternalSSTFileBasicTest, SyncFailure) {
|
||||
}
|
||||
|
||||
Options sst_file_writer_options;
|
||||
sst_file_writer_options.env = env_;
|
||||
std::unique_ptr<SstFileWriter> sst_file_writer(
|
||||
new SstFileWriter(EnvOptions(), sst_file_writer_options));
|
||||
std::string file_name =
|
||||
@@ -1137,11 +1138,12 @@ TEST_F(ExternalSSTFileBasicTest, SyncFailure) {
|
||||
TEST_F(ExternalSSTFileBasicTest, VerifyChecksumReadahead) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
SpecialEnv senv(Env::Default());
|
||||
SpecialEnv senv(env_);
|
||||
options.env = &senv;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Options sst_file_writer_options;
|
||||
sst_file_writer_options.env = env_;
|
||||
std::unique_ptr<SstFileWriter> sst_file_writer(
|
||||
new SstFileWriter(EnvOptions(), sst_file_writer_options));
|
||||
std::string file_name = sst_files_dir_ + "verify_checksum_readahead_test.sst";
|
||||
|
||||
@@ -192,10 +192,13 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
// Step 1: generate the checksum for ingested sst file.
|
||||
if (need_generate_file_checksum_) {
|
||||
for (size_t i = 0; i < files_to_ingest_.size(); i++) {
|
||||
std::string generated_checksum, generated_checksum_func_name;
|
||||
std::string generated_checksum;
|
||||
std::string generated_checksum_func_name;
|
||||
std::string requested_checksum_func_name;
|
||||
IOStatus io_s = GenerateOneFileChecksum(
|
||||
fs_.get(), files_to_ingest_[i].internal_file_path,
|
||||
db_options_.file_checksum_gen_factory.get(), &generated_checksum,
|
||||
db_options_.file_checksum_gen_factory.get(),
|
||||
requested_checksum_func_name, &generated_checksum,
|
||||
&generated_checksum_func_name,
|
||||
ingestion_options_.verify_checksums_readahead_size,
|
||||
db_options_.allow_mmap_reads, io_tracer_);
|
||||
@@ -313,8 +316,8 @@ Status ExternalSstFileIngestionJob::NeedsFlush(bool* flush_needed,
|
||||
ranges.emplace_back(file_to_ingest.smallest_internal_key.user_key(),
|
||||
file_to_ingest.largest_internal_key.user_key());
|
||||
}
|
||||
Status status =
|
||||
cfd_->RangesOverlapWithMemtables(ranges, super_version, flush_needed);
|
||||
Status status = cfd_->RangesOverlapWithMemtables(
|
||||
ranges, super_version, db_options_.allow_data_in_errors, flush_needed);
|
||||
if (status.ok() && *flush_needed &&
|
||||
!ingestion_options_.allow_blocking_flush) {
|
||||
status = Status::InvalidArgument("External file requires flush");
|
||||
@@ -599,22 +602,28 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
file_to_ingest->largest_internal_key =
|
||||
InternalKey("", 0, ValueType::kTypeValue);
|
||||
bool bounds_set = false;
|
||||
bool allow_data_in_errors = db_options_.allow_data_in_errors;
|
||||
iter->SeekToFirst();
|
||||
if (iter->Valid()) {
|
||||
if (ParseInternalKey(iter->key(), &key) != Status::OK()) {
|
||||
return Status::Corruption("external file have corrupted keys");
|
||||
Status pik_status =
|
||||
ParseInternalKey(iter->key(), &key, allow_data_in_errors);
|
||||
if (!pik_status.ok()) {
|
||||
return Status::Corruption("Corrupted key in external file. ",
|
||||
pik_status.getState());
|
||||
}
|
||||
if (key.sequence != 0) {
|
||||
return Status::Corruption("external file have non zero sequence number");
|
||||
return Status::Corruption("External file has non zero sequence number");
|
||||
}
|
||||
file_to_ingest->smallest_internal_key.SetFrom(key);
|
||||
|
||||
iter->SeekToLast();
|
||||
if (ParseInternalKey(iter->key(), &key) != Status::OK()) {
|
||||
return Status::Corruption("external file have corrupted keys");
|
||||
pik_status = ParseInternalKey(iter->key(), &key, allow_data_in_errors);
|
||||
if (!pik_status.ok()) {
|
||||
return Status::Corruption("Corrupted key in external file. ",
|
||||
pik_status.getState());
|
||||
}
|
||||
if (key.sequence != 0) {
|
||||
return Status::Corruption("external file have non zero sequence number");
|
||||
return Status::Corruption("External file has non zero sequence number");
|
||||
}
|
||||
file_to_ingest->largest_internal_key.SetFrom(key);
|
||||
|
||||
@@ -627,8 +636,11 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
if (range_del_iter != nullptr) {
|
||||
for (range_del_iter->SeekToFirst(); range_del_iter->Valid();
|
||||
range_del_iter->Next()) {
|
||||
if (ParseInternalKey(range_del_iter->key(), &key) != Status::OK()) {
|
||||
return Status::Corruption("external file have corrupted keys");
|
||||
Status pik_status =
|
||||
ParseInternalKey(range_del_iter->key(), &key, allow_data_in_errors);
|
||||
if (!pik_status.ok()) {
|
||||
return Status::Corruption("Corrupted key in external file. ",
|
||||
pik_status.getState());
|
||||
}
|
||||
RangeTombstone tombstone(key, range_del_iter->value());
|
||||
|
||||
@@ -830,11 +842,13 @@ IOStatus ExternalSstFileIngestionJob::GenerateChecksumForIngestedFile(
|
||||
// file checksum generated during Prepare(). This step will be skipped.
|
||||
return IOStatus::OK();
|
||||
}
|
||||
std::string file_checksum, file_checksum_func_name;
|
||||
std::string file_checksum;
|
||||
std::string file_checksum_func_name;
|
||||
std::string requested_checksum_func_name;
|
||||
IOStatus io_s = GenerateOneFileChecksum(
|
||||
fs_.get(), file_to_ingest->internal_file_path,
|
||||
db_options_.file_checksum_gen_factory.get(), &file_checksum,
|
||||
&file_checksum_func_name,
|
||||
db_options_.file_checksum_gen_factory.get(), requested_checksum_func_name,
|
||||
&file_checksum, &file_checksum_func_name,
|
||||
ingestion_options_.verify_checksums_readahead_size,
|
||||
db_options_.allow_mmap_reads, io_tracer_);
|
||||
if (!io_s.ok()) {
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#include "db/version_set.h"
|
||||
#include "env/mock_env.h"
|
||||
#include "file/filename.h"
|
||||
#include "logging/logging.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
|
||||
+18
-19
@@ -10,7 +10,6 @@
|
||||
#include "file/filename.h"
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "logging/logging.h"
|
||||
#include "port/port.h"
|
||||
#include "test_util/testharness.h"
|
||||
|
||||
@@ -35,23 +34,23 @@ TEST_F(FileNameTest, Parse) {
|
||||
FileType type;
|
||||
char mode;
|
||||
} cases[] = {
|
||||
{"100.log", 100, kLogFile, kAllMode},
|
||||
{"0.log", 0, kLogFile, kAllMode},
|
||||
{"0.sst", 0, kTableFile, kAllMode},
|
||||
{"CURRENT", 0, kCurrentFile, kAllMode},
|
||||
{"LOCK", 0, kDBLockFile, kAllMode},
|
||||
{"MANIFEST-2", 2, kDescriptorFile, kAllMode},
|
||||
{"MANIFEST-7", 7, kDescriptorFile, kAllMode},
|
||||
{"METADB-2", 2, kMetaDatabase, kAllMode},
|
||||
{"METADB-7", 7, kMetaDatabase, kAllMode},
|
||||
{"LOG", 0, kInfoLogFile, kDefautInfoLogDir},
|
||||
{"LOG.old", 0, kInfoLogFile, kDefautInfoLogDir},
|
||||
{"LOG.old.6688", 6688, kInfoLogFile, kDefautInfoLogDir},
|
||||
{"rocksdb_dir_LOG", 0, kInfoLogFile, kDifferentInfoLogDir},
|
||||
{"rocksdb_dir_LOG.old", 0, kInfoLogFile, kDifferentInfoLogDir},
|
||||
{"rocksdb_dir_LOG.old.6688", 6688, kInfoLogFile, kDifferentInfoLogDir},
|
||||
{"18446744073709551615.log", 18446744073709551615ull, kLogFile,
|
||||
kAllMode}, };
|
||||
{"100.log", 100, kWalFile, kAllMode},
|
||||
{"0.log", 0, kWalFile, kAllMode},
|
||||
{"0.sst", 0, kTableFile, kAllMode},
|
||||
{"CURRENT", 0, kCurrentFile, kAllMode},
|
||||
{"LOCK", 0, kDBLockFile, kAllMode},
|
||||
{"MANIFEST-2", 2, kDescriptorFile, kAllMode},
|
||||
{"MANIFEST-7", 7, kDescriptorFile, kAllMode},
|
||||
{"METADB-2", 2, kMetaDatabase, kAllMode},
|
||||
{"METADB-7", 7, kMetaDatabase, kAllMode},
|
||||
{"LOG", 0, kInfoLogFile, kDefautInfoLogDir},
|
||||
{"LOG.old", 0, kInfoLogFile, kDefautInfoLogDir},
|
||||
{"LOG.old.6688", 6688, kInfoLogFile, kDefautInfoLogDir},
|
||||
{"rocksdb_dir_LOG", 0, kInfoLogFile, kDifferentInfoLogDir},
|
||||
{"rocksdb_dir_LOG.old", 0, kInfoLogFile, kDifferentInfoLogDir},
|
||||
{"rocksdb_dir_LOG.old.6688", 6688, kInfoLogFile, kDifferentInfoLogDir},
|
||||
{"18446744073709551615.log", 18446744073709551615ull, kWalFile, kAllMode},
|
||||
};
|
||||
for (char mode : {kDifferentInfoLogDir, kDefautInfoLogDir, kNoCheckLogDir}) {
|
||||
for (unsigned int i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
|
||||
InfoLogPrefix info_log_prefix(mode != kDefautInfoLogDir, "/rocksdb/dir");
|
||||
@@ -142,7 +141,7 @@ TEST_F(FileNameTest, Construction) {
|
||||
ASSERT_EQ("foo/", std::string(fname.data(), 4));
|
||||
ASSERT_TRUE(ParseFileName(fname.c_str() + 4, &number, &type));
|
||||
ASSERT_EQ(192U, number);
|
||||
ASSERT_EQ(kLogFile, type);
|
||||
ASSERT_EQ(kWalFile, type);
|
||||
|
||||
fname = TableFileName({DbPath("bar", 0)}, 200, 0);
|
||||
std::string fname1 =
|
||||
|
||||
+20
-17
@@ -94,7 +94,8 @@ FlushJob::FlushJob(
|
||||
Statistics* stats, EventLogger* event_logger, bool measure_io_stats,
|
||||
const bool sync_output_directory, const bool write_manifest,
|
||||
Env::Priority thread_pri, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::string& db_id, const std::string& db_session_id)
|
||||
const std::string& db_id, const std::string& db_session_id,
|
||||
std::string full_history_ts_low)
|
||||
: dbname_(dbname),
|
||||
db_id_(db_id),
|
||||
db_session_id_(db_session_id),
|
||||
@@ -123,7 +124,8 @@ FlushJob::FlushJob(
|
||||
base_(nullptr),
|
||||
pick_memtable_called(false),
|
||||
thread_pri_(thread_pri),
|
||||
io_tracer_(io_tracer) {
|
||||
io_tracer_(io_tracer),
|
||||
full_history_ts_low_(std::move(full_history_ts_low)) {
|
||||
// Update the thread status to indicate flush.
|
||||
ReportStartedFlush();
|
||||
TEST_SYNC_POINT("FlushJob::FlushJob()");
|
||||
@@ -398,13 +400,14 @@ Status FlushJob::WriteLevel0Table() {
|
||||
: meta_.oldest_ancester_time;
|
||||
|
||||
IOStatus io_s;
|
||||
const std::string* const full_history_ts_low =
|
||||
(full_history_ts_low_.empty()) ? nullptr : &full_history_ts_low_;
|
||||
s = BuildTable(
|
||||
dbname_, versions_, db_options_.env, db_options_.fs.get(),
|
||||
*cfd_->ioptions(), mutable_cf_options_, file_options_,
|
||||
cfd_->table_cache(), iter.get(), std::move(range_del_iters), &meta_,
|
||||
&blob_file_additions, cfd_->internal_comparator(),
|
||||
cfd_->int_tbl_prop_collector_factories(), cfd_->GetID(),
|
||||
cfd_->GetName(), existing_snapshots_,
|
||||
dbname_, versions_, db_options_, *cfd_->ioptions(),
|
||||
mutable_cf_options_, file_options_, cfd_->table_cache(), iter.get(),
|
||||
std::move(range_del_iters), &meta_, &blob_file_additions,
|
||||
cfd_->internal_comparator(), cfd_->int_tbl_prop_collector_factories(),
|
||||
cfd_->GetID(), cfd_->GetName(), existing_snapshots_,
|
||||
earliest_write_conflict_snapshot_, snapshot_checker_,
|
||||
output_compression_, mutable_cf_options_.sample_for_compression,
|
||||
mutable_cf_options_.compression_opts,
|
||||
@@ -412,7 +415,7 @@ Status FlushJob::WriteLevel0Table() {
|
||||
TableFileCreationReason::kFlush, &io_s, io_tracer_, event_logger_,
|
||||
job_context_->job_id, Env::IO_HIGH, &table_properties_, 0 /* level */,
|
||||
creation_time, oldest_key_time, write_hint, current_time, db_id_,
|
||||
db_session_id_);
|
||||
db_session_id_, full_history_ts_low);
|
||||
if (!io_s.ok()) {
|
||||
io_status_ = io_s;
|
||||
}
|
||||
@@ -438,7 +441,6 @@ Status FlushJob::WriteLevel0Table() {
|
||||
// Note that if file_size is zero, the file has been deleted and
|
||||
// should not be added to the manifest.
|
||||
const bool has_output = meta_.fd.GetFileSize() > 0;
|
||||
assert(has_output || blob_file_additions.empty());
|
||||
|
||||
if (s.ok() && has_output) {
|
||||
// if we have more than 1 background thread, then we cannot
|
||||
@@ -467,15 +469,16 @@ Status FlushJob::WriteLevel0Table() {
|
||||
|
||||
if (has_output) {
|
||||
stats.bytes_written = meta_.fd.GetFileSize();
|
||||
|
||||
const auto& blobs = edit_->GetBlobFileAdditions();
|
||||
for (const auto& blob : blobs) {
|
||||
stats.bytes_written += blob.GetTotalBlobBytes();
|
||||
}
|
||||
|
||||
stats.num_output_files = static_cast<int>(blobs.size()) + 1;
|
||||
stats.num_output_files = 1;
|
||||
}
|
||||
|
||||
const auto& blobs = edit_->GetBlobFileAdditions();
|
||||
for (const auto& blob : blobs) {
|
||||
stats.bytes_written += blob.GetTotalBlobBytes();
|
||||
}
|
||||
|
||||
stats.num_output_files += static_cast<int>(blobs.size());
|
||||
|
||||
RecordTimeToHistogram(stats_, FLUSH_TIME, stats.micros);
|
||||
cfd_->internal_stats()->AddCompactionStats(0 /* level */, thread_pri_, stats);
|
||||
cfd_->internal_stats()->AddCFStats(InternalStats::BYTES_FLUSHED,
|
||||
|
||||
+4
-2
@@ -73,8 +73,8 @@ class FlushJob {
|
||||
EventLogger* event_logger, bool measure_io_stats,
|
||||
const bool sync_output_directory, const bool write_manifest,
|
||||
Env::Priority thread_pri, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::string& db_id = "",
|
||||
const std::string& db_session_id = "");
|
||||
const std::string& db_id = "", const std::string& db_session_id = "",
|
||||
std::string full_history_ts_low = "");
|
||||
|
||||
~FlushJob();
|
||||
|
||||
@@ -164,6 +164,8 @@ class FlushJob {
|
||||
IOStatus io_status_;
|
||||
|
||||
const std::shared_ptr<IOTracer> io_tracer_;
|
||||
|
||||
const std::string full_history_ts_low_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
+178
-29
@@ -28,49 +28,33 @@ namespace ROCKSDB_NAMESPACE {
|
||||
// TODO(icanadi) Mock out everything else:
|
||||
// 1. VersionSet
|
||||
// 2. Memtable
|
||||
class FlushJobTest : public testing::Test {
|
||||
public:
|
||||
FlushJobTest()
|
||||
class FlushJobTestBase : public testing::Test {
|
||||
protected:
|
||||
FlushJobTestBase(std::string dbname, const Comparator* ucmp)
|
||||
: env_(Env::Default()),
|
||||
fs_(std::make_shared<LegacyFileSystemWrapper>(env_)),
|
||||
dbname_(test::PerThreadDBPath("flush_job_test")),
|
||||
dbname_(std::move(dbname)),
|
||||
ucmp_(ucmp),
|
||||
options_(),
|
||||
db_options_(options_),
|
||||
column_family_names_({kDefaultColumnFamilyName, "foo", "bar"}),
|
||||
table_cache_(NewLRUCache(50000, 16)),
|
||||
write_buffer_manager_(db_options_.db_write_buffer_size),
|
||||
shutting_down_(false),
|
||||
mock_table_factory_(new mock::MockTableFactory()) {
|
||||
EXPECT_OK(env_->CreateDirIfMissing(dbname_));
|
||||
db_options_.db_paths.emplace_back(dbname_,
|
||||
std::numeric_limits<uint64_t>::max());
|
||||
db_options_.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
// TODO(icanadi) Remove this once we mock out VersionSet
|
||||
NewDB();
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
cf_options_.table_factory = mock_table_factory_;
|
||||
for (const auto& cf_name : column_family_names_) {
|
||||
column_families.emplace_back(cf_name, cf_options_);
|
||||
}
|
||||
mock_table_factory_(new mock::MockTableFactory()) {}
|
||||
|
||||
db_options_.env = env_;
|
||||
db_options_.fs = fs_;
|
||||
versions_.reset(
|
||||
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr));
|
||||
EXPECT_OK(versions_->Recover(column_families, false));
|
||||
virtual ~FlushJobTestBase() {
|
||||
if (getenv("KEEP_DB")) {
|
||||
fprintf(stdout, "db is still in %s\n", dbname_.c_str());
|
||||
} else {
|
||||
EXPECT_OK(DestroyDir(env_, dbname_));
|
||||
}
|
||||
}
|
||||
|
||||
void NewDB() {
|
||||
SetIdentityFile(env_, dbname_);
|
||||
VersionEdit new_db;
|
||||
if (db_options_.write_dbid_to_manifest) {
|
||||
DBImpl* impl = new DBImpl(DBOptions(), dbname_);
|
||||
std::string db_id;
|
||||
impl->GetDbIdentityFromIdentityFile(&db_id);
|
||||
new_db.SetDBId(db_id);
|
||||
}
|
||||
|
||||
new_db.SetLogNumber(0);
|
||||
new_db.SetNextFile(2);
|
||||
new_db.SetLastSequence(0);
|
||||
@@ -82,6 +66,7 @@ class FlushJobTest : public testing::Test {
|
||||
VersionEdit new_cf;
|
||||
new_cf.AddColumnFamily(column_family_names_[i]);
|
||||
new_cf.SetColumnFamily(cf_id++);
|
||||
new_cf.SetComparatorName(ucmp_->Name());
|
||||
new_cf.SetLogNumber(0);
|
||||
new_cf.SetNextFile(2);
|
||||
new_cf.SetLastSequence(last_seq++);
|
||||
@@ -114,9 +99,37 @@ class FlushJobTest : public testing::Test {
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
void SetUp() override {
|
||||
EXPECT_OK(env_->CreateDirIfMissing(dbname_));
|
||||
|
||||
// TODO(icanadi) Remove this once we mock out VersionSet
|
||||
NewDB();
|
||||
|
||||
db_options_.env = env_;
|
||||
db_options_.fs = fs_;
|
||||
db_options_.db_paths.emplace_back(dbname_,
|
||||
std::numeric_limits<uint64_t>::max());
|
||||
db_options_.statistics = CreateDBStatistics();
|
||||
|
||||
cf_options_.comparator = ucmp_;
|
||||
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
cf_options_.table_factory = mock_table_factory_;
|
||||
for (const auto& cf_name : column_family_names_) {
|
||||
column_families.emplace_back(cf_name, cf_options_);
|
||||
}
|
||||
|
||||
versions_.reset(
|
||||
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr));
|
||||
EXPECT_OK(versions_->Recover(column_families, false));
|
||||
}
|
||||
|
||||
Env* env_;
|
||||
std::shared_ptr<FileSystem> fs_;
|
||||
std::string dbname_;
|
||||
const Comparator* const ucmp_;
|
||||
EnvOptions env_options_;
|
||||
Options options_;
|
||||
ImmutableDBOptions db_options_;
|
||||
@@ -131,6 +144,13 @@ class FlushJobTest : public testing::Test {
|
||||
std::shared_ptr<mock::MockTableFactory> mock_table_factory_;
|
||||
};
|
||||
|
||||
class FlushJobTest : public FlushJobTestBase {
|
||||
public:
|
||||
FlushJobTest()
|
||||
: FlushJobTestBase(test::PerThreadDBPath("flush_job_test"),
|
||||
BytewiseComparator()) {}
|
||||
};
|
||||
|
||||
TEST_F(FlushJobTest, Empty) {
|
||||
JobContext job_context(0);
|
||||
auto cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
@@ -487,6 +507,135 @@ TEST_F(FlushJobTest, Snapshots) {
|
||||
job_context.Clean();
|
||||
}
|
||||
|
||||
class FlushJobTimestampTest : public FlushJobTestBase {
|
||||
public:
|
||||
FlushJobTimestampTest()
|
||||
: FlushJobTestBase(test::PerThreadDBPath("flush_job_ts_gc_test"),
|
||||
test::ComparatorWithU64Ts()) {}
|
||||
|
||||
void AddKeyValueToMemtable(MemTable* memtable, std::string key, uint64_t ts,
|
||||
SequenceNumber seq, ValueType value_type,
|
||||
Slice value) {
|
||||
std::string key_str(std::move(key));
|
||||
PutFixed64(&key_str, ts);
|
||||
memtable->Add(seq, value_type, key_str, value);
|
||||
}
|
||||
|
||||
protected:
|
||||
static constexpr uint64_t kStartTs = 10;
|
||||
static constexpr SequenceNumber kStartSeq = 0;
|
||||
SequenceNumber curr_seq_{kStartSeq};
|
||||
std::atomic<uint64_t> curr_ts_{kStartTs};
|
||||
};
|
||||
|
||||
TEST_F(FlushJobTimestampTest, AllKeysExpired) {
|
||||
ColumnFamilyData* cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
autovector<MemTable*> to_delete;
|
||||
|
||||
{
|
||||
MemTable* new_mem = cfd->ConstructNewMemtable(
|
||||
*cfd->GetLatestMutableCFOptions(), kMaxSequenceNumber);
|
||||
new_mem->Ref();
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
uint64_t ts = curr_ts_.fetch_add(1);
|
||||
SequenceNumber seq = (curr_seq_++);
|
||||
AddKeyValueToMemtable(new_mem, test::EncodeInt(0), ts, seq,
|
||||
ValueType::kTypeValue, "0_value");
|
||||
}
|
||||
uint64_t ts = curr_ts_.fetch_add(1);
|
||||
SequenceNumber seq = (curr_seq_++);
|
||||
AddKeyValueToMemtable(new_mem, test::EncodeInt(0), ts, seq,
|
||||
ValueType::kTypeDeletionWithTimestamp, "");
|
||||
cfd->imm()->Add(new_mem, &to_delete);
|
||||
}
|
||||
|
||||
std::vector<SequenceNumber> snapshots;
|
||||
constexpr SnapshotChecker* const snapshot_checker = nullptr;
|
||||
JobContext job_context(0);
|
||||
EventLogger event_logger(db_options_.info_log.get());
|
||||
std::string full_history_ts_low;
|
||||
PutFixed64(&full_history_ts_low, std::numeric_limits<uint64_t>::max());
|
||||
FlushJob flush_job(
|
||||
dbname_, cfd, db_options_, *cfd->GetLatestMutableCFOptions(),
|
||||
nullptr /* memtable_id */, env_options_, versions_.get(), &mutex_,
|
||||
&shutting_down_, snapshots, kMaxSequenceNumber, snapshot_checker,
|
||||
&job_context, nullptr, nullptr, nullptr, kNoCompression,
|
||||
db_options_.statistics.get(), &event_logger, true,
|
||||
true /* sync_output_directory */, true /* write_manifest */,
|
||||
Env::Priority::USER, nullptr /*IOTracer*/, /*db_id=*/"",
|
||||
/*db_session_id=*/"", full_history_ts_low);
|
||||
|
||||
FileMetaData fmeta;
|
||||
mutex_.Lock();
|
||||
flush_job.PickMemTable();
|
||||
ASSERT_OK(flush_job.Run(/*prep_tracker=*/nullptr, &fmeta));
|
||||
mutex_.Unlock();
|
||||
|
||||
{
|
||||
std::string key = test::EncodeInt(0);
|
||||
key.append(test::EncodeInt(curr_ts_.load(std::memory_order_relaxed) - 1));
|
||||
InternalKey ikey(key, curr_seq_ - 1, ValueType::kTypeDeletionWithTimestamp);
|
||||
ASSERT_EQ(ikey.Encode(), fmeta.smallest.Encode());
|
||||
ASSERT_EQ(ikey.Encode(), fmeta.largest.Encode());
|
||||
}
|
||||
|
||||
job_context.Clean();
|
||||
ASSERT_TRUE(to_delete.empty());
|
||||
}
|
||||
|
||||
TEST_F(FlushJobTimestampTest, NoKeyExpired) {
|
||||
ColumnFamilyData* cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
autovector<MemTable*> to_delete;
|
||||
|
||||
{
|
||||
MemTable* new_mem = cfd->ConstructNewMemtable(
|
||||
*cfd->GetLatestMutableCFOptions(), kMaxSequenceNumber);
|
||||
new_mem->Ref();
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
uint64_t ts = curr_ts_.fetch_add(1);
|
||||
SequenceNumber seq = (curr_seq_++);
|
||||
AddKeyValueToMemtable(new_mem, test::EncodeInt(0), ts, seq,
|
||||
ValueType::kTypeValue, "0_value");
|
||||
}
|
||||
cfd->imm()->Add(new_mem, &to_delete);
|
||||
}
|
||||
|
||||
std::vector<SequenceNumber> snapshots;
|
||||
SnapshotChecker* const snapshot_checker = nullptr;
|
||||
JobContext job_context(0);
|
||||
EventLogger event_logger(db_options_.info_log.get());
|
||||
std::string full_history_ts_low;
|
||||
PutFixed64(&full_history_ts_low, 0);
|
||||
FlushJob flush_job(
|
||||
dbname_, cfd, db_options_, *cfd->GetLatestMutableCFOptions(),
|
||||
nullptr /* memtable_id */, env_options_, versions_.get(), &mutex_,
|
||||
&shutting_down_, snapshots, kMaxSequenceNumber, snapshot_checker,
|
||||
&job_context, nullptr, nullptr, nullptr, kNoCompression,
|
||||
db_options_.statistics.get(), &event_logger, true,
|
||||
true /* sync_output_directory */, true /* write_manifest */,
|
||||
Env::Priority::USER, nullptr /*IOTracer*/, /*db_id=*/"",
|
||||
/*db_session_id=*/"", full_history_ts_low);
|
||||
|
||||
FileMetaData fmeta;
|
||||
mutex_.Lock();
|
||||
flush_job.PickMemTable();
|
||||
ASSERT_OK(flush_job.Run(/*prep_tracker=*/nullptr, &fmeta));
|
||||
mutex_.Unlock();
|
||||
|
||||
{
|
||||
std::string ukey = test::EncodeInt(0);
|
||||
std::string smallest_key =
|
||||
ukey + test::EncodeInt(curr_ts_.load(std::memory_order_relaxed) - 1);
|
||||
std::string largest_key = ukey + test::EncodeInt(kStartTs);
|
||||
InternalKey smallest(smallest_key, curr_seq_ - 1, ValueType::kTypeValue);
|
||||
InternalKey largest(largest_key, kStartSeq, ValueType::kTypeValue);
|
||||
ASSERT_EQ(smallest.Encode(), fmeta.smallest.Encode());
|
||||
ASSERT_EQ(largest.Encode(), fmeta.largest.Encode());
|
||||
}
|
||||
job_context.Clean();
|
||||
ASSERT_TRUE(to_delete.empty());
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -662,10 +662,9 @@ void ForwardIterator::RebuildIterators(bool refresh_sv) {
|
||||
read_options_, sv_->current->version_set()->LastSequence()));
|
||||
range_del_agg.AddTombstones(std::move(range_del_iter));
|
||||
// Always return Status::OK().
|
||||
assert(
|
||||
sv_->imm
|
||||
->AddRangeTombstoneIterators(read_options_, &arena_, &range_del_agg)
|
||||
.ok());
|
||||
Status temp_s = sv_->imm->AddRangeTombstoneIterators(read_options_, &arena_,
|
||||
&range_del_agg);
|
||||
assert(temp_s.ok());
|
||||
}
|
||||
has_iter_trimmed_for_upper_bound_ = false;
|
||||
|
||||
@@ -728,10 +727,9 @@ void ForwardIterator::RenewIterators() {
|
||||
read_options_, sv_->current->version_set()->LastSequence()));
|
||||
range_del_agg.AddTombstones(std::move(range_del_iter));
|
||||
// Always return Status::OK().
|
||||
assert(
|
||||
svnew->imm
|
||||
->AddRangeTombstoneIterators(read_options_, &arena_, &range_del_agg)
|
||||
.ok());
|
||||
Status temp_s = svnew->imm->AddRangeTombstoneIterators(
|
||||
read_options_, &arena_, &range_del_agg);
|
||||
assert(temp_s.ok());
|
||||
}
|
||||
|
||||
const auto* vstorage = sv_->current->storage_info();
|
||||
|
||||
@@ -252,15 +252,21 @@ Status ImportColumnFamilyJob::GetIngestedFileInfo(
|
||||
|
||||
// Get first (smallest) key from file
|
||||
iter->SeekToFirst();
|
||||
if (ParseInternalKey(iter->key(), &key) != Status::OK()) {
|
||||
return Status::Corruption("external file have corrupted keys");
|
||||
Status pik_status =
|
||||
ParseInternalKey(iter->key(), &key, db_options_.allow_data_in_errors);
|
||||
if (!pik_status.ok()) {
|
||||
return Status::Corruption("Corrupted Key in external file. ",
|
||||
pik_status.getState());
|
||||
}
|
||||
file_to_import->smallest_internal_key.SetFrom(key);
|
||||
|
||||
// Get last (largest) key from file
|
||||
iter->SeekToLast();
|
||||
if (ParseInternalKey(iter->key(), &key) != Status::OK()) {
|
||||
return Status::Corruption("external file have corrupted keys");
|
||||
pik_status =
|
||||
ParseInternalKey(iter->key(), &key, db_options_.allow_data_in_errors);
|
||||
if (!pik_status.ok()) {
|
||||
return Status::Corruption("Corrupted Key in external file. ",
|
||||
pik_status.getState());
|
||||
}
|
||||
file_to_import->largest_internal_key.SetFrom(key);
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ class ImportColumnFamilyTest : public DBTestBase {
|
||||
ImportColumnFamilyTest()
|
||||
: DBTestBase("/import_column_family_test", /*env_do_fsync=*/true) {
|
||||
sst_files_dir_ = dbname_ + "/sst_files/";
|
||||
DestroyAndRecreateExternalSSTFilesDir();
|
||||
export_files_dir_ = test::PerThreadDBPath(env_, "export");
|
||||
DestroyAndRecreateExternalSSTFilesDir();
|
||||
import_cfh_ = nullptr;
|
||||
import_cfh2_ = nullptr;
|
||||
metadata_ptr_ = nullptr;
|
||||
|
||||
@@ -1406,7 +1406,7 @@ void InternalStats::DumpCFFileHistogram(std::string* value) {
|
||||
<< blob_file_read_latency_.ToString() << '\n';
|
||||
}
|
||||
|
||||
*value = oss.str();
|
||||
value->append(oss.str());
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
@@ -673,6 +673,8 @@ class InternalStats {
|
||||
|
||||
HistogramImpl* GetFileReadHist(int /*level*/) { return nullptr; }
|
||||
|
||||
HistogramImpl* GetBlobFileReadHist() { return nullptr; }
|
||||
|
||||
uint64_t GetBackgroundErrorCount() const { return 0; }
|
||||
|
||||
uint64_t BumpAndGetBackgroundErrorCount() { return 0; }
|
||||
|
||||
+11
-10
@@ -10,7 +10,6 @@
|
||||
#include "db/version_set.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "file/filename.h"
|
||||
#include "logging/logging.h"
|
||||
#include "memtable/hash_linklist_rep.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "rocksdb/cache.h"
|
||||
@@ -676,7 +675,7 @@ class TableFileCreationListener : public EventListener {
|
||||
public:
|
||||
class TestEnv : public EnvWrapper {
|
||||
public:
|
||||
TestEnv() : EnvWrapper(Env::Default()) {}
|
||||
explicit TestEnv(Env* t) : EnvWrapper(t) {}
|
||||
|
||||
void SetStatus(Status s) { status_ = s; }
|
||||
|
||||
@@ -688,7 +687,7 @@ class TableFileCreationListener : public EventListener {
|
||||
return status_;
|
||||
}
|
||||
}
|
||||
return Env::Default()->NewWritableFile(fname, result, options);
|
||||
return target()->NewWritableFile(fname, result, options);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -766,7 +765,6 @@ class TableFileCreationListener : public EventListener {
|
||||
}
|
||||
}
|
||||
|
||||
TestEnv test_env;
|
||||
int started_[2];
|
||||
int finished_[2];
|
||||
int failure_[2];
|
||||
@@ -775,9 +773,11 @@ class TableFileCreationListener : public EventListener {
|
||||
TEST_F(EventListenerTest, TableFileCreationListenersTest) {
|
||||
auto listener = std::make_shared<TableFileCreationListener>();
|
||||
Options options;
|
||||
std::unique_ptr<TableFileCreationListener::TestEnv> test_env(
|
||||
new TableFileCreationListener::TestEnv(CurrentOptions().env));
|
||||
options.create_if_missing = true;
|
||||
options.listeners.push_back(listener);
|
||||
options.env = &listener->test_env;
|
||||
options.env = test_env.get();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put("foo", "aaa"));
|
||||
@@ -785,13 +785,12 @@ TEST_F(EventListenerTest, TableFileCreationListenersTest) {
|
||||
ASSERT_OK(Flush());
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
listener->CheckAndResetCounters(1, 1, 0, 0, 0, 0);
|
||||
|
||||
ASSERT_OK(Put("foo", "aaa1"));
|
||||
ASSERT_OK(Put("bar", "bbb1"));
|
||||
listener->test_env.SetStatus(Status::NotSupported("not supported"));
|
||||
test_env->SetStatus(Status::NotSupported("not supported"));
|
||||
ASSERT_NOK(Flush());
|
||||
listener->CheckAndResetCounters(1, 1, 1, 0, 0, 0);
|
||||
listener->test_env.SetStatus(Status::OK());
|
||||
test_env->SetStatus(Status::OK());
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("foo", "aaa2"));
|
||||
@@ -809,10 +808,11 @@ TEST_F(EventListenerTest, TableFileCreationListenersTest) {
|
||||
ASSERT_OK(Put("foo", "aaa3"));
|
||||
ASSERT_OK(Put("bar", "bbb3"));
|
||||
ASSERT_OK(Flush());
|
||||
listener->test_env.SetStatus(Status::NotSupported("not supported"));
|
||||
test_env->SetStatus(Status::NotSupported("not supported"));
|
||||
dbfull()->CompactRange(CompactRangeOptions(), &kRangeStart, &kRangeEnd);
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
listener->CheckAndResetCounters(1, 1, 0, 1, 1, 1);
|
||||
Close();
|
||||
}
|
||||
|
||||
class MemTableSealedListener : public EventListener {
|
||||
@@ -833,6 +833,7 @@ public:
|
||||
TEST_F(EventListenerTest, MemTableSealedListenerTest) {
|
||||
auto listener = std::make_shared<MemTableSealedListener>();
|
||||
Options options;
|
||||
options.env = CurrentOptions().env;
|
||||
options.create_if_missing = true;
|
||||
options.listeners.push_back(listener);
|
||||
DestroyAndReopen(options);
|
||||
@@ -1066,7 +1067,7 @@ TEST_F(EventListenerTest, OnFileOperationTest) {
|
||||
TestFileOperationListener* listener = new TestFileOperationListener();
|
||||
options.listeners.emplace_back(listener);
|
||||
|
||||
options.use_direct_io_for_flush_and_compaction = true;
|
||||
options.use_direct_io_for_flush_and_compaction = false;
|
||||
Status s = TryReopen(options);
|
||||
if (s.IsInvalidArgument()) {
|
||||
options.use_direct_io_for_flush_and_compaction = false;
|
||||
|
||||
+41
-16
@@ -119,16 +119,26 @@ bool Reader::ReadRecord(Slice* record, std::string* scratch,
|
||||
break;
|
||||
|
||||
case kBadHeader:
|
||||
if (wal_recovery_mode == WALRecoveryMode::kAbsoluteConsistency) {
|
||||
// in clean shutdown we don't expect any error in the log files
|
||||
if (wal_recovery_mode == WALRecoveryMode::kAbsoluteConsistency ||
|
||||
wal_recovery_mode == WALRecoveryMode::kPointInTimeRecovery) {
|
||||
// In clean shutdown we don't expect any error in the log files.
|
||||
// In point-in-time recovery an incomplete record at the end could
|
||||
// produce a hole in the recovered data. Report an error here, which
|
||||
// higher layers can choose to ignore when it's provable there is no
|
||||
// hole.
|
||||
ReportCorruption(drop_size, "truncated header");
|
||||
}
|
||||
FALLTHROUGH_INTENDED;
|
||||
|
||||
case kEof:
|
||||
if (in_fragmented_record) {
|
||||
if (wal_recovery_mode == WALRecoveryMode::kAbsoluteConsistency) {
|
||||
// in clean shutdown we don't expect any error in the log files
|
||||
if (wal_recovery_mode == WALRecoveryMode::kAbsoluteConsistency ||
|
||||
wal_recovery_mode == WALRecoveryMode::kPointInTimeRecovery) {
|
||||
// In clean shutdown we don't expect any error in the log files.
|
||||
// In point-in-time recovery an incomplete record at the end could
|
||||
// produce a hole in the recovered data. Report an error here, which
|
||||
// higher layers can choose to ignore when it's provable there is no
|
||||
// hole.
|
||||
ReportCorruption(scratch->size(), "error reading trailing data");
|
||||
}
|
||||
// This can be caused by the writer dying immediately after
|
||||
@@ -142,8 +152,13 @@ bool Reader::ReadRecord(Slice* record, std::string* scratch,
|
||||
if (wal_recovery_mode != WALRecoveryMode::kSkipAnyCorruptedRecords) {
|
||||
// Treat a record from a previous instance of the log as EOF.
|
||||
if (in_fragmented_record) {
|
||||
if (wal_recovery_mode == WALRecoveryMode::kAbsoluteConsistency) {
|
||||
// in clean shutdown we don't expect any error in the log files
|
||||
if (wal_recovery_mode == WALRecoveryMode::kAbsoluteConsistency ||
|
||||
wal_recovery_mode == WALRecoveryMode::kPointInTimeRecovery) {
|
||||
// In clean shutdown we don't expect any error in the log files.
|
||||
// In point-in-time recovery an incomplete record at the end could
|
||||
// produce a hole in the recovered data. Report an error here,
|
||||
// which higher layers can choose to ignore when it's provable
|
||||
// there is no hole.
|
||||
ReportCorruption(scratch->size(), "error reading trailing data");
|
||||
}
|
||||
// This can be caused by the writer dying immediately after
|
||||
@@ -164,6 +179,20 @@ bool Reader::ReadRecord(Slice* record, std::string* scratch,
|
||||
break;
|
||||
|
||||
case kBadRecordLen:
|
||||
if (eof_) {
|
||||
if (wal_recovery_mode == WALRecoveryMode::kAbsoluteConsistency ||
|
||||
wal_recovery_mode == WALRecoveryMode::kPointInTimeRecovery) {
|
||||
// In clean shutdown we don't expect any error in the log files.
|
||||
// In point-in-time recovery an incomplete record at the end could
|
||||
// produce a hole in the recovered data. Report an error here, which
|
||||
// higher layers can choose to ignore when it's provable there is no
|
||||
// hole.
|
||||
ReportCorruption(drop_size, "truncated record body");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
FALLTHROUGH_INTENDED;
|
||||
|
||||
case kBadRecordChecksum:
|
||||
if (recycled_ &&
|
||||
wal_recovery_mode ==
|
||||
@@ -355,18 +384,14 @@ unsigned int Reader::ReadPhysicalRecord(Slice* result, size_t* drop_size) {
|
||||
}
|
||||
}
|
||||
if (header_size + length > buffer_.size()) {
|
||||
assert(buffer_.size() >= static_cast<size_t>(header_size));
|
||||
*drop_size = buffer_.size();
|
||||
buffer_.clear();
|
||||
if (!eof_) {
|
||||
return kBadRecordLen;
|
||||
}
|
||||
// If the end of the file has been reached without reading |length|
|
||||
// bytes of payload, assume the writer died in the middle of writing the
|
||||
// record. Don't report a corruption unless requested.
|
||||
if (*drop_size) {
|
||||
return kBadHeader;
|
||||
}
|
||||
return kEof;
|
||||
// If the end of the read has been reached without seeing
|
||||
// `header_size + length` bytes of payload, report a corruption. The
|
||||
// higher layers can decide how to handle it based on the recovery mode,
|
||||
// whether this occurred at EOF, whether this is the final WAL, etc.
|
||||
return kBadRecordLen;
|
||||
}
|
||||
|
||||
if (type == kZeroType && length == 0) {
|
||||
|
||||
+2
-4
@@ -465,7 +465,7 @@ TEST_P(LogTest, BadLengthAtEndIsNotIgnored) {
|
||||
ShrinkSize(1);
|
||||
ASSERT_EQ("EOF", Read(WALRecoveryMode::kAbsoluteConsistency));
|
||||
ASSERT_GT(DroppedBytes(), 0U);
|
||||
ASSERT_EQ("OK", MatchError("Corruption: truncated header"));
|
||||
ASSERT_EQ("OK", MatchError("Corruption: truncated record body"));
|
||||
}
|
||||
|
||||
TEST_P(LogTest, ChecksumMismatch) {
|
||||
@@ -573,9 +573,7 @@ TEST_P(LogTest, PartialLastIsNotIgnored) {
|
||||
ShrinkSize(1);
|
||||
ASSERT_EQ("EOF", Read(WALRecoveryMode::kAbsoluteConsistency));
|
||||
ASSERT_GT(DroppedBytes(), 0U);
|
||||
ASSERT_EQ("OK", MatchError(
|
||||
"Corruption: truncated headerCorruption: "
|
||||
"error reading trailing data"));
|
||||
ASSERT_EQ("OK", MatchError("Corruption: truncated record body"));
|
||||
}
|
||||
|
||||
TEST_P(LogTest, ErrorJoinsRecords) {
|
||||
|
||||
+8
-5
@@ -541,7 +541,8 @@ bool MemTable::Add(SequenceNumber s, ValueType type,
|
||||
|
||||
if (bloom_filter_ && prefix_extractor_ &&
|
||||
prefix_extractor_->InDomain(key)) {
|
||||
bloom_filter_->Add(prefix_extractor_->Transform(key));
|
||||
bloom_filter_->Add(
|
||||
prefix_extractor_->Transform(StripTimestampFromUserKey(key, ts_sz)));
|
||||
}
|
||||
if (bloom_filter_ && moptions_.memtable_whole_key_filtering) {
|
||||
bloom_filter_->Add(StripTimestampFromUserKey(key, ts_sz));
|
||||
@@ -908,16 +909,18 @@ void MemTable::MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
int num_keys = 0;
|
||||
for (auto iter = temp_range.begin(); iter != temp_range.end(); ++iter) {
|
||||
if (!prefix_extractor_) {
|
||||
keys[num_keys++] = &iter->ukey;
|
||||
} else if (prefix_extractor_->InDomain(iter->ukey)) {
|
||||
prefixes.emplace_back(prefix_extractor_->Transform(iter->ukey));
|
||||
keys[num_keys++] = &iter->ukey_without_ts;
|
||||
} else if (prefix_extractor_->InDomain(iter->ukey_without_ts)) {
|
||||
prefixes.emplace_back(
|
||||
prefix_extractor_->Transform(iter->ukey_without_ts));
|
||||
keys[num_keys++] = &prefixes.back();
|
||||
}
|
||||
}
|
||||
bloom_filter_->MayContain(num_keys, &keys[0], &may_match[0]);
|
||||
int idx = 0;
|
||||
for (auto iter = temp_range.begin(); iter != temp_range.end(); ++iter) {
|
||||
if (prefix_extractor_ && !prefix_extractor_->InDomain(iter->ukey)) {
|
||||
if (prefix_extractor_ &&
|
||||
!prefix_extractor_->InDomain(iter->ukey_without_ts)) {
|
||||
PERF_COUNTER_ADD(bloom_memtable_hit_count, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
+138
-79
@@ -473,91 +473,41 @@ Status MemTableList::TryInstallMemtableFlushResults(
|
||||
|
||||
// TODO(myabandeh): Not sure how batch_count could be 0 here.
|
||||
if (batch_count > 0) {
|
||||
uint64_t min_wal_number_to_keep = 0;
|
||||
if (vset->db_options()->allow_2pc) {
|
||||
assert(edit_list.size() > 0);
|
||||
min_wal_number_to_keep = PrecomputeMinLogNumberToKeep2PC(
|
||||
vset, *cfd, edit_list, memtables_to_flush, prep_tracker);
|
||||
// We piggyback the information of earliest log file to keep in the
|
||||
// manifest entry for the last file flushed.
|
||||
edit_list.back()->SetMinLogNumberToKeep(PrecomputeMinLogNumberToKeep(
|
||||
vset, *cfd, edit_list, memtables_to_flush, prep_tracker));
|
||||
edit_list.back()->SetMinLogNumberToKeep(min_wal_number_to_keep);
|
||||
} else {
|
||||
min_wal_number_to_keep =
|
||||
PrecomputeMinLogNumberToKeepNon2PC(vset, *cfd, edit_list);
|
||||
}
|
||||
|
||||
std::unique_ptr<VersionEdit> wal_deletion;
|
||||
if (vset->db_options()->track_and_verify_wals_in_manifest) {
|
||||
const auto& wals = vset->GetWalSet().GetWals();
|
||||
if (!wals.empty() && min_wal_number_to_keep > wals.begin()->first) {
|
||||
wal_deletion.reset(new VersionEdit);
|
||||
wal_deletion->DeleteWalsBefore(min_wal_number_to_keep);
|
||||
edit_list.push_back(wal_deletion.get());
|
||||
}
|
||||
}
|
||||
|
||||
const auto manifest_write_cb = [this, cfd, batch_count, log_buffer,
|
||||
to_delete, mu](const Status& status) {
|
||||
RemoveMemTablesOrRestoreFlags(status, cfd, batch_count, log_buffer,
|
||||
to_delete, mu);
|
||||
};
|
||||
|
||||
// this can release and reacquire the mutex.
|
||||
s = vset->LogAndApply(cfd, mutable_cf_options, edit_list, mu,
|
||||
db_directory);
|
||||
db_directory, /*new_descriptor_log=*/false,
|
||||
/*column_family_options=*/nullptr,
|
||||
manifest_write_cb);
|
||||
*io_s = vset->io_status();
|
||||
|
||||
// we will be changing the version in the next code path,
|
||||
// so we better create a new one, since versions are immutable
|
||||
InstallNewVersion();
|
||||
|
||||
// All the later memtables that have the same filenum
|
||||
// are part of the same batch. They can be committed now.
|
||||
uint64_t mem_id = 1; // how many memtables have been flushed.
|
||||
|
||||
// commit new state only if the column family is NOT dropped.
|
||||
// The reason is as follows (refer to
|
||||
// ColumnFamilyTest.FlushAndDropRaceCondition).
|
||||
// If the column family is dropped, then according to LogAndApply, its
|
||||
// corresponding flush operation is NOT written to the MANIFEST. This
|
||||
// means the DB is not aware of the L0 files generated from the flush.
|
||||
// By committing the new state, we remove the memtable from the memtable
|
||||
// list. Creating an iterator on this column family will not be able to
|
||||
// read full data since the memtable is removed, and the DB is not aware
|
||||
// of the L0 files, causing MergingIterator unable to build child
|
||||
// iterators. RocksDB contract requires that the iterator can be created
|
||||
// on a dropped column family, and we must be able to
|
||||
// read full data as long as column family handle is not deleted, even if
|
||||
// the column family is dropped.
|
||||
if (s.ok() && !cfd->IsDropped()) { // commit new state
|
||||
while (batch_count-- > 0) {
|
||||
MemTable* m = current_->memlist_.back();
|
||||
if (m->edit_.GetBlobFileAdditions().empty()) {
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[%s] Level-0 commit table #%" PRIu64
|
||||
": memtable #%" PRIu64 " done",
|
||||
cfd->GetName().c_str(), m->file_number_, mem_id);
|
||||
} else {
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[%s] Level-0 commit table #%" PRIu64
|
||||
" (+%zu blob files)"
|
||||
": memtable #%" PRIu64 " done",
|
||||
cfd->GetName().c_str(), m->file_number_,
|
||||
m->edit_.GetBlobFileAdditions().size(), mem_id);
|
||||
}
|
||||
|
||||
assert(m->file_number_ > 0);
|
||||
current_->Remove(m, to_delete);
|
||||
UpdateCachedValuesFromMemTableListVersion();
|
||||
ResetTrimHistoryNeeded();
|
||||
++mem_id;
|
||||
}
|
||||
} else {
|
||||
for (auto it = current_->memlist_.rbegin(); batch_count-- > 0; ++it) {
|
||||
MemTable* m = *it;
|
||||
// commit failed. setup state so that we can flush again.
|
||||
if (m->edit_.GetBlobFileAdditions().empty()) {
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"Level-0 commit table #%" PRIu64
|
||||
": memtable #%" PRIu64 " failed",
|
||||
m->file_number_, mem_id);
|
||||
} else {
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"Level-0 commit table #%" PRIu64
|
||||
" (+%zu blob files)"
|
||||
": memtable #%" PRIu64 " failed",
|
||||
m->file_number_,
|
||||
m->edit_.GetBlobFileAdditions().size(), mem_id);
|
||||
}
|
||||
|
||||
m->flush_completed_ = false;
|
||||
m->flush_in_progress_ = false;
|
||||
m->edit_.Clear();
|
||||
num_flush_not_started_++;
|
||||
m->file_number_ = 0;
|
||||
imm_flush_needed.store(true, std::memory_order_release);
|
||||
++mem_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
commit_in_progress_ = false;
|
||||
@@ -642,6 +592,87 @@ void MemTableList::InstallNewVersion() {
|
||||
}
|
||||
}
|
||||
|
||||
void MemTableList::RemoveMemTablesOrRestoreFlags(
|
||||
const Status& s, ColumnFamilyData* cfd, size_t batch_count,
|
||||
LogBuffer* log_buffer, autovector<MemTable*>* to_delete,
|
||||
InstrumentedMutex* mu) {
|
||||
assert(mu);
|
||||
mu->AssertHeld();
|
||||
assert(to_delete);
|
||||
// we will be changing the version in the next code path,
|
||||
// so we better create a new one, since versions are immutable
|
||||
InstallNewVersion();
|
||||
|
||||
// All the later memtables that have the same filenum
|
||||
// are part of the same batch. They can be committed now.
|
||||
uint64_t mem_id = 1; // how many memtables have been flushed.
|
||||
|
||||
// commit new state only if the column family is NOT dropped.
|
||||
// The reason is as follows (refer to
|
||||
// ColumnFamilyTest.FlushAndDropRaceCondition).
|
||||
// If the column family is dropped, then according to LogAndApply, its
|
||||
// corresponding flush operation is NOT written to the MANIFEST. This
|
||||
// means the DB is not aware of the L0 files generated from the flush.
|
||||
// By committing the new state, we remove the memtable from the memtable
|
||||
// list. Creating an iterator on this column family will not be able to
|
||||
// read full data since the memtable is removed, and the DB is not aware
|
||||
// of the L0 files, causing MergingIterator unable to build child
|
||||
// iterators. RocksDB contract requires that the iterator can be created
|
||||
// on a dropped column family, and we must be able to
|
||||
// read full data as long as column family handle is not deleted, even if
|
||||
// the column family is dropped.
|
||||
if (s.ok() && !cfd->IsDropped()) { // commit new state
|
||||
while (batch_count-- > 0) {
|
||||
MemTable* m = current_->memlist_.back();
|
||||
if (m->edit_.GetBlobFileAdditions().empty()) {
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[%s] Level-0 commit table #%" PRIu64
|
||||
": memtable #%" PRIu64 " done",
|
||||
cfd->GetName().c_str(), m->file_number_, mem_id);
|
||||
} else {
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[%s] Level-0 commit table #%" PRIu64
|
||||
" (+%zu blob files)"
|
||||
": memtable #%" PRIu64 " done",
|
||||
cfd->GetName().c_str(), m->file_number_,
|
||||
m->edit_.GetBlobFileAdditions().size(), mem_id);
|
||||
}
|
||||
|
||||
assert(m->file_number_ > 0);
|
||||
current_->Remove(m, to_delete);
|
||||
UpdateCachedValuesFromMemTableListVersion();
|
||||
ResetTrimHistoryNeeded();
|
||||
++mem_id;
|
||||
}
|
||||
} else {
|
||||
for (auto it = current_->memlist_.rbegin(); batch_count-- > 0; ++it) {
|
||||
MemTable* m = *it;
|
||||
// commit failed. setup state so that we can flush again.
|
||||
if (m->edit_.GetBlobFileAdditions().empty()) {
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"Level-0 commit table #%" PRIu64 ": memtable #%" PRIu64
|
||||
" failed",
|
||||
m->file_number_, mem_id);
|
||||
} else {
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"Level-0 commit table #%" PRIu64
|
||||
" (+%zu blob files)"
|
||||
": memtable #%" PRIu64 " failed",
|
||||
m->file_number_,
|
||||
m->edit_.GetBlobFileAdditions().size(), mem_id);
|
||||
}
|
||||
|
||||
m->flush_completed_ = false;
|
||||
m->flush_in_progress_ = false;
|
||||
m->edit_.Clear();
|
||||
num_flush_not_started_++;
|
||||
m->file_number_ = 0;
|
||||
imm_flush_needed.store(true, std::memory_order_release);
|
||||
++mem_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t MemTableList::PrecomputeMinLogContainingPrepSection(
|
||||
const autovector<MemTable*>& memtables_to_flush) {
|
||||
uint64_t min_log = 0;
|
||||
@@ -688,6 +719,10 @@ Status InstallMemtableAtomicFlushResults(
|
||||
if (imm_lists != nullptr) {
|
||||
assert(imm_lists->size() == num);
|
||||
}
|
||||
if (num == 0) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
for (size_t k = 0; k != num; ++k) {
|
||||
#ifndef NDEBUG
|
||||
const auto* imm =
|
||||
@@ -716,12 +751,36 @@ Status InstallMemtableAtomicFlushResults(
|
||||
++num_entries;
|
||||
edit_lists.emplace_back(edits);
|
||||
}
|
||||
|
||||
// TODO(cc): after https://github.com/facebook/rocksdb/pull/7570, handle 2pc
|
||||
// here.
|
||||
std::unique_ptr<VersionEdit> wal_deletion;
|
||||
if (vset->db_options()->track_and_verify_wals_in_manifest) {
|
||||
uint64_t min_wal_number_to_keep =
|
||||
PrecomputeMinLogNumberToKeepNon2PC(vset, *cfds[0], edit_lists[0]);
|
||||
for (size_t i = 1; i < cfds.size(); i++) {
|
||||
min_wal_number_to_keep = std::min(
|
||||
min_wal_number_to_keep,
|
||||
PrecomputeMinLogNumberToKeepNon2PC(vset, *cfds[i], edit_lists[i]));
|
||||
}
|
||||
const auto& wals = vset->GetWalSet().GetWals();
|
||||
if (!wals.empty() && min_wal_number_to_keep > wals.begin()->first) {
|
||||
wal_deletion.reset(new VersionEdit);
|
||||
wal_deletion->DeleteWalsBefore(min_wal_number_to_keep);
|
||||
edit_lists.back().push_back(wal_deletion.get());
|
||||
++num_entries;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the version edits as an atomic group if the number of version edits
|
||||
// exceeds 1.
|
||||
if (cfds.size() > 1) {
|
||||
for (auto& edits : edit_lists) {
|
||||
assert(edits.size() == 1);
|
||||
edits[0]->MarkAtomicGroup(--num_entries);
|
||||
for (size_t i = 0; i < edit_lists.size(); i++) {
|
||||
assert((edit_lists[i].size() == 1) ||
|
||||
((edit_lists[i].size() == 2) && (i == edit_lists.size() - 1)));
|
||||
for (auto& e : edit_lists[i]) {
|
||||
e->MarkAtomicGroup(--num_entries);
|
||||
}
|
||||
}
|
||||
assert(0 == num_entries);
|
||||
}
|
||||
|
||||
@@ -389,6 +389,13 @@ class MemTableList {
|
||||
// DB mutex held
|
||||
void InstallNewVersion();
|
||||
|
||||
// DB mutex held
|
||||
// Called after writing to MANIFEST
|
||||
void RemoveMemTablesOrRestoreFlags(const Status& s, ColumnFamilyData* cfd,
|
||||
size_t batch_count, LogBuffer* log_buffer,
|
||||
autovector<MemTable*>* to_delete,
|
||||
InstrumentedMutex* mu);
|
||||
|
||||
const int min_write_buffer_number_to_merge_;
|
||||
|
||||
MemTableListVersion* current_;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user