mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 11:02:36 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
986b8b9f20 | ||
|
|
35b30e4c3d | ||
|
|
4d706dc4db | ||
|
|
b375f596ec | ||
|
|
54bb0183e0 | ||
|
|
8d08b843eb | ||
|
|
8767167425 | ||
|
|
0d13952bcc | ||
|
|
b5fed4a509 | ||
|
|
366a61aee6 | ||
|
|
519f2a41fb | ||
|
|
e81393e81e | ||
|
|
4d9f9733b2 | ||
|
|
4155087746 | ||
|
|
543191f2ea | ||
|
|
d7567d5eee | ||
|
|
ef0c3f08fa | ||
|
|
2e514e4b4a | ||
|
|
0836a2b26d | ||
|
|
55590012ae | ||
|
|
a80e3f6c57 | ||
|
|
0bb3a26d89 | ||
|
|
d5bc30befa | ||
|
|
42266939ab | ||
|
|
933ee295f4 | ||
|
|
9135a61ec6 | ||
|
|
2296c624fa | ||
|
|
84af7cf0bd | ||
|
|
018eede679 | ||
|
|
25d4379cc8 | ||
|
|
2fd850c7eb | ||
|
|
9ded9f789f | ||
|
|
f3aef8cad7 | ||
|
|
50b0879d50 | ||
|
|
c9d8e6a5bf | ||
|
|
dc576af0fd | ||
|
|
6e3429b8a6 | ||
|
|
261e9be7b3 | ||
|
|
648fe25bc0 | ||
|
|
90e160733e | ||
|
|
d010b02e86 | ||
|
|
b2fe14817e | ||
|
|
d2daa10afc | ||
|
|
d367b34cc9 | ||
|
|
5e2906c288 | ||
|
|
20b4f1356e | ||
|
|
4bd5aa4f55 | ||
|
|
5b11f5a3a2 | ||
|
|
98ab2d80fa | ||
|
|
f1aa17c73f | ||
|
|
77d160ef47 | ||
|
|
8a9cfd5292 | ||
|
|
ee0829ba76 | ||
|
|
229a6e5f55 | ||
|
|
51d7e6a49e | ||
|
|
21a12363e1 | ||
|
|
2dc63c8911 | ||
|
|
1d5bddbc58 | ||
|
|
8e949116f7 | ||
|
|
141b872bd4 | ||
|
|
40b618f234 | ||
|
|
c13569e41d | ||
|
|
97f6f475bc | ||
|
|
5fbea87859 | ||
|
|
10fd05e394 | ||
|
|
b00fa5597e | ||
|
|
7bebd3036d | ||
|
|
be879cc56b | ||
|
|
d98a9cfb27 | ||
|
|
3c4cc6c2cc | ||
|
|
63ed868840 | ||
|
|
02443dd93f | ||
|
|
2cfe53ec05 | ||
|
|
01e2d33565 | ||
|
|
8b566964b8 | ||
|
|
6b4315ee8b | ||
|
|
35a0250293 | ||
|
|
fce04587b8 | ||
|
|
719f5511f6 | ||
|
|
7ea6e724fa | ||
|
|
6c564e2e17 | ||
|
|
1c871a4d86 | ||
|
|
bd655b9af3 | ||
|
|
49da91ec09 |
No files matched your search
+71
-3
@@ -69,7 +69,7 @@ commands:
|
||||
command: |
|
||||
mkdir build
|
||||
cd build
|
||||
& $Env:CMAKE_BIN -G "$Env:CMAKE_GENERATOR" -DCMAKE_BUILD_TYPE=Debug -DOPTDBG=1 -DPORTABLE=1 -DSNAPPY=1 -DJNI=1 ..
|
||||
& $Env:CMAKE_BIN -G "$Env:CMAKE_GENERATOR" -DCMAKE_BUILD_TYPE=Debug -DOPTDBG=1 -DPORTABLE="$Env:CMAKE_PORTABLE" -DSNAPPY=1 -DJNI=1 ..
|
||||
cd ..
|
||||
echo "Building with VS version: $Env:CMAKE_GENERATOR"
|
||||
msbuild.exe build/rocksdb.sln -maxCpuCount -property:Configuration=Debug -property:Platform=x64
|
||||
@@ -78,6 +78,11 @@ commands:
|
||||
shell: powershell.exe
|
||||
command: |
|
||||
build_tools\run_ci_db_test.ps1 -SuiteRun arena_test,db_basic_test,db_test,db_test2,db_merge_operand_test,bloom_test,c_test,coding_test,crc32c_test,dynamic_bloom_test,env_basic_test,env_test,hash_test,random_test -Concurrency 16
|
||||
- run:
|
||||
name: "Test RocksJava"
|
||||
command: |
|
||||
cd build\java
|
||||
& $Env:CTEST_BIN -C Debug -j 16
|
||||
pre-steps-macos:
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -105,6 +110,15 @@ commands:
|
||||
path: /tmp/core_dumps
|
||||
when: on_fail
|
||||
|
||||
post-pmd-steps:
|
||||
steps:
|
||||
- store_artifacts:
|
||||
path: /home/circleci/project/java/target/pmd.xml
|
||||
when: on_fail
|
||||
- store_artifacts:
|
||||
path: /home/circleci/project/java/target/site
|
||||
when: on_fail
|
||||
|
||||
upgrade-cmake:
|
||||
steps:
|
||||
- run:
|
||||
@@ -127,6 +141,13 @@ commands:
|
||||
command: |
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install gflags
|
||||
|
||||
install-maven:
|
||||
steps:
|
||||
- run:
|
||||
name: Install maven
|
||||
command: |
|
||||
sudo apt-get update -y && sudo apt-get install -y maven
|
||||
|
||||
setup-folly:
|
||||
steps:
|
||||
- run:
|
||||
@@ -206,6 +227,9 @@ executors:
|
||||
# $ docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -it zjay437/rocksdb:0.5 bash
|
||||
# option `--cap-add=SYS_PTRACE --security-opt seccomp=unconfined` is used to enable gdb to attach an existing process
|
||||
- image: zjay437/rocksdb:0.6
|
||||
linux-java-docker:
|
||||
docker:
|
||||
- image: evolvedbinary/rocksjava:centos6_x64-be
|
||||
|
||||
jobs:
|
||||
build-macos:
|
||||
@@ -542,6 +566,23 @@ jobs:
|
||||
no_output_timeout: 100m
|
||||
- post-steps
|
||||
|
||||
build-windows-vs2022-avx2:
|
||||
executor:
|
||||
name: win/server-2022
|
||||
size: 2xlarge
|
||||
environment:
|
||||
THIRDPARTY_HOME: C:/Users/circleci/thirdparty
|
||||
CMAKE_HOME: C:/Program Files/CMake
|
||||
CMAKE_BIN: C:/Program Files/CMake/bin/cmake.exe
|
||||
CTEST_BIN: C:/Program Files/CMake/bin/ctest.exe
|
||||
SNAPPY_HOME: C:/Users/circleci/thirdparty/snappy-1.1.8
|
||||
SNAPPY_INCLUDE: C:/Users/circleci/thirdparty/snappy-1.1.8;C:/Users/circleci/thirdparty/snappy-1.1.8/build
|
||||
SNAPPY_LIB_DEBUG: C:/Users/circleci/thirdparty/snappy-1.1.8/build/Debug/snappy.lib
|
||||
CMAKE_GENERATOR: Visual Studio 17 2022
|
||||
CMAKE_PORTABLE: AVX2
|
||||
steps:
|
||||
- windows-build-steps
|
||||
|
||||
build-windows-vs2022:
|
||||
executor:
|
||||
name: win/server-2022
|
||||
@@ -550,10 +591,12 @@ jobs:
|
||||
THIRDPARTY_HOME: C:/Users/circleci/thirdparty
|
||||
CMAKE_HOME: C:/Program Files/CMake
|
||||
CMAKE_BIN: C:/Program Files/CMake/bin/cmake.exe
|
||||
CTEST_BIN: C:/Program Files/CMake/bin/ctest.exe
|
||||
SNAPPY_HOME: C:/Users/circleci/thirdparty/snappy-1.1.8
|
||||
SNAPPY_INCLUDE: C:/Users/circleci/thirdparty/snappy-1.1.8;C:/Users/circleci/thirdparty/snappy-1.1.8/build
|
||||
SNAPPY_LIB_DEBUG: C:/Users/circleci/thirdparty/snappy-1.1.8/build/Debug/snappy.lib
|
||||
CMAKE_GENERATOR: Visual Studio 17 2022
|
||||
CMAKE_PORTABLE: 1
|
||||
steps:
|
||||
- windows-build-steps
|
||||
|
||||
@@ -565,10 +608,12 @@ jobs:
|
||||
THIRDPARTY_HOME: C:/Users/circleci/thirdparty
|
||||
CMAKE_HOME: C:/Program Files/CMake
|
||||
CMAKE_BIN: C:/Program Files/CMake/bin/cmake.exe
|
||||
CTEST_BIN: C:/Program Files/CMake/bin/ctest.exe
|
||||
SNAPPY_HOME: C:/Users/circleci/thirdparty/snappy-1.1.8
|
||||
SNAPPY_INCLUDE: C:/Users/circleci/thirdparty/snappy-1.1.8;C:/Users/circleci/thirdparty/snappy-1.1.8/build
|
||||
SNAPPY_LIB_DEBUG: C:/Users/circleci/thirdparty/snappy-1.1.8/build/Debug/snappy.lib
|
||||
CMAKE_GENERATOR: Visual Studio 16 2019
|
||||
CMAKE_PORTABLE: 1
|
||||
steps:
|
||||
- windows-build-steps
|
||||
|
||||
@@ -589,8 +634,29 @@ jobs:
|
||||
command: make V=1 J=8 -j8 jtest
|
||||
- post-steps
|
||||
|
||||
build-linux-java-pmd:
|
||||
machine:
|
||||
image: ubuntu-2004:202111-02
|
||||
resource_class: large
|
||||
environment:
|
||||
JAVA_HOME: /usr/lib/jvm/java-8-openjdk-amd64
|
||||
steps:
|
||||
- install-maven
|
||||
- pre-steps
|
||||
- run:
|
||||
name: "Set Java Environment"
|
||||
command: |
|
||||
echo "JAVA_HOME=${JAVA_HOME}"
|
||||
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> $BASH_ENV
|
||||
which java && java -version
|
||||
which javac && javac -version
|
||||
- run:
|
||||
name: "PMD RocksDBJava"
|
||||
command: make V=1 J=8 -j8 jpmd
|
||||
- post-pmd-steps
|
||||
|
||||
build-linux-java-static:
|
||||
executor: linux-docker
|
||||
executor: linux-java-docker
|
||||
resource_class: large
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -603,7 +669,7 @@ jobs:
|
||||
which javac && javac -version
|
||||
- run:
|
||||
name: "Build RocksDBJava Static Library"
|
||||
command: make V=1 J=8 -j8 rocksdbjavastatic
|
||||
command: scl enable devtoolset-7 'make V=1 J=8 -j8 rocksdbjavastatic'
|
||||
- post-steps
|
||||
|
||||
build-macos-java:
|
||||
@@ -843,6 +909,7 @@ workflows:
|
||||
- build-linux-mini-crashtest
|
||||
jobs-windows:
|
||||
jobs:
|
||||
- build-windows-vs2022-avx2
|
||||
- build-windows-vs2022
|
||||
- build-windows-vs2019
|
||||
- build-cmake-mingw
|
||||
@@ -853,6 +920,7 @@ workflows:
|
||||
- build-macos-java
|
||||
- build-macos-java-static
|
||||
- build-macos-java-static-universal
|
||||
- build-linux-java-pmd
|
||||
jobs-macos:
|
||||
jobs:
|
||||
- build-macos
|
||||
|
||||
+77
@@ -1,6 +1,83 @@
|
||||
# Rocksdb Change Log
|
||||
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
|
||||
|
||||
## 8.8.1 (11/17/2023)
|
||||
### Bug fixes
|
||||
* Make the cache memory reservation accounting in Tiered cache (primary and compressed secondary cache) more accurate to avoid over/under charging the secondary cache.
|
||||
* Allow increasing the compressed_secondary_ratio in the Tiered cache after setting it to 0 to disable.
|
||||
|
||||
## 8.8.0 (10/23/2023)
|
||||
### New Features
|
||||
* Introduce AttributeGroup by adding the first AttributeGroup support API, MultiGetEntity(). Through the use of Column Families, AttributeGroup enables users to logically group wide-column entities. More APIs to support AttributeGroup will come soon, including GetEntity, PutEntity, and others.
|
||||
* Added new tickers `rocksdb.fifo.{max.size|ttl}.compactions` to count FIFO compactions that drop files for different reasons
|
||||
* Add an experimental offpeak duration awareness by setting `DBOptions::daily_offpeak_time_utc` in "HH:mm-HH:mm" format. This information will be used for resource optimization in the future
|
||||
* Users can now change the max bytes granted in a single refill period (i.e, burst) during runtime by `SetSingleBurstBytes()` for RocksDB rate limiter
|
||||
|
||||
### Public API Changes
|
||||
* The default value of `DBOptions::fail_if_options_file_error` changed from `false` to `true`. Operations that set in-memory options (e.g., `DB::Open*()`, `DB::SetOptions()`, `DB::CreateColumnFamily*()`, and `DB::DropColumnFamily()`) but fail to persist the change will now return a non-OK `Status` by default.
|
||||
* Add new Cache APIs GetSecondaryCacheCapacity() and GetSecondaryCachePinnedUsage() to return the configured capacity, and cache reservation charged to the secondary cache.
|
||||
|
||||
### Behavior Changes
|
||||
* For non direct IO, eliminate the file system prefetching attempt for compaction read when `Options::compaction_readahead_size` is 0
|
||||
* During a write stop, writes now block on in-progress recovery attempts
|
||||
* Deleting stale files upon recovery are delegated to SstFileManger if available so they can be rate limited.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a bug in auto_readahead_size where first_internal_key of index blocks wasn't copied properly resulting in corruption error when first_internal_key was used for comparison.
|
||||
* Fixed a bug where compaction read under non direct IO still falls back to RocksDB internal prefetching after file system's prefetching returns non-OK status other than `Status::NotSupported()`
|
||||
* Add bounds check in WBWIIteratorImpl and make BaseDeltaIterator, WriteUnpreparedTxn and WritePreparedTxn respect the upper bound and lower bound in ReadOption. See 11680.
|
||||
* Fixed the handling of wide-column base values in the `max_successive_merges` logic.
|
||||
* Fixed a rare race bug involving a concurrent combination of Create/DropColumnFamily and/or Set(DB)Options that could lead to inconsistency between (a) the DB's reported options state, (b) the DB options in effect, and (c) the latest persisted OPTIONS file.
|
||||
* Fixed a possible underflow when computing the compressed secondary cache share of memory reservations while updating the compressed secondary to total block cache ratio.
|
||||
|
||||
### Performance Improvements
|
||||
* Improved the I/O efficiency of DB::Open a new DB with `create_missing_column_families=true` and many column families.
|
||||
|
||||
## 8.7.0 (09/22/2023)
|
||||
### New Features
|
||||
* Added an experimental new "automatic" variant of HyperClockCache that does not require a prior estimate of the average size of cache entries. This variant is activated when HyperClockCacheOptions::estimated\_entry\_charge = 0 and has essentially the same concurrency benefits as the existing HyperClockCache.
|
||||
* Add a new statistic `COMPACTION_CPU_TOTAL_TIME` that records cumulative compaction cpu time. This ticker is updated regularly while a compaction is running.
|
||||
* Add `GetEntity()` API for ReadOnly DB and Secondary DB.
|
||||
* Add a new iterator API `Iterator::Refresh(const Snapshot *)` that allows iterator to be refreshed while using the input snapshot to read.
|
||||
* Added a new read option `merge_operand_count_threshold`. When the number of merge operands applied during a successful point lookup exceeds this threshold, the query will return a special OK status with a new subcode `kMergeOperandThresholdExceeded`. Applications might use this signal to take action to reduce the number of merge operands for the affected key(s), for example by running a compaction.
|
||||
* For `NewRibbonFilterPolicy()`, made the `bloom_before_level` option mutable through the Configurable interface and the SetOptions API, allowing dynamic switching between all-Bloom and all-Ribbon configurations, and configurations in between. See comments on `NewRibbonFilterPolicy()`
|
||||
* RocksDB now allows the block cache to be stacked on top of a compressed secondary cache and a non-volatile secondary cache, thus creating a three-tier cache. To set it up, use the `NewTieredCache()` API in rocksdb/cache.h..
|
||||
* Added a new wide-column aware full merge API called `FullMergeV3` to `MergeOperator`. `FullMergeV3` supports wide columns both as base value and merge result, which enables the application to perform more general transformations during merges. For backward compatibility, the default implementation implements the earlier logic of applying the merge operation to the default column of any wide-column entities. Specifically, if there is no base value or the base value is a plain key-value, the default implementation falls back to `FullMergeV2`. If the base value is a wide-column entity, the default implementation invokes `FullMergeV2` to perform the merge on the default column, and leaves any other columns unchanged.
|
||||
* Add wide column support to ldb commands (scan, dump, idump, dump_wal) and sst_dump tool's scan command
|
||||
|
||||
### Public API Changes
|
||||
* Expose more information about input files used in table creation (if any) in `CompactionFilter::Context`. See `CompactionFilter::Context::input_start_level`,`CompactionFilter::Context::input_table_properties` for more.
|
||||
* `Options::compaction_readahead_size` 's default value is changed from 0 to 2MB.
|
||||
* When using LZ4 compression, the `acceleration` parameter is configurable by setting the negated value in `CompressionOptions::level`. For example, `CompressionOptions::level=-10` will set `acceleration=10`
|
||||
* The `NewTieredCache` API has been changed to take the total cache capacity (inclusive of both the primary and the compressed secondary cache) and the ratio of total capacity to allocate to the compressed cache. These are specified in `TieredCacheOptions`. Any capacity specified in `LRUCacheOptions`, `HyperClockCacheOptions` and `CompressedSecondaryCacheOptions` is ignored. A new API, `UpdateTieredCache` is provided to dynamically update the total capacity, ratio of compressed cache, and admission policy.
|
||||
* The `NewTieredVolatileCache()` API in rocksdb/cache.h has been renamed to `NewTieredCache()`.
|
||||
|
||||
### Behavior Changes
|
||||
* Compaction read performance will regress when `Options::compaction_readahead_size` is explicitly set to 0
|
||||
* Universal size amp compaction will conditionally exclude some of the newest L0 files when selecting input with a small negative impact to size amp. This is to prevent a large number of L0 files from being locked by a size amp compaction, potentially leading to write stop with a few more flushes.
|
||||
* Change ldb scan command delimiter from ':' to '==>'.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a bug where if there is an error reading from offset 0 of a file from L1+ and that the file is not the first file in the sorted run, data can be lost in compaction and read/scan can return incorrect results.
|
||||
* Fix a bug where iterator may return incorrect result for DeleteRange() users if there was an error reading from a file.
|
||||
* Fix a bug with atomic_flush=true that can cause DB to stuck after a flush fails (#11872).
|
||||
* Fix a bug where RocksDB (with atomic_flush=false) can delete output SST files of pending flushes when a previous concurrent flush fails (#11865). This can result in DB entering read-only state with error message like `IO error: No such file or directory: While open a file for random read: /tmp/rocksdbtest-501/db_flush_test_87732_4230653031040984171/000013.sst`.
|
||||
* Fix an assertion fault during seek with async_io when readahead trimming is enabled.
|
||||
* When the compressed secondary cache capacity is reduced to 0, it should be completely disabled. Before this fix, inserts and lookups would still go to the backing `LRUCache` before returning, thus incurring locking overhead. With this fix, inserts and lookups are no-ops and do not add any overhead.
|
||||
* Updating the tiered cache (cache allocated using NewTieredCache()) by calling SetCapacity() on it was not working properly. The initial creation would set the primary cache capacity to the combined primary and compressed secondary cache capacity. But SetCapacity() would just set the primary cache capacity. With this fix, the user always specifies the total budget and compressed secondary cache ratio on creation. Subsequently, SetCapacity() will distribute the new capacity across the two caches by the same ratio.
|
||||
* Fixed a bug in `MultiGet` for cleaning up SuperVersion acquired with locking db mutex.
|
||||
* Fix a bug where row cache can falsely return kNotFound even though row cache entry is hit.
|
||||
* Fixed a race condition in `GenericRateLimiter` that could cause it to stop granting requests
|
||||
* Fix a bug (Issue #10257) where DB can hang after write stall since no compaction is scheduled (#11764).
|
||||
* Add a fix for async_io where during seek, when reading a block for seeking a target key in a file without any readahead, the iterator aligned the read on a page boundary and reading more than necessary. This increased the storage read bandwidth usage.
|
||||
* Fix an issue in sst dump tool to handle bounds specified for data with user-defined timestamps.
|
||||
* When auto_readahead_size is enabled, update readahead upper bound during readahead trimming when reseek changes iterate_upper_bound dynamically.
|
||||
* Fixed a bug where `rocksdb.file.read.verify.file.checksums.micros` is not populated
|
||||
|
||||
### Performance Improvements
|
||||
* Added additional improvements in tuning readahead_size during Scans when auto_readahead_size is enabled. However it's not supported with Iterator::Prev operation and will return NotSupported error.
|
||||
* During async_io, the Seek happens in 2 phases. Phase 1 starts an asynchronous read on a block cache miss, and phase 2 waits for it to complete and finishes the seek. In both phases, it tries to lookup the block cache for the data block first before looking in the prefetch buffer. It's optimized by doing the block cache lookup only in the first phase that would save some CPU.
|
||||
|
||||
## 8.6.0 (08/18/2023)
|
||||
### New Features
|
||||
* Added enhanced data integrity checking on SST files with new format_version=6. Performance impact is very small or negligible. Previously if SST data was misplaced or re-arranged by the storage layer, it could pass block checksum with higher than 1 in 4 billion probability. With format_version=6, block checksums depend on what file they are in and location within the file. This way, misplaced SST data is no more likely to pass checksum verification than randomly corrupted data. Also in format_version=6, SST footers are checksum-protected.
|
||||
|
||||
+2
-2
@@ -17,7 +17,7 @@ There are few options when compiling RocksDB:
|
||||
* `make check` will compile and run all the unit tests. `make check` will compile RocksDB in debug mode.
|
||||
|
||||
* `make all` will compile our static library, and all our tools and unit tests. Our tools
|
||||
depend on gflags. You will need to have gflags installed to run `make all`. This will compile RocksDB in debug mode. Don't
|
||||
depend on gflags 2.2.0 or newer. You will need to have gflags installed to run `make all`. This will compile RocksDB in debug mode. Don't
|
||||
use binaries compiled by `make all` in production.
|
||||
|
||||
* By default the binary we produce is optimized for the CPU you're compiling on
|
||||
@@ -77,7 +77,7 @@ most processors made since roughly 2013.
|
||||
|
||||
git clone https://github.com/gflags/gflags.git
|
||||
cd gflags
|
||||
git checkout v2.0
|
||||
git checkout v2.2.0
|
||||
./configure && make && sudo make install
|
||||
|
||||
**Notice**: Once installed, please add the include path for gflags to your `CPATH` environment variable and the
|
||||
|
||||
@@ -2094,8 +2094,8 @@ ROCKSDB_JAVADOCS_JAR = rocksdbjni-$(ROCKSDB_JAVA_VERSION)-javadoc.jar
|
||||
ROCKSDB_SOURCES_JAR = rocksdbjni-$(ROCKSDB_JAVA_VERSION)-sources.jar
|
||||
SHA256_CMD = sha256sum
|
||||
|
||||
ZLIB_VER ?= 1.2.13
|
||||
ZLIB_SHA256 ?= b3a24de97a8fdbc835b9833169501030b8977031bcb54b3b3ac13740f846ab30
|
||||
ZLIB_VER ?= 1.3
|
||||
ZLIB_SHA256 ?= ff0ba4c292013dbc27530b3a81e1f9a813cd39de01ca5e0f8bf355702efa593e
|
||||
ZLIB_DOWNLOAD_BASE ?= http://zlib.net
|
||||
BZIP2_VER ?= 1.0.8
|
||||
BZIP2_SHA256 ?= ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269
|
||||
@@ -2103,11 +2103,11 @@ BZIP2_DOWNLOAD_BASE ?= http://sourceware.org/pub/bzip2
|
||||
SNAPPY_VER ?= 1.1.8
|
||||
SNAPPY_SHA256 ?= 16b677f07832a612b0836178db7f374e414f94657c138e6993cbfc5dcc58651f
|
||||
SNAPPY_DOWNLOAD_BASE ?= https://github.com/google/snappy/archive
|
||||
LZ4_VER ?= 1.9.3
|
||||
LZ4_SHA256 ?= 030644df4611007ff7dc962d981f390361e6c97a34e5cbc393ddfbe019ffe2c1
|
||||
LZ4_VER ?= 1.9.4
|
||||
LZ4_SHA256 ?= 0b0e3aa07c8c063ddf40b082bdf7e37a1562bda40a0ff5272957f3e987e0e54b
|
||||
LZ4_DOWNLOAD_BASE ?= https://github.com/lz4/lz4/archive
|
||||
ZSTD_VER ?= 1.4.9
|
||||
ZSTD_SHA256 ?= acf714d98e3db7b876e5b540cbf6dee298f60eb3c0723104f6d3f065cd60d6a8
|
||||
ZSTD_VER ?= 1.5.5
|
||||
ZSTD_SHA256 ?= 98e9c3d949d1b924e28e01eccb7deed865eefebf25c2f21c702e5cd5b63b85e1
|
||||
ZSTD_DOWNLOAD_BASE ?= https://github.com/facebook/zstd/archive
|
||||
CURL_SSL_OPTS ?= --tlsv1
|
||||
|
||||
@@ -2419,6 +2419,9 @@ jtest_run:
|
||||
jtest: rocksdbjava
|
||||
cd java;$(MAKE) sample test
|
||||
|
||||
jpmd: rocksdbjava rocksdbjavageneratepom
|
||||
cd java;$(MAKE) pmd
|
||||
|
||||
jdb_bench:
|
||||
cd java;$(MAKE) db_bench;
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
# --> DO NOT EDIT MANUALLY <--
|
||||
# This file is a Facebook-specific integration for buck builds, so can
|
||||
# only be validated by Facebook employees.
|
||||
#
|
||||
# @noautodeps @nocodemods
|
||||
load("//rocks/buckifier:defs.bzl", "cpp_library_wrapper","rocks_cpp_library_wrapper","cpp_binary_wrapper","cpp_unittest_wrapper","fancy_bench_wrapper","add_c_test_wrapper")
|
||||
|
||||
|
||||
@@ -395,6 +393,7 @@ rocks_cpp_library_wrapper(name="rocksdb_stress_lib", srcs=[
|
||||
"db_stress_tool/db_stress_stat.cc",
|
||||
"db_stress_tool/db_stress_test_base.cc",
|
||||
"db_stress_tool/db_stress_tool.cc",
|
||||
"db_stress_tool/db_stress_wide_merge_operator.cc",
|
||||
"db_stress_tool/expected_state.cc",
|
||||
"db_stress_tool/expected_value.cc",
|
||||
"db_stress_tool/multi_ops_txns_stress.cc",
|
||||
@@ -407,6 +406,8 @@ rocks_cpp_library_wrapper(name="rocksdb_stress_lib", srcs=[
|
||||
|
||||
cpp_binary_wrapper(name="db_stress", srcs=["db_stress_tool/db_stress.cc"], deps=[":rocksdb_stress_lib"], extra_preprocessor_flags=[], extra_bench_libs=False)
|
||||
|
||||
cpp_binary_wrapper(name="cache_bench", srcs=["cache/cache_bench.cc"], deps=[":rocksdb_cache_bench_tools_lib"], extra_preprocessor_flags=[], extra_bench_libs=False)
|
||||
|
||||
cpp_binary_wrapper(name="ribbon_bench", srcs=["microbench/ribbon_bench.cc"], deps=[], extra_preprocessor_flags=[], extra_bench_libs=True)
|
||||
|
||||
cpp_binary_wrapper(name="db_basic_bench", srcs=["microbench/db_basic_bench.cc"], deps=[], extra_preprocessor_flags=[], extra_bench_libs=True)
|
||||
|
||||
@@ -197,6 +197,10 @@ def generate_targets(repo_path, deps_map):
|
||||
TARGETS.add_binary(
|
||||
"db_stress", ["db_stress_tool/db_stress.cc"], [":rocksdb_stress_lib"]
|
||||
)
|
||||
# cache_bench binary
|
||||
TARGETS.add_binary(
|
||||
"cache_bench", ["cache/cache_bench.cc"], [":rocksdb_cache_bench_tools_lib"]
|
||||
)
|
||||
# bench binaries
|
||||
for src in src_mk.get("MICROBENCH_SOURCES", []):
|
||||
name = src.rsplit("/", 1)[1].split(".")[0] if "/" in src else src.split(".")[0]
|
||||
|
||||
@@ -6,8 +6,6 @@ rocksdb_target_header_template = """# This file \100generated by:
|
||||
# --> DO NOT EDIT MANUALLY <--
|
||||
# This file is a Facebook-specific integration for buck builds, so can
|
||||
# only be validated by Facebook employees.
|
||||
#
|
||||
# @noautodeps @nocodemods
|
||||
load("//rocks/buckifier:defs.bzl", "cpp_library_wrapper","rocks_cpp_library_wrapper","cpp_binary_wrapper","cpp_unittest_wrapper","fancy_bench_wrapper","add_c_test_wrapper")
|
||||
|
||||
"""
|
||||
|
||||
@@ -674,7 +674,7 @@ else
|
||||
fi
|
||||
|
||||
if [[ "${PLATFORM}" == "OS_MACOSX" ]]; then
|
||||
# For portability compile for macOS 10.14 or newer
|
||||
# For portability compile for macOS 10.14 (2018) or newer
|
||||
COMMON_FLAGS="$COMMON_FLAGS -mmacosx-version-min=10.14"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -mmacosx-version-min=10.14"
|
||||
# -mmacosx-version-min must come first here.
|
||||
|
||||
Vendored
+75
-43
@@ -1790,6 +1790,8 @@ inline bool MatchAndRef(const UniqueId64x2* hashed_key, const ClockHandle& h,
|
||||
}
|
||||
}
|
||||
|
||||
// Assumes a chain rewrite lock prevents concurrent modification of
|
||||
// these chain pointers
|
||||
void UpgradeShiftsOnRange(AutoHyperClockTable::HandleImpl* arr,
|
||||
size_t& frontier, uint64_t stop_before_or_new_tail,
|
||||
int old_shift, int new_shift) {
|
||||
@@ -1805,7 +1807,6 @@ void UpgradeShiftsOnRange(AutoHyperClockTable::HandleImpl* arr,
|
||||
if (next_with_shift == stop_before_or_new_tail) {
|
||||
// Stopping at entry with pointer matching "stop before"
|
||||
assert(!HandleImpl::IsEnd(next_with_shift));
|
||||
// We need to keep a reference to it also to keep it stable.
|
||||
return;
|
||||
}
|
||||
if (HandleImpl::IsEnd(next_with_shift)) {
|
||||
@@ -1817,8 +1818,7 @@ void UpgradeShiftsOnRange(AutoHyperClockTable::HandleImpl* arr,
|
||||
frontier = SIZE_MAX;
|
||||
return;
|
||||
}
|
||||
// Next is another entry to process, so upgrade and unref and advance
|
||||
// frontier
|
||||
// Next is another entry to process, so upgrade and advance frontier
|
||||
arr[frontier].chain_next_with_shift.fetch_add(1U,
|
||||
std::memory_order_acq_rel);
|
||||
assert(GetShiftFromNextWithShift(next_with_shift + 1) == new_shift);
|
||||
@@ -2015,14 +2015,28 @@ AutoHyperClockTable::AutoHyperClockTable(
|
||||
}
|
||||
|
||||
AutoHyperClockTable::~AutoHyperClockTable() {
|
||||
// Assumes there are no references or active operations on any slot/element
|
||||
// in the table.
|
||||
size_t end = GetTableSize();
|
||||
// As usual, destructor assumes there are no references or active operations
|
||||
// on any slot/element in the table.
|
||||
|
||||
// It's possible that there were not enough Insert() after final concurrent
|
||||
// Grow to ensure length_info_ (published GetTableSize()) is fully up to
|
||||
// date. Probe for first unused slot to ensure we see the whole structure.
|
||||
size_t used_end = GetTableSize();
|
||||
while (used_end < array_.Count() &&
|
||||
array_[used_end].head_next_with_shift.load() !=
|
||||
HandleImpl::kUnusedMarker) {
|
||||
used_end++;
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
std::vector<bool> was_populated(end);
|
||||
std::vector<bool> was_pointed_to(end);
|
||||
for (size_t i = used_end; i < array_.Count(); i++) {
|
||||
assert(array_[i].head_next_with_shift.load() == 0);
|
||||
assert(array_[i].chain_next_with_shift.load() == 0);
|
||||
assert(array_[i].meta.load() == 0);
|
||||
}
|
||||
std::vector<bool> was_populated(used_end);
|
||||
std::vector<bool> was_pointed_to(used_end);
|
||||
#endif
|
||||
for (size_t i = 0; i < end; i++) {
|
||||
for (size_t i = 0; i < used_end; i++) {
|
||||
HandleImpl& h = array_[i];
|
||||
switch (h.meta >> ClockHandle::kStateShift) {
|
||||
case ClockHandle::kStateEmpty:
|
||||
@@ -2061,7 +2075,7 @@ AutoHyperClockTable::~AutoHyperClockTable() {
|
||||
// This check is not perfect, but should detect most reasonable cases
|
||||
// of abandonned or floating entries, etc. (A floating cycle would not
|
||||
// be reported as bad.)
|
||||
for (size_t i = 0; i < end; i++) {
|
||||
for (size_t i = 0; i < used_end; i++) {
|
||||
if (was_populated[i]) {
|
||||
assert(was_pointed_to[i]);
|
||||
} else {
|
||||
@@ -2070,8 +2084,9 @@ AutoHyperClockTable::~AutoHyperClockTable() {
|
||||
}
|
||||
#endif
|
||||
|
||||
// Metadata charging only follows the published table size
|
||||
assert(usage_.load() == 0 ||
|
||||
usage_.load() == size_t{GetTableSize()} * sizeof(HandleImpl));
|
||||
usage_.load() == GetTableSize() * sizeof(HandleImpl));
|
||||
assert(occupancy_ == 0);
|
||||
}
|
||||
|
||||
@@ -2099,8 +2114,8 @@ void AutoHyperClockTable::StartInsert(InsertState& state) {
|
||||
// and a larger limit is used to break cycles should they occur in production.
|
||||
#define CHECK_TOO_MANY_ITERATIONS(i) \
|
||||
{ \
|
||||
assert(i < 0x2000); \
|
||||
if (UNLIKELY(i >= 0x8000)) { \
|
||||
assert(i < 768); \
|
||||
if (UNLIKELY(i >= 4096)) { \
|
||||
std::terminate(); \
|
||||
} \
|
||||
}
|
||||
@@ -2160,12 +2175,19 @@ bool AutoHyperClockTable::Grow(InsertState& state) {
|
||||
bool own = array_[grow_home].head_next_with_shift.compare_exchange_strong(
|
||||
expected_zero, empty_head, std::memory_order_acq_rel);
|
||||
if (own) {
|
||||
assert(array_[grow_home].meta.load(std::memory_order_acquire) == 0);
|
||||
break;
|
||||
} else {
|
||||
// Taken by another thread. Try next slot.
|
||||
assert(expected_zero != 0);
|
||||
}
|
||||
}
|
||||
#ifdef COERCE_CONTEXT_SWITCH
|
||||
// This is useful in reproducing concurrency issues in Grow()
|
||||
while (Random::GetTLSInstance()->OneIn(2)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
#endif
|
||||
// Basically, to implement https://en.wikipedia.org/wiki/Linear_hashing
|
||||
// entries that belong in a new chain starting at grow_home will be
|
||||
// split off from the chain starting at old_home, which is computed here.
|
||||
@@ -2518,7 +2540,7 @@ void AutoHyperClockTable::SplitForGrow(size_t grow_home, size_t old_home,
|
||||
// BHome --------------------New------------> [A1] -Old-> ...
|
||||
// And we need to upgrade as much as we can on the "first" chain
|
||||
// (the one eventually pointing to the other's frontier). This will
|
||||
// also finish off any case in which one of the targer chains will be empty.
|
||||
// also finish off any case in which one of the target chains will be empty.
|
||||
if (chain_frontier_first >= 0) {
|
||||
size_t& first_frontier = chain_frontier_first == 0
|
||||
? /*&*/ zero_chain_frontier
|
||||
@@ -2638,7 +2660,9 @@ void AutoHyperClockTable::PurgeImplLocked(OpData* op_data,
|
||||
int home_shift = GetShiftFromNextWithShift(next_with_shift);
|
||||
(void)home;
|
||||
(void)home_shift;
|
||||
HandleImpl* h = &arr[GetNextFromNextWithShift(next_with_shift)];
|
||||
size_t next = GetNextFromNextWithShift(next_with_shift);
|
||||
assert(next < array_.Count());
|
||||
HandleImpl* h = &arr[next];
|
||||
HandleImpl* prev_to_keep = nullptr;
|
||||
#ifndef NDEBUG
|
||||
uint64_t prev_to_keep_next_with_shift = 0;
|
||||
@@ -2669,8 +2693,7 @@ void AutoHyperClockTable::PurgeImplLocked(OpData* op_data,
|
||||
// Entries for eviction become purgeable
|
||||
purgeable = true;
|
||||
assert((h->meta.load(std::memory_order_acquire) >>
|
||||
ClockHandle::kStateShift) &
|
||||
ClockHandle::kStateOccupiedBit);
|
||||
ClockHandle::kStateShift) == ClockHandle::kStateConstruction);
|
||||
}
|
||||
} else {
|
||||
(void)op_data;
|
||||
@@ -2682,8 +2705,7 @@ void AutoHyperClockTable::PurgeImplLocked(OpData* op_data,
|
||||
|
||||
if (purgeable) {
|
||||
assert((h->meta.load(std::memory_order_acquire) >>
|
||||
ClockHandle::kStateShift) &
|
||||
ClockHandle::kStateOccupiedBit);
|
||||
ClockHandle::kStateShift) == ClockHandle::kStateConstruction);
|
||||
pending_purge = true;
|
||||
} else if (pending_purge) {
|
||||
if (prev_to_keep) {
|
||||
@@ -2703,9 +2725,12 @@ void AutoHyperClockTable::PurgeImplLocked(OpData* op_data,
|
||||
// Can simply restart (GetNewHead() already updated from CAS failure).
|
||||
next_with_shift = rewrite_lock.GetNewHead();
|
||||
assert(!HandleImpl::IsEnd(next_with_shift));
|
||||
h = &arr[GetNextFromNextWithShift(next_with_shift)];
|
||||
next = GetNextFromNextWithShift(next_with_shift);
|
||||
assert(next < array_.Count());
|
||||
h = &arr[next];
|
||||
pending_purge = false;
|
||||
assert(prev_to_keep == nullptr);
|
||||
assert(GetShiftFromNextWithShift(next_with_shift) == home_shift);
|
||||
continue;
|
||||
}
|
||||
pending_purge = false;
|
||||
@@ -2733,7 +2758,9 @@ void AutoHyperClockTable::PurgeImplLocked(OpData* op_data,
|
||||
if (HandleImpl::IsEnd(next_with_shift)) {
|
||||
h = nullptr;
|
||||
} else {
|
||||
h = &arr[GetNextFromNextWithShift(next_with_shift)];
|
||||
next = GetNextFromNextWithShift(next_with_shift);
|
||||
assert(next < array_.Count());
|
||||
h = &arr[next];
|
||||
assert(h != prev_to_keep);
|
||||
}
|
||||
}
|
||||
@@ -3228,7 +3255,7 @@ AutoHyperClockTable::HandleImpl* AutoHyperClockTable::Lookup(
|
||||
// Follow the next and check for full key match, home match, or neither
|
||||
h = &arr[GetNextFromNextWithShift(next_with_shift)];
|
||||
bool full_match_or_unknown = false;
|
||||
if (MatchAndRef(&hashed_key, *h, home_shift, home,
|
||||
if (MatchAndRef(&hashed_key, *h, shift, effective_home,
|
||||
&full_match_or_unknown)) {
|
||||
// Got a read ref on next (h).
|
||||
//
|
||||
@@ -3237,29 +3264,16 @@ AutoHyperClockTable::HandleImpl* AutoHyperClockTable::Lookup(
|
||||
// reinserted it into the same chain, causing us to cycle back in the
|
||||
// same chain and potentially see some entries again if we keep walking.
|
||||
// Newly-inserted entries are inserted before older ones, so we are at
|
||||
// least guaranteed not to miss anything.
|
||||
// * For kIsLookup, this is ok, as it's just a transient, slight hiccup
|
||||
// in performance.
|
||||
// * For kIsRemove, we are careful in overwriting the next pointer. The
|
||||
// replacement value comes from the next pointer on an entry that we
|
||||
// exclusively own. If that entry is still connected to the chain, its
|
||||
// next must be valid for the chain. If it's not still connected to the
|
||||
// chain (e.g. to unblock another thread Grow op), we will either not
|
||||
// find the entry to remove on the chain or the CAS attempt to replace
|
||||
// the appropriate next will fail, in which case we'll try again to find
|
||||
// the removal target on the chain.
|
||||
// * For kIsClockUpdateChain, we essentially have a special case of
|
||||
// kIsRemove, as we only need to remove entries where we have taken
|
||||
// ownership of one for eviction. In rare cases, we might
|
||||
// double-clock-update some entries (ok as long as it's rare).
|
||||
// least guaranteed not to miss anything. Here in Lookup, it's just a
|
||||
// transient, slight hiccup in performance.
|
||||
|
||||
// With new usable read ref, can release old one if applicable
|
||||
if (read_ref_on_chain) {
|
||||
// Pretend we never took the reference.
|
||||
Unref(*read_ref_on_chain);
|
||||
}
|
||||
if (full_match_or_unknown) {
|
||||
// Full match.
|
||||
// Release old read ref on chain if applicable
|
||||
if (read_ref_on_chain) {
|
||||
// Pretend we never took the reference.
|
||||
Unref(*read_ref_on_chain);
|
||||
}
|
||||
// Update the hit bit
|
||||
if (eviction_callback_) {
|
||||
h->meta.fetch_or(uint64_t{1} << ClockHandle::kHitBitShift,
|
||||
@@ -3267,8 +3281,26 @@ AutoHyperClockTable::HandleImpl* AutoHyperClockTable::Lookup(
|
||||
}
|
||||
// All done.
|
||||
return h;
|
||||
} else if (UNLIKELY(shift != home_shift) &&
|
||||
home != BottomNBits(h->hashed_key[1], home_shift)) {
|
||||
// This chain is in a Grow operation and we've landed on an entry
|
||||
// that belongs to the wrong destination chain. We can keep going, but
|
||||
// there's a chance we'll need to backtrack back *before* this entry,
|
||||
// if the Grow finishes before this Lookup. We cannot save this entry
|
||||
// for backtracking because it might soon or already be on the wrong
|
||||
// chain.
|
||||
// NOTE: if we simply backtrack rather than continuing, we would
|
||||
// be in a wait loop (not allowed in Lookup!) until the other thread
|
||||
// finishes its Grow.
|
||||
Unref(*h);
|
||||
} else {
|
||||
// Correct home location, so we are on the right chain
|
||||
// Correct home location, so we are on the right chain.
|
||||
// With new usable read ref, can release old one (if applicable).
|
||||
if (read_ref_on_chain) {
|
||||
// Pretend we never took the reference.
|
||||
Unref(*read_ref_on_chain);
|
||||
}
|
||||
// And keep the new one.
|
||||
read_ref_on_chain = h;
|
||||
}
|
||||
} else {
|
||||
|
||||
Vendored
+4
@@ -33,9 +33,13 @@ std::unique_ptr<SecondaryCacheResultHandle> CompressedSecondaryCache::Lookup(
|
||||
Cache::CreateContext* create_context, bool /*wait*/, bool advise_erase,
|
||||
bool& kept_in_sec_cache) {
|
||||
assert(helper);
|
||||
// This is a minor optimization. Its ok to skip it in TSAN in order to
|
||||
// avoid a false positive.
|
||||
#ifndef __SANITIZE_THREAD__
|
||||
if (disable_cache_) {
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle;
|
||||
kept_in_sec_cache = false;
|
||||
|
||||
+54
-6
@@ -1224,13 +1224,11 @@ TEST_P(CompressedSecCacheTestWithTiered, DynamicUpdate) {
|
||||
ASSERT_OK(sec_cache->GetCapacity(sec_capacity));
|
||||
ASSERT_EQ(sec_capacity, 0);
|
||||
|
||||
ASSERT_NOK(UpdateTieredCache(tiered_cache, -1, 0.3));
|
||||
// Only check usage for LRU cache. HCC shows a 64KB usage for some reason
|
||||
if (std::get<0>(GetParam()) == PrimaryCacheType::kCacheTypeLRU) {
|
||||
ASSERT_EQ(GetCache()->GetUsage(), 0);
|
||||
}
|
||||
ASSERT_OK(UpdateTieredCache(tiered_cache, -1, 0.3));
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, GetCache()->GetUsage(), (30 << 20),
|
||||
GetPercent(30 << 20, 1));
|
||||
ASSERT_OK(sec_cache->GetCapacity(sec_capacity));
|
||||
ASSERT_EQ(sec_capacity, 0);
|
||||
ASSERT_EQ(sec_capacity, (30 << 20));
|
||||
}
|
||||
|
||||
TEST_P(CompressedSecCacheTestWithTiered, DynamicUpdateWithReservation) {
|
||||
@@ -1276,6 +1274,13 @@ TEST_P(CompressedSecCacheTestWithTiered, DynamicUpdateWithReservation) {
|
||||
ASSERT_OK(sec_cache->GetCapacity(sec_capacity));
|
||||
ASSERT_EQ(sec_capacity, (30 << 20));
|
||||
|
||||
ASSERT_OK(tiered_cache->GetSecondaryCacheCapacity(sec_capacity));
|
||||
ASSERT_EQ(sec_capacity, 30 << 20);
|
||||
size_t sec_usage;
|
||||
ASSERT_OK(tiered_cache->GetSecondaryCachePinnedUsage(sec_usage));
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, sec_usage, 3 << 20,
|
||||
GetPercent(3 << 20, 1));
|
||||
|
||||
ASSERT_OK(UpdateTieredCache(tiered_cache, -1, 0.39));
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, GetCache()->GetUsage(), (45 << 20),
|
||||
GetPercent(45 << 20, 1));
|
||||
@@ -1309,6 +1314,49 @@ TEST_P(CompressedSecCacheTestWithTiered, DynamicUpdateWithReservation) {
|
||||
ASSERT_OK(sec_cache->GetCapacity(sec_capacity));
|
||||
ASSERT_EQ(sec_capacity, 0);
|
||||
|
||||
ASSERT_OK(UpdateTieredCache(tiered_cache, -1, 0.3));
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, GetCache()->GetUsage(), (37 << 20),
|
||||
GetPercent(37 << 20, 1));
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, sec_cache->TEST_GetUsage(), (3 << 20),
|
||||
GetPercent(3 << 20, 1));
|
||||
ASSERT_OK(sec_cache->GetCapacity(sec_capacity));
|
||||
ASSERT_EQ(sec_capacity, 30 << 20);
|
||||
|
||||
ASSERT_OK(cache_res_mgr()->UpdateCacheReservation(0));
|
||||
}
|
||||
|
||||
TEST_P(CompressedSecCacheTestWithTiered, ReservationOverCapacity) {
|
||||
CompressedSecondaryCache* sec_cache =
|
||||
reinterpret_cast<CompressedSecondaryCache*>(GetSecondaryCache());
|
||||
std::shared_ptr<Cache> tiered_cache = GetTieredCache();
|
||||
|
||||
ASSERT_OK(cache_res_mgr()->UpdateCacheReservation(110 << 20));
|
||||
// Use EXPECT_PRED3 instead of EXPECT_NEAR to void too many size_t to
|
||||
// double explicit casts
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, GetCache()->GetUsage(), (110 << 20),
|
||||
GetPercent(110 << 20, 1));
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, sec_cache->TEST_GetUsage(), (30 << 20),
|
||||
GetPercent(30 << 20, 1));
|
||||
size_t sec_capacity;
|
||||
ASSERT_OK(sec_cache->GetCapacity(sec_capacity));
|
||||
ASSERT_EQ(sec_capacity, (30 << 20));
|
||||
|
||||
ASSERT_OK(UpdateTieredCache(tiered_cache, -1, 0.39));
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, GetCache()->GetUsage(), (110 << 20),
|
||||
GetPercent(110 << 20, 1));
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, sec_cache->TEST_GetUsage(), (39 << 20),
|
||||
GetPercent(39 << 20, 1));
|
||||
ASSERT_OK(sec_cache->GetCapacity(sec_capacity));
|
||||
ASSERT_EQ(sec_capacity, (39 << 20));
|
||||
|
||||
ASSERT_OK(cache_res_mgr()->UpdateCacheReservation(90 << 20));
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, GetCache()->GetUsage(), (94 << 20),
|
||||
GetPercent(94 << 20, 1));
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, sec_cache->TEST_GetUsage(), (35 << 20),
|
||||
GetPercent(35 << 20, 1));
|
||||
ASSERT_OK(sec_cache->GetCapacity(sec_capacity));
|
||||
ASSERT_EQ(sec_capacity, (39 << 20));
|
||||
|
||||
ASSERT_OK(cache_res_mgr()->UpdateCacheReservation(0));
|
||||
}
|
||||
|
||||
|
||||
Vendored
+137
-43
@@ -5,8 +5,11 @@
|
||||
|
||||
#include "cache/secondary_cache_adapter.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "cache/tiered_secondary_cache.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/cast_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -80,7 +83,10 @@ CacheWithSecondaryAdapter::CacheWithSecondaryAdapter(
|
||||
: CacheWrapper(std::move(target)),
|
||||
secondary_cache_(std::move(secondary_cache)),
|
||||
adm_policy_(adm_policy),
|
||||
distribute_cache_res_(distribute_cache_res) {
|
||||
distribute_cache_res_(distribute_cache_res),
|
||||
placeholder_usage_(0),
|
||||
reserved_usage_(0),
|
||||
sec_reserved_(0) {
|
||||
target_->SetEvictionCallback(
|
||||
[this](const Slice& key, Handle* handle, bool was_hit) {
|
||||
return EvictionHandler(key, handle, was_hit);
|
||||
@@ -109,7 +115,7 @@ CacheWithSecondaryAdapter::~CacheWithSecondaryAdapter() {
|
||||
// use after free
|
||||
target_->SetEvictionCallback({});
|
||||
#ifndef NDEBUG
|
||||
if (distribute_cache_res_ && !ratio_changed_) {
|
||||
if (distribute_cache_res_) {
|
||||
size_t sec_capacity = 0;
|
||||
Status s = secondary_cache_->GetCapacity(sec_capacity);
|
||||
assert(s.ok());
|
||||
@@ -232,18 +238,38 @@ Status CacheWithSecondaryAdapter::Insert(const Slice& key, ObjectPtr value,
|
||||
const Slice& compressed_value,
|
||||
CompressionType type) {
|
||||
Status s = target_->Insert(key, value, helper, charge, handle, priority);
|
||||
if (s.ok() && value == nullptr && distribute_cache_res_) {
|
||||
size_t sec_charge = static_cast<size_t>(charge * (sec_cache_res_ratio_));
|
||||
s = secondary_cache_->Deflate(sec_charge);
|
||||
assert(s.ok());
|
||||
s = pri_cache_res_->UpdateCacheReservation(sec_charge, /*increase=*/false);
|
||||
assert(s.ok());
|
||||
if (s.ok() && value == nullptr && distribute_cache_res_ && handle) {
|
||||
charge = target_->GetCharge(*handle);
|
||||
|
||||
MutexLock l(&cache_res_mutex_);
|
||||
placeholder_usage_ += charge;
|
||||
// Check if total placeholder reservation is more than the overall
|
||||
// cache capacity. If it is, then we don't try to charge the
|
||||
// secondary cache because we don't want to overcharge it (beyond
|
||||
// its capacity).
|
||||
// In order to make this a bit more lightweight, we also check if
|
||||
// the difference between placeholder_usage_ and reserved_usage_ is
|
||||
// atleast kReservationChunkSize and avoid any adjustments if not.
|
||||
if ((placeholder_usage_ <= target_->GetCapacity()) &&
|
||||
((placeholder_usage_ - reserved_usage_) >= kReservationChunkSize)) {
|
||||
reserved_usage_ = placeholder_usage_ & ~(kReservationChunkSize - 1);
|
||||
size_t new_sec_reserved =
|
||||
static_cast<size_t>(reserved_usage_ * sec_cache_res_ratio_);
|
||||
size_t sec_charge = new_sec_reserved - sec_reserved_;
|
||||
s = secondary_cache_->Deflate(sec_charge);
|
||||
assert(s.ok());
|
||||
s = pri_cache_res_->UpdateCacheReservation(sec_charge,
|
||||
/*increase=*/false);
|
||||
assert(s.ok());
|
||||
sec_reserved_ += sec_charge;
|
||||
}
|
||||
}
|
||||
// Warm up the secondary cache with the compressed block. The secondary
|
||||
// cache may choose to ignore it based on the admission policy.
|
||||
if (value != nullptr && !compressed_value.empty()) {
|
||||
if (value != nullptr && !compressed_value.empty() &&
|
||||
adm_policy_ == TieredAdmissionPolicy::kAdmPolicyThreeQueue) {
|
||||
Status status = secondary_cache_->InsertSaved(key, compressed_value, type);
|
||||
assert(status.ok());
|
||||
assert(status.ok() || status.IsNotSupported());
|
||||
}
|
||||
|
||||
return s;
|
||||
@@ -281,11 +307,27 @@ bool CacheWithSecondaryAdapter::Release(Handle* handle,
|
||||
ObjectPtr v = target_->Value(handle);
|
||||
if (v == nullptr && distribute_cache_res_) {
|
||||
size_t charge = target_->GetCharge(handle);
|
||||
size_t sec_charge = static_cast<size_t>(charge * (sec_cache_res_ratio_));
|
||||
Status s = secondary_cache_->Inflate(sec_charge);
|
||||
assert(s.ok());
|
||||
s = pri_cache_res_->UpdateCacheReservation(sec_charge, /*increase=*/true);
|
||||
assert(s.ok());
|
||||
|
||||
MutexLock l(&cache_res_mutex_);
|
||||
placeholder_usage_ -= charge;
|
||||
// Check if total placeholder reservation is more than the overall
|
||||
// cache capacity. If it is, then we do nothing as reserved_usage_ must
|
||||
// be already maxed out
|
||||
if ((placeholder_usage_ <= target_->GetCapacity()) &&
|
||||
(placeholder_usage_ < reserved_usage_)) {
|
||||
// Adjust reserved_usage_ in chunks of kReservationChunkSize, so
|
||||
// we don't hit this slow path too often.
|
||||
reserved_usage_ = placeholder_usage_ & ~(kReservationChunkSize - 1);
|
||||
size_t new_sec_reserved =
|
||||
static_cast<size_t>(reserved_usage_ * sec_cache_res_ratio_);
|
||||
size_t sec_charge = sec_reserved_ - new_sec_reserved;
|
||||
Status s = secondary_cache_->Inflate(sec_charge);
|
||||
assert(s.ok());
|
||||
s = pri_cache_res_->UpdateCacheReservation(sec_charge,
|
||||
/*increase=*/true);
|
||||
assert(s.ok());
|
||||
sec_reserved_ -= sec_charge;
|
||||
}
|
||||
}
|
||||
}
|
||||
return target_->Release(handle, erase_if_last_ref);
|
||||
@@ -436,7 +478,7 @@ void CacheWithSecondaryAdapter::SetCapacity(size_t capacity) {
|
||||
size_t old_sec_capacity = 0;
|
||||
|
||||
if (distribute_cache_res_) {
|
||||
MutexLock m(&mutex_);
|
||||
MutexLock m(&cache_res_mutex_);
|
||||
|
||||
Status s = secondary_cache_->GetCapacity(old_sec_capacity);
|
||||
if (!s.ok()) {
|
||||
@@ -451,9 +493,17 @@ void CacheWithSecondaryAdapter::SetCapacity(size_t capacity) {
|
||||
// 3. Decrease the primary cache capacity to the total budget
|
||||
s = secondary_cache_->SetCapacity(sec_capacity);
|
||||
if (s.ok()) {
|
||||
if (placeholder_usage_ > capacity) {
|
||||
// Adjust reserved_usage_ down
|
||||
reserved_usage_ = capacity & ~(kReservationChunkSize - 1);
|
||||
}
|
||||
size_t new_sec_reserved =
|
||||
static_cast<size_t>(reserved_usage_ * sec_cache_res_ratio_);
|
||||
s = pri_cache_res_->UpdateCacheReservation(
|
||||
old_sec_capacity - sec_capacity,
|
||||
(old_sec_capacity - sec_capacity) -
|
||||
(sec_reserved_ - new_sec_reserved),
|
||||
/*increase=*/false);
|
||||
sec_reserved_ = new_sec_reserved;
|
||||
assert(s.ok());
|
||||
target_->SetCapacity(capacity);
|
||||
}
|
||||
@@ -478,6 +528,29 @@ void CacheWithSecondaryAdapter::SetCapacity(size_t capacity) {
|
||||
}
|
||||
}
|
||||
|
||||
Status CacheWithSecondaryAdapter::GetSecondaryCacheCapacity(
|
||||
size_t& size) const {
|
||||
return secondary_cache_->GetCapacity(size);
|
||||
}
|
||||
|
||||
Status CacheWithSecondaryAdapter::GetSecondaryCachePinnedUsage(
|
||||
size_t& size) const {
|
||||
Status s;
|
||||
if (distribute_cache_res_) {
|
||||
MutexLock m(&cache_res_mutex_);
|
||||
size_t capacity = 0;
|
||||
s = secondary_cache_->GetCapacity(capacity);
|
||||
if (s.ok()) {
|
||||
size = capacity - pri_cache_res_->GetTotalMemoryUsed();
|
||||
} else {
|
||||
size = 0;
|
||||
}
|
||||
} else {
|
||||
size = 0;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// Update the secondary/primary allocation ratio (remember, the primary
|
||||
// capacity is the total memory budget when distribute_cache_res_ is true).
|
||||
// When the ratio changes, we may accumulate some error in the calculations
|
||||
@@ -492,11 +565,11 @@ void CacheWithSecondaryAdapter::SetCapacity(size_t capacity) {
|
||||
// in the future.
|
||||
Status CacheWithSecondaryAdapter::UpdateCacheReservationRatio(
|
||||
double compressed_secondary_ratio) {
|
||||
if (!distribute_cache_res_ || sec_cache_res_ratio_ == 0.0) {
|
||||
if (!distribute_cache_res_) {
|
||||
return Status::NotSupported();
|
||||
}
|
||||
|
||||
MutexLock m(&mutex_);
|
||||
MutexLock m(&cache_res_mutex_);
|
||||
size_t pri_capacity = target_->GetCapacity();
|
||||
size_t sec_capacity =
|
||||
static_cast<size_t>(pri_capacity * compressed_secondary_ratio);
|
||||
@@ -506,19 +579,17 @@ Status CacheWithSecondaryAdapter::UpdateCacheReservationRatio(
|
||||
return s;
|
||||
}
|
||||
|
||||
assert(old_sec_capacity >= pri_cache_res_->GetTotalMemoryUsed());
|
||||
size_t old_sec_reserved =
|
||||
old_sec_capacity - pri_cache_res_->GetTotalMemoryUsed();
|
||||
// Calculate the new secondary cache reservation
|
||||
size_t sec_reserved = static_cast<size_t>(
|
||||
old_sec_reserved *
|
||||
(double)(compressed_secondary_ratio / sec_cache_res_ratio_));
|
||||
// reserved_usage_ will never be > the cache capacity, so we don't
|
||||
// have to worry about adjusting it here.
|
||||
sec_cache_res_ratio_ = compressed_secondary_ratio;
|
||||
size_t new_sec_reserved =
|
||||
static_cast<size_t>(reserved_usage_ * sec_cache_res_ratio_);
|
||||
if (sec_capacity > old_sec_capacity) {
|
||||
// We're increasing the ratio, thus ending up with a larger secondary
|
||||
// cache and a smaller usable primary cache capacity. Similar to
|
||||
// SetCapacity(), we try to avoid a temporary increase in total usage
|
||||
// beyond teh configured capacity -
|
||||
// beyond the configured capacity -
|
||||
// 1. A higher secondary cache ratio means it gets a higher share of
|
||||
// cache reservations. So first account for that by deflating the
|
||||
// secondary cache
|
||||
@@ -526,13 +597,13 @@ Status CacheWithSecondaryAdapter::UpdateCacheReservationRatio(
|
||||
// cache utilization (increase in capacity - increase in share of cache
|
||||
// reservation)
|
||||
// 3. Increase secondary cache capacity
|
||||
assert(sec_reserved > old_sec_reserved || sec_reserved == 0);
|
||||
s = secondary_cache_->Deflate(sec_reserved - old_sec_reserved);
|
||||
s = secondary_cache_->Deflate(new_sec_reserved - sec_reserved_);
|
||||
assert(s.ok());
|
||||
s = pri_cache_res_->UpdateCacheReservation(
|
||||
(sec_capacity - old_sec_capacity) - (sec_reserved - old_sec_reserved),
|
||||
(sec_capacity - old_sec_capacity) - (new_sec_reserved - sec_reserved_),
|
||||
/*increase=*/true);
|
||||
assert(s.ok());
|
||||
sec_reserved_ = new_sec_reserved;
|
||||
s = secondary_cache_->SetCapacity(sec_capacity);
|
||||
assert(s.ok());
|
||||
} else {
|
||||
@@ -543,24 +614,19 @@ Status CacheWithSecondaryAdapter::UpdateCacheReservationRatio(
|
||||
// reservations)
|
||||
// 3. Inflate the secondary cache to give it back the reduction in its
|
||||
// share of cache reservations
|
||||
assert(old_sec_reserved > sec_reserved || sec_reserved == 0);
|
||||
s = secondary_cache_->SetCapacity(sec_capacity);
|
||||
if (s.ok()) {
|
||||
s = pri_cache_res_->UpdateCacheReservation(
|
||||
(old_sec_capacity - sec_capacity) - (old_sec_reserved - sec_reserved),
|
||||
(old_sec_capacity - sec_capacity) -
|
||||
(sec_reserved_ - new_sec_reserved),
|
||||
/*increase=*/false);
|
||||
assert(s.ok());
|
||||
s = secondary_cache_->Inflate(old_sec_reserved - sec_reserved);
|
||||
s = secondary_cache_->Inflate(sec_reserved_ - new_sec_reserved);
|
||||
assert(s.ok());
|
||||
sec_reserved_ = new_sec_reserved;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
// As mentioned in the function comments, we may accumulate some erros when
|
||||
// the ratio is changed. We set a flag here which disables some assertions
|
||||
// in the destructor
|
||||
ratio_changed_ = true;
|
||||
#endif
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -575,11 +641,40 @@ std::shared_ptr<Cache> NewTieredCache(const TieredCacheOptions& _opts) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (_opts.adm_policy >= TieredAdmissionPolicy::kAdmPolicyMax) {
|
||||
return nullptr;
|
||||
TieredCacheOptions opts = _opts;
|
||||
{
|
||||
bool valid_adm_policy = true;
|
||||
|
||||
switch (_opts.adm_policy) {
|
||||
case TieredAdmissionPolicy::kAdmPolicyAuto:
|
||||
// Select an appropriate default policy
|
||||
if (opts.adm_policy == TieredAdmissionPolicy::kAdmPolicyAuto) {
|
||||
if (opts.nvm_sec_cache) {
|
||||
opts.adm_policy = TieredAdmissionPolicy::kAdmPolicyThreeQueue;
|
||||
} else {
|
||||
opts.adm_policy = TieredAdmissionPolicy::kAdmPolicyPlaceholder;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TieredAdmissionPolicy::kAdmPolicyPlaceholder:
|
||||
case TieredAdmissionPolicy::kAdmPolicyAllowCacheHits:
|
||||
if (opts.nvm_sec_cache) {
|
||||
valid_adm_policy = false;
|
||||
}
|
||||
break;
|
||||
case TieredAdmissionPolicy::kAdmPolicyThreeQueue:
|
||||
if (!opts.nvm_sec_cache) {
|
||||
valid_adm_policy = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
valid_adm_policy = false;
|
||||
}
|
||||
if (!valid_adm_policy) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
TieredCacheOptions opts = _opts;
|
||||
std::shared_ptr<Cache> cache;
|
||||
if (opts.cache_type == PrimaryCacheType::kCacheTypeLRU) {
|
||||
LRUCacheOptions cache_opts =
|
||||
@@ -602,8 +697,7 @@ std::shared_ptr<Cache> NewTieredCache(const TieredCacheOptions& _opts) {
|
||||
sec_cache = NewCompressedSecondaryCache(opts.comp_cache_opts);
|
||||
|
||||
if (opts.nvm_sec_cache) {
|
||||
if (opts.adm_policy == TieredAdmissionPolicy::kAdmPolicyThreeQueue ||
|
||||
opts.adm_policy == TieredAdmissionPolicy::kAdmPolicyAuto) {
|
||||
if (opts.adm_policy == TieredAdmissionPolicy::kAdmPolicyThreeQueue) {
|
||||
sec_cache = std::make_shared<TieredSecondaryCache>(
|
||||
sec_cache, opts.nvm_sec_cache,
|
||||
TieredAdmissionPolicy::kAdmPolicyThreeQueue);
|
||||
|
||||
Vendored
+17
-4
@@ -47,6 +47,10 @@ class CacheWithSecondaryAdapter : public CacheWrapper {
|
||||
|
||||
void SetCapacity(size_t capacity) override;
|
||||
|
||||
Status GetSecondaryCacheCapacity(size_t& size) const override;
|
||||
|
||||
Status GetSecondaryCachePinnedUsage(size_t& size) const override;
|
||||
|
||||
Status UpdateCacheReservationRatio(double ratio);
|
||||
|
||||
Status UpdateAdmissionPolicy(TieredAdmissionPolicy adm_policy);
|
||||
@@ -56,6 +60,8 @@ class CacheWithSecondaryAdapter : public CacheWrapper {
|
||||
SecondaryCache* TEST_GetSecondaryCache() { return secondary_cache_.get(); }
|
||||
|
||||
private:
|
||||
static constexpr size_t kReservationChunkSize = 1 << 20;
|
||||
|
||||
bool EvictionHandler(const Slice& key, Handle* handle, bool was_hit);
|
||||
|
||||
void StartAsyncLookupOnMySecondary(AsyncLookupHandle& async_handle);
|
||||
@@ -81,10 +87,17 @@ class CacheWithSecondaryAdapter : public CacheWrapper {
|
||||
// Fraction of a cache memory reservation to be assigned to the secondary
|
||||
// cache
|
||||
double sec_cache_res_ratio_;
|
||||
port::Mutex mutex_;
|
||||
#ifndef NDEBUG
|
||||
bool ratio_changed_ = false;
|
||||
#endif
|
||||
// Mutex for use when managing cache memory reservations. Should not be used
|
||||
// for other purposes, as it may risk causing deadlocks.
|
||||
mutable port::Mutex cache_res_mutex_;
|
||||
// Total memory reserved by placeholder entriesin the cache
|
||||
size_t placeholder_usage_;
|
||||
// Total placeholoder memory charged to both the primary and secondary
|
||||
// caches. Will be <= placeholder_usage_.
|
||||
size_t reserved_usage_;
|
||||
// Amount of memory reserved in the secondary cache. This should be
|
||||
// reserved_usage_ * sec_cache_res_ratio_ in steady state.
|
||||
size_t sec_reserved_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+10
@@ -83,6 +83,16 @@ size_t ShardedCacheBase::GetCapacity() const {
|
||||
return capacity_;
|
||||
}
|
||||
|
||||
Status ShardedCacheBase::GetSecondaryCacheCapacity(size_t& size) const {
|
||||
size = 0;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status ShardedCacheBase::GetSecondaryCachePinnedUsage(size_t& size) const {
|
||||
size = 0;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
bool ShardedCacheBase::HasStrictCapacityLimit() const {
|
||||
MutexLock l(&config_mutex_);
|
||||
return strict_capacity_limit_;
|
||||
|
||||
Vendored
+2
@@ -99,6 +99,8 @@ class ShardedCacheBase : public Cache {
|
||||
|
||||
bool HasStrictCapacityLimit() const override;
|
||||
size_t GetCapacity() const override;
|
||||
Status GetSecondaryCacheCapacity(size_t& size) const override;
|
||||
Status GetSecondaryCachePinnedUsage(size_t& size) const override;
|
||||
|
||||
using Cache::GetUsage;
|
||||
size_t GetUsage(Handle* handle) const override;
|
||||
|
||||
Vendored
+78
-12
@@ -3,6 +3,8 @@
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
//
|
||||
#include "cache/compressed_secondary_cache.h"
|
||||
#include "cache/secondary_cache_adapter.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
@@ -179,7 +181,9 @@ class DBTieredSecondaryCacheTest : public DBTestBase {
|
||||
|
||||
std::shared_ptr<Cache> NewCache(size_t pri_capacity,
|
||||
size_t compressed_capacity,
|
||||
size_t nvm_capacity) {
|
||||
size_t nvm_capacity,
|
||||
TieredAdmissionPolicy adm_policy =
|
||||
TieredAdmissionPolicy::kAdmPolicyAuto) {
|
||||
LRUCacheOptions lru_opts;
|
||||
TieredCacheOptions opts;
|
||||
lru_opts.capacity = 0;
|
||||
@@ -187,30 +191,30 @@ class DBTieredSecondaryCacheTest : public DBTestBase {
|
||||
lru_opts.high_pri_pool_ratio = 0;
|
||||
opts.cache_opts = &lru_opts;
|
||||
opts.cache_type = PrimaryCacheType::kCacheTypeLRU;
|
||||
opts.adm_policy = TieredAdmissionPolicy::kAdmPolicyThreeQueue;
|
||||
opts.comp_cache_opts.capacity = 0;
|
||||
opts.comp_cache_opts.num_shard_bits = 0;
|
||||
opts.total_capacity = pri_capacity + compressed_capacity;
|
||||
opts.compressed_secondary_ratio =
|
||||
(double)compressed_capacity / opts.total_capacity;
|
||||
nvm_sec_cache_.reset(new TestSecondaryCache(nvm_capacity));
|
||||
opts.nvm_sec_cache = nvm_sec_cache_;
|
||||
if (nvm_capacity > 0) {
|
||||
nvm_sec_cache_.reset(new TestSecondaryCache(nvm_capacity));
|
||||
opts.nvm_sec_cache = nvm_sec_cache_;
|
||||
}
|
||||
opts.adm_policy = adm_policy;
|
||||
cache_ = NewTieredCache(opts);
|
||||
assert(cache_ != nullptr);
|
||||
|
||||
#if 0
|
||||
CacheWithSecondaryAdapter* adapter_cache_ =
|
||||
static_cast<CacheWithSecondaryAdapter*>(cache_.get());
|
||||
TieredSecondaryCache* tiered_cache_ =
|
||||
static_cast<TieredSecondaryCache*>(
|
||||
adapter_cache_->TEST_GetSecondaryCache());
|
||||
#endif
|
||||
|
||||
return cache_;
|
||||
}
|
||||
|
||||
TestSecondaryCache* nvm_sec_cache() { return nvm_sec_cache_.get(); }
|
||||
|
||||
CompressedSecondaryCache* compressed_secondary_cache() {
|
||||
return static_cast<CompressedSecondaryCache*>(
|
||||
static_cast<CacheWithSecondaryAdapter*>(cache_.get())
|
||||
->TEST_GetSecondaryCache());
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Cache> cache_;
|
||||
std::shared_ptr<TestSecondaryCache> nvm_sec_cache_;
|
||||
@@ -636,6 +640,68 @@ TEST_F(DBTieredSecondaryCacheTest, IterateTest) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
class DBTieredAdmPolicyTest
|
||||
: public DBTieredSecondaryCacheTest,
|
||||
public testing::WithParamInterface<TieredAdmissionPolicy> {};
|
||||
|
||||
TEST_P(DBTieredAdmPolicyTest, CompressedOnlyTest) {
|
||||
if (!LZ4_Supported()) {
|
||||
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
||||
return;
|
||||
}
|
||||
|
||||
BlockBasedTableOptions table_options;
|
||||
// We want a block cache of size 10KB, and a compressed secondary cache of
|
||||
// size 10KB. However, we specify a block cache size of 256KB here in order
|
||||
// to take into account the cache reservation in the block cache on
|
||||
// behalf of the compressed cache. The unit of cache reservation is 256KB.
|
||||
// The effective block cache capacity will be calculated as 256 + 10 = 266KB,
|
||||
// and 256KB will be reserved for the compressed cache, leaving 10KB for
|
||||
// the primary block cache. We only have to worry about this here because
|
||||
// the cache size is so small.
|
||||
table_options.block_cache = NewCache(256 * 1024, 10 * 1024, 0, GetParam());
|
||||
table_options.block_size = 4 * 1024;
|
||||
table_options.cache_index_and_filter_blocks = false;
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
size_t comp_cache_usage = compressed_secondary_cache()->TEST_GetUsage();
|
||||
// Disable paranoid_file_checks so that flush will not read back the newly
|
||||
// written file
|
||||
options.paranoid_file_checks = false;
|
||||
DestroyAndReopen(options);
|
||||
Random rnd(301);
|
||||
const int N = 256;
|
||||
for (int i = 0; i < N; i++) {
|
||||
std::string p_v;
|
||||
test::CompressibleString(&rnd, 0.5, 1007, &p_v);
|
||||
ASSERT_OK(Put(Key(i), p_v));
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// The first 2 Gets, for keys 0 and 5, will load the corresponding data
|
||||
// blocks as they will be cache misses. Since this is a 2-tier cache (
|
||||
// primary and compressed), no warm-up should happen with the compressed
|
||||
// blocks.
|
||||
std::string v = Get(Key(0));
|
||||
ASSERT_EQ(1007, v.size());
|
||||
|
||||
v = Get(Key(5));
|
||||
ASSERT_EQ(1007, v.size());
|
||||
|
||||
ASSERT_EQ(compressed_secondary_cache()->TEST_GetUsage(), comp_cache_usage);
|
||||
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
DBTieredAdmPolicyTest, DBTieredAdmPolicyTest,
|
||||
::testing::Values(TieredAdmissionPolicy::kAdmPolicyAuto,
|
||||
TieredAdmissionPolicy::kAdmPolicyPlaceholder,
|
||||
TieredAdmissionPolicy::kAdmPolicyAllowCacheHits));
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -168,6 +168,7 @@ TEST_F(DBBlobBasicTest, IterateBlobsFromCache) {
|
||||
ASSERT_EQ(iter->value().ToString(), blobs[i]);
|
||||
++i;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(i, num_blobs);
|
||||
ASSERT_EQ(options.statistics->getAndResetTickerCount(BLOB_DB_CACHE_ADD), 0);
|
||||
}
|
||||
@@ -203,6 +204,7 @@ TEST_F(DBBlobBasicTest, IterateBlobsFromCache) {
|
||||
ASSERT_EQ(iter->value().ToString(), blobs[i]);
|
||||
++i;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(i, num_blobs);
|
||||
ASSERT_EQ(options.statistics->getAndResetTickerCount(BLOB_DB_CACHE_ADD),
|
||||
num_blobs);
|
||||
@@ -224,6 +226,7 @@ TEST_F(DBBlobBasicTest, IterateBlobsFromCache) {
|
||||
ASSERT_EQ(iter->value().ToString(), blobs[i]);
|
||||
++i;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(i, num_blobs);
|
||||
ASSERT_EQ(options.statistics->getAndResetTickerCount(BLOB_DB_CACHE_ADD), 0);
|
||||
}
|
||||
@@ -2123,6 +2126,7 @@ TEST_F(DBBlobWithTimestampTest, IterateBlobs) {
|
||||
/*key_is_internal*/ false);
|
||||
iter->Prev();
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
|
||||
// Backward iteration, then reverse to forward.
|
||||
@@ -2169,6 +2173,7 @@ TEST_F(DBBlobWithTimestampTest, IterateBlobs) {
|
||||
iter->Next();
|
||||
}
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
|
||||
// Backward iterating multiple versions of the same key, get in this order:
|
||||
@@ -2187,6 +2192,7 @@ TEST_F(DBBlobWithTimestampTest, IterateBlobs) {
|
||||
iter->Prev();
|
||||
}
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
|
||||
int upper_bound_idx = num_blobs - 2;
|
||||
@@ -2209,6 +2215,7 @@ TEST_F(DBBlobWithTimestampTest, IterateBlobs) {
|
||||
iter->Next();
|
||||
}
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
|
||||
// Backward iteration with upper and lower bound.
|
||||
@@ -2224,6 +2231,7 @@ TEST_F(DBBlobWithTimestampTest, IterateBlobs) {
|
||||
iter->Prev();
|
||||
}
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -294,12 +294,12 @@ Status BuildTable(
|
||||
if (!s.ok() || empty) {
|
||||
builder->Abandon();
|
||||
} else {
|
||||
std::string seqno_time_mapping_str;
|
||||
std::string seqno_to_time_mapping_str;
|
||||
seqno_to_time_mapping.Encode(
|
||||
seqno_time_mapping_str, meta->fd.smallest_seqno,
|
||||
seqno_to_time_mapping_str, meta->fd.smallest_seqno,
|
||||
meta->fd.largest_seqno, meta->file_creation_time);
|
||||
builder->SetSeqnoTimeTableProperties(
|
||||
seqno_time_mapping_str,
|
||||
seqno_to_time_mapping_str,
|
||||
ioptions.compaction_style == CompactionStyle::kCompactionStyleFIFO
|
||||
? meta->file_creation_time
|
||||
: meta->oldest_ancester_time);
|
||||
|
||||
@@ -1748,4 +1748,20 @@ const Comparator* GetColumnFamilyUserComparator(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const ImmutableOptions& GetImmutableOptions(ColumnFamilyHandle* column_family) {
|
||||
assert(column_family);
|
||||
|
||||
ColumnFamilyHandleImpl* const handle =
|
||||
static_cast_with_check<ColumnFamilyHandleImpl>(column_family);
|
||||
assert(handle);
|
||||
|
||||
const ColumnFamilyData* const cfd = handle->cfd();
|
||||
assert(cfd);
|
||||
|
||||
const ImmutableOptions* ioptions = cfd->ioptions();
|
||||
assert(ioptions);
|
||||
|
||||
return *ioptions;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -877,4 +877,7 @@ extern uint32_t GetColumnFamilyID(ColumnFamilyHandle* column_family);
|
||||
extern const Comparator* GetColumnFamilyUserComparator(
|
||||
ColumnFamilyHandle* column_family);
|
||||
|
||||
extern const ImmutableOptions& GetImmutableOptions(
|
||||
ColumnFamilyHandle* column_family);
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+102
-6
@@ -8,6 +8,7 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
@@ -27,6 +28,7 @@
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/defer.h"
|
||||
#include "util/string_util.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
@@ -2169,13 +2171,57 @@ TEST_P(ColumnFamilyTest, FlushStaleColumnFamilies) {
|
||||
Close();
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct CountOptionsFilesFs : public FileSystemWrapper {
|
||||
explicit CountOptionsFilesFs(const std::shared_ptr<FileSystem>& t)
|
||||
: FileSystemWrapper(t) {}
|
||||
const char* Name() const override { return "CountOptionsFilesFs"; }
|
||||
|
||||
IOStatus NewWritableFile(const std::string& f, const FileOptions& file_opts,
|
||||
std::unique_ptr<FSWritableFile>* r,
|
||||
IODebugContext* dbg) override {
|
||||
if (f.find("OPTIONS-") != std::string::npos) {
|
||||
options_files_created.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
return FileSystemWrapper::NewWritableFile(f, file_opts, r, dbg);
|
||||
}
|
||||
|
||||
std::atomic<int> options_files_created{};
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_P(ColumnFamilyTest, CreateMissingColumnFamilies) {
|
||||
Status s = TryOpen({"one", "two"});
|
||||
ASSERT_TRUE(!s.ok());
|
||||
db_options_.create_missing_column_families = true;
|
||||
s = TryOpen({"default", "one", "two"});
|
||||
ASSERT_TRUE(s.ok());
|
||||
// Can't accidentally add CFs to an existing DB
|
||||
Open();
|
||||
Close();
|
||||
ASSERT_FALSE(db_options_.create_missing_column_families);
|
||||
ASSERT_NOK(TryOpen({"one", "two"}));
|
||||
|
||||
// Nor accidentally create in a new DB
|
||||
Destroy();
|
||||
db_options_.create_if_missing = true;
|
||||
ASSERT_NOK(TryOpen({"one", "two"}));
|
||||
|
||||
// Only with the option (new DB case)
|
||||
db_options_.create_missing_column_families = true;
|
||||
// Also setup to count number of options files created (see check below)
|
||||
auto my_fs =
|
||||
std::make_shared<CountOptionsFilesFs>(db_options_.env->GetFileSystem());
|
||||
auto my_env = std::make_unique<CompositeEnvWrapper>(db_options_.env, my_fs);
|
||||
SaveAndRestore<Env*> save_restore_env(&db_options_.env, my_env.get());
|
||||
|
||||
ASSERT_OK(TryOpen({"default", "one", "two"}));
|
||||
Close();
|
||||
|
||||
// An older version would write an updated options file for each column
|
||||
// family created under create_missing_column_families, which would be
|
||||
// quadratic I/O in the number of column families.
|
||||
ASSERT_EQ(my_fs->options_files_created.load(), 1);
|
||||
|
||||
// Add to existing DB case
|
||||
ASSERT_OK(TryOpen({"default", "one", "two", "three", "four"}));
|
||||
Close();
|
||||
ASSERT_EQ(my_fs->options_files_created.load(), 2);
|
||||
}
|
||||
|
||||
TEST_P(ColumnFamilyTest, SanitizeOptions) {
|
||||
@@ -2427,7 +2473,10 @@ void DropSingleColumnFamily(ColumnFamilyTest* cf_test, int cf_id,
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
TEST_P(ColumnFamilyTest, CreateAndDropRace) {
|
||||
// This test attempts to set up a race condition in a way that is no longer
|
||||
// possible, causing the test to hang. If DBImpl::options_mutex_ is removed
|
||||
// in the future, this test might become relevant again.
|
||||
TEST_P(ColumnFamilyTest, DISABLED_CreateAndDropRace) {
|
||||
const int kCfCount = 5;
|
||||
std::vector<ColumnFamilyOptions> cf_opts;
|
||||
std::vector<Comparator*> comparators;
|
||||
@@ -2489,6 +2538,53 @@ TEST_P(ColumnFamilyTest, CreateAndDropRace) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_P(ColumnFamilyTest, CreateAndDropPeriodicRace) {
|
||||
// This is a mini-stress test looking for inconsistency between the set of
|
||||
// CFs in the DB, particularly whether any use preserve_internal_time_seconds,
|
||||
// and whether that is accurately reflected in the periodic task setup.
|
||||
constexpr size_t kNumThreads = 12;
|
||||
std::vector<std::thread> threads;
|
||||
bool last_cf_on = Random::GetTLSInstance()->OneIn(2);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::RegisterRecordSeqnoTimeWorker:BeforePeriodicTaskType",
|
||||
[&](void* /*arg*/) { std::this_thread::yield(); });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_EQ(column_family_options_.preserve_internal_time_seconds, 0U);
|
||||
ColumnFamilyOptions other_opts = column_family_options_;
|
||||
ColumnFamilyOptions last_opts = column_family_options_;
|
||||
(last_cf_on ? last_opts : other_opts).preserve_internal_time_seconds =
|
||||
1000000;
|
||||
Open();
|
||||
|
||||
for (size_t i = 0; i < kNumThreads; i++) {
|
||||
threads.emplace_back([this, &other_opts, i]() {
|
||||
ColumnFamilyHandle* cfh;
|
||||
ASSERT_OK(db_->CreateColumnFamily(other_opts, std::to_string(i), &cfh));
|
||||
ASSERT_OK(db_->DropColumnFamily(cfh));
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(cfh));
|
||||
});
|
||||
}
|
||||
|
||||
ColumnFamilyHandle* last_cfh;
|
||||
ASSERT_OK(db_->CreateColumnFamily(last_opts, "last", &last_cfh));
|
||||
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
bool task_enabled = dbfull()->TEST_GetPeriodicTaskScheduler().TEST_HasTask(
|
||||
PeriodicTaskType::kRecordSeqnoTime);
|
||||
ASSERT_EQ(last_cf_on, task_enabled);
|
||||
|
||||
ASSERT_OK(db_->DropColumnFamily(last_cfh));
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(last_cfh));
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_P(ColumnFamilyTest, WriteStallSingleColumnFamily) {
|
||||
const uint64_t kBaseRate = 800000u;
|
||||
db_options_.delayed_write_rate = kBaseRate;
|
||||
|
||||
@@ -288,23 +288,23 @@ void CompactionJob::Prepare() {
|
||||
|
||||
if (preserve_time_duration > 0) {
|
||||
const ReadOptions read_options(Env::IOActivity::kCompaction);
|
||||
// setup seqno_time_mapping_
|
||||
seqno_time_mapping_.SetMaxTimeDuration(preserve_time_duration);
|
||||
// setup seqno_to_time_mapping_
|
||||
seqno_to_time_mapping_.SetMaxTimeDuration(preserve_time_duration);
|
||||
for (const auto& each_level : *c->inputs()) {
|
||||
for (const auto& fmd : each_level.files) {
|
||||
std::shared_ptr<const TableProperties> tp;
|
||||
Status s =
|
||||
cfd->current()->GetTableProperties(read_options, &tp, fmd, nullptr);
|
||||
if (s.ok()) {
|
||||
seqno_time_mapping_.Add(tp->seqno_to_time_mapping)
|
||||
seqno_to_time_mapping_.Add(tp->seqno_to_time_mapping)
|
||||
.PermitUncheckedError();
|
||||
seqno_time_mapping_.Add(fmd->fd.smallest_seqno,
|
||||
fmd->oldest_ancester_time);
|
||||
seqno_to_time_mapping_.Add(fmd->fd.smallest_seqno,
|
||||
fmd->oldest_ancester_time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto status = seqno_time_mapping_.Sort();
|
||||
auto status = seqno_to_time_mapping_.Sort();
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Invalid sequence number to time mapping: Status: %s",
|
||||
@@ -320,13 +320,17 @@ void CompactionJob::Prepare() {
|
||||
preserve_time_min_seqno_ = 0;
|
||||
preclude_last_level_min_seqno_ = 0;
|
||||
} else {
|
||||
seqno_time_mapping_.TruncateOldEntries(_current_time);
|
||||
seqno_to_time_mapping_.TruncateOldEntries(_current_time);
|
||||
uint64_t preserve_time =
|
||||
static_cast<uint64_t>(_current_time) > preserve_time_duration
|
||||
? _current_time - preserve_time_duration
|
||||
: 0;
|
||||
// GetProximalSeqnoBeforeTime tells us the last seqno known to have been
|
||||
// written at or before the given time. + 1 to get the minimum we should
|
||||
// preserve without excluding anything that might have been written on or
|
||||
// after the given time.
|
||||
preserve_time_min_seqno_ =
|
||||
seqno_time_mapping_.GetOldestSequenceNum(preserve_time);
|
||||
seqno_to_time_mapping_.GetProximalSeqnoBeforeTime(preserve_time) + 1;
|
||||
if (c->immutable_options()->preclude_last_level_data_seconds > 0) {
|
||||
uint64_t preclude_last_level_time =
|
||||
static_cast<uint64_t>(_current_time) >
|
||||
@@ -335,7 +339,9 @@ void CompactionJob::Prepare() {
|
||||
c->immutable_options()->preclude_last_level_data_seconds
|
||||
: 0;
|
||||
preclude_last_level_min_seqno_ =
|
||||
seqno_time_mapping_.GetOldestSequenceNum(preclude_last_level_time);
|
||||
seqno_to_time_mapping_.GetProximalSeqnoBeforeTime(
|
||||
preclude_last_level_time) +
|
||||
1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1570,7 +1576,7 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
|
||||
const uint64_t current_entries = outputs.NumEntries();
|
||||
|
||||
s = outputs.Finish(s, seqno_time_mapping_);
|
||||
s = outputs.Finish(s, seqno_to_time_mapping_);
|
||||
|
||||
if (s.ok()) {
|
||||
// With accurate smallest and largest key, we can get a slightly more
|
||||
@@ -1938,6 +1944,7 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
|
||||
db_options_.stats, listeners, db_options_.file_checksum_gen_factory.get(),
|
||||
tmp_set.Contains(FileType::kTableFile), false));
|
||||
|
||||
// TODO(hx235): pass in the correct `oldest_key_time` instead of `0`
|
||||
TableBuilderOptions tboptions(
|
||||
*cfd->ioptions(), *(sub_compact->compaction->mutable_cf_options()),
|
||||
cfd->internal_comparator(), cfd->int_tbl_prop_collector_factories(),
|
||||
|
||||
@@ -350,7 +350,7 @@ class CompactionJob {
|
||||
|
||||
// Stores the sequence number to time mapping gathered from all input files
|
||||
// it also collects the smallest_seqno -> oldest_ancester_time from the SST.
|
||||
SeqnoToTimeMapping seqno_time_mapping_;
|
||||
SeqnoToTimeMapping seqno_to_time_mapping_;
|
||||
|
||||
// Minimal sequence number for preserving the time information. The time info
|
||||
// older than this sequence number won't be preserved after the compaction and
|
||||
|
||||
@@ -18,16 +18,18 @@ void CompactionOutputs::NewBuilder(const TableBuilderOptions& tboptions) {
|
||||
builder_.reset(NewTableBuilder(tboptions, file_writer_.get()));
|
||||
}
|
||||
|
||||
Status CompactionOutputs::Finish(const Status& intput_status,
|
||||
const SeqnoToTimeMapping& seqno_time_mapping) {
|
||||
Status CompactionOutputs::Finish(
|
||||
const Status& intput_status,
|
||||
const SeqnoToTimeMapping& seqno_to_time_mapping) {
|
||||
FileMetaData* meta = GetMetaData();
|
||||
assert(meta != nullptr);
|
||||
Status s = intput_status;
|
||||
if (s.ok()) {
|
||||
std::string seqno_time_mapping_str;
|
||||
seqno_time_mapping.Encode(seqno_time_mapping_str, meta->fd.smallest_seqno,
|
||||
meta->fd.largest_seqno, meta->file_creation_time);
|
||||
builder_->SetSeqnoTimeTableProperties(seqno_time_mapping_str,
|
||||
std::string seqno_to_time_mapping_str;
|
||||
seqno_to_time_mapping.Encode(
|
||||
seqno_to_time_mapping_str, meta->fd.smallest_seqno,
|
||||
meta->fd.largest_seqno, meta->file_creation_time);
|
||||
builder_->SetSeqnoTimeTableProperties(seqno_to_time_mapping_str,
|
||||
meta->oldest_ancester_time);
|
||||
s = builder_->Finish();
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ class CompactionOutputs {
|
||||
|
||||
// Finish the current output file
|
||||
Status Finish(const Status& intput_status,
|
||||
const SeqnoToTimeMapping& seqno_time_mapping);
|
||||
const SeqnoToTimeMapping& seqno_to_time_mapping);
|
||||
|
||||
// Update output table properties from table builder
|
||||
void UpdateTableProperties() {
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
#include "logging/log_buffer.h"
|
||||
#include "logging/logging.h"
|
||||
#include "options/options_helper.h"
|
||||
#include "rocksdb/listener.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -1216,6 +1216,7 @@ class PrecludeLastLevelTest : public DBTestBase {
|
||||
PrecludeLastLevelTest()
|
||||
: DBTestBase("preclude_last_level_test", /*env_do_fsync=*/false) {
|
||||
mock_clock_ = std::make_shared<MockSystemClock>(env_->GetSystemClock());
|
||||
mock_clock_->SetCurrentTime(kMockStartTime);
|
||||
mock_env_ = std::make_unique<CompositeEnvWrapper>(env_, mock_clock_);
|
||||
}
|
||||
|
||||
@@ -1223,6 +1224,10 @@ class PrecludeLastLevelTest : public DBTestBase {
|
||||
std::unique_ptr<Env> mock_env_;
|
||||
std::shared_ptr<MockSystemClock> mock_clock_;
|
||||
|
||||
// Sufficient starting time that preserve time doesn't under-flow into
|
||||
// pre-history
|
||||
static constexpr uint32_t kMockStartTime = 10000000;
|
||||
|
||||
void SetUp() override {
|
||||
mock_clock_->InstallTimedWaitFixCallback();
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
@@ -1231,7 +1236,7 @@ class PrecludeLastLevelTest : public DBTestBase {
|
||||
reinterpret_cast<PeriodicTaskScheduler*>(arg);
|
||||
periodic_task_scheduler_ptr->TEST_OverrideTimer(mock_clock_.get());
|
||||
});
|
||||
mock_clock_->SetCurrentTime(0);
|
||||
mock_clock_->SetCurrentTime(kMockStartTime);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1249,11 +1254,6 @@ TEST_F(PrecludeLastLevelTest, MigrationFromPreserveTimeManualCompaction) {
|
||||
options.num_levels = kNumLevels;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// pass some time first, otherwise the first a few keys write time are going
|
||||
// to be zero, and internally zero has special meaning: kUnknownSeqnoTime
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun(
|
||||
[&] { mock_clock_->MockSleepForSeconds(static_cast<int>(kKeyPerSec)); });
|
||||
|
||||
int sst_num = 0;
|
||||
// Write files that are overlap and enough to trigger compaction
|
||||
for (; sst_num < kNumTrigger; sst_num++) {
|
||||
@@ -1311,11 +1311,6 @@ TEST_F(PrecludeLastLevelTest, MigrationFromPreserveTimeAutoCompaction) {
|
||||
options.num_levels = kNumLevels;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// pass some time first, otherwise the first a few keys write time are going
|
||||
// to be zero, and internally zero has special meaning: kUnknownSeqnoTime
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun(
|
||||
[&] { mock_clock_->MockSleepForSeconds(static_cast<int>(kKeyPerSec)); });
|
||||
|
||||
int sst_num = 0;
|
||||
// Write files that are overlap and enough to trigger compaction
|
||||
for (; sst_num < kNumTrigger; sst_num++) {
|
||||
@@ -1387,11 +1382,6 @@ TEST_F(PrecludeLastLevelTest, MigrationFromPreserveTimePartial) {
|
||||
options.num_levels = kNumLevels;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// pass some time first, otherwise the first a few keys write time are going
|
||||
// to be zero, and internally zero has special meaning: kUnknownSeqnoTime
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun(
|
||||
[&] { mock_clock_->MockSleepForSeconds(static_cast<int>(kKeyPerSec)); });
|
||||
|
||||
int sst_num = 0;
|
||||
// Write files that are overlap and enough to trigger compaction
|
||||
for (; sst_num < kNumTrigger; sst_num++) {
|
||||
@@ -1514,11 +1504,6 @@ TEST_F(PrecludeLastLevelTest, LastLevelOnlyCompactionPartial) {
|
||||
options.num_levels = kNumLevels;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// pass some time first, otherwise the first a few keys write time are going
|
||||
// to be zero, and internally zero has special meaning: kUnknownSeqnoTime
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun(
|
||||
[&] { mock_clock_->MockSleepForSeconds(static_cast<int>(kKeyPerSec)); });
|
||||
|
||||
int sst_num = 0;
|
||||
// Write files that are overlap and enough to trigger compaction
|
||||
for (; sst_num < kNumTrigger; sst_num++) {
|
||||
@@ -1592,11 +1577,6 @@ TEST_P(PrecludeLastLevelTestWithParms, LastLevelOnlyCompactionNoPreclude) {
|
||||
options.num_levels = kNumLevels;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// pass some time first, otherwise the first a few keys write time are going
|
||||
// to be zero, and internally zero has special meaning: kUnknownSeqnoTime
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun(
|
||||
[&] { mock_clock_->MockSleepForSeconds(static_cast<int>(kKeyPerSec)); });
|
||||
|
||||
Random rnd(301);
|
||||
int sst_num = 0;
|
||||
// Write files that are overlap and enough to trigger compaction
|
||||
@@ -1906,11 +1886,6 @@ TEST_F(PrecludeLastLevelTest, PartialPenultimateLevelCompaction) {
|
||||
options.num_levels = kNumLevels;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// pass some time first, otherwise the first a few keys write time are going
|
||||
// to be zero, and internally zero has special meaning: kUnknownSeqnoTime
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun(
|
||||
[&] { mock_clock_->MockSleepForSeconds(static_cast<int>(10)); });
|
||||
|
||||
Random rnd(301);
|
||||
|
||||
for (int i = 0; i < 300; i++) {
|
||||
@@ -1996,7 +1971,13 @@ TEST_F(PrecludeLastLevelTest, PartialPenultimateLevelCompaction) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(PrecludeLastLevelTest, RangeDelsCauseFileEndpointsToOverlap) {
|
||||
// FIXME broken test:
|
||||
// dbfull()->TEST_WaitForCompact()
|
||||
// Corruption: force_consistency_checks(DEBUG): VersionBuilder: L5 has
|
||||
// overlapping ranges:
|
||||
// file #14 largest key: '6B6579303030303134' seq:32, type:1 vs.
|
||||
// file #19 smallest key: '6B6579303030303130' seq:10, type:1
|
||||
TEST_F(PrecludeLastLevelTest, DISABLED_RangeDelsCauseFileEndpointsToOverlap) {
|
||||
const int kNumLevels = 7;
|
||||
const int kSecondsPerKey = 10;
|
||||
const int kNumFiles = 3;
|
||||
@@ -2017,12 +1998,6 @@ TEST_F(PrecludeLastLevelTest, RangeDelsCauseFileEndpointsToOverlap) {
|
||||
options.target_file_size_base = kFileBytes;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// pass some time first, otherwise the first a few keys write time are going
|
||||
// to be zero, and internally zero has special meaning: kUnknownSeqnoTime
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun([&] {
|
||||
mock_clock_->MockSleepForSeconds(static_cast<int>(kSecondsPerKey));
|
||||
});
|
||||
|
||||
// Flush an L0 file with the following contents (new to old):
|
||||
//
|
||||
// Range deletions [4, 6) [7, 8) [9, 11)
|
||||
@@ -2139,7 +2114,6 @@ TEST_F(PrecludeLastLevelTest, RangeDelsCauseFileEndpointsToOverlap) {
|
||||
Close();
|
||||
}
|
||||
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -165,6 +165,7 @@ void DoRandomIteraratorTest(DB* db, std::vector<std::string> source_strings,
|
||||
AssertItersEqual(iter.get(), result_iter.get());
|
||||
is_valid = iter->Valid();
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
|
||||
class DoubleComparator : public Comparator {
|
||||
|
||||
@@ -138,6 +138,7 @@ TEST_F(DBBasicTest, ReadOnlyDB) {
|
||||
ASSERT_OK(iter->status());
|
||||
++count;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
// Always expect two keys: "foo" and "bar"
|
||||
ASSERT_EQ(count, 2);
|
||||
};
|
||||
|
||||
@@ -389,6 +389,7 @@ TEST_F(DBBlockCacheTest, FillCacheAndIterateDB) {
|
||||
while (iter->Valid()) {
|
||||
iter->Next();
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
delete iter;
|
||||
iter = nullptr;
|
||||
}
|
||||
|
||||
@@ -1905,6 +1905,7 @@ TEST_F(DBBloomFilterTest, PrefixExtractorWithFilter2) {
|
||||
for (iter->Seek("zzzzz_AAAA"); iter->Valid(); iter->Next()) {
|
||||
iter_res.emplace_back(iter->value().ToString());
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
std::vector<std::string> expected_res = {"val1", "val2", "val3", "val4"};
|
||||
ASSERT_EQ(iter_res, expected_res);
|
||||
|
||||
@@ -805,6 +805,7 @@ TEST_F(DBTestCompactionFilter, CompactionFilterIgnoreSnapshot) {
|
||||
count++;
|
||||
iter->Next();
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(count, 6);
|
||||
read_options.snapshot = nullptr;
|
||||
std::unique_ptr<Iterator> iter1(db_->NewIterator(read_options));
|
||||
@@ -815,6 +816,7 @@ TEST_F(DBTestCompactionFilter, CompactionFilterIgnoreSnapshot) {
|
||||
count++;
|
||||
iter1->Next();
|
||||
}
|
||||
ASSERT_OK(iter1->status());
|
||||
// We have deleted 10 keys from 40 using the compaction filter
|
||||
// Keys 6-9 before the snapshot and 100-105 after the snapshot
|
||||
ASSERT_EQ(count, 30);
|
||||
|
||||
@@ -2673,6 +2673,7 @@ TEST_P(DBCompactionTestWithParam, ConvertCompactionStyle) {
|
||||
keys_in_db.append(iter->key().ToString());
|
||||
keys_in_db.push_back(',');
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
delete iter;
|
||||
|
||||
std::string expected_keys;
|
||||
|
||||
@@ -1377,6 +1377,7 @@ TEST_F(DBFlushTest, MemPurgeDeleteAndDeleteRange) {
|
||||
ASSERT_EQ(value, NOT_FOUND);
|
||||
count++;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
// Expected count here is 3: KEY3, KEY4, KEY5.
|
||||
ASSERT_EQ(count, EXPECTED_COUNT_FORLOOP);
|
||||
|
||||
+285
-131
@@ -13,7 +13,6 @@
|
||||
#include <alloca.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <cstdio>
|
||||
#include <map>
|
||||
@@ -273,8 +272,9 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
periodic_task_functions_.emplace(PeriodicTaskType::kFlushInfoLog,
|
||||
[this]() { this->FlushInfoLog(); });
|
||||
periodic_task_functions_.emplace(
|
||||
PeriodicTaskType::kRecordSeqnoTime,
|
||||
[this]() { this->RecordSeqnoToTimeMapping(); });
|
||||
PeriodicTaskType::kRecordSeqnoTime, [this]() {
|
||||
this->RecordSeqnoToTimeMapping(/*populate_historical_seconds=*/0);
|
||||
});
|
||||
|
||||
versions_.reset(new VersionSet(dbname_, &immutable_db_options_, file_options_,
|
||||
table_cache_.get(), write_buffer_manager_,
|
||||
@@ -354,7 +354,6 @@ Status DBImpl::ResumeImpl(DBRecoverContext context) {
|
||||
}
|
||||
|
||||
// Make sure the IO Status stored in version set is set to OK.
|
||||
bool file_deletion_disabled = !IsFileDeletionsEnabled();
|
||||
if (s.ok()) {
|
||||
IOStatus io_s = versions_->io_status();
|
||||
if (io_s.IsIOError()) {
|
||||
@@ -363,7 +362,7 @@ Status DBImpl::ResumeImpl(DBRecoverContext context) {
|
||||
// clean-up phase MANIFEST writing. We must have also disabled file
|
||||
// deletions.
|
||||
assert(!versions_->descriptor_log_);
|
||||
assert(file_deletion_disabled);
|
||||
assert(!IsFileDeletionsEnabled());
|
||||
// Since we are trying to recover from MANIFEST write error, we need to
|
||||
// switch to a new MANIFEST anyway. The old MANIFEST can be corrupted.
|
||||
// Therefore, force writing a dummy version edit because we do not know
|
||||
@@ -387,13 +386,18 @@ Status DBImpl::ResumeImpl(DBRecoverContext context) {
|
||||
}
|
||||
}
|
||||
|
||||
// We cannot guarantee consistency of the WAL. So force flush Memtables of
|
||||
// all the column families
|
||||
if (s.ok()) {
|
||||
FlushOptions flush_opts;
|
||||
// We allow flush to stall write since we are trying to resume from error.
|
||||
flush_opts.allow_write_stall = true;
|
||||
s = FlushAllColumnFamilies(flush_opts, context.flush_reason);
|
||||
if (context.flush_reason == FlushReason::kErrorRecoveryRetryFlush) {
|
||||
s = RetryFlushesForErrorRecovery(FlushReason::kErrorRecoveryRetryFlush,
|
||||
true /* wait */);
|
||||
} else {
|
||||
// We cannot guarantee consistency of the WAL. So force flush Memtables of
|
||||
// all the column families
|
||||
FlushOptions flush_opts;
|
||||
// We allow flush to stall write since we are trying to resume from error.
|
||||
flush_opts.allow_write_stall = true;
|
||||
s = FlushAllColumnFamilies(flush_opts, context.flush_reason);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"DB resume requested but failed due to Flush failure [%s]",
|
||||
@@ -401,34 +405,6 @@ Status DBImpl::ResumeImpl(DBRecoverContext context) {
|
||||
}
|
||||
}
|
||||
|
||||
JobContext job_context(0);
|
||||
FindObsoleteFiles(&job_context, true);
|
||||
mutex_.Unlock();
|
||||
|
||||
job_context.manifest_file_number = 1;
|
||||
if (job_context.HaveSomethingToDelete()) {
|
||||
PurgeObsoleteFiles(job_context);
|
||||
}
|
||||
job_context.Clean();
|
||||
|
||||
if (s.ok()) {
|
||||
assert(versions_->io_status().ok());
|
||||
// If we reach here, we should re-enable file deletions if it was disabled
|
||||
// during previous error handling.
|
||||
if (file_deletion_disabled) {
|
||||
// Always return ok
|
||||
s = EnableFileDeletions(/*force=*/true);
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_INFO(
|
||||
immutable_db_options_.info_log,
|
||||
"DB resume requested but could not enable file deletions [%s]",
|
||||
s.ToString().c_str());
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutex_.Lock();
|
||||
if (s.ok()) {
|
||||
// This will notify and unblock threads waiting for error recovery to
|
||||
// finish. Those previouly waiting threads can now proceed, which may
|
||||
@@ -441,6 +417,15 @@ Status DBImpl::ResumeImpl(DBRecoverContext context) {
|
||||
error_handler_.GetRecoveryError().PermitUncheckedError();
|
||||
}
|
||||
|
||||
JobContext job_context(0);
|
||||
FindObsoleteFiles(&job_context, true);
|
||||
mutex_.Unlock();
|
||||
job_context.manifest_file_number = 1;
|
||||
if (job_context.HaveSomethingToDelete()) {
|
||||
PurgeObsoleteFiles(job_context);
|
||||
}
|
||||
job_context.Clean();
|
||||
|
||||
if (s.ok()) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "Successfully resumed DB");
|
||||
} else {
|
||||
@@ -448,20 +433,18 @@ Status DBImpl::ResumeImpl(DBRecoverContext context) {
|
||||
s.ToString().c_str());
|
||||
}
|
||||
|
||||
mutex_.Lock();
|
||||
// Check for shutdown again before scheduling further compactions,
|
||||
// since we released and re-acquired the lock above
|
||||
if (shutdown_initiated_) {
|
||||
s = Status::ShutdownInProgress();
|
||||
}
|
||||
if (s.ok()) {
|
||||
if (s.ok() && context.flush_after_recovery) {
|
||||
// Since we drop all non-recovery flush requests during recovery,
|
||||
// and new memtable may fill up during recovery,
|
||||
// schedule one more round of flush.
|
||||
FlushOptions flush_opts;
|
||||
flush_opts.allow_write_stall = false;
|
||||
flush_opts.wait = false;
|
||||
Status status = FlushAllColumnFamilies(
|
||||
flush_opts, FlushReason::kCatchUpAfterErrorRecovery);
|
||||
Status status = RetryFlushesForErrorRecovery(
|
||||
FlushReason::kCatchUpAfterErrorRecovery, false /* wait */);
|
||||
if (!status.ok()) {
|
||||
// FlushAllColumnFamilies internally should take care of setting
|
||||
// background error if needed.
|
||||
@@ -472,14 +455,16 @@ Status DBImpl::ResumeImpl(DBRecoverContext context) {
|
||||
// FlushAllColumnFamilies releases and re-acquires mutex.
|
||||
if (shutdown_initiated_) {
|
||||
s = Status::ShutdownInProgress();
|
||||
} else {
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
SchedulePendingCompaction(cfd);
|
||||
}
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
SchedulePendingCompaction(cfd);
|
||||
}
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
|
||||
// Wake up any waiters - in this case, it could be the shutdown thread
|
||||
bg_cv_.SignalAll();
|
||||
|
||||
@@ -811,42 +796,107 @@ Status DBImpl::StartPeriodicTaskScheduler() {
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::RegisterRecordSeqnoTimeWorker() {
|
||||
uint64_t min_time_duration = std::numeric_limits<uint64_t>::max();
|
||||
uint64_t max_time_duration = std::numeric_limits<uint64_t>::min();
|
||||
Status DBImpl::RegisterRecordSeqnoTimeWorker(bool is_new_db) {
|
||||
options_mutex_.AssertHeld();
|
||||
|
||||
uint64_t min_preserve_seconds = std::numeric_limits<uint64_t>::max();
|
||||
uint64_t max_preserve_seconds = std::numeric_limits<uint64_t>::min();
|
||||
bool mapping_was_empty = false;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
// preserve time is the max of 2 options.
|
||||
uint64_t preserve_time_duration =
|
||||
uint64_t preserve_seconds =
|
||||
std::max(cfd->ioptions()->preserve_internal_time_seconds,
|
||||
cfd->ioptions()->preclude_last_level_data_seconds);
|
||||
if (!cfd->IsDropped() && preserve_time_duration > 0) {
|
||||
min_time_duration = std::min(preserve_time_duration, min_time_duration);
|
||||
max_time_duration = std::max(preserve_time_duration, max_time_duration);
|
||||
if (!cfd->IsDropped() && preserve_seconds > 0) {
|
||||
min_preserve_seconds = std::min(preserve_seconds, min_preserve_seconds);
|
||||
max_preserve_seconds = std::max(preserve_seconds, max_preserve_seconds);
|
||||
}
|
||||
}
|
||||
if (min_time_duration == std::numeric_limits<uint64_t>::max()) {
|
||||
seqno_time_mapping_.Resize(0, 0);
|
||||
if (min_preserve_seconds == std::numeric_limits<uint64_t>::max()) {
|
||||
seqno_to_time_mapping_.Resize(0, 0);
|
||||
} else {
|
||||
seqno_time_mapping_.Resize(min_time_duration, max_time_duration);
|
||||
seqno_to_time_mapping_.Resize(min_preserve_seconds, max_preserve_seconds);
|
||||
}
|
||||
mapping_was_empty = seqno_to_time_mapping_.Empty();
|
||||
}
|
||||
|
||||
uint64_t seqno_time_cadence = 0;
|
||||
if (min_time_duration != std::numeric_limits<uint64_t>::max()) {
|
||||
if (min_preserve_seconds != std::numeric_limits<uint64_t>::max()) {
|
||||
// round up to 1 when the time_duration is smaller than
|
||||
// kMaxSeqnoTimePairsPerCF
|
||||
seqno_time_cadence =
|
||||
(min_time_duration + SeqnoToTimeMapping::kMaxSeqnoTimePairsPerCF - 1) /
|
||||
SeqnoToTimeMapping::kMaxSeqnoTimePairsPerCF;
|
||||
seqno_time_cadence = (min_preserve_seconds +
|
||||
SeqnoToTimeMapping::kMaxSeqnoTimePairsPerCF - 1) /
|
||||
SeqnoToTimeMapping::kMaxSeqnoTimePairsPerCF;
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"DBImpl::RegisterRecordSeqnoTimeWorker:BeforePeriodicTaskType", nullptr);
|
||||
|
||||
Status s;
|
||||
if (seqno_time_cadence == 0) {
|
||||
s = periodic_task_scheduler_.Unregister(PeriodicTaskType::kRecordSeqnoTime);
|
||||
} else {
|
||||
// Before registering the periodic task, we need to be sure to fulfill two
|
||||
// promises:
|
||||
// 1) Any DB created with preserve/preclude options set from the beginning
|
||||
// will get pre-allocated seqnos with pre-populated time mappings back to
|
||||
// the times we are interested in. (This will enable future import of data
|
||||
// while preserving rough write time. We can only do this reliably from
|
||||
// DB::Open, as otherwise there could be a race between CreateColumnFamily
|
||||
// and the first Write to the DB, and seqno-to-time mappings need to be
|
||||
// monotonic.
|
||||
// 2) In any DB, any data written after setting preserve/preclude options
|
||||
// must have a reasonable time estimate (so that we can accurately place
|
||||
// the data), which means at least one entry in seqno_to_time_mapping_.
|
||||
//
|
||||
// FIXME: We don't currently guarantee that if the first column family with
|
||||
// that setting is added or configured after initial DB::Open but before
|
||||
// the first user Write. Fixing this causes complications with the crash
|
||||
// test because if DB starts without preserve/preclude option, does some
|
||||
// user writes but all those writes are lost in crash, then re-opens with
|
||||
// preserve/preclude option, it sees seqno==1 which looks like one of the
|
||||
// user writes was recovered, when actually it was not.
|
||||
bool last_seqno_zero = GetLatestSequenceNumber() == 0;
|
||||
assert(!is_new_db || last_seqno_zero);
|
||||
if (is_new_db && last_seqno_zero) {
|
||||
// Pre-allocate seqnos and pre-populate historical mapping
|
||||
assert(mapping_was_empty);
|
||||
|
||||
// We can simply modify these, before writes are allowed
|
||||
constexpr uint64_t kMax = SeqnoToTimeMapping::kMaxSeqnoTimePairsPerSST;
|
||||
versions_->SetLastAllocatedSequence(kMax);
|
||||
versions_->SetLastPublishedSequence(kMax);
|
||||
versions_->SetLastSequence(kMax);
|
||||
|
||||
// And record in manifest, to avoid going backwards in seqno on re-open
|
||||
// (potentially with different options). Concurrency is simple because we
|
||||
// are in DB::Open
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
VersionEdit edit;
|
||||
edit.SetLastSequence(kMax);
|
||||
s = versions_->LogAndApplyToDefaultColumnFamily(
|
||||
{}, &edit, &mutex_, directories_.GetDbDir());
|
||||
if (!s.ok() && versions_->io_status().IsIOError()) {
|
||||
s = error_handler_.SetBGError(versions_->io_status(),
|
||||
BackgroundErrorReason::kManifestWrite);
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-populate mappings for reserved sequence numbers.
|
||||
RecordSeqnoToTimeMapping(max_preserve_seconds);
|
||||
} else if (mapping_was_empty) {
|
||||
if (!last_seqno_zero) {
|
||||
// Ensure at least one mapping (or log a warning)
|
||||
RecordSeqnoToTimeMapping(/*populate_historical_seconds=*/0);
|
||||
} else {
|
||||
// FIXME (see limitation described above)
|
||||
}
|
||||
}
|
||||
|
||||
s = periodic_task_scheduler_.Register(
|
||||
PeriodicTaskType::kRecordSeqnoTime,
|
||||
periodic_task_functions_.at(PeriodicTaskType::kRecordSeqnoTime),
|
||||
@@ -873,6 +923,7 @@ Status DBImpl::CancelPeriodicTaskScheduler() {
|
||||
|
||||
// esitmate the total size of stats_history_
|
||||
size_t DBImpl::EstimateInMemoryStatsHistorySize() const {
|
||||
stats_history_mutex_.AssertHeld();
|
||||
size_t size_total =
|
||||
sizeof(std::map<uint64_t, std::map<std::string, uint64_t>>);
|
||||
if (stats_history_.size() == 0) return size_total;
|
||||
@@ -1163,6 +1214,7 @@ Status DBImpl::SetOptions(
|
||||
return Status::InvalidArgument("empty input");
|
||||
}
|
||||
|
||||
InstrumentedMutexLock ol(&options_mutex_);
|
||||
MutableCFOptions new_options;
|
||||
Status s;
|
||||
Status persist_options_status;
|
||||
@@ -1182,8 +1234,7 @@ Status DBImpl::SetOptions(
|
||||
// thread.
|
||||
InstallSuperVersionAndScheduleWork(cfd, &sv_context, new_options);
|
||||
|
||||
persist_options_status = WriteOptionsFile(
|
||||
false /*need_mutex_lock*/, true /*need_enter_write_thread*/);
|
||||
persist_options_status = WriteOptionsFile(true /*db_mutex_already_held*/);
|
||||
bg_cv_.SignalAll();
|
||||
}
|
||||
}
|
||||
@@ -1221,10 +1272,12 @@ Status DBImpl::SetDBOptions(
|
||||
return Status::InvalidArgument("empty input");
|
||||
}
|
||||
|
||||
InstrumentedMutexLock ol(&options_mutex_);
|
||||
MutableDBOptions new_options;
|
||||
Status s;
|
||||
Status persist_options_status = Status::OK();
|
||||
bool wal_changed = false;
|
||||
bool wal_size_option_changed = false;
|
||||
bool wal_other_option_changed = false;
|
||||
WriteContext write_context;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
@@ -1325,8 +1378,10 @@ Status DBImpl::SetDBOptions(
|
||||
table_cache_.get()->SetCapacity(new_options.max_open_files == -1
|
||||
? TableCache::kInfiniteCapacity
|
||||
: new_options.max_open_files - 10);
|
||||
wal_changed = mutable_db_options_.wal_bytes_per_sync !=
|
||||
new_options.wal_bytes_per_sync;
|
||||
wal_other_option_changed = mutable_db_options_.wal_bytes_per_sync !=
|
||||
new_options.wal_bytes_per_sync;
|
||||
wal_size_option_changed = mutable_db_options_.max_total_wal_size !=
|
||||
new_options.max_total_wal_size;
|
||||
mutable_db_options_ = new_options;
|
||||
file_options_for_compaction_ = FileOptions(new_db_options);
|
||||
file_options_for_compaction_ = fs_->OptimizeForCompactionTableWrite(
|
||||
@@ -1337,19 +1392,21 @@ Status DBImpl::SetDBOptions(
|
||||
file_options_for_compaction_, immutable_db_options_);
|
||||
file_options_for_compaction_.compaction_readahead_size =
|
||||
mutable_db_options_.compaction_readahead_size;
|
||||
WriteThread::Writer w;
|
||||
write_thread_.EnterUnbatched(&w, &mutex_);
|
||||
if (total_log_size_ > GetMaxTotalWalSize() || wal_changed) {
|
||||
Status purge_wal_status = SwitchWAL(&write_context);
|
||||
if (!purge_wal_status.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Unable to purge WAL files in SetDBOptions() -- %s",
|
||||
purge_wal_status.ToString().c_str());
|
||||
if (wal_other_option_changed || wal_size_option_changed) {
|
||||
WriteThread::Writer w;
|
||||
write_thread_.EnterUnbatched(&w, &mutex_);
|
||||
if (wal_other_option_changed ||
|
||||
total_log_size_ > GetMaxTotalWalSize()) {
|
||||
Status purge_wal_status = SwitchWAL(&write_context);
|
||||
if (!purge_wal_status.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Unable to purge WAL files in SetDBOptions() -- %s",
|
||||
purge_wal_status.ToString().c_str());
|
||||
}
|
||||
}
|
||||
write_thread_.ExitUnbatched(&w);
|
||||
}
|
||||
persist_options_status = WriteOptionsFile(
|
||||
false /*need_mutex_lock*/, false /*need_enter_write_thread*/);
|
||||
write_thread_.ExitUnbatched(&w);
|
||||
persist_options_status = WriteOptionsFile(true /*db_mutex_already_held*/);
|
||||
} else {
|
||||
// To get here, we must have had invalid options and will not attempt to
|
||||
// persist the options, which means the status is "OK/Uninitialized.
|
||||
@@ -3292,14 +3349,86 @@ void DBImpl::MultiGetEntity(const ReadOptions& _read_options,
|
||||
statuses, sorted_input);
|
||||
}
|
||||
|
||||
void DBImpl::MultiGetEntity(const ReadOptions& _read_options, size_t num_keys,
|
||||
const Slice* keys,
|
||||
PinnableAttributeGroups* results) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kMultiGetEntity) {
|
||||
Status s = Status::InvalidArgument(
|
||||
"Can only call MultiGetEntity with ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kMultiGetEntity`");
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
for (size_t j = 0; j < results[i].size(); ++j) {
|
||||
results[i][j].SetStatus(s);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kMultiGetEntity;
|
||||
}
|
||||
|
||||
std::vector<ColumnFamilyHandle*> column_families;
|
||||
std::vector<Slice> all_keys;
|
||||
size_t total_count = 0;
|
||||
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
for (size_t j = 0; j < results[i].size(); ++j) {
|
||||
// Adding the same key slice for different CFs
|
||||
all_keys.emplace_back(keys[i]);
|
||||
column_families.emplace_back(results[i][j].column_family());
|
||||
++total_count;
|
||||
}
|
||||
}
|
||||
std::vector<Status> statuses(total_count);
|
||||
std::vector<PinnableWideColumns> columns(total_count);
|
||||
MultiGetCommon(read_options, total_count, column_families.data(),
|
||||
all_keys.data(),
|
||||
/* values */ nullptr, columns.data(),
|
||||
/* timestamps */ nullptr, statuses.data(),
|
||||
/* sorted_input */ false);
|
||||
|
||||
// Set results
|
||||
size_t index = 0;
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
for (size_t j = 0; j < results[i].size(); ++j) {
|
||||
results[i][j].Reset();
|
||||
results[i][j].SetStatus(std::move(statuses[index]));
|
||||
results[i][j].SetColumns(std::move(columns[index]));
|
||||
++index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Status DBImpl::WrapUpCreateColumnFamilies(
|
||||
const std::vector<const ColumnFamilyOptions*>& cf_options) {
|
||||
// NOTE: this function is skipped for create_missing_column_families and
|
||||
// DB::Open, so new functionality here might need to go into Open also.
|
||||
bool register_worker = false;
|
||||
for (auto* opts_ptr : cf_options) {
|
||||
if (opts_ptr->preserve_internal_time_seconds > 0 ||
|
||||
opts_ptr->preclude_last_level_data_seconds > 0) {
|
||||
register_worker = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Attempt both follow-up actions even if one fails
|
||||
Status s = WriteOptionsFile(false /*db_mutex_already_held*/);
|
||||
if (register_worker) {
|
||||
s.UpdateIfOk(RegisterRecordSeqnoTimeWorker(/*from_db_open=*/false));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::CreateColumnFamily(const ColumnFamilyOptions& cf_options,
|
||||
const std::string& column_family,
|
||||
ColumnFamilyHandle** handle) {
|
||||
assert(handle != nullptr);
|
||||
InstrumentedMutexLock ol(&options_mutex_);
|
||||
Status s = CreateColumnFamilyImpl(cf_options, column_family, handle);
|
||||
if (s.ok()) {
|
||||
s = WriteOptionsFile(true /*need_mutex_lock*/,
|
||||
true /*need_enter_write_thread*/);
|
||||
s.UpdateIfOk(WrapUpCreateColumnFamilies({&cf_options}));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -3309,6 +3438,7 @@ Status DBImpl::CreateColumnFamilies(
|
||||
const std::vector<std::string>& column_family_names,
|
||||
std::vector<ColumnFamilyHandle*>* handles) {
|
||||
assert(handles != nullptr);
|
||||
InstrumentedMutexLock ol(&options_mutex_);
|
||||
handles->clear();
|
||||
size_t num_cf = column_family_names.size();
|
||||
Status s;
|
||||
@@ -3323,11 +3453,7 @@ Status DBImpl::CreateColumnFamilies(
|
||||
success_once = true;
|
||||
}
|
||||
if (success_once) {
|
||||
Status persist_options_status = WriteOptionsFile(
|
||||
true /*need_mutex_lock*/, true /*need_enter_write_thread*/);
|
||||
if (s.ok() && !persist_options_status.ok()) {
|
||||
s = persist_options_status;
|
||||
}
|
||||
s.UpdateIfOk(WrapUpCreateColumnFamilies({&cf_options}));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -3336,10 +3462,13 @@ Status DBImpl::CreateColumnFamilies(
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
std::vector<ColumnFamilyHandle*>* handles) {
|
||||
assert(handles != nullptr);
|
||||
InstrumentedMutexLock ol(&options_mutex_);
|
||||
handles->clear();
|
||||
size_t num_cf = column_families.size();
|
||||
Status s;
|
||||
bool success_once = false;
|
||||
std::vector<const ColumnFamilyOptions*> cf_opts;
|
||||
cf_opts.reserve(num_cf);
|
||||
for (size_t i = 0; i < num_cf; i++) {
|
||||
ColumnFamilyHandle* handle;
|
||||
s = CreateColumnFamilyImpl(column_families[i].options,
|
||||
@@ -3349,13 +3478,10 @@ Status DBImpl::CreateColumnFamilies(
|
||||
}
|
||||
handles->push_back(handle);
|
||||
success_once = true;
|
||||
cf_opts.push_back(&column_families[i].options);
|
||||
}
|
||||
if (success_once) {
|
||||
Status persist_options_status = WriteOptionsFile(
|
||||
true /*need_mutex_lock*/, true /*need_enter_write_thread*/);
|
||||
if (s.ok() && !persist_options_status.ok()) {
|
||||
s = persist_options_status;
|
||||
}
|
||||
s.UpdateIfOk(WrapUpCreateColumnFamilies(cf_opts));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -3363,6 +3489,7 @@ Status DBImpl::CreateColumnFamilies(
|
||||
Status DBImpl::CreateColumnFamilyImpl(const ColumnFamilyOptions& cf_options,
|
||||
const std::string& column_family_name,
|
||||
ColumnFamilyHandle** handle) {
|
||||
options_mutex_.AssertHeld();
|
||||
// TODO: plumb Env::IOActivity
|
||||
const ReadOptions read_options;
|
||||
Status s;
|
||||
@@ -3443,10 +3570,6 @@ Status DBImpl::CreateColumnFamilyImpl(const ColumnFamilyOptions& cf_options,
|
||||
}
|
||||
} // InstrumentedMutexLock l(&mutex_)
|
||||
|
||||
if (cf_options.preserve_internal_time_seconds > 0 ||
|
||||
cf_options.preclude_last_level_data_seconds > 0) {
|
||||
s = RegisterRecordSeqnoTimeWorker();
|
||||
}
|
||||
sv_context.Clean();
|
||||
// this is outside the mutex
|
||||
if (s.ok()) {
|
||||
@@ -3458,16 +3581,17 @@ Status DBImpl::CreateColumnFamilyImpl(const ColumnFamilyOptions& cf_options,
|
||||
|
||||
Status DBImpl::DropColumnFamily(ColumnFamilyHandle* column_family) {
|
||||
assert(column_family != nullptr);
|
||||
InstrumentedMutexLock ol(&options_mutex_);
|
||||
Status s = DropColumnFamilyImpl(column_family);
|
||||
if (s.ok()) {
|
||||
s = WriteOptionsFile(true /*need_mutex_lock*/,
|
||||
true /*need_enter_write_thread*/);
|
||||
s = WriteOptionsFile(false /*db_mutex_already_held*/);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::DropColumnFamilies(
|
||||
const std::vector<ColumnFamilyHandle*>& column_families) {
|
||||
InstrumentedMutexLock ol(&options_mutex_);
|
||||
Status s;
|
||||
bool success_once = false;
|
||||
for (auto* handle : column_families) {
|
||||
@@ -3478,8 +3602,8 @@ Status DBImpl::DropColumnFamilies(
|
||||
success_once = true;
|
||||
}
|
||||
if (success_once) {
|
||||
Status persist_options_status = WriteOptionsFile(
|
||||
true /*need_mutex_lock*/, true /*need_enter_write_thread*/);
|
||||
Status persist_options_status =
|
||||
WriteOptionsFile(false /*db_mutex_already_held*/);
|
||||
if (s.ok() && !persist_options_status.ok()) {
|
||||
s = persist_options_status;
|
||||
}
|
||||
@@ -3540,7 +3664,7 @@ Status DBImpl::DropColumnFamilyImpl(ColumnFamilyHandle* column_family) {
|
||||
|
||||
if (cfd->ioptions()->preserve_internal_time_seconds > 0 ||
|
||||
cfd->ioptions()->preclude_last_level_data_seconds > 0) {
|
||||
s = RegisterRecordSeqnoTimeWorker();
|
||||
s = RegisterRecordSeqnoTimeWorker(/*from_db_open=*/false);
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
@@ -4047,7 +4171,8 @@ void DBImpl::ReleaseSnapshot(const Snapshot* s) {
|
||||
CfdList cf_scheduled;
|
||||
for (auto* cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (!cfd->ioptions()->allow_ingest_behind) {
|
||||
cfd->current()->storage_info()->UpdateOldestSnapshot(oldest_snapshot);
|
||||
cfd->current()->storage_info()->UpdateOldestSnapshot(
|
||||
oldest_snapshot, /*allow_ingest_behind=*/false);
|
||||
if (!cfd->current()
|
||||
->storage_info()
|
||||
->BottommostFilesMarkedForCompaction()
|
||||
@@ -5106,16 +5231,13 @@ Status DestroyDB(const std::string& dbname, const Options& options,
|
||||
return result;
|
||||
}
|
||||
|
||||
Status DBImpl::WriteOptionsFile(bool need_mutex_lock,
|
||||
bool need_enter_write_thread) {
|
||||
WriteThread::Writer w;
|
||||
if (need_mutex_lock) {
|
||||
mutex_.Lock();
|
||||
} else {
|
||||
Status DBImpl::WriteOptionsFile(bool db_mutex_already_held) {
|
||||
options_mutex_.AssertHeld();
|
||||
|
||||
if (db_mutex_already_held) {
|
||||
mutex_.AssertHeld();
|
||||
}
|
||||
if (need_enter_write_thread) {
|
||||
write_thread_.EnterUnbatched(&w, &mutex_);
|
||||
} else {
|
||||
mutex_.Lock();
|
||||
}
|
||||
|
||||
std::vector<std::string> cf_names;
|
||||
@@ -5130,10 +5252,10 @@ Status DBImpl::WriteOptionsFile(bool need_mutex_lock,
|
||||
cf_opts.push_back(cfd->GetLatestCFOptions());
|
||||
}
|
||||
|
||||
// Unlock during expensive operations. New writes cannot get here
|
||||
// because the single write thread ensures all new writes get queued.
|
||||
DBOptions db_options =
|
||||
BuildDBOptions(immutable_db_options_, mutable_db_options_);
|
||||
|
||||
// Unlock during expensive operations.
|
||||
mutex_.Unlock();
|
||||
|
||||
TEST_SYNC_POINT("DBImpl::WriteOptionsFile:1");
|
||||
@@ -5158,22 +5280,22 @@ Status DBImpl::WriteOptionsFile(bool need_mutex_lock,
|
||||
}
|
||||
}
|
||||
|
||||
// restore lock
|
||||
if (!need_mutex_lock) {
|
||||
mutex_.Lock();
|
||||
}
|
||||
if (need_enter_write_thread) {
|
||||
write_thread_.ExitUnbatched(&w);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Unnable to persist options -- %s", s.ToString().c_str());
|
||||
if (immutable_db_options_.fail_if_options_file_error) {
|
||||
return Status::IOError("Unable to persist options.",
|
||||
s.ToString().c_str());
|
||||
s = Status::IOError("Unable to persist options.", s.ToString().c_str());
|
||||
} else {
|
||||
// Ignore error
|
||||
s = Status::OK();
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
|
||||
// Restore lock if appropriate
|
||||
if (db_mutex_already_held) {
|
||||
mutex_.Lock();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -6368,19 +6490,51 @@ Status DBImpl::GetCreationTimeOfOldestFile(uint64_t* creation_time) {
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::RecordSeqnoToTimeMapping() {
|
||||
// Get time first then sequence number, so the actual time of seqno is <=
|
||||
// unix_time recorded
|
||||
int64_t unix_time = 0;
|
||||
immutable_db_options_.clock->GetCurrentTime(&unix_time)
|
||||
.PermitUncheckedError(); // Ignore error
|
||||
void DBImpl::RecordSeqnoToTimeMapping(uint64_t populate_historical_seconds) {
|
||||
// TECHNICALITY: Sample last sequence number *before* time, as prescribed
|
||||
// for SeqnoToTimeMapping. We don't know how long it has been since the last
|
||||
// sequence number was written, so we at least have a one-sided bound by
|
||||
// sampling in this order.
|
||||
SequenceNumber seqno = GetLatestSequenceNumber();
|
||||
int64_t unix_time_signed = 0;
|
||||
immutable_db_options_.clock->GetCurrentTime(&unix_time_signed)
|
||||
.PermitUncheckedError(); // Ignore error
|
||||
uint64_t unix_time = static_cast<uint64_t>(unix_time_signed);
|
||||
bool appended = false;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
appended = seqno_time_mapping_.Append(seqno, unix_time);
|
||||
if (populate_historical_seconds > 0) {
|
||||
if (seqno > 1 && unix_time > populate_historical_seconds) {
|
||||
// seqno=0 is reserved
|
||||
SequenceNumber from_seqno = 1;
|
||||
appended = seqno_to_time_mapping_.PrePopulate(
|
||||
from_seqno, seqno, unix_time - populate_historical_seconds,
|
||||
unix_time);
|
||||
} else {
|
||||
// One of these will fail
|
||||
assert(seqno > 1);
|
||||
assert(unix_time > populate_historical_seconds);
|
||||
}
|
||||
} else {
|
||||
// FIXME: assert(seqno > 0);
|
||||
appended = seqno_to_time_mapping_.Append(seqno, unix_time);
|
||||
}
|
||||
}
|
||||
if (!appended) {
|
||||
if (populate_historical_seconds > 0) {
|
||||
if (appended) {
|
||||
ROCKS_LOG_INFO(
|
||||
immutable_db_options_.info_log,
|
||||
"Pre-populated sequence number to time entries: [1,%" PRIu64
|
||||
"] -> [%" PRIu64 ",%" PRIu64 "]",
|
||||
seqno, unix_time - populate_historical_seconds, unix_time);
|
||||
} else {
|
||||
ROCKS_LOG_WARN(
|
||||
immutable_db_options_.info_log,
|
||||
"Failed to pre-populate sequence number to time entries: [1,%" PRIu64
|
||||
"] -> [%" PRIu64 ",%" PRIu64 "]",
|
||||
seqno, unix_time - populate_historical_seconds, unix_time);
|
||||
}
|
||||
} else if (!appended) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Failed to insert sequence number to time entry: %" PRIu64
|
||||
" -> %" PRIu64,
|
||||
|
||||
+54
-15
@@ -311,6 +311,9 @@ class DBImpl : public DB {
|
||||
ColumnFamilyHandle** column_families, const Slice* keys,
|
||||
PinnableWideColumns* results, Status* statuses,
|
||||
bool sorted_input) override;
|
||||
void MultiGetEntity(const ReadOptions& options, size_t num_keys,
|
||||
const Slice* keys,
|
||||
PinnableAttributeGroups* results) override;
|
||||
|
||||
virtual Status CreateColumnFamily(const ColumnFamilyOptions& cf_options,
|
||||
const std::string& column_family,
|
||||
@@ -1197,6 +1200,10 @@ class DBImpl : public DB {
|
||||
|
||||
const PeriodicTaskScheduler& TEST_GetPeriodicTaskScheduler() const;
|
||||
|
||||
static Status TEST_ValidateOptions(const DBOptions& db_options) {
|
||||
return ValidateOptions(db_options);
|
||||
}
|
||||
|
||||
#endif // NDEBUG
|
||||
|
||||
// persist stats to column family "_persistent_stats"
|
||||
@@ -1208,8 +1215,11 @@ class DBImpl : public DB {
|
||||
// flush LOG out of application buffer
|
||||
void FlushInfoLog();
|
||||
|
||||
// record current sequence number to time mapping
|
||||
void RecordSeqnoToTimeMapping();
|
||||
// record current sequence number to time mapping. If
|
||||
// populate_historical_seconds > 0 then pre-populate all the
|
||||
// sequence numbers from [1, last] to map to [now minus
|
||||
// populate_historical_seconds, now].
|
||||
void RecordSeqnoToTimeMapping(uint64_t populate_historical_seconds);
|
||||
|
||||
// Interface to block and signal the DB in case of stalling writes by
|
||||
// WriteBufferManager. Each DBImpl object contains ptr to WBMStallInterface.
|
||||
@@ -1383,15 +1393,15 @@ class DBImpl : public DB {
|
||||
autovector<ColumnFamilyData*> cfds_;
|
||||
autovector<const MutableCFOptions*> mutable_cf_opts_;
|
||||
autovector<autovector<VersionEdit*>> edit_lists_;
|
||||
// files_to_delete_ contains sst files
|
||||
std::unordered_set<std::string> files_to_delete_;
|
||||
// Stale SST files to delete found upon recovery. This stores a mapping from
|
||||
// such a file's absolute path to its parent directory.
|
||||
std::unordered_map<std::string, std::string> files_to_delete_;
|
||||
bool is_new_db_ = false;
|
||||
};
|
||||
|
||||
// Except in DB::Open(), WriteOptionsFile can only be called when:
|
||||
// Persist options to options file.
|
||||
// If need_mutex_lock = false, the method will lock DB mutex.
|
||||
// If need_enter_write_thread = false, the method will enter write thread.
|
||||
Status WriteOptionsFile(bool need_mutex_lock, bool need_enter_write_thread);
|
||||
// Persist options to options file. Must be holding options_mutex_.
|
||||
// Will lock DB mutex if !db_mutex_already_held.
|
||||
Status WriteOptionsFile(bool db_mutex_already_held);
|
||||
|
||||
Status CompactRangeInternal(const CompactRangeOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
@@ -1819,10 +1829,15 @@ class DBImpl : public DB {
|
||||
|
||||
const Status CreateArchivalDirectory();
|
||||
|
||||
// Create a column family, without some of the follow-up work yet
|
||||
Status CreateColumnFamilyImpl(const ColumnFamilyOptions& cf_options,
|
||||
const std::string& cf_name,
|
||||
ColumnFamilyHandle** handle);
|
||||
|
||||
// Follow-up work to user creating a column family or (families)
|
||||
Status WrapUpCreateColumnFamilies(
|
||||
const std::vector<const ColumnFamilyOptions*>& cf_options);
|
||||
|
||||
Status DropColumnFamilyImpl(ColumnFamilyHandle* column_family);
|
||||
|
||||
// Delete any unneeded files and stale in-memory entries.
|
||||
@@ -1852,7 +1867,8 @@ class DBImpl : public DB {
|
||||
void ReleaseFileNumberFromPendingOutputs(
|
||||
std::unique_ptr<std::list<uint64_t>::iterator>& v);
|
||||
|
||||
IOStatus SyncClosedLogs(JobContext* job_context, VersionEdit* synced_wals);
|
||||
IOStatus SyncClosedLogs(JobContext* job_context, VersionEdit* synced_wals,
|
||||
bool error_recovery_in_prog);
|
||||
|
||||
// Flush the in-memory write buffer to storage. Switches to a new
|
||||
// log-file/memtable and writes a new descriptor iff successful. Then
|
||||
@@ -1947,6 +1963,8 @@ class DBImpl : public DB {
|
||||
const autovector<ColumnFamilyData*>& provided_candidate_cfds = {},
|
||||
bool entered_write_thread = false);
|
||||
|
||||
Status RetryFlushesForErrorRecovery(FlushReason flush_reason, bool wait);
|
||||
|
||||
// Wait until flushing this column family won't stall writes
|
||||
Status WaitUntilFlushWouldNotStallWrites(ColumnFamilyData* cfd,
|
||||
bool* flush_needed);
|
||||
@@ -2095,6 +2113,12 @@ class DBImpl : public DB {
|
||||
#endif /* !NDEBUG */
|
||||
};
|
||||
|
||||
// In case of atomic flush, generates a `FlushRequest` for the latest atomic
|
||||
// cuts for these `cfds`. Atomic cuts are recorded in
|
||||
// `AssignAtomicFlushSeq()`. For each entry in `cfds`, all CFDs sharing the
|
||||
// same latest atomic cut must also be present.
|
||||
//
|
||||
// REQUIRES: mutex held
|
||||
void GenerateFlushRequest(const autovector<ColumnFamilyData*>& cfds,
|
||||
FlushReason flush_reason, FlushRequest* req);
|
||||
|
||||
@@ -2146,7 +2170,7 @@ class DBImpl : public DB {
|
||||
// Cancel scheduled periodic tasks
|
||||
Status CancelPeriodicTaskScheduler();
|
||||
|
||||
Status RegisterRecordSeqnoTimeWorker();
|
||||
Status RegisterRecordSeqnoTimeWorker(bool is_new_db);
|
||||
|
||||
void PrintStatistics();
|
||||
|
||||
@@ -2226,6 +2250,7 @@ class DBImpl : public DB {
|
||||
bool ShouldntRunManualCompaction(ManualCompactionState* m);
|
||||
bool HaveManualCompaction(ColumnFamilyData* cfd);
|
||||
bool MCOverlap(ManualCompactionState* m, ManualCompactionState* m1);
|
||||
void UpdateDeletionCompactionStats(const std::unique_ptr<Compaction>& c);
|
||||
void BuildCompactionJobInfo(const ColumnFamilyData* cfd, Compaction* c,
|
||||
const Status& st,
|
||||
const CompactionJobStats& compaction_job_stats,
|
||||
@@ -2351,6 +2376,10 @@ class DBImpl : public DB {
|
||||
|
||||
Status DisableFileDeletionsWithLock();
|
||||
|
||||
// Safely decrease `disable_delete_obsolete_files_` by one while holding lock
|
||||
// and return its remaning value.
|
||||
int EnableFileDeletionsWithLock();
|
||||
|
||||
Status IncreaseFullHistoryTsLowImpl(ColumnFamilyData* cfd,
|
||||
std::string ts_low);
|
||||
|
||||
@@ -2359,9 +2388,19 @@ class DBImpl : public DB {
|
||||
// Lock over the persistent DB state. Non-nullptr iff successfully acquired.
|
||||
FileLock* db_lock_;
|
||||
|
||||
// In addition to mutex_, log_write_mutex_ protected writes to stats_history_
|
||||
// Guards changes to DB and CF options to ensure consistency between
|
||||
// * In-memory options objects
|
||||
// * Settings in effect
|
||||
// * Options file contents
|
||||
// while allowing the DB mutex to be released during slow operations like
|
||||
// persisting options file or modifying global periodic task timer.
|
||||
// Always acquired *before* DB mutex when this one is applicable.
|
||||
InstrumentedMutex options_mutex_;
|
||||
|
||||
// Guards reads and writes to in-memory stats_history_.
|
||||
InstrumentedMutex stats_history_mutex_;
|
||||
// In addition to mutex_, log_write_mutex_ protected writes to logs_ and
|
||||
|
||||
// In addition to mutex_, log_write_mutex_ protects writes to logs_ and
|
||||
// logfile_number_. With two_write_queues it also protects alive_log_files_,
|
||||
// and log_empty_. Refer to the definition of each variable below for more
|
||||
// details.
|
||||
@@ -2748,9 +2787,9 @@ class DBImpl : public DB {
|
||||
// Pointer to WriteBufferManager stalling interface.
|
||||
std::unique_ptr<StallInterface> wbm_stall_;
|
||||
|
||||
// seqno_time_mapping_ stores the sequence number to time mapping, it's not
|
||||
// seqno_to_time_mapping_ stores the sequence number to time mapping, it's not
|
||||
// thread safe, both read and write need db mutex hold.
|
||||
SeqnoToTimeMapping seqno_time_mapping_;
|
||||
SeqnoToTimeMapping seqno_to_time_mapping_;
|
||||
|
||||
// Stop write token that is acquired when first LockWAL() is called.
|
||||
// Destroyed when last UnlockWAL() is called. Controlled by DB mutex.
|
||||
|
||||
@@ -113,7 +113,8 @@ bool DBImpl::ShouldRescheduleFlushRequestToRetainUDT(
|
||||
}
|
||||
|
||||
IOStatus DBImpl::SyncClosedLogs(JobContext* job_context,
|
||||
VersionEdit* synced_wals) {
|
||||
VersionEdit* synced_wals,
|
||||
bool error_recovery_in_prog) {
|
||||
TEST_SYNC_POINT("DBImpl::SyncClosedLogs:Start");
|
||||
InstrumentedMutexLock l(&log_write_mutex_);
|
||||
autovector<log::Writer*, 1> logs_to_sync;
|
||||
@@ -139,7 +140,7 @@ IOStatus DBImpl::SyncClosedLogs(JobContext* job_context,
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"[JOB %d] Syncing log #%" PRIu64, job_context->job_id,
|
||||
log->get_log_number());
|
||||
if (error_handler_.IsRecoveryInProgress()) {
|
||||
if (error_recovery_in_prog) {
|
||||
log->file()->reset_seen_error();
|
||||
}
|
||||
io_s = log->file()->Sync(immutable_db_options_.use_fsync);
|
||||
@@ -148,7 +149,7 @@ IOStatus DBImpl::SyncClosedLogs(JobContext* job_context,
|
||||
}
|
||||
|
||||
if (immutable_db_options_.recycle_log_file_num > 0) {
|
||||
if (error_handler_.IsRecoveryInProgress()) {
|
||||
if (error_recovery_in_prog) {
|
||||
log->file()->reset_seen_error();
|
||||
}
|
||||
io_s = log->Close();
|
||||
@@ -222,9 +223,10 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
// `snapshot_seqs` has already been computed before this function starts.
|
||||
// Recording the max memtable ID ensures that the flush job does not flush
|
||||
// a memtable without knowing such snapshot(s).
|
||||
uint64_t max_memtable_id = needs_to_sync_closed_wals
|
||||
? cfd->imm()->GetLatestMemTableID()
|
||||
: std::numeric_limits<uint64_t>::max();
|
||||
uint64_t max_memtable_id =
|
||||
needs_to_sync_closed_wals
|
||||
? cfd->imm()->GetLatestMemTableID(false /* for_atomic_flush */)
|
||||
: std::numeric_limits<uint64_t>::max();
|
||||
|
||||
// If needs_to_sync_closed_wals is false, then the flush job will pick ALL
|
||||
// existing memtables of the column family when PickMemTable() is called
|
||||
@@ -233,7 +235,7 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
// releases and re-acquires the db mutex. In the meantime, the application
|
||||
// can still insert into the memtables and increase the db's sequence number.
|
||||
// The application can take a snapshot, hoping that the latest visible state
|
||||
// to this snapshto is preserved. This is hard to guarantee since db mutex
|
||||
// to this snapshot is preserved. This is hard to guarantee since db mutex
|
||||
// not held. This newly-created snapshot is not included in `snapshot_seqs`
|
||||
// and the flush job is unaware of its presence. Consequently, the flush job
|
||||
// may drop certain keys when generating the L0, causing incorrect data to be
|
||||
@@ -250,7 +252,7 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
GetCompressionFlush(*cfd->ioptions(), mutable_cf_options), stats_,
|
||||
&event_logger_, mutable_cf_options.report_bg_io_stats,
|
||||
true /* sync_output_directory */, true /* write_manifest */, thread_pri,
|
||||
io_tracer_, seqno_time_mapping_, db_id_, db_session_id_,
|
||||
io_tracer_, seqno_to_time_mapping_, db_id_, db_session_id_,
|
||||
cfd->GetFullHistoryTsLow(), &blob_callback_);
|
||||
FileMetaData file_meta;
|
||||
|
||||
@@ -261,8 +263,10 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
// SyncClosedLogs() may unlock and re-lock the log_write_mutex multiple
|
||||
// times.
|
||||
VersionEdit synced_wals;
|
||||
bool error_recovery_in_prog = error_handler_.IsRecoveryInProgress();
|
||||
mutex_.Unlock();
|
||||
log_io_s = SyncClosedLogs(job_context, &synced_wals);
|
||||
log_io_s =
|
||||
SyncClosedLogs(job_context, &synced_wals, error_recovery_in_prog);
|
||||
mutex_.Lock();
|
||||
if (log_io_s.ok() && synced_wals.IsWalAddition()) {
|
||||
const ReadOptions read_options(Env::IOActivity::kFlush);
|
||||
@@ -522,7 +526,7 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
GetCompressionFlush(*cfd->ioptions(), mutable_cf_options), stats_,
|
||||
&event_logger_, mutable_cf_options.report_bg_io_stats,
|
||||
false /* sync_output_directory */, false /* write_manifest */,
|
||||
thread_pri, io_tracer_, seqno_time_mapping_, db_id_, db_session_id_,
|
||||
thread_pri, io_tracer_, seqno_to_time_mapping_, db_id_, db_session_id_,
|
||||
cfd->GetFullHistoryTsLow(), &blob_callback_));
|
||||
}
|
||||
|
||||
@@ -546,8 +550,10 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
// TODO (yanqin) investigate whether we should sync the closed logs for
|
||||
// single column family case.
|
||||
VersionEdit synced_wals;
|
||||
bool error_recovery_in_prog = error_handler_.IsRecoveryInProgress();
|
||||
mutex_.Unlock();
|
||||
log_io_s = SyncClosedLogs(job_context, &synced_wals);
|
||||
log_io_s =
|
||||
SyncClosedLogs(job_context, &synced_wals, error_recovery_in_prog);
|
||||
mutex_.Lock();
|
||||
if (log_io_s.ok() && synced_wals.IsWalAddition()) {
|
||||
const ReadOptions read_options(Env::IOActivity::kFlush);
|
||||
@@ -2230,7 +2236,8 @@ void DBImpl::GenerateFlushRequest(const autovector<ColumnFamilyData*>& cfds,
|
||||
// cfd may be null, see DBImpl::ScheduleFlushes
|
||||
continue;
|
||||
}
|
||||
uint64_t max_memtable_id = cfd->imm()->GetLatestMemTableID();
|
||||
uint64_t max_memtable_id = cfd->imm()->GetLatestMemTableID(
|
||||
immutable_db_options_.atomic_flush /* for_atomic_flush */);
|
||||
req->cfd_to_max_mem_id_to_persist.emplace(cfd, max_memtable_id);
|
||||
}
|
||||
}
|
||||
@@ -2274,19 +2281,7 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
|
||||
}
|
||||
WaitForPendingWrites();
|
||||
|
||||
if (flush_reason != FlushReason::kErrorRecoveryRetryFlush &&
|
||||
flush_reason != FlushReason::kCatchUpAfterErrorRecovery &&
|
||||
(!cfd->mem()->IsEmpty() || !cached_recoverable_state_empty_.load())) {
|
||||
// Note that, when flush reason is kErrorRecoveryRetryFlush, during the
|
||||
// auto retry resume, we want to avoid creating new small memtables.
|
||||
// If flush reason is kCatchUpAfterErrorRecovery, we try to flush any new
|
||||
// memtable that filled up during recovery, and we also want to avoid
|
||||
// switching memtable to create small memtables.
|
||||
// Therefore, SwitchMemtable will not be called. Also, since ResumeImpl
|
||||
// will iterate through all the CFs and call FlushMemtable during auto
|
||||
// retry resume, it is possible that in some CFs,
|
||||
// cfd->imm()->NumNotFlushed() = 0. In this case, so no flush request will
|
||||
// be created and scheduled, status::OK() will be returned.
|
||||
if (!cfd->mem()->IsEmpty() || !cached_recoverable_state_empty_.load()) {
|
||||
s = SwitchMemtable(cfd, &context);
|
||||
}
|
||||
const uint64_t flush_memtable_id = std::numeric_limits<uint64_t>::max();
|
||||
@@ -2295,10 +2290,10 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
|
||||
!cached_recoverable_state_empty_.load()) {
|
||||
FlushRequest req{flush_reason, {{cfd, flush_memtable_id}}};
|
||||
flush_reqs.emplace_back(std::move(req));
|
||||
memtable_ids_to_wait.emplace_back(cfd->imm()->GetLatestMemTableID());
|
||||
memtable_ids_to_wait.emplace_back(
|
||||
cfd->imm()->GetLatestMemTableID(false /* for_atomic_flush */));
|
||||
}
|
||||
if (immutable_db_options_.persist_stats_to_disk &&
|
||||
flush_reason != FlushReason::kErrorRecoveryRetryFlush) {
|
||||
if (immutable_db_options_.persist_stats_to_disk) {
|
||||
ColumnFamilyData* cfd_stats =
|
||||
versions_->GetColumnFamilySet()->GetColumnFamily(
|
||||
kPersistentStatsColumnFamilyName);
|
||||
@@ -2324,7 +2319,8 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
|
||||
FlushRequest req{flush_reason, {{cfd_stats, flush_memtable_id}}};
|
||||
flush_reqs.emplace_back(std::move(req));
|
||||
memtable_ids_to_wait.emplace_back(
|
||||
cfd_stats->imm()->GetLatestMemTableID());
|
||||
cfd_stats->imm()->GetLatestMemTableID(
|
||||
false /* for_atomic_flush */));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2375,8 +2371,7 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
|
||||
}
|
||||
s = WaitForFlushMemTables(
|
||||
cfds, flush_memtable_ids,
|
||||
(flush_reason == FlushReason::kErrorRecovery ||
|
||||
flush_reason == FlushReason::kErrorRecoveryRetryFlush));
|
||||
flush_reason == FlushReason::kErrorRecovery /* resuming_from_bg_err */);
|
||||
InstrumentedMutexLock lock_guard(&mutex_);
|
||||
for (auto* tmp_cfd : cfds) {
|
||||
tmp_cfd->UnrefAndTryDelete();
|
||||
@@ -2471,9 +2466,7 @@ Status DBImpl::AtomicFlushMemTables(
|
||||
}
|
||||
|
||||
for (auto cfd : cfds) {
|
||||
if ((cfd->mem()->IsEmpty() && cached_recoverable_state_empty_.load()) ||
|
||||
flush_reason == FlushReason::kErrorRecoveryRetryFlush ||
|
||||
flush_reason == FlushReason::kCatchUpAfterErrorRecovery) {
|
||||
if (cfd->mem()->IsEmpty() && cached_recoverable_state_empty_.load()) {
|
||||
continue;
|
||||
}
|
||||
cfd->Ref();
|
||||
@@ -2518,8 +2511,7 @@ Status DBImpl::AtomicFlushMemTables(
|
||||
}
|
||||
s = WaitForFlushMemTables(
|
||||
cfds, flush_memtable_ids,
|
||||
(flush_reason == FlushReason::kErrorRecovery ||
|
||||
flush_reason == FlushReason::kErrorRecoveryRetryFlush));
|
||||
flush_reason == FlushReason::kErrorRecovery /* resuming_from_bg_err */);
|
||||
InstrumentedMutexLock lock_guard(&mutex_);
|
||||
for (auto* cfd : cfds) {
|
||||
cfd->UnrefAndTryDelete();
|
||||
@@ -2528,6 +2520,68 @@ Status DBImpl::AtomicFlushMemTables(
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::RetryFlushesForErrorRecovery(FlushReason flush_reason,
|
||||
bool wait) {
|
||||
mutex_.AssertHeld();
|
||||
assert(flush_reason == FlushReason::kErrorRecoveryRetryFlush ||
|
||||
flush_reason == FlushReason::kCatchUpAfterErrorRecovery);
|
||||
|
||||
// Collect referenced CFDs.
|
||||
autovector<ColumnFamilyData*> cfds;
|
||||
for (ColumnFamilyData* cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (!cfd->IsDropped() && cfd->initialized() &&
|
||||
cfd->imm()->NumNotFlushed() != 0) {
|
||||
cfd->Ref();
|
||||
cfd->imm()->FlushRequested();
|
||||
cfds.push_back(cfd);
|
||||
}
|
||||
}
|
||||
|
||||
// Submit flush requests for all immutable memtables needing flush.
|
||||
// `flush_memtable_ids` will be populated such that all immutable
|
||||
// memtables eligible for flush are waited on before this function
|
||||
// returns.
|
||||
autovector<uint64_t> flush_memtable_ids;
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
FlushRequest flush_req;
|
||||
GenerateFlushRequest(cfds, flush_reason, &flush_req);
|
||||
SchedulePendingFlush(flush_req);
|
||||
for (auto& iter : flush_req.cfd_to_max_mem_id_to_persist) {
|
||||
flush_memtable_ids.push_back(iter.second);
|
||||
}
|
||||
} else {
|
||||
for (auto cfd : cfds) {
|
||||
flush_memtable_ids.push_back(
|
||||
cfd->imm()->GetLatestMemTableID(false /* for_atomic_flush */));
|
||||
// Impose no bound on the highest memtable ID flushed. There is no
|
||||
// reason to do so outside of atomic flush.
|
||||
FlushRequest flush_req{
|
||||
flush_reason,
|
||||
{{cfd,
|
||||
std::numeric_limits<uint64_t>::max() /* max_mem_id_to_persist */}}};
|
||||
SchedulePendingFlush(flush_req);
|
||||
}
|
||||
}
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
|
||||
Status s;
|
||||
if (wait) {
|
||||
mutex_.Unlock();
|
||||
autovector<const uint64_t*> flush_memtable_id_ptrs;
|
||||
for (auto& flush_memtable_id : flush_memtable_ids) {
|
||||
flush_memtable_id_ptrs.push_back(&flush_memtable_id);
|
||||
}
|
||||
s = WaitForFlushMemTables(cfds, flush_memtable_id_ptrs,
|
||||
true /* resuming_from_bg_err */);
|
||||
mutex_.Lock();
|
||||
}
|
||||
|
||||
for (auto* cfd : cfds) {
|
||||
cfd->UnrefAndTryDelete();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// Calling FlushMemTable(), whether from DB::Flush() or from Backup Engine, can
|
||||
// cause write stall, for example if one memtable is being flushed already.
|
||||
// This method tries to avoid write stall (similar to CompactRange() behavior)
|
||||
@@ -3650,6 +3704,9 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
ROCKS_LOG_BUFFER(log_buffer, "[%s] Deleted %d files\n",
|
||||
c->column_family_data()->GetName().c_str(),
|
||||
c->num_input_files(0));
|
||||
if (status.ok() && io_s.ok()) {
|
||||
UpdateDeletionCompactionStats(c);
|
||||
}
|
||||
*made_progress = true;
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:AfterCompaction",
|
||||
c->column_family_data());
|
||||
@@ -4028,6 +4085,27 @@ bool DBImpl::MCOverlap(ManualCompactionState* m, ManualCompactionState* m1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void DBImpl::UpdateDeletionCompactionStats(
|
||||
const std::unique_ptr<Compaction>& c) {
|
||||
if (c == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
CompactionReason reason = c->compaction_reason();
|
||||
|
||||
switch (reason) {
|
||||
case CompactionReason::kFIFOMaxSize:
|
||||
RecordTick(stats_, FIFO_MAX_SIZE_COMPACTIONS);
|
||||
break;
|
||||
case CompactionReason::kFIFOTtl:
|
||||
RecordTick(stats_, FIFO_TTL_COMPACTIONS);
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::BuildCompactionJobInfo(
|
||||
const ColumnFamilyData* cfd, Compaction* c, const Status& st,
|
||||
const CompactionJobStats& compaction_job_stats, const int job_id,
|
||||
|
||||
@@ -306,11 +306,12 @@ const PeriodicTaskScheduler& DBImpl::TEST_GetPeriodicTaskScheduler() const {
|
||||
|
||||
SeqnoToTimeMapping DBImpl::TEST_GetSeqnoToTimeMapping() const {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
return seqno_time_mapping_;
|
||||
return seqno_to_time_mapping_;
|
||||
}
|
||||
|
||||
|
||||
size_t DBImpl::TEST_EstimateInMemoryStatsHistorySize() const {
|
||||
InstrumentedMutexLock l(&const_cast<DBImpl*>(this)->stats_history_mutex_);
|
||||
return EstimateInMemoryStatsHistorySize();
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -100,6 +100,14 @@ Status DBImpl::EnableFileDeletions(bool force) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
int DBImpl::EnableFileDeletionsWithLock() {
|
||||
mutex_.AssertHeld();
|
||||
// In case others have called EnableFileDeletions(true /* force */) in between
|
||||
disable_delete_obsolete_files_ =
|
||||
std::max(0, disable_delete_obsolete_files_ - 1);
|
||||
return disable_delete_obsolete_files_;
|
||||
}
|
||||
|
||||
bool DBImpl::IsFileDeletionsEnabled() const {
|
||||
return 0 == disable_delete_obsolete_files_;
|
||||
}
|
||||
@@ -995,7 +1003,7 @@ Status DBImpl::DeleteUnreferencedSstFiles(RecoveryContext* recovery_ctx) {
|
||||
if (type == kTableFile && number >= next_file_number &&
|
||||
recovery_ctx->files_to_delete_.find(normalized_fpath) ==
|
||||
recovery_ctx->files_to_delete_.end()) {
|
||||
recovery_ctx->files_to_delete_.emplace(normalized_fpath);
|
||||
recovery_ctx->files_to_delete_.emplace(normalized_fpath, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+33
-13
@@ -25,6 +25,7 @@
|
||||
#include "rocksdb/wal_filter.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/rate_limiter_impl.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/udt_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -291,6 +292,18 @@ Status DBImpl::ValidateOptions(const DBOptions& db_options) {
|
||||
"writes in direct IO require writable_file_max_buffer_size > 0");
|
||||
}
|
||||
|
||||
if (db_options.daily_offpeak_time_utc != "") {
|
||||
int start_time, end_time;
|
||||
if (!TryParseTimeRangeString(db_options.daily_offpeak_time_utc, start_time,
|
||||
end_time)) {
|
||||
return Status::InvalidArgument(
|
||||
"daily_offpeak_time_utc should be set in the format HH:mm-HH:mm "
|
||||
"(e.g. 04:30-07:30)");
|
||||
} else if (start_time == end_time) {
|
||||
return Status::InvalidArgument(
|
||||
"start_time and end_time cannot be the same");
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -405,7 +418,8 @@ Status DBImpl::Recover(
|
||||
uint64_t* recovered_seq, RecoveryContext* recovery_ctx) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
bool is_new_db = false;
|
||||
bool tmp_is_new_db = false;
|
||||
bool& is_new_db = recovery_ctx ? recovery_ctx->is_new_db_ : tmp_is_new_db;
|
||||
assert(db_lock_ == nullptr);
|
||||
std::vector<std::string> files_in_dbname;
|
||||
if (!read_only) {
|
||||
@@ -858,7 +872,8 @@ Status DBImpl::PersistentStatsProcessFormatVersion() {
|
||||
if (s.ok()) {
|
||||
ColumnFamilyOptions cfo;
|
||||
OptimizeForPersistentStats(&cfo);
|
||||
s = CreateColumnFamily(cfo, kPersistentStatsColumnFamilyName, &handle);
|
||||
s = CreateColumnFamilyImpl(cfo, kPersistentStatsColumnFamilyName,
|
||||
&handle);
|
||||
}
|
||||
if (s.ok()) {
|
||||
persist_stats_cf_handle_ = static_cast<ColumnFamilyHandleImpl*>(handle);
|
||||
@@ -911,7 +926,7 @@ Status DBImpl::InitPersistStatsColumnFamily() {
|
||||
ColumnFamilyHandle* handle = nullptr;
|
||||
ColumnFamilyOptions cfo;
|
||||
OptimizeForPersistentStats(&cfo);
|
||||
s = CreateColumnFamily(cfo, kPersistentStatsColumnFamilyName, &handle);
|
||||
s = CreateColumnFamilyImpl(cfo, kPersistentStatsColumnFamilyName, &handle);
|
||||
persist_stats_cf_handle_ = static_cast<ColumnFamilyHandleImpl*>(handle);
|
||||
mutex_.Lock();
|
||||
}
|
||||
@@ -927,8 +942,11 @@ Status DBImpl::LogAndApplyForRecovery(const RecoveryContext& recovery_ctx) {
|
||||
recovery_ctx.edit_lists_, &mutex_, directories_.GetDbDir());
|
||||
if (s.ok() && !(recovery_ctx.files_to_delete_.empty())) {
|
||||
mutex_.Unlock();
|
||||
for (const auto& fname : recovery_ctx.files_to_delete_) {
|
||||
s = env_->DeleteFile(fname);
|
||||
for (const auto& stale_sst_file : recovery_ctx.files_to_delete_) {
|
||||
s = DeleteDBFile(&immutable_db_options_, stale_sst_file.first,
|
||||
stale_sst_file.second,
|
||||
/*force_bg=*/false,
|
||||
/*force_fg=*/false);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
@@ -1649,7 +1667,7 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
TableFileCreationReason::kRecovery, 0 /* oldest_key_time */,
|
||||
0 /* file_creation_time */, db_id_, db_session_id_,
|
||||
0 /* target_file_size */, meta.fd.GetNumber());
|
||||
SeqnoToTimeMapping empty_seqno_time_mapping;
|
||||
SeqnoToTimeMapping empty_seqno_to_time_mapping;
|
||||
Version* version = cfd->current();
|
||||
version->Ref();
|
||||
const ReadOptions read_option(Env::IOActivity::kDBOpen);
|
||||
@@ -1661,7 +1679,7 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
snapshot_seqs, earliest_write_conflict_snapshot, kMaxSequenceNumber,
|
||||
snapshot_checker, paranoid_file_checks, cfd->internal_stats(), &io_s,
|
||||
io_tracer_, BlobFileCreationReason::kRecovery,
|
||||
empty_seqno_time_mapping, &event_logger_, job_id, Env::IO_HIGH,
|
||||
empty_seqno_to_time_mapping, &event_logger_, job_id, Env::IO_HIGH,
|
||||
nullptr /* table_properties */, write_hint,
|
||||
nullptr /*full_history_ts_low*/, &blob_callback_, version,
|
||||
&num_input_entries);
|
||||
@@ -1975,6 +1993,7 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
|
||||
impl->wal_in_db_path_ = impl->immutable_db_options_.IsWalDirSameAsDBPath();
|
||||
RecoveryContext recovery_ctx;
|
||||
impl->options_mutex_.Lock();
|
||||
impl->mutex_.Lock();
|
||||
|
||||
// Handles create_if_missing, error_if_exists
|
||||
@@ -2056,7 +2075,9 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
// missing column family, create it
|
||||
ColumnFamilyHandle* handle = nullptr;
|
||||
impl->mutex_.Unlock();
|
||||
s = impl->CreateColumnFamily(cf.options, cf.name, &handle);
|
||||
// NOTE: the work normally done in WrapUpCreateColumnFamilies will
|
||||
// be done separately below.
|
||||
s = impl->CreateColumnFamilyImpl(cf.options, cf.name, &handle);
|
||||
impl->mutex_.Lock();
|
||||
if (s.ok()) {
|
||||
handles->push_back(handle);
|
||||
@@ -2107,9 +2128,8 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
if (s.ok()) {
|
||||
// Persist RocksDB Options before scheduling the compaction.
|
||||
// The WriteOptionsFile() will release and lock the mutex internally.
|
||||
persist_options_status = impl->WriteOptionsFile(
|
||||
false /*need_mutex_lock*/, false /*need_enter_write_thread*/);
|
||||
|
||||
persist_options_status =
|
||||
impl->WriteOptionsFile(true /*db_mutex_already_held*/);
|
||||
*dbptr = impl;
|
||||
impl->opened_successfully_ = true;
|
||||
impl->DeleteObsoleteFiles();
|
||||
@@ -2230,10 +2250,10 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
if (s.ok()) {
|
||||
s = impl->StartPeriodicTaskScheduler();
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
s = impl->RegisterRecordSeqnoTimeWorker();
|
||||
s = impl->RegisterRecordSeqnoTimeWorker(recovery_ctx.is_new_db_);
|
||||
}
|
||||
impl->options_mutex_.Unlock();
|
||||
if (!s.ok()) {
|
||||
for (auto* h : *handles) {
|
||||
delete h;
|
||||
|
||||
+19
-16
@@ -403,17 +403,6 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
IOStatus io_s;
|
||||
Status pre_release_cb_status;
|
||||
if (status.ok()) {
|
||||
// TODO: this use of operator bool on `tracer_` can avoid unnecessary lock
|
||||
// grabs but does not seem thread-safe.
|
||||
if (tracer_) {
|
||||
InstrumentedMutexLock lock(&trace_mutex_);
|
||||
if (tracer_ && tracer_->IsWriteOrderPreserved()) {
|
||||
for (auto* writer : write_group) {
|
||||
// TODO: maybe handle the tracing status?
|
||||
tracer_->Write(writer->batch).PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Rules for when we can update the memtable concurrently
|
||||
// 1. supported by memtable
|
||||
// 2. Puts are not okay if inplace_update_support
|
||||
@@ -446,6 +435,20 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: this use of operator bool on `tracer_` can avoid unnecessary lock
|
||||
// grabs but does not seem thread-safe.
|
||||
if (tracer_) {
|
||||
InstrumentedMutexLock lock(&trace_mutex_);
|
||||
if (tracer_ && tracer_->IsWriteOrderPreserved()) {
|
||||
for (auto* writer : write_group) {
|
||||
if (writer->CallbackFailed()) {
|
||||
continue;
|
||||
}
|
||||
// TODO: maybe handle the tracing status?
|
||||
tracer_->Write(writer->batch).PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Note about seq_per_batch_: either disableWAL is set for the entire write
|
||||
// group or not. In either case we inc seq for each write batch with no
|
||||
// failed callback. This means that there could be a batch with
|
||||
@@ -1858,11 +1861,11 @@ Status DBImpl::DelayWrite(uint64_t num_bytes, WriteThread& write_thread,
|
||||
write_thread.EndWriteStall();
|
||||
}
|
||||
|
||||
// Don't wait if there's a background error, even if its a soft error. We
|
||||
// might wait here indefinitely as the background compaction may never
|
||||
// finish successfully, resulting in the stall condition lasting
|
||||
// indefinitely
|
||||
while (error_handler_.GetBGError().ok() && write_controller_.IsStopped() &&
|
||||
// Don't wait if there's a background error that is not pending recovery
|
||||
// since recovery might never be attempted.
|
||||
while ((error_handler_.GetBGError().ok() ||
|
||||
error_handler_.IsRecoveryInProgress()) &&
|
||||
write_controller_.IsStopped() &&
|
||||
!shutting_down_.load(std::memory_order_relaxed)) {
|
||||
if (write_options.no_slowdown) {
|
||||
return Status::Incomplete("Write stall");
|
||||
|
||||
+12
-4
@@ -83,7 +83,8 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
|
||||
cfd_(cfd),
|
||||
timestamp_ub_(read_options.timestamp),
|
||||
timestamp_lb_(read_options.iter_start_ts),
|
||||
timestamp_size_(timestamp_ub_ ? timestamp_ub_->size() : 0) {
|
||||
timestamp_size_(timestamp_ub_ ? timestamp_ub_->size() : 0),
|
||||
auto_readahead_size_(read_options.auto_readahead_size) {
|
||||
RecordTick(statistics_, NO_ITERATOR_CREATED);
|
||||
if (pin_thru_lifetime_) {
|
||||
pinned_iters_mgr_.StartPinning();
|
||||
@@ -743,15 +744,22 @@ bool DBIter::ReverseToBackward() {
|
||||
// When current_entry_is_merged_ is true, iter_ may be positioned on the next
|
||||
// key, which may not exist or may have prefix different from current.
|
||||
// If that's the case, seek to saved_key_.
|
||||
if (current_entry_is_merged_ &&
|
||||
(!expect_total_order_inner_iter() || !iter_.Valid())) {
|
||||
//
|
||||
// In case of auto_readahead_size enabled, index_iter moves forward during
|
||||
// forward scan for block cache lookup and points to different block. If Prev
|
||||
// op is called, it needs to call SeekForPrev to point to right index_iter_ in
|
||||
// BlockBasedTableIterator. This only happens when direction is changed from
|
||||
// forward to backward.
|
||||
if ((current_entry_is_merged_ &&
|
||||
(!expect_total_order_inner_iter() || !iter_.Valid())) ||
|
||||
auto_readahead_size_) {
|
||||
IterKey last_key;
|
||||
// Using kMaxSequenceNumber and kValueTypeForSeek
|
||||
// (not kValueTypeForSeekForPrev) to seek to a key strictly smaller
|
||||
// than saved_key_.
|
||||
last_key.SetInternalKey(ParsedInternalKey(
|
||||
saved_key_.GetUserKey(), kMaxSequenceNumber, kValueTypeForSeek));
|
||||
if (!expect_total_order_inner_iter()) {
|
||||
if (!expect_total_order_inner_iter() || auto_readahead_size_) {
|
||||
iter_.SeekForPrev(last_key.GetInternalKey());
|
||||
} else {
|
||||
// Some iterators may not support SeekForPrev(), so we avoid using it
|
||||
|
||||
@@ -402,6 +402,7 @@ class DBIter final : public Iterator {
|
||||
const Slice* const timestamp_lb_;
|
||||
const size_t timestamp_size_;
|
||||
std::string saved_timestamp_;
|
||||
bool auto_readahead_size_;
|
||||
};
|
||||
|
||||
// Return a new iterator that converts internal keys (yielded by
|
||||
|
||||
@@ -275,6 +275,7 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
|
||||
db_iter->Next();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
}
|
||||
// Test to check the SeekToLast() with iterate_upper_bound not set
|
||||
{
|
||||
@@ -1415,6 +1416,7 @@ TEST_F(DBIteratorTest, DBIterator1) {
|
||||
ASSERT_EQ(db_iter->key().ToString(), "b");
|
||||
db_iter->Next();
|
||||
ASSERT_FALSE(db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
}
|
||||
|
||||
TEST_F(DBIteratorTest, DBIterator2) {
|
||||
@@ -1528,6 +1530,7 @@ TEST_F(DBIteratorTest, DBIterator5) {
|
||||
ASSERT_EQ(db_iter->value().ToString(), "merge_1");
|
||||
db_iter->Prev();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1552,6 +1555,7 @@ TEST_F(DBIteratorTest, DBIterator5) {
|
||||
ASSERT_EQ(db_iter->value().ToString(), "merge_1,merge_2");
|
||||
db_iter->Prev();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1576,6 +1580,7 @@ TEST_F(DBIteratorTest, DBIterator5) {
|
||||
ASSERT_EQ(db_iter->value().ToString(), "merge_1,merge_2,merge_3");
|
||||
db_iter->Prev();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1600,6 +1605,7 @@ TEST_F(DBIteratorTest, DBIterator5) {
|
||||
ASSERT_EQ(db_iter->value().ToString(), "put_1");
|
||||
db_iter->Prev();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1624,6 +1630,7 @@ TEST_F(DBIteratorTest, DBIterator5) {
|
||||
ASSERT_EQ(db_iter->value().ToString(), "put_1,merge_4");
|
||||
db_iter->Prev();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1648,6 +1655,7 @@ TEST_F(DBIteratorTest, DBIterator5) {
|
||||
ASSERT_EQ(db_iter->value().ToString(), "put_1,merge_4,merge_5");
|
||||
db_iter->Prev();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1672,6 +1680,7 @@ TEST_F(DBIteratorTest, DBIterator5) {
|
||||
ASSERT_EQ(db_iter->value().ToString(), "put_1,merge_4,merge_5,merge_6");
|
||||
db_iter->Prev();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1726,6 +1735,7 @@ TEST_F(DBIteratorTest, DBIterator6) {
|
||||
ASSERT_EQ(db_iter->value().ToString(), "merge_1");
|
||||
db_iter->Prev();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1750,6 +1760,7 @@ TEST_F(DBIteratorTest, DBIterator6) {
|
||||
ASSERT_EQ(db_iter->value().ToString(), "merge_1,merge_2");
|
||||
db_iter->Prev();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1774,6 +1785,7 @@ TEST_F(DBIteratorTest, DBIterator6) {
|
||||
ASSERT_EQ(db_iter->value().ToString(), "merge_1,merge_2,merge_3");
|
||||
db_iter->Prev();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1794,6 +1806,7 @@ TEST_F(DBIteratorTest, DBIterator6) {
|
||||
nullptr /* read_callback */));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1818,6 +1831,7 @@ TEST_F(DBIteratorTest, DBIterator6) {
|
||||
ASSERT_EQ(db_iter->value().ToString(), "merge_4");
|
||||
db_iter->Prev();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1842,6 +1856,7 @@ TEST_F(DBIteratorTest, DBIterator6) {
|
||||
ASSERT_EQ(db_iter->value().ToString(), "merge_4,merge_5");
|
||||
db_iter->Prev();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1866,6 +1881,7 @@ TEST_F(DBIteratorTest, DBIterator6) {
|
||||
ASSERT_EQ(db_iter->value().ToString(), "merge_4,merge_5,merge_6");
|
||||
db_iter->Prev();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1910,6 +1926,7 @@ TEST_F(DBIteratorTest, DBIterator7) {
|
||||
ASSERT_EQ(db_iter->value().ToString(), "merge_1");
|
||||
db_iter->Prev();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1952,6 +1969,7 @@ TEST_F(DBIteratorTest, DBIterator7) {
|
||||
ASSERT_EQ(db_iter->value().ToString(), "merge_1");
|
||||
db_iter->Prev();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1994,6 +2012,7 @@ TEST_F(DBIteratorTest, DBIterator7) {
|
||||
ASSERT_EQ(db_iter->value().ToString(), "merge_1");
|
||||
db_iter->Prev();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -2041,6 +2060,7 @@ TEST_F(DBIteratorTest, DBIterator7) {
|
||||
ASSERT_EQ(db_iter->value().ToString(), "merge_1");
|
||||
db_iter->Prev();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -2089,6 +2109,7 @@ TEST_F(DBIteratorTest, DBIterator7) {
|
||||
ASSERT_EQ(db_iter->value().ToString(), "merge_1");
|
||||
db_iter->Prev();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -2131,6 +2152,7 @@ TEST_F(DBIteratorTest, DBIterator7) {
|
||||
ASSERT_EQ(db_iter->value().ToString(), "merge_1");
|
||||
db_iter->Prev();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -2179,6 +2201,7 @@ TEST_F(DBIteratorTest, DBIterator7) {
|
||||
ASSERT_EQ(db_iter->value().ToString(), "merge_1");
|
||||
db_iter->Prev();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -2228,6 +2251,7 @@ TEST_F(DBIteratorTest, DBIterator7) {
|
||||
ASSERT_EQ(db_iter->value().ToString(), "merge_1");
|
||||
db_iter->Prev();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -2271,6 +2295,7 @@ TEST_F(DBIteratorTest, DBIterator7) {
|
||||
ASSERT_EQ(db_iter->value().ToString(), "merge_1");
|
||||
db_iter->Prev();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2440,6 +2465,7 @@ TEST_F(DBIteratorTest, SeekToLastOccurrenceSeq0) {
|
||||
ASSERT_EQ(db_iter->value().ToString(), "2");
|
||||
db_iter->Next();
|
||||
ASSERT_FALSE(db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
}
|
||||
|
||||
TEST_F(DBIteratorTest, DBIterator11) {
|
||||
@@ -2469,6 +2495,7 @@ TEST_F(DBIteratorTest, DBIterator11) {
|
||||
ASSERT_EQ(db_iter->key().ToString(), "b");
|
||||
db_iter->Next();
|
||||
ASSERT_FALSE(db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
}
|
||||
|
||||
TEST_F(DBIteratorTest, DBIterator12) {
|
||||
@@ -2497,6 +2524,7 @@ TEST_F(DBIteratorTest, DBIterator12) {
|
||||
ASSERT_EQ(db_iter->value().ToString(), "1");
|
||||
db_iter->Prev();
|
||||
ASSERT_FALSE(db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
}
|
||||
|
||||
TEST_F(DBIteratorTest, DBIterator13) {
|
||||
@@ -2635,6 +2663,7 @@ TEST_F(DBIterWithMergeIterTest, InnerMergeIterator1) {
|
||||
ASSERT_EQ(db_iter_->value().ToString(), "3");
|
||||
db_iter_->Next();
|
||||
ASSERT_FALSE(db_iter_->Valid());
|
||||
ASSERT_OK(db_iter_->status());
|
||||
}
|
||||
|
||||
TEST_F(DBIterWithMergeIterTest, InnerMergeIterator2) {
|
||||
|
||||
+27
-9
@@ -183,6 +183,7 @@ TEST_P(DBIteratorTest, NonBlockingIteration) {
|
||||
ASSERT_OK(iter->status());
|
||||
count++;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(count, 1);
|
||||
delete iter;
|
||||
|
||||
@@ -217,6 +218,7 @@ TEST_P(DBIteratorTest, NonBlockingIteration) {
|
||||
ASSERT_OK(iter->status());
|
||||
count++;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(count, 1);
|
||||
ASSERT_EQ(numopen, TestGetTickerCount(options, NO_FILE_OPENS));
|
||||
ASSERT_EQ(cache_added, TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
||||
@@ -870,6 +872,7 @@ TEST_P(DBIteratorTest, IterWithSnapshot) {
|
||||
}
|
||||
}
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
ASSERT_OK(iter->status());
|
||||
delete iter;
|
||||
} while (ChangeOptions());
|
||||
}
|
||||
@@ -1214,6 +1217,7 @@ TEST_P(DBIteratorTest, DBIteratorBoundOptimizationTest) {
|
||||
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(upper_bound_hits, 1);
|
||||
}
|
||||
}
|
||||
@@ -1338,6 +1342,7 @@ TEST_P(DBIteratorTest, IndexWithFirstKey) {
|
||||
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
EXPECT_EQ(3, stats->getTickerCount(BLOCK_CACHE_DATA_HIT));
|
||||
EXPECT_EQ(7, stats->getTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
}
|
||||
@@ -1579,6 +1584,7 @@ class DBIteratorTestForPinnedData : public DBIteratorTest {
|
||||
ASSERT_EQ("1", prop_value);
|
||||
all_keys.push_back(iter->key());
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(all_keys.size(), true_data.size());
|
||||
|
||||
// Verify that all keys slices are valid (backward)
|
||||
@@ -1682,7 +1688,7 @@ TEST_P(DBIteratorTest, PinnedDataIteratorMultipleFiles) {
|
||||
ASSERT_EQ(kv.first, data_iter->first);
|
||||
ASSERT_EQ(kv.second, data_iter->second);
|
||||
}
|
||||
|
||||
ASSERT_OK(iter->status());
|
||||
delete iter;
|
||||
}
|
||||
|
||||
@@ -1728,6 +1734,7 @@ TEST_P(DBIteratorTest, PinnedDataIteratorMergeOperator) {
|
||||
ASSERT_EQ("1", prop_value);
|
||||
results.emplace_back(iter->key(), iter->value().ToString());
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
ASSERT_EQ(results.size(), 1000);
|
||||
for (size_t i = 0; i < results.size(); i++) {
|
||||
@@ -1785,6 +1792,7 @@ TEST_P(DBIteratorTest, PinnedDataIteratorReadAfterUpdate) {
|
||||
ASSERT_EQ("1", prop_value);
|
||||
results.emplace_back(iter->key(), iter->value().ToString());
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
auto data_iter = true_data.begin();
|
||||
for (size_t i = 0; i < results.size(); i++, data_iter++) {
|
||||
@@ -2079,6 +2087,7 @@ TEST_P(DBIteratorTest, IterPrevKeyCrossingBlocksRandomized) {
|
||||
ASSERT_EQ(iter->value().ToString(), data_iter->second);
|
||||
data_iter++;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(data_iter, true_data.rend());
|
||||
|
||||
delete iter;
|
||||
@@ -2136,6 +2145,7 @@ TEST_P(DBIteratorTest, IterPrevKeyCrossingBlocksRandomized) {
|
||||
entries_right++;
|
||||
data_iter++;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(data_iter, true_data.rend());
|
||||
|
||||
delete iter;
|
||||
@@ -2175,6 +2185,7 @@ TEST_P(DBIteratorTest, IteratorWithLocalStatistics) {
|
||||
total_next++;
|
||||
|
||||
if (!iter->Valid()) {
|
||||
EXPECT_OK(iter->status());
|
||||
break;
|
||||
}
|
||||
total_next_found++;
|
||||
@@ -2202,6 +2213,7 @@ TEST_P(DBIteratorTest, IteratorWithLocalStatistics) {
|
||||
total_prev++;
|
||||
|
||||
if (!iter->Valid()) {
|
||||
EXPECT_OK(iter->status());
|
||||
break;
|
||||
}
|
||||
total_prev_found++;
|
||||
@@ -2416,6 +2428,7 @@ TEST_P(DBIteratorTest, Refresh) {
|
||||
ASSERT_EQ(iter->key().compare(Slice("x")), 0);
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
iter.reset();
|
||||
}
|
||||
@@ -2469,32 +2482,32 @@ TEST_P(DBIteratorTest, RefreshWithSnapshot) {
|
||||
verify_iter(1, 3);
|
||||
// Refresh to same snapshot
|
||||
ASSERT_OK(iter->Refresh(snapshot));
|
||||
ASSERT_TRUE(iter->status().ok() && !iter->Valid());
|
||||
ASSERT_TRUE(!iter->Valid() && iter->status().ok());
|
||||
iter->Seek(Key(3));
|
||||
verify_iter(3, 6);
|
||||
ASSERT_TRUE(iter->status().ok() && !iter->Valid());
|
||||
ASSERT_TRUE(!iter->Valid() && iter->status().ok());
|
||||
|
||||
// Refresh to a newer snapshot
|
||||
ASSERT_OK(iter->Refresh(snapshot2));
|
||||
ASSERT_TRUE(iter->status().ok() && !iter->Valid());
|
||||
ASSERT_TRUE(!iter->Valid() && iter->status().ok());
|
||||
iter->SeekToFirst();
|
||||
verify_iter(0, 4, /*new_key2=*/true);
|
||||
ASSERT_TRUE(iter->status().ok() && !iter->Valid());
|
||||
ASSERT_TRUE(!iter->Valid() && iter->status().ok());
|
||||
|
||||
// Refresh to an older snapshot
|
||||
ASSERT_OK(iter->Refresh(snapshot));
|
||||
ASSERT_TRUE(iter->status().ok() && !iter->Valid());
|
||||
ASSERT_TRUE(!iter->Valid() && iter->status().ok());
|
||||
iter->Seek(Key(3));
|
||||
verify_iter(3, 6);
|
||||
ASSERT_TRUE(iter->status().ok() && !iter->Valid());
|
||||
ASSERT_TRUE(!iter->Valid() && iter->status().ok());
|
||||
|
||||
// Refresh to no snapshot
|
||||
ASSERT_OK(iter->Refresh());
|
||||
ASSERT_TRUE(iter->status().ok() && !iter->Valid());
|
||||
ASSERT_TRUE(!iter->Valid() && iter->status().ok());
|
||||
iter->Seek(Key(2));
|
||||
verify_iter(2, 4, /*new_key2=*/true);
|
||||
verify_iter(6, 7);
|
||||
ASSERT_TRUE(iter->status().ok() && !iter->Valid());
|
||||
ASSERT_TRUE(!iter->Valid() && iter->status().ok());
|
||||
|
||||
// Change LSM shape, new SuperVersion is created.
|
||||
ASSERT_OK(Flush());
|
||||
@@ -2599,6 +2612,7 @@ TEST_P(DBIteratorTest, TableFilter) {
|
||||
ASSERT_EQ(IterStatus(iter), "f->6");
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(unseen.empty());
|
||||
delete iter;
|
||||
}
|
||||
@@ -2621,6 +2635,7 @@ TEST_P(DBIteratorTest, TableFilter) {
|
||||
ASSERT_EQ(IterStatus(iter), "f->6");
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
delete iter;
|
||||
}
|
||||
}
|
||||
@@ -2705,6 +2720,7 @@ TEST_P(DBIteratorTest, SkipStatistics) {
|
||||
ASSERT_OK(iter->status());
|
||||
count++;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(count, 3);
|
||||
delete iter;
|
||||
skip_count += 8; // Same as above, but in reverse order
|
||||
@@ -2740,6 +2756,7 @@ TEST_P(DBIteratorTest, SkipStatistics) {
|
||||
ASSERT_OK(iter->status());
|
||||
count++;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(count, 2);
|
||||
delete iter;
|
||||
// 3 deletes + 3 original keys + lower sequence of "a"
|
||||
@@ -3322,6 +3339,7 @@ TEST_F(DBIteratorTest, BackwardIterationOnInplaceUpdateMemtable) {
|
||||
for (iter->SeekToLast(); iter->Valid(); iter->Prev()) {
|
||||
++count;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(kNumKeys, count);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
#include <vector>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/forward_iterator.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
#include "rocksdb/snapshot.h"
|
||||
#include "rocksdb/utilities/debug.h"
|
||||
#include "util/random.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
#include "utilities/merge_operators/string_append/stringappend2.h"
|
||||
@@ -949,6 +951,98 @@ TEST_P(PerConfigMergeOperatorPinningTest, Randomized) {
|
||||
VerifyDBFromMap(true_data);
|
||||
}
|
||||
|
||||
TEST_F(DBMergeOperatorTest, MaxSuccessiveMergesBaseValues) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.merge_operator = MergeOperators::CreatePutOperator();
|
||||
options.max_successive_merges = 1;
|
||||
options.env = env_;
|
||||
Reopen(options);
|
||||
|
||||
constexpr char foo[] = "foo";
|
||||
constexpr char bar[] = "bar";
|
||||
constexpr char baz[] = "baz";
|
||||
constexpr char qux[] = "qux";
|
||||
constexpr char corge[] = "corge";
|
||||
|
||||
// No base value
|
||||
{
|
||||
constexpr char key[] = "key1";
|
||||
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), db_->DefaultColumnFamily(), key, foo));
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), db_->DefaultColumnFamily(), key, bar));
|
||||
|
||||
PinnableSlice result;
|
||||
ASSERT_OK(
|
||||
db_->Get(ReadOptions(), db_->DefaultColumnFamily(), key, &result));
|
||||
ASSERT_EQ(result, bar);
|
||||
|
||||
// We expect the second Merge to be converted to a Put because of
|
||||
// max_successive_merges.
|
||||
constexpr size_t max_key_versions = 8;
|
||||
std::vector<KeyVersion> key_versions;
|
||||
ASSERT_OK(GetAllKeyVersions(db_, db_->DefaultColumnFamily(), key, key,
|
||||
max_key_versions, &key_versions));
|
||||
ASSERT_EQ(key_versions.size(), 2);
|
||||
ASSERT_EQ(key_versions[0].type, kTypeValue);
|
||||
ASSERT_EQ(key_versions[1].type, kTypeMerge);
|
||||
}
|
||||
|
||||
// Plain base value
|
||||
{
|
||||
constexpr char key[] = "key2";
|
||||
|
||||
ASSERT_OK(db_->Put(WriteOptions(), db_->DefaultColumnFamily(), key, foo));
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), db_->DefaultColumnFamily(), key, bar));
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), db_->DefaultColumnFamily(), key, baz));
|
||||
|
||||
PinnableSlice result;
|
||||
ASSERT_OK(
|
||||
db_->Get(ReadOptions(), db_->DefaultColumnFamily(), key, &result));
|
||||
ASSERT_EQ(result, baz);
|
||||
|
||||
// We expect the second Merge to be converted to a Put because of
|
||||
// max_successive_merges.
|
||||
constexpr size_t max_key_versions = 8;
|
||||
std::vector<KeyVersion> key_versions;
|
||||
ASSERT_OK(GetAllKeyVersions(db_, db_->DefaultColumnFamily(), key, key,
|
||||
max_key_versions, &key_versions));
|
||||
ASSERT_EQ(key_versions.size(), 3);
|
||||
ASSERT_EQ(key_versions[0].type, kTypeValue);
|
||||
ASSERT_EQ(key_versions[1].type, kTypeMerge);
|
||||
ASSERT_EQ(key_versions[2].type, kTypeValue);
|
||||
}
|
||||
|
||||
// Wide-column base value
|
||||
{
|
||||
constexpr char key[] = "key3";
|
||||
const WideColumns columns{{kDefaultWideColumnName, foo}, {bar, baz}};
|
||||
|
||||
ASSERT_OK(db_->PutEntity(WriteOptions(), db_->DefaultColumnFamily(), key,
|
||||
columns));
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), db_->DefaultColumnFamily(), key, qux));
|
||||
ASSERT_OK(
|
||||
db_->Merge(WriteOptions(), db_->DefaultColumnFamily(), key, corge));
|
||||
|
||||
PinnableWideColumns result;
|
||||
ASSERT_OK(db_->GetEntity(ReadOptions(), db_->DefaultColumnFamily(), key,
|
||||
&result));
|
||||
const WideColumns expected{{kDefaultWideColumnName, corge}, {bar, baz}};
|
||||
ASSERT_EQ(result.columns(), expected);
|
||||
|
||||
// We expect the second Merge to be converted to a PutEntity because of
|
||||
// max_successive_merges.
|
||||
constexpr size_t max_key_versions = 8;
|
||||
std::vector<KeyVersion> key_versions;
|
||||
ASSERT_OK(GetAllKeyVersions(db_, db_->DefaultColumnFamily(), key, key,
|
||||
max_key_versions, &key_versions));
|
||||
ASSERT_EQ(key_versions.size(), 3);
|
||||
ASSERT_EQ(key_versions[0].type, kTypeWideColumnEntity);
|
||||
ASSERT_EQ(key_versions[1].type, kTypeMerge);
|
||||
ASSERT_EQ(key_versions[2].type, kTypeWideColumnEntity);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/rate_limiter.h"
|
||||
#include "rocksdb/stats_history.h"
|
||||
#include "rocksdb/utilities/options_util.h"
|
||||
#include "test_util/mock_time_env.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/random.h"
|
||||
@@ -740,6 +742,55 @@ TEST_F(DBOptionsTest, SetStatsDumpPeriodSec) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, SetStatsDumpPeriodSecRace) {
|
||||
// This is a mini-stress test looking for inconsistency between the reported
|
||||
// state of the option and the behavior in effect for the DB, after the last
|
||||
// modification to that option (indefinite inconsistency).
|
||||
std::vector<std::thread> threads;
|
||||
for (int i = 0; i < 12; i++) {
|
||||
threads.emplace_back([this, i]() {
|
||||
ASSERT_OK(dbfull()->SetDBOptions(
|
||||
{{"stats_dump_period_sec", i % 2 ? "100" : "0"}}));
|
||||
});
|
||||
}
|
||||
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
bool stats_dump_set = dbfull()->GetDBOptions().stats_dump_period_sec > 0;
|
||||
bool task_enabled = dbfull()->TEST_GetPeriodicTaskScheduler().TEST_HasTask(
|
||||
PeriodicTaskType::kDumpStats);
|
||||
|
||||
ASSERT_EQ(stats_dump_set, task_enabled);
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, SetOptionsAndFileRace) {
|
||||
// This is a mini-stress test looking for inconsistency between the reported
|
||||
// state of the option and what is persisted in the options file, after the
|
||||
// last modification to that option (indefinite inconsistency).
|
||||
std::vector<std::thread> threads;
|
||||
for (int i = 0; i < 12; i++) {
|
||||
threads.emplace_back([this, i]() {
|
||||
ASSERT_OK(dbfull()->SetOptions({{"ttl", std::to_string(i * 100)}}));
|
||||
});
|
||||
}
|
||||
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
auto setting_in_mem = dbfull()->GetOptions().ttl;
|
||||
|
||||
std::vector<ColumnFamilyDescriptor> cf_descs;
|
||||
DBOptions db_options;
|
||||
ConfigOptions cfg;
|
||||
cfg.env = env_;
|
||||
ASSERT_OK(LoadLatestOptions(cfg, dbname_, &db_options, &cf_descs, nullptr));
|
||||
ASSERT_EQ(cf_descs.size(), 1);
|
||||
ASSERT_EQ(setting_in_mem, cf_descs[0].options.ttl);
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, SetOptionsStatsPersistPeriodSec) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
@@ -908,6 +959,7 @@ TEST_F(DBOptionsTest, SetFIFOCompactionOptions) {
|
||||
Options options;
|
||||
options.env = CurrentOptions().env;
|
||||
options.compaction_style = kCompactionStyleFIFO;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
options.write_buffer_size = 10 << 10; // 10KB
|
||||
options.arena_block_size = 4096;
|
||||
options.compression = kNoCompression;
|
||||
@@ -941,6 +993,9 @@ TEST_F(DBOptionsTest, SetFIFOCompactionOptions) {
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 10);
|
||||
|
||||
ASSERT_EQ(options.statistics->getTickerCount(FIFO_TTL_COMPACTIONS), 0);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(FIFO_MAX_SIZE_COMPACTIONS), 0);
|
||||
|
||||
// Set ttl to 1 minute. So all files should get deleted.
|
||||
ASSERT_OK(dbfull()->SetOptions({{"ttl", "60"}}));
|
||||
ASSERT_EQ(dbfull()->GetOptions().ttl, 60);
|
||||
@@ -948,6 +1003,10 @@ TEST_F(DBOptionsTest, SetFIFOCompactionOptions) {
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
|
||||
|
||||
ASSERT_GT(options.statistics->getTickerCount(FIFO_TTL_COMPACTIONS), 0);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(FIFO_MAX_SIZE_COMPACTIONS), 0);
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
|
||||
// NOTE: Presumed unnecessary and removed: resetting mock time in env
|
||||
|
||||
// Test dynamically changing compaction_options_fifo.max_table_files_size
|
||||
@@ -971,6 +1030,9 @@ TEST_F(DBOptionsTest, SetFIFOCompactionOptions) {
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 10);
|
||||
|
||||
ASSERT_EQ(options.statistics->getTickerCount(FIFO_MAX_SIZE_COMPACTIONS), 0);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(FIFO_TTL_COMPACTIONS), 0);
|
||||
|
||||
// Set max_table_files_size to 12 KB. So only 1 file should remain now.
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
{{"compaction_options_fifo", "{max_table_files_size=12288;}"}}));
|
||||
@@ -980,6 +1042,10 @@ TEST_F(DBOptionsTest, SetFIFOCompactionOptions) {
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 1);
|
||||
|
||||
ASSERT_GT(options.statistics->getTickerCount(FIFO_MAX_SIZE_COMPACTIONS), 0);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(FIFO_TTL_COMPACTIONS), 0);
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
|
||||
// Test dynamically changing compaction_options_fifo.allow_compaction
|
||||
options.compaction_options_fifo.max_table_files_size = 500 << 10; // 500KB
|
||||
options.ttl = 0;
|
||||
@@ -1033,6 +1099,198 @@ TEST_F(DBOptionsTest, SetFIFOCompactionOptions) {
|
||||
ASSERT_EQ(fifo_temp_opt[1].age, 30000);
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, OffPeakTimes) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
Random rnd(test::RandomSeed());
|
||||
|
||||
auto verify_invalid = [&]() {
|
||||
Status s = DBImpl::TEST_ValidateOptions(options);
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_TRUE(s.IsInvalidArgument());
|
||||
};
|
||||
|
||||
auto verify_valid = [&]() {
|
||||
Status s = DBImpl::TEST_ValidateOptions(options);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_FALSE(s.IsInvalidArgument());
|
||||
};
|
||||
std::vector<std::string> invalid_cases = {
|
||||
"06:30-",
|
||||
"-23:30", // Both need to be set
|
||||
"00:00-00:00",
|
||||
"06:30-06:30" // Start time cannot be the same as end time
|
||||
"12:30 PM-23:30",
|
||||
"12:01AM-11:00PM", // Invalid format
|
||||
"01:99-22:00", // Invalid value for minutes
|
||||
"00:00-24:00", // 24:00 is an invalid value
|
||||
"6-7",
|
||||
"6:-7",
|
||||
"06:31.42-7:00",
|
||||
"6.31:42-7:00",
|
||||
"6:0-7:",
|
||||
"15:0.2-3:.7",
|
||||
":00-00:02",
|
||||
"02:00-:00",
|
||||
"random-value",
|
||||
"No:No-Hi:Hi",
|
||||
};
|
||||
|
||||
std::vector<std::string> valid_cases = {
|
||||
"", // Not enabled. Valid case
|
||||
"06:30-11:30",
|
||||
"06:30-23:30",
|
||||
"13:30-14:30",
|
||||
"00:00-23:59", // Entire Day
|
||||
"23:30-01:15", // From 11:30PM to 1:15AM next day. Valid case.
|
||||
"1:0000000000000-2:000000000042", // Weird, but we can parse the int.
|
||||
};
|
||||
|
||||
for (std::string invalid_case : invalid_cases) {
|
||||
options.daily_offpeak_time_utc = invalid_case;
|
||||
verify_invalid();
|
||||
}
|
||||
for (std::string valid_case : valid_cases) {
|
||||
options.daily_offpeak_time_utc = valid_case;
|
||||
verify_valid();
|
||||
}
|
||||
|
||||
auto verify_is_now_offpeak = [&](bool expected, int now_utc_hour,
|
||||
int now_utc_minute, int now_utc_second = 0) {
|
||||
auto mock_clock = std::make_shared<MockSystemClock>(env_->GetSystemClock());
|
||||
// Add some extra random days to current time
|
||||
int days = rnd.Uniform(100);
|
||||
mock_clock->SetCurrentTime(days * 86400 + now_utc_hour * 3600 +
|
||||
now_utc_minute * 60 + now_utc_second);
|
||||
Status s = DBImpl::TEST_ValidateOptions(options);
|
||||
ASSERT_OK(s);
|
||||
auto db_options = MutableDBOptions(options);
|
||||
ASSERT_EQ(expected, db_options.IsNowOffPeak(mock_clock.get()));
|
||||
};
|
||||
|
||||
options.daily_offpeak_time_utc = "";
|
||||
verify_is_now_offpeak(false, 12, 30);
|
||||
|
||||
options.daily_offpeak_time_utc = "06:30-11:30";
|
||||
verify_is_now_offpeak(false, 5, 30);
|
||||
verify_is_now_offpeak(true, 6, 30);
|
||||
verify_is_now_offpeak(true, 10, 30);
|
||||
verify_is_now_offpeak(true, 11, 30);
|
||||
verify_is_now_offpeak(false, 13, 30);
|
||||
|
||||
options.daily_offpeak_time_utc = "23:30-04:30";
|
||||
verify_is_now_offpeak(false, 6, 30);
|
||||
verify_is_now_offpeak(true, 23, 30);
|
||||
verify_is_now_offpeak(true, 0, 0);
|
||||
verify_is_now_offpeak(true, 1, 0);
|
||||
verify_is_now_offpeak(true, 4, 30);
|
||||
verify_is_now_offpeak(false, 4, 31);
|
||||
|
||||
// Entire day offpeak
|
||||
options.daily_offpeak_time_utc = "00:00-23:59";
|
||||
verify_is_now_offpeak(true, 0, 0);
|
||||
verify_is_now_offpeak(true, 12, 00);
|
||||
verify_is_now_offpeak(true, 23, 59);
|
||||
verify_is_now_offpeak(true, 23, 59, 1);
|
||||
verify_is_now_offpeak(true, 23, 59, 59);
|
||||
|
||||
// Open the db and test by Get/SetDBOptions
|
||||
options.daily_offpeak_time_utc = "";
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_EQ("", dbfull()->GetDBOptions().daily_offpeak_time_utc);
|
||||
for (std::string invalid_case : invalid_cases) {
|
||||
ASSERT_NOK(
|
||||
dbfull()->SetDBOptions({{"daily_offpeak_time_utc", invalid_case}}));
|
||||
}
|
||||
for (std::string valid_case : valid_cases) {
|
||||
ASSERT_OK(dbfull()->SetDBOptions({{"daily_offpeak_time_utc", valid_case}}));
|
||||
ASSERT_EQ(valid_case, dbfull()->GetDBOptions().daily_offpeak_time_utc);
|
||||
}
|
||||
Close();
|
||||
|
||||
// Sets off-peak time from 11:30PM to 4:30AM next day.
|
||||
// Starting at 1:30PM, use mock sleep to make time pass
|
||||
// and see if IsNowOffPeak() returns correctly per time changes
|
||||
int now_hour = 13;
|
||||
int now_minute = 30;
|
||||
options.daily_offpeak_time_utc = "23:30-04:30";
|
||||
auto mock_clock = std::make_shared<MockSystemClock>(env_->GetSystemClock());
|
||||
auto mock_env = std::make_unique<CompositeEnvWrapper>(env_, mock_clock);
|
||||
// Add some extra random days to current time
|
||||
int days = rnd.Uniform(100);
|
||||
mock_clock->SetCurrentTime(days * 86400 + now_hour * 3600 + now_minute * 60);
|
||||
options.env = mock_env.get();
|
||||
|
||||
// Starting at 1:30PM. It's not off-peak
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_FALSE(MutableDBOptions(dbfull()->GetDBOptions())
|
||||
.IsNowOffPeak(mock_clock.get()));
|
||||
|
||||
// Now it's at 4:30PM. Still not off-peak
|
||||
mock_clock->MockSleepForSeconds(3 * 3600);
|
||||
ASSERT_FALSE(MutableDBOptions(dbfull()->GetDBOptions())
|
||||
.IsNowOffPeak(mock_clock.get()));
|
||||
|
||||
// Now it's at 11:30PM. It's off-peak
|
||||
mock_clock->MockSleepForSeconds(7 * 3600);
|
||||
ASSERT_TRUE(MutableDBOptions(dbfull()->GetDBOptions())
|
||||
.IsNowOffPeak(mock_clock.get()));
|
||||
|
||||
// Now it's at 2:30AM next day. It's still off-peak
|
||||
mock_clock->MockSleepForSeconds(3 * 3600);
|
||||
ASSERT_TRUE(MutableDBOptions(dbfull()->GetDBOptions())
|
||||
.IsNowOffPeak(mock_clock.get()));
|
||||
|
||||
// Now it's at 4:30AM. It's still off-peak
|
||||
mock_clock->MockSleepForSeconds(2 * 3600);
|
||||
ASSERT_TRUE(MutableDBOptions(dbfull()->GetDBOptions())
|
||||
.IsNowOffPeak(mock_clock.get()));
|
||||
|
||||
// Sleep for one more minute. It's at 4:31AM It's no longer off-peak
|
||||
mock_clock->MockSleepForSeconds(60);
|
||||
ASSERT_FALSE(MutableDBOptions(dbfull()->GetDBOptions())
|
||||
.IsNowOffPeak(mock_clock.get()));
|
||||
Close();
|
||||
|
||||
// Entire day offpeak
|
||||
options.daily_offpeak_time_utc = "00:00-23:59";
|
||||
DestroyAndReopen(options);
|
||||
// It doesn't matter what time it is. It should be just offpeak.
|
||||
ASSERT_TRUE(MutableDBOptions(dbfull()->GetDBOptions())
|
||||
.IsNowOffPeak(mock_clock.get()));
|
||||
|
||||
// Mock Sleep for 3 hours. It's still off-peak
|
||||
mock_clock->MockSleepForSeconds(3 * 3600);
|
||||
ASSERT_TRUE(MutableDBOptions(dbfull()->GetDBOptions())
|
||||
.IsNowOffPeak(mock_clock.get()));
|
||||
|
||||
// Mock Sleep for 20 hours. It's still off-peak
|
||||
mock_clock->MockSleepForSeconds(20 * 3600);
|
||||
ASSERT_TRUE(MutableDBOptions(dbfull()->GetDBOptions())
|
||||
.IsNowOffPeak(mock_clock.get()));
|
||||
|
||||
// Mock Sleep for 59 minutes. It's still off-peak
|
||||
mock_clock->MockSleepForSeconds(59 * 60);
|
||||
ASSERT_TRUE(MutableDBOptions(dbfull()->GetDBOptions())
|
||||
.IsNowOffPeak(mock_clock.get()));
|
||||
|
||||
// Mock Sleep for 59 seconds. It's still off-peak
|
||||
mock_clock->MockSleepForSeconds(59);
|
||||
ASSERT_TRUE(MutableDBOptions(dbfull()->GetDBOptions())
|
||||
.IsNowOffPeak(mock_clock.get()));
|
||||
|
||||
// Mock Sleep for 1 second (exactly 24h passed). It's still off-peak
|
||||
mock_clock->MockSleepForSeconds(1);
|
||||
ASSERT_TRUE(MutableDBOptions(dbfull()->GetDBOptions())
|
||||
.IsNowOffPeak(mock_clock.get()));
|
||||
// Another second for sanity check
|
||||
mock_clock->MockSleepForSeconds(1);
|
||||
ASSERT_TRUE(MutableDBOptions(dbfull()->GetDBOptions())
|
||||
.IsNowOffPeak(mock_clock.get()));
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, CompactionReadaheadSizeChange) {
|
||||
for (bool use_direct_reads : {true, false}) {
|
||||
SpecialEnv env(env_);
|
||||
|
||||
@@ -682,6 +682,7 @@ TEST_F(DBRangeDelTest, TableEvictedDuringScan) {
|
||||
// soon as its refcount drops to zero.
|
||||
bbto.block_cache->EraseUnRefEntries();
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(kNum, expected);
|
||||
delete iter;
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
@@ -840,6 +841,7 @@ TEST_F(DBRangeDelTest, IteratorRemovesCoveredKeys) {
|
||||
++expected;
|
||||
}
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(kNum, expected);
|
||||
delete iter;
|
||||
}
|
||||
@@ -908,6 +910,7 @@ TEST_F(DBRangeDelTest, IteratorIgnoresRangeDeletions) {
|
||||
std::string key;
|
||||
ASSERT_EQ(expected[i], iter->key());
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(3, i);
|
||||
delete iter;
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
@@ -1382,6 +1385,7 @@ TEST_F(DBRangeDelTest, UntruncatedTombstoneDoesNotDeleteNewerKey) {
|
||||
for (; iter->Valid(); iter->Next()) {
|
||||
++keys_found;
|
||||
}
|
||||
EXPECT_OK(iter->status());
|
||||
delete iter;
|
||||
return keys_found;
|
||||
};
|
||||
@@ -1485,6 +1489,7 @@ TEST_F(DBRangeDelTest, DeletedMergeOperandReappearsIterPrev) {
|
||||
for (; iter->Valid(); iter->Prev()) {
|
||||
++keys_found;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
delete iter;
|
||||
ASSERT_EQ(kNumKeys, keys_found);
|
||||
|
||||
@@ -1519,6 +1524,7 @@ TEST_F(DBRangeDelTest, SnapshotPreventsDroppedKeys) {
|
||||
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
delete iter;
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
@@ -1564,6 +1570,7 @@ TEST_F(DBRangeDelTest, SnapshotPreventsDroppedKeysInImmMemTables) {
|
||||
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, RangeTombstoneWrittenToMinimalSsts) {
|
||||
@@ -1978,6 +1985,7 @@ TEST_F(DBRangeDelTest, IteratorRefresh) {
|
||||
ASSERT_EQ("key1", iter->key());
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
delete iter;
|
||||
}
|
||||
|
||||
@@ -220,6 +220,7 @@ TEST_P(DBRateLimiterOnReadTest, Iterator) {
|
||||
++expected;
|
||||
}
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
// Reverse scan does not read evenly (one block per iteration) due to
|
||||
// descending seqno ordering, so wait until after the loop to check total.
|
||||
ASSERT_EQ(expected, options_.rate_limiter->GetTotalRequests(Env::IO_USER));
|
||||
|
||||
@@ -240,6 +240,7 @@ TEST_F(DBReadOnlyTestWithTimestamp, IteratorAndGet) {
|
||||
get_value_and_check(db_, read_opts, it->key(), it->value(),
|
||||
write_timestamps[i]);
|
||||
}
|
||||
ASSERT_OK(it->status());
|
||||
size_t expected_count = kMaxKey - start_keys[i] + 1;
|
||||
ASSERT_EQ(expected_count, count);
|
||||
|
||||
@@ -252,6 +253,7 @@ TEST_F(DBReadOnlyTestWithTimestamp, IteratorAndGet) {
|
||||
get_value_and_check(db_, read_opts, it->key(), it->value(),
|
||||
write_timestamps[i]);
|
||||
}
|
||||
ASSERT_OK(it->status());
|
||||
ASSERT_EQ(static_cast<size_t>(kMaxKey) - start_keys[i] + 1, count);
|
||||
|
||||
// SeekToFirst()/SeekToLast() with lower/upper bounds.
|
||||
@@ -273,6 +275,7 @@ TEST_F(DBReadOnlyTestWithTimestamp, IteratorAndGet) {
|
||||
get_value_and_check(db_, read_opts, it->key(), it->value(),
|
||||
write_timestamps[i]);
|
||||
}
|
||||
ASSERT_OK(it->status());
|
||||
ASSERT_EQ(r - std::max(l, start_keys[i]), count);
|
||||
|
||||
for (it->SeekToLast(), key = std::min(r, kMaxKey + 1), count = 0;
|
||||
@@ -282,6 +285,7 @@ TEST_F(DBReadOnlyTestWithTimestamp, IteratorAndGet) {
|
||||
get_value_and_check(db_, read_opts, it->key(), it->value(),
|
||||
write_timestamps[i]);
|
||||
}
|
||||
ASSERT_OK(it->status());
|
||||
l += (kMaxKey / 100);
|
||||
r -= (kMaxKey / 100);
|
||||
}
|
||||
@@ -328,6 +332,7 @@ TEST_F(DBReadOnlyTestWithTimestamp, Iterators) {
|
||||
CheckIterUserEntry(iters[0], Key1(key), kTypeValue,
|
||||
"value" + std::to_string(key), write_timestamp);
|
||||
}
|
||||
ASSERT_OK(iters[0]->status());
|
||||
|
||||
size_t expected_count = kMaxKey - 0 + 1;
|
||||
ASSERT_EQ(expected_count, count);
|
||||
|
||||
@@ -200,6 +200,7 @@ TEST_F(DBSecondaryTest, ReopenAsSecondary) {
|
||||
}
|
||||
++count;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
delete iter;
|
||||
ASSERT_EQ(3, count);
|
||||
}
|
||||
@@ -534,6 +535,8 @@ TEST_F(DBSecondaryTest, SecondaryCloseFiles) {
|
||||
}
|
||||
ASSERT_FALSE(iter1->Valid());
|
||||
ASSERT_FALSE(iter2->Valid());
|
||||
ASSERT_OK(iter1->status());
|
||||
ASSERT_OK(iter2->status());
|
||||
};
|
||||
|
||||
ASSERT_OK(Put("a", "value"));
|
||||
@@ -806,6 +809,7 @@ TEST_F(DBSecondaryTest, MissingTableFileDuringOpen) {
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
++count;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(2, count);
|
||||
delete iter;
|
||||
}
|
||||
@@ -863,6 +867,7 @@ TEST_F(DBSecondaryTest, MissingTableFile) {
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
++count;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(2, count);
|
||||
delete iter;
|
||||
}
|
||||
@@ -935,6 +940,7 @@ TEST_F(DBSecondaryTest, SwitchManifest) {
|
||||
ASSERT_EQ("value_" + std::to_string(kNumFiles - 1),
|
||||
iter->value().ToString());
|
||||
}
|
||||
EXPECT_OK(iter->status());
|
||||
};
|
||||
|
||||
range_scan_db();
|
||||
@@ -1485,6 +1491,7 @@ TEST_F(DBSecondaryTestWithTimestamp, IteratorAndGet) {
|
||||
get_value_and_check(db_, read_opts, it->key(), it->value(),
|
||||
write_timestamps[i]);
|
||||
}
|
||||
ASSERT_OK(it->status());
|
||||
size_t expected_count = kMaxKey - start_keys[i] + 1;
|
||||
ASSERT_EQ(expected_count, count);
|
||||
|
||||
@@ -1497,6 +1504,7 @@ TEST_F(DBSecondaryTestWithTimestamp, IteratorAndGet) {
|
||||
get_value_and_check(db_, read_opts, it->key(), it->value(),
|
||||
write_timestamps[i]);
|
||||
}
|
||||
ASSERT_OK(it->status());
|
||||
ASSERT_EQ(static_cast<size_t>(kMaxKey) - start_keys[i] + 1, count);
|
||||
|
||||
// SeekToFirst()/SeekToLast() with lower/upper bounds.
|
||||
@@ -1518,6 +1526,7 @@ TEST_F(DBSecondaryTestWithTimestamp, IteratorAndGet) {
|
||||
get_value_and_check(db_, read_opts, it->key(), it->value(),
|
||||
write_timestamps[i]);
|
||||
}
|
||||
ASSERT_OK(it->status());
|
||||
ASSERT_EQ(r - std::max(l, start_keys[i]), count);
|
||||
|
||||
for (it->SeekToLast(), key = std::min(r, kMaxKey + 1), count = 0;
|
||||
@@ -1527,6 +1536,7 @@ TEST_F(DBSecondaryTestWithTimestamp, IteratorAndGet) {
|
||||
get_value_and_check(db_, read_opts, it->key(), it->value(),
|
||||
write_timestamps[i]);
|
||||
}
|
||||
ASSERT_OK(it->status());
|
||||
l += (kMaxKey / 100);
|
||||
r -= (kMaxKey / 100);
|
||||
}
|
||||
@@ -1736,6 +1746,7 @@ TEST_F(DBSecondaryTestWithTimestamp, Iterators) {
|
||||
CheckIterUserEntry(iters[0], Key1(key), kTypeValue,
|
||||
"value" + std::to_string(key), write_timestamp);
|
||||
}
|
||||
ASSERT_OK(iters[0]->status());
|
||||
|
||||
size_t expected_count = kMaxKey - 0 + 1;
|
||||
ASSERT_EQ(expected_count, count);
|
||||
|
||||
+21
-3
@@ -937,12 +937,21 @@ INSTANTIATE_TEST_CASE_P(DBWALTestWithParam, DBWALTestWithParam,
|
||||
::testing::Values(std::make_tuple("", true),
|
||||
std::make_tuple("_wal_dir", false)));
|
||||
|
||||
TEST_F(DBSSTTest, OpenDBWithExistingTrash) {
|
||||
TEST_F(DBSSTTest, OpenDBWithExistingTrashAndObsoleteSstFile) {
|
||||
Options options = CurrentOptions();
|
||||
|
||||
options.sst_file_manager.reset(
|
||||
NewSstFileManager(env_, nullptr, "", 1024 * 1024 /* 1 MB/sec */));
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(options.sst_file_manager.get());
|
||||
// Set an extra high trash ratio to prevent immediate/non-rate limited
|
||||
// deletions
|
||||
sfm->SetDeleteRateBytesPerSecond(1024 * 1024);
|
||||
sfm->delete_scheduler()->SetMaxTrashDBRatio(1000.0);
|
||||
|
||||
int bg_delete_file = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DeleteScheduler::DeleteTrashFile:DeleteFile",
|
||||
[&](void* /*arg*/) { bg_delete_file++; });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Destroy(last_options_);
|
||||
|
||||
@@ -951,13 +960,22 @@ TEST_F(DBSSTTest, OpenDBWithExistingTrash) {
|
||||
ASSERT_OK(WriteStringToFile(env_, "abc", dbname_ + "/" + "001.sst.trash"));
|
||||
ASSERT_OK(WriteStringToFile(env_, "abc", dbname_ + "/" + "002.sst.trash"));
|
||||
ASSERT_OK(WriteStringToFile(env_, "abc", dbname_ + "/" + "003.sst.trash"));
|
||||
// Manually add an obsolete sst file. Obsolete SST files are discovered and
|
||||
// deleted upon recovery.
|
||||
constexpr uint64_t kSstFileNumber = 100;
|
||||
const std::string kObsoleteSstFile =
|
||||
MakeTableFileName(dbname_, kSstFileNumber);
|
||||
ASSERT_OK(WriteStringToFile(env_, "abc", kObsoleteSstFile));
|
||||
|
||||
// Reopen the DB and verify that it deletes existing trash files
|
||||
// Reopen the DB and verify that it deletes existing trash files and obsolete
|
||||
// SST files with rate limiting.
|
||||
Reopen(options);
|
||||
sfm->WaitForEmptyTrash();
|
||||
ASSERT_NOK(env_->FileExists(dbname_ + "/" + "001.sst.trash"));
|
||||
ASSERT_NOK(env_->FileExists(dbname_ + "/" + "002.sst.trash"));
|
||||
ASSERT_NOK(env_->FileExists(dbname_ + "/" + "003.sst.trash"));
|
||||
ASSERT_NOK(env_->FileExists(kObsoleteSstFile));
|
||||
ASSERT_EQ(bg_delete_file, 4);
|
||||
}
|
||||
|
||||
// Create a DB with 2 db_paths, and generate multiple files in the 2
|
||||
|
||||
@@ -52,6 +52,7 @@ TEST_P(DBTestTailingIterator, TailingIteratorSingle) {
|
||||
|
||||
iter->Next();
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
|
||||
TEST_P(DBTestTailingIterator, TailingIteratorKeepAdding) {
|
||||
@@ -361,7 +362,7 @@ TEST_P(DBTestTailingIterator, TailingIteratorDeletes) {
|
||||
int count = 0;
|
||||
for (; iter->Valid(); iter->Next(), ++count)
|
||||
;
|
||||
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(count, num_records);
|
||||
}
|
||||
Close();
|
||||
@@ -408,6 +409,7 @@ TEST_P(DBTestTailingIterator, TailingIteratorPrefixSeek) {
|
||||
|
||||
iter->Next();
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
@@ -127,6 +127,7 @@ TEST_F(DBTest, MockEnvTest) {
|
||||
iterator->Next();
|
||||
}
|
||||
ASSERT_TRUE(!iterator->Valid());
|
||||
ASSERT_OK(iterator->status());
|
||||
delete iterator;
|
||||
|
||||
DBImpl* dbi = static_cast_with_check<DBImpl>(db);
|
||||
@@ -171,6 +172,7 @@ TEST_F(DBTest, MemEnvTest) {
|
||||
iterator->Next();
|
||||
}
|
||||
ASSERT_TRUE(!iterator->Valid());
|
||||
ASSERT_OK(iterator->status());
|
||||
delete iterator;
|
||||
|
||||
DBImpl* dbi = static_cast_with_check<DBImpl>(db);
|
||||
@@ -2983,6 +2985,7 @@ TEST_F(DBTest, GroupCommitTest) {
|
||||
itr->Next();
|
||||
}
|
||||
ASSERT_TRUE(!itr->Valid());
|
||||
ASSERT_OK(itr->status());
|
||||
delete itr;
|
||||
|
||||
HistogramData hist_data;
|
||||
@@ -3511,6 +3514,8 @@ static bool CompareIterators(int step, DB* model, DB* db,
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
EXPECT_OK(miter->status());
|
||||
EXPECT_OK(dbiter->status());
|
||||
(void)count;
|
||||
delete miter;
|
||||
delete dbiter;
|
||||
@@ -6008,6 +6013,7 @@ TEST_F(DBTest, MergeTestTime) {
|
||||
ASSERT_OK(iter->status());
|
||||
++count;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
ASSERT_EQ(1, count);
|
||||
ASSERT_EQ(4000000, TestGetTickerCount(options, MERGE_OPERATION_TOTAL_TIME));
|
||||
|
||||
@@ -1805,6 +1805,7 @@ TEST_P(CompressionFailuresTest, CompressionFailures) {
|
||||
ASSERT_EQ(key_value_written[key], value);
|
||||
key_value_written.erase(key);
|
||||
}
|
||||
ASSERT_OK(db_iter->status());
|
||||
ASSERT_EQ(0, key_value_written.size());
|
||||
} else if (compression_failure_type_ == kTestDecompressionFail) {
|
||||
ASSERT_EQ(std::string(s.getState()),
|
||||
@@ -3801,6 +3802,7 @@ TEST_F(DBTest2, MemtableOnlyIterator) {
|
||||
count++;
|
||||
}
|
||||
ASSERT_TRUE(!it->Valid());
|
||||
ASSERT_OK(it->status());
|
||||
ASSERT_EQ(2, count);
|
||||
delete it;
|
||||
|
||||
@@ -7588,6 +7590,7 @@ TEST_F(DBTest2, BestEffortsRecoveryWithSstUniqueIdVerification) {
|
||||
ASSERT_EQ(std::to_string(cnt), it->key());
|
||||
ASSERT_EQ(expected_v, it->value());
|
||||
}
|
||||
EXPECT_OK(it->status());
|
||||
ASSERT_EQ(expected_count, cnt);
|
||||
};
|
||||
|
||||
|
||||
@@ -699,6 +699,7 @@ void DBTestBase::Destroy(const Options& options, bool delete_cf_paths) {
|
||||
}
|
||||
|
||||
Status DBTestBase::ReadOnlyReopen(const Options& options) {
|
||||
Close();
|
||||
MaybeInstallTimeElapseOnlySleep(options);
|
||||
return DB::OpenForReadOnly(options, dbname_, &db_);
|
||||
}
|
||||
@@ -942,6 +943,7 @@ std::string DBTestBase::Contents(int cf) {
|
||||
EXPECT_EQ(IterStatus(iter), forward[forward.size() - matched - 1]);
|
||||
matched++;
|
||||
}
|
||||
EXPECT_OK(iter->status());
|
||||
EXPECT_EQ(matched, forward.size());
|
||||
|
||||
delete iter;
|
||||
@@ -1364,6 +1366,7 @@ std::string DBTestBase::IterStatus(Iterator* iter) {
|
||||
if (iter->Valid()) {
|
||||
result = iter->key().ToString() + "->" + iter->value().ToString();
|
||||
} else {
|
||||
EXPECT_OK(iter->status());
|
||||
result = "(invalid)";
|
||||
}
|
||||
return result;
|
||||
@@ -1582,6 +1585,7 @@ void DBTestBase::VerifyDBFromMap(std::map<std::string, std::string> true_data,
|
||||
iter_cnt++;
|
||||
total_reads++;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(data_iter, true_data.end())
|
||||
<< iter_cnt << " / " << true_data.size();
|
||||
delete iter;
|
||||
@@ -1605,6 +1609,7 @@ void DBTestBase::VerifyDBFromMap(std::map<std::string, std::string> true_data,
|
||||
iter_cnt++;
|
||||
total_reads++;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(data_rev, true_data.rend())
|
||||
<< iter_cnt << " / " << true_data.size();
|
||||
|
||||
|
||||
@@ -233,6 +233,7 @@ class SpecialEnv : public EnvWrapper {
|
||||
size_t GetUniqueId(char* id, size_t max_size) const override {
|
||||
return base_->GetUniqueId(id, max_size);
|
||||
}
|
||||
uint64_t GetFileSize() final { return base_->GetFileSize(); }
|
||||
};
|
||||
class ManifestFile : public WritableFile {
|
||||
public:
|
||||
@@ -345,6 +346,7 @@ class SpecialEnv : public EnvWrapper {
|
||||
Status Allocate(uint64_t offset, uint64_t len) override {
|
||||
return base_->Allocate(offset, len);
|
||||
}
|
||||
uint64_t GetFileSize() final { return base_->GetFileSize(); }
|
||||
|
||||
private:
|
||||
SpecialEnv* env_;
|
||||
|
||||
@@ -1469,6 +1469,7 @@ TEST_P(DBTestUniversalCompaction, IncreaseUniversalCompactionNumLevels) {
|
||||
keys_in_db.append(iter->key().ToString());
|
||||
keys_in_db.push_back(',');
|
||||
}
|
||||
EXPECT_OK(iter->status());
|
||||
delete iter;
|
||||
|
||||
std::string expected_keys;
|
||||
|
||||
@@ -2228,6 +2228,7 @@ TEST_P(DBWALTestWithParamsVaryingRecoveryMode,
|
||||
data.push_back(
|
||||
std::make_pair(iter->key().ToString(), iter->value().ToString()));
|
||||
}
|
||||
EXPECT_OK(iter->status());
|
||||
delete iter;
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -525,6 +525,7 @@ TEST_F(DBBasicTestWithTimestamp, SimpleIterate) {
|
||||
CheckIterUserEntry(it.get(), Key1(key), kTypeValue,
|
||||
"value" + std::to_string(i), write_timestamps[i]);
|
||||
}
|
||||
ASSERT_OK(it->status());
|
||||
ASSERT_EQ(static_cast<size_t>(kMaxKey) - start_keys[i] + 1, count);
|
||||
|
||||
// SeekToFirst()/SeekToLast() with lower/upper bounds.
|
||||
@@ -544,6 +545,7 @@ TEST_F(DBBasicTestWithTimestamp, SimpleIterate) {
|
||||
CheckIterUserEntry(it.get(), Key1(key), kTypeValue,
|
||||
"value" + std::to_string(i), write_timestamps[i]);
|
||||
}
|
||||
ASSERT_OK(it->status());
|
||||
ASSERT_EQ(r - std::max(l, start_keys[i]), count);
|
||||
|
||||
for (it->SeekToLast(), key = std::min(r, kMaxKey + 1), count = 0;
|
||||
@@ -551,6 +553,7 @@ TEST_F(DBBasicTestWithTimestamp, SimpleIterate) {
|
||||
CheckIterUserEntry(it.get(), Key1(key - 1), kTypeValue,
|
||||
"value" + std::to_string(i), write_timestamps[i]);
|
||||
}
|
||||
ASSERT_OK(it->status());
|
||||
l += (kMaxKey / 100);
|
||||
r -= (kMaxKey / 100);
|
||||
}
|
||||
@@ -733,6 +736,7 @@ TEST_P(DBBasicTestWithTimestampTableOptions, GetAndMultiGet) {
|
||||
ASSERT_EQ(it->value(), value_from_get);
|
||||
ASSERT_EQ(Timestamp(1, 0), timestamp);
|
||||
}
|
||||
ASSERT_OK(it->status());
|
||||
|
||||
// verify MultiGet()
|
||||
constexpr uint64_t step = 2;
|
||||
@@ -1065,6 +1069,7 @@ TEST_F(DBBasicTestWithTimestamp, SimpleForwardIterateLowerTsBound) {
|
||||
write_timestamps[i - 1]);
|
||||
}
|
||||
}
|
||||
ASSERT_OK(it->status());
|
||||
size_t expected_count = kMaxKey + 1;
|
||||
ASSERT_EQ(expected_count, count);
|
||||
}
|
||||
@@ -1143,6 +1148,7 @@ TEST_F(DBBasicTestWithTimestamp, BackwardIterateLowerTsBound) {
|
||||
write_timestamps[1]);
|
||||
}
|
||||
}
|
||||
ASSERT_OK(it->status());
|
||||
size_t expected_count = kMaxKey + 1;
|
||||
ASSERT_EQ(expected_count, count);
|
||||
}
|
||||
@@ -1173,6 +1179,7 @@ TEST_F(DBBasicTestWithTimestamp, BackwardIterateLowerTsBound) {
|
||||
CheckIterEntry(it.get(), Key1(key), kTypeDeletionWithTimestamp, Slice(),
|
||||
write_timestamp);
|
||||
}
|
||||
ASSERT_OK(it->status());
|
||||
ASSERT_EQ(kMaxKey + 1, count);
|
||||
}
|
||||
Close();
|
||||
@@ -1278,6 +1285,7 @@ TEST_F(DBBasicTestWithTimestamp, BackwardIterateLowerTsBound_Reseek) {
|
||||
CheckIterEntry(it.get(), "a", kTypeValue, "v" + std::to_string(4 + i),
|
||||
Timestamp(4 + i, 0));
|
||||
}
|
||||
ASSERT_OK(it->status());
|
||||
}
|
||||
|
||||
Close();
|
||||
@@ -3145,6 +3153,7 @@ TEST_P(DBBasicTestWithTimestampPrefixSeek, IterateWithPrefix) {
|
||||
"value" + std::to_string(i), write_ts_list[i]);
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
// Seek to kMinKey
|
||||
iter->Seek(Key1(kMinKey));
|
||||
@@ -3152,6 +3161,7 @@ TEST_P(DBBasicTestWithTimestampPrefixSeek, IterateWithPrefix) {
|
||||
"value" + std::to_string(i), write_ts_list[i]);
|
||||
iter->Prev();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
const std::vector<uint64_t> targets = {kMinKey, kMinKey + 0x10,
|
||||
kMinKey + 0x100, kMaxKey};
|
||||
@@ -3190,6 +3200,7 @@ TEST_P(DBBasicTestWithTimestampPrefixSeek, IterateWithPrefix) {
|
||||
++expected_key;
|
||||
it->Next();
|
||||
}
|
||||
ASSERT_OK(it->status());
|
||||
ASSERT_EQ(expected_ub - targets[j] + 1, count);
|
||||
|
||||
count = 0;
|
||||
@@ -3208,6 +3219,7 @@ TEST_P(DBBasicTestWithTimestampPrefixSeek, IterateWithPrefix) {
|
||||
--expected_key;
|
||||
it->Prev();
|
||||
}
|
||||
ASSERT_OK(it->status());
|
||||
ASSERT_EQ(targets[j] - std::max(expected_lb, kMinKey) + 1, count);
|
||||
}
|
||||
}
|
||||
@@ -3313,6 +3325,7 @@ TEST_P(DBBasicTestWithTsIterTombstones, IterWithDelete) {
|
||||
ASSERT_EQ(Key1(key), iter->key());
|
||||
ASSERT_EQ("value1" + std::to_string(key), iter->value());
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ((kMaxKey - kMinKey + 1) / 2, count);
|
||||
}
|
||||
Close();
|
||||
@@ -3932,6 +3945,7 @@ TEST_P(DBBasicTestWithTimestampTableOptions, DeleteRangeBaiscReadAndIterate) {
|
||||
++expected;
|
||||
}
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(kNum, expected);
|
||||
|
||||
expected = kNum / 2;
|
||||
@@ -3939,6 +3953,7 @@ TEST_P(DBBasicTestWithTimestampTableOptions, DeleteRangeBaiscReadAndIterate) {
|
||||
ASSERT_EQ(Key1(expected), iter->key());
|
||||
++expected;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(kNum, expected);
|
||||
|
||||
expected = kRangeBegin - 1;
|
||||
@@ -3946,6 +3961,7 @@ TEST_P(DBBasicTestWithTimestampTableOptions, DeleteRangeBaiscReadAndIterate) {
|
||||
ASSERT_EQ(Key1(expected), iter->key());
|
||||
--expected;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(-1, expected);
|
||||
|
||||
read_ts = Timestamp(0, 0);
|
||||
@@ -4227,6 +4243,7 @@ TEST_F(DBBasicTestWithTimestamp, MergeBasic) {
|
||||
ASSERT_EQ(value, it->value());
|
||||
ASSERT_EQ(write_ts_strs[i], it->timestamp());
|
||||
}
|
||||
EXPECT_OK(it->status());
|
||||
ASSERT_EQ(kNumOfUniqKeys, key_int_val);
|
||||
|
||||
key_int_val = kNumOfUniqKeys - 1;
|
||||
@@ -4238,6 +4255,7 @@ TEST_F(DBBasicTestWithTimestamp, MergeBasic) {
|
||||
ASSERT_EQ(value, it->value());
|
||||
ASSERT_EQ(write_ts_strs[i], it->timestamp());
|
||||
}
|
||||
ASSERT_OK(it->status());
|
||||
ASSERT_EQ(std::numeric_limits<size_t>::max(), key_int_val);
|
||||
|
||||
value_suffix = value_suffix + "." + std::to_string(i + 1);
|
||||
|
||||
@@ -575,6 +575,7 @@ TEST_F(DeleteFileTest, DeleteNonDefaultColumnFamily) {
|
||||
ASSERT_OK(itr->status());
|
||||
++count;
|
||||
}
|
||||
ASSERT_OK(itr->status());
|
||||
ASSERT_EQ(count, 1000);
|
||||
}
|
||||
|
||||
@@ -588,6 +589,7 @@ TEST_F(DeleteFileTest, DeleteNonDefaultColumnFamily) {
|
||||
ASSERT_OK(itr->status());
|
||||
++count;
|
||||
}
|
||||
ASSERT_OK(itr->status());
|
||||
ASSERT_EQ(count, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
+50
-34
@@ -320,7 +320,7 @@ const Status& ErrorHandler::HandleKnownErrors(const Status& bg_err,
|
||||
// Check if recovery is currently in progress. If it is, we will save this
|
||||
// error so we can check it at the end to see if recovery succeeded or not
|
||||
if (recovery_in_prog_ && recovery_error_.ok()) {
|
||||
recovery_error_ = new_bg_err;
|
||||
recovery_error_ = status_to_io_status(Status(new_bg_err));
|
||||
}
|
||||
|
||||
bool auto_recovery = auto_recovery_;
|
||||
@@ -396,14 +396,13 @@ const Status& ErrorHandler::SetBGError(const Status& bg_status,
|
||||
ROCKS_LOG_WARN(db_options_.info_log, "Background IO error %s",
|
||||
bg_io_err.ToString().c_str());
|
||||
|
||||
if (recovery_in_prog_ && recovery_io_error_.ok()) {
|
||||
recovery_io_error_ = bg_io_err;
|
||||
}
|
||||
if (BackgroundErrorReason::kManifestWrite == reason ||
|
||||
BackgroundErrorReason::kManifestWriteNoWAL == reason) {
|
||||
if (!recovery_disabled_file_deletion_ &&
|
||||
(BackgroundErrorReason::kManifestWrite == reason ||
|
||||
BackgroundErrorReason::kManifestWriteNoWAL == reason)) {
|
||||
// Always returns ok
|
||||
ROCKS_LOG_INFO(db_options_.info_log, "Disabling File Deletions");
|
||||
db_->DisableFileDeletionsWithLock().PermitUncheckedError();
|
||||
recovery_disabled_file_deletion_ = true;
|
||||
}
|
||||
|
||||
Status new_bg_io_err = bg_io_err;
|
||||
@@ -502,10 +501,6 @@ const Status& ErrorHandler::SetBGError(const Status& bg_status,
|
||||
RecordTick(bg_error_stats_.get(),
|
||||
ERROR_HANDLER_BG_IO_ERROR_COUNT_MISSPELLED);
|
||||
}
|
||||
// HandleKnownErrors() will use recovery_error_, so ignore
|
||||
// recovery_io_error_.
|
||||
// TODO: Do some refactoring and use only one recovery_error_
|
||||
recovery_io_error_.PermitUncheckedError();
|
||||
return HandleKnownErrors(new_bg_io_err, reason);
|
||||
}
|
||||
}
|
||||
@@ -562,11 +557,23 @@ Status ErrorHandler::ClearBGError() {
|
||||
old_bg_error.PermitUncheckedError();
|
||||
// Clear and check the recovery IO and BG error
|
||||
bg_error_ = Status::OK();
|
||||
recovery_io_error_ = IOStatus::OK();
|
||||
recovery_error_ = IOStatus::OK();
|
||||
bg_error_.PermitUncheckedError();
|
||||
recovery_io_error_.PermitUncheckedError();
|
||||
recovery_error_.PermitUncheckedError();
|
||||
recovery_in_prog_ = false;
|
||||
soft_error_no_bg_work_ = false;
|
||||
if (recovery_disabled_file_deletion_) {
|
||||
recovery_disabled_file_deletion_ = false;
|
||||
int remain_counter = db_->EnableFileDeletionsWithLock();
|
||||
if (remain_counter == 0) {
|
||||
ROCKS_LOG_INFO(db_options_.info_log, "File Deletions Enabled");
|
||||
} else {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"File Deletions Enable, but not really enabled. Counter: %d",
|
||||
remain_counter);
|
||||
}
|
||||
}
|
||||
EventHelpers::NotifyOnErrorRecoveryEnd(db_options_.listeners, old_bg_error,
|
||||
bg_error_, db_mutex_);
|
||||
}
|
||||
@@ -602,14 +609,14 @@ Status ErrorHandler::RecoverFromBGError(bool is_manual) {
|
||||
if (bg_error_.severity() == Status::Severity::kSoftError &&
|
||||
recover_context_.flush_reason == FlushReason::kErrorRecovery) {
|
||||
// Simply clear the background error and return
|
||||
recovery_error_ = Status::OK();
|
||||
recovery_error_ = IOStatus::OK();
|
||||
return ClearBGError();
|
||||
}
|
||||
|
||||
// Reset recovery_error_. We will use this to record any errors that happen
|
||||
// during the recovery process. While recovering, the only operations that
|
||||
// can generate background errors should be the flush operations
|
||||
recovery_error_ = Status::OK();
|
||||
recovery_error_ = IOStatus::OK();
|
||||
recovery_error_.PermitUncheckedError();
|
||||
Status s = db_->ResumeImpl(recover_context_);
|
||||
if (s.ok()) {
|
||||
@@ -645,21 +652,27 @@ const Status& ErrorHandler::StartRecoverFromRetryableBGIOError(
|
||||
ROCKS_LOG_INFO(
|
||||
db_options_.info_log,
|
||||
"ErrorHandler: Call StartRecoverFromRetryableBGIOError to resume\n");
|
||||
// Needs to be set in the same lock hold as setting BG error, otherwise
|
||||
// intervening writes could see a BG error without a recovery and bail out.
|
||||
recovery_in_prog_ = true;
|
||||
|
||||
if (recovery_thread_) {
|
||||
// Ensure only one thread can execute the join().
|
||||
std::unique_ptr<port::Thread> old_recovery_thread(
|
||||
std::move(recovery_thread_));
|
||||
// In this case, if recovery_in_prog_ is false, current thread should
|
||||
// wait the previous recover thread to finish and create a new thread
|
||||
// to recover from the bg error.
|
||||
db_mutex_->Unlock();
|
||||
recovery_thread_->join();
|
||||
old_recovery_thread->join();
|
||||
db_mutex_->Lock();
|
||||
}
|
||||
|
||||
recovery_in_prog_ = true;
|
||||
TEST_SYNC_POINT("StartRecoverFromRetryableBGIOError::in_progress");
|
||||
recovery_thread_.reset(
|
||||
new port::Thread(&ErrorHandler::RecoverFromRetryableBGIOError, this));
|
||||
|
||||
if (recovery_io_error_.ok() && recovery_error_.ok()) {
|
||||
if (recovery_error_.ok()) {
|
||||
return recovery_error_;
|
||||
} else {
|
||||
return bg_error_;
|
||||
@@ -676,9 +689,12 @@ void ErrorHandler::RecoverFromRetryableBGIOError() {
|
||||
EventHelpers::NotifyOnErrorRecoveryEnd(db_options_.listeners, bg_error_,
|
||||
Status::ShutdownInProgress(),
|
||||
db_mutex_);
|
||||
|
||||
recovery_in_prog_ = false;
|
||||
return;
|
||||
}
|
||||
DBRecoverContext context = recover_context_;
|
||||
context.flush_after_recovery = true;
|
||||
int resume_count = db_options_.max_bgerror_resume_count;
|
||||
uint64_t wait_interval = db_options_.bgerror_resume_retry_interval;
|
||||
uint64_t retry_count = 0;
|
||||
@@ -688,12 +704,12 @@ void ErrorHandler::RecoverFromRetryableBGIOError() {
|
||||
EventHelpers::NotifyOnErrorRecoveryEnd(db_options_.listeners, bg_error_,
|
||||
Status::ShutdownInProgress(),
|
||||
db_mutex_);
|
||||
recovery_in_prog_ = false;
|
||||
return;
|
||||
}
|
||||
TEST_SYNC_POINT("RecoverFromRetryableBGIOError:BeforeResume0");
|
||||
TEST_SYNC_POINT("RecoverFromRetryableBGIOError:BeforeResume1");
|
||||
recovery_io_error_ = IOStatus::OK();
|
||||
recovery_error_ = Status::OK();
|
||||
recovery_error_ = IOStatus::OK();
|
||||
retry_count++;
|
||||
Status s = db_->ResumeImpl(context);
|
||||
if (bg_error_stats_ != nullptr) {
|
||||
@@ -713,9 +729,9 @@ void ErrorHandler::RecoverFromRetryableBGIOError() {
|
||||
bg_error_, db_mutex_);
|
||||
return;
|
||||
}
|
||||
if (!recovery_io_error_.ok() &&
|
||||
if (!recovery_error_.ok() &&
|
||||
recovery_error_.severity() <= Status::Severity::kHardError &&
|
||||
recovery_io_error_.GetRetryable()) {
|
||||
recovery_error_.GetRetryable()) {
|
||||
// If new BG IO error happens during auto recovery and it is retryable
|
||||
// and its severity is Hard Error or lower, the auto resmue sleep for
|
||||
// a period of time and redo auto resume if it is allowed.
|
||||
@@ -724,10 +740,10 @@ void ErrorHandler::RecoverFromRetryableBGIOError() {
|
||||
int64_t wait_until = db_options_.clock->NowMicros() + wait_interval;
|
||||
cv_.TimedWait(wait_until);
|
||||
} else {
|
||||
// There are three possibility: 1) recover_io_error is set during resume
|
||||
// There are three possibility: 1) recovery_error_ is set during resume
|
||||
// and the error is not retryable, 2) recover is successful, 3) other
|
||||
// error happens during resume and cannot be resumed here.
|
||||
if (recovery_io_error_.ok() && recovery_error_.ok() && s.ok()) {
|
||||
if (recovery_error_.ok() && s.ok()) {
|
||||
// recover from the retryable IO error and no other BG errors. Clean
|
||||
// the bg_error and notify user.
|
||||
TEST_SYNC_POINT("RecoverFromRetryableBGIOError:RecoverSuccess");
|
||||
@@ -749,8 +765,8 @@ void ErrorHandler::RecoverFromRetryableBGIOError() {
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
// In this case: 1) recovery_io_error is more serious or not retryable
|
||||
// 2) other Non IO recovery_error happens. The auto recovery stops.
|
||||
// In this case: 1) recovery_error_ is more serious or not retryable
|
||||
// 2) other error happens. The auto recovery stops.
|
||||
recovery_in_prog_ = false;
|
||||
if (bg_error_stats_ != nullptr) {
|
||||
RecordInHistogram(bg_error_stats_.get(),
|
||||
@@ -758,10 +774,7 @@ void ErrorHandler::RecoverFromRetryableBGIOError() {
|
||||
}
|
||||
EventHelpers::NotifyOnErrorRecoveryEnd(
|
||||
db_options_.listeners, bg_error_,
|
||||
!recovery_io_error_.ok()
|
||||
? recovery_io_error_
|
||||
: (!recovery_error_.ok() ? recovery_error_ : s),
|
||||
db_mutex_);
|
||||
!recovery_error_.ok() ? recovery_error_ : s, db_mutex_);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -781,7 +794,7 @@ void ErrorHandler::RecoverFromRetryableBGIOError() {
|
||||
|
||||
void ErrorHandler::CheckAndSetRecoveryAndBGError(const Status& bg_err) {
|
||||
if (recovery_in_prog_ && recovery_error_.ok()) {
|
||||
recovery_error_ = bg_err;
|
||||
recovery_error_ = status_to_io_status(Status(bg_err));
|
||||
}
|
||||
if (bg_err.severity() > bg_error_.severity()) {
|
||||
bg_error_ = bg_err;
|
||||
@@ -797,12 +810,15 @@ void ErrorHandler::EndAutoRecovery() {
|
||||
if (!end_recovery_) {
|
||||
end_recovery_ = true;
|
||||
}
|
||||
cv_.SignalAll();
|
||||
db_mutex_->Unlock();
|
||||
if (recovery_thread_) {
|
||||
recovery_thread_->join();
|
||||
// Ensure only one thread can execute the join().
|
||||
std::unique_ptr<port::Thread> old_recovery_thread(
|
||||
std::move(recovery_thread_));
|
||||
db_mutex_->Unlock();
|
||||
cv_.SignalAll();
|
||||
old_recovery_thread->join();
|
||||
db_mutex_->Lock();
|
||||
}
|
||||
db_mutex_->Lock();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+13
-9
@@ -19,10 +19,13 @@ class DBImpl;
|
||||
// FlushReason, which tells the flush job why this flush is called.
|
||||
struct DBRecoverContext {
|
||||
FlushReason flush_reason;
|
||||
bool flush_after_recovery;
|
||||
|
||||
DBRecoverContext() : flush_reason(FlushReason::kErrorRecovery) {}
|
||||
|
||||
DBRecoverContext(FlushReason reason) : flush_reason(reason) {}
|
||||
DBRecoverContext()
|
||||
: flush_reason(FlushReason::kErrorRecovery),
|
||||
flush_after_recovery(false) {}
|
||||
DBRecoverContext(FlushReason reason)
|
||||
: flush_reason(reason), flush_after_recovery(false) {}
|
||||
};
|
||||
|
||||
class ErrorHandler {
|
||||
@@ -39,11 +42,11 @@ class ErrorHandler {
|
||||
recovery_in_prog_(false),
|
||||
soft_error_no_bg_work_(false),
|
||||
is_db_stopped_(false),
|
||||
bg_error_stats_(db_options.statistics) {
|
||||
bg_error_stats_(db_options.statistics),
|
||||
recovery_disabled_file_deletion_(false) {
|
||||
// Clear the checked flag for uninitialized errors
|
||||
bg_error_.PermitUncheckedError();
|
||||
recovery_error_.PermitUncheckedError();
|
||||
recovery_io_error_.PermitUncheckedError();
|
||||
}
|
||||
|
||||
void EnableAutoRecovery() { auto_recovery_ = true; }
|
||||
@@ -84,10 +87,7 @@ class ErrorHandler {
|
||||
Status bg_error_;
|
||||
// A separate Status variable used to record any errors during the
|
||||
// recovery process from hard errors
|
||||
Status recovery_error_;
|
||||
// A separate IO Status variable used to record any IO errors during
|
||||
// the recovery process. At the same time, recovery_error_ is also set.
|
||||
IOStatus recovery_io_error_;
|
||||
IOStatus recovery_error_;
|
||||
// The condition variable used with db_mutex during auto resume for time
|
||||
// wait.
|
||||
InstrumentedCondVar cv_;
|
||||
@@ -109,6 +109,10 @@ class ErrorHandler {
|
||||
// The pointer of DB statistics.
|
||||
std::shared_ptr<Statistics> bg_error_stats_;
|
||||
|
||||
// Tracks whether the recovery has disabled file deletion. This boolean flag
|
||||
// is updated while holding db mutex.
|
||||
bool recovery_disabled_file_deletion_;
|
||||
|
||||
const Status& HandleKnownErrors(const Status& bg_err,
|
||||
BackgroundErrorReason reason);
|
||||
Status OverrideNoSpaceError(const Status& bg_error, bool* auto_recovery);
|
||||
|
||||
@@ -2623,6 +2623,7 @@ TEST_P(ExternalSSTFileTest,
|
||||
"AfterRead");
|
||||
ingest_thread.join();
|
||||
for (auto* iter : iters) {
|
||||
ASSERT_OK(iter->status());
|
||||
delete iter;
|
||||
}
|
||||
iters.clear();
|
||||
|
||||
+7
-6
@@ -100,7 +100,7 @@ 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 SeqnoToTimeMapping& seqno_time_mapping, const std::string& db_id,
|
||||
const SeqnoToTimeMapping& seqno_to_time_mapping, const std::string& db_id,
|
||||
const std::string& db_session_id, std::string full_history_ts_low,
|
||||
BlobFileCompletionCallback* blob_callback)
|
||||
: dbname_(dbname),
|
||||
@@ -136,7 +136,7 @@ FlushJob::FlushJob(
|
||||
clock_(db_options_.clock),
|
||||
full_history_ts_low_(std::move(full_history_ts_low)),
|
||||
blob_callback_(blob_callback),
|
||||
db_impl_seqno_time_mapping_(seqno_time_mapping) {
|
||||
db_impl_seqno_to_time_mapping_(seqno_to_time_mapping) {
|
||||
// Update the thread status to indicate flush.
|
||||
ReportStartedFlush();
|
||||
TEST_SYNC_POINT("FlushJob::FlushJob()");
|
||||
@@ -851,10 +851,11 @@ Status FlushJob::WriteLevel0Table() {
|
||||
Status s;
|
||||
|
||||
SequenceNumber smallest_seqno = mems_.front()->GetEarliestSequenceNumber();
|
||||
if (!db_impl_seqno_time_mapping_.Empty()) {
|
||||
// make a local copy, as the seqno_time_mapping from db_impl is not thread
|
||||
// safe, which will be used while not holding the db_mutex.
|
||||
seqno_to_time_mapping_ = db_impl_seqno_time_mapping_.Copy(smallest_seqno);
|
||||
if (!db_impl_seqno_to_time_mapping_.Empty()) {
|
||||
// make a local copy, as the seqno_to_time_mapping from db_impl is not
|
||||
// thread safe, which will be used while not holding the db_mutex.
|
||||
seqno_to_time_mapping_ =
|
||||
db_impl_seqno_to_time_mapping_.Copy(smallest_seqno);
|
||||
}
|
||||
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
+3
-3
@@ -210,9 +210,9 @@ class FlushJob {
|
||||
const std::string full_history_ts_low_;
|
||||
BlobFileCompletionCallback* blob_callback_;
|
||||
|
||||
// reference to the seqno_time_mapping_ in db_impl.h, not safe to read without
|
||||
// db mutex
|
||||
const SeqnoToTimeMapping& db_impl_seqno_time_mapping_;
|
||||
// reference to the seqno_to_time_mapping_ in db_impl.h, not safe to read
|
||||
// without db mutex
|
||||
const SeqnoToTimeMapping& db_impl_seqno_to_time_mapping_;
|
||||
SeqnoToTimeMapping seqno_to_time_mapping_;
|
||||
|
||||
// Keeps track of the newest user-defined timestamp for this flush job if
|
||||
|
||||
@@ -457,7 +457,8 @@ TEST_F(FlushJobTest, FlushMemtablesMultipleColumnFamilies) {
|
||||
// Verify that imm is empty
|
||||
ASSERT_EQ(std::numeric_limits<uint64_t>::max(),
|
||||
all_cfds[k]->imm()->GetEarliestMemTableID());
|
||||
ASSERT_EQ(0, all_cfds[k]->imm()->GetLatestMemTableID());
|
||||
ASSERT_EQ(0, all_cfds[k]->imm()->GetLatestMemTableID(
|
||||
false /* for_atomic_flush */));
|
||||
++k;
|
||||
}
|
||||
|
||||
|
||||
@@ -124,6 +124,7 @@ TEST_F(ManualCompactionTest, CompactTouchesAllKeys) {
|
||||
ASSERT_EQ("key3", itr->key().ToString());
|
||||
itr->Next();
|
||||
ASSERT_TRUE(!itr->Valid());
|
||||
ASSERT_OK(itr->status());
|
||||
delete itr;
|
||||
|
||||
delete options.compaction_filter;
|
||||
@@ -179,6 +180,7 @@ TEST_F(ManualCompactionTest, Test) {
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
num_keys++;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
delete iter;
|
||||
ASSERT_EQ(kNumKeys, num_keys) << "Bad number of keys";
|
||||
|
||||
|
||||
+11
-1
@@ -386,11 +386,21 @@ class MemTableList {
|
||||
return memlist.back()->GetID();
|
||||
}
|
||||
|
||||
uint64_t GetLatestMemTableID() const {
|
||||
uint64_t GetLatestMemTableID(bool for_atomic_flush) const {
|
||||
auto& memlist = current_->memlist_;
|
||||
if (memlist.empty()) {
|
||||
return 0;
|
||||
}
|
||||
if (for_atomic_flush) {
|
||||
// Scan the memtable list from new to old
|
||||
for (auto it = memlist.begin(); it != memlist.end(); ++it) {
|
||||
MemTable* m = *it;
|
||||
if (m->atomic_flush_seqno_ != kMaxSequenceNumber) {
|
||||
return m->GetID();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return memlist.front()->GetID();
|
||||
}
|
||||
|
||||
|
||||
@@ -833,7 +833,7 @@ TEST_F(MemTableListTest, FlushPendingTest) {
|
||||
// Add another table
|
||||
list.Add(tables[5], &to_delete);
|
||||
ASSERT_EQ(1, list.NumNotFlushed());
|
||||
ASSERT_EQ(5, list.GetLatestMemTableID());
|
||||
ASSERT_EQ(5, list.GetLatestMemTableID(false /* for_atomic_flush */));
|
||||
memtable_id = 4;
|
||||
// Pick tables to flush. The tables to pick must have ID smaller than or
|
||||
// equal to 4. Therefore, no table will be selected in this case.
|
||||
|
||||
@@ -298,6 +298,20 @@ Status MergeHelper::TimedFullMerge(
|
||||
result_type, op_failure_scope);
|
||||
}
|
||||
|
||||
Status MergeHelper::TimedFullMerge(
|
||||
const MergeOperator* merge_operator, const Slice& key, WideBaseValueTag,
|
||||
const WideColumns& columns, const std::vector<Slice>& operands,
|
||||
Logger* logger, Statistics* statistics, SystemClock* clock,
|
||||
bool update_num_ops_stats, std::string* result, Slice* result_operand,
|
||||
ValueType* result_type, MergeOperator::OpFailureScope* op_failure_scope) {
|
||||
MergeOperator::MergeOperationInputV3::ExistingValue existing_value(columns);
|
||||
|
||||
return TimedFullMergeImpl(merge_operator, key, std::move(existing_value),
|
||||
operands, logger, statistics, clock,
|
||||
update_num_ops_stats, result, result_operand,
|
||||
result_type, op_failure_scope);
|
||||
}
|
||||
|
||||
Status MergeHelper::TimedFullMerge(
|
||||
const MergeOperator* merge_operator, const Slice& key, NoBaseValueTag,
|
||||
const std::vector<Slice>& operands, Logger* logger, Statistics* statistics,
|
||||
@@ -351,6 +365,21 @@ Status MergeHelper::TimedFullMerge(
|
||||
op_failure_scope);
|
||||
}
|
||||
|
||||
Status MergeHelper::TimedFullMerge(
|
||||
const MergeOperator* merge_operator, const Slice& key, WideBaseValueTag,
|
||||
const WideColumns& columns, const std::vector<Slice>& operands,
|
||||
Logger* logger, Statistics* statistics, SystemClock* clock,
|
||||
bool update_num_ops_stats, std::string* result_value,
|
||||
PinnableWideColumns* result_entity,
|
||||
MergeOperator::OpFailureScope* op_failure_scope) {
|
||||
MergeOperator::MergeOperationInputV3::ExistingValue existing_value(columns);
|
||||
|
||||
return TimedFullMergeImpl(merge_operator, key, std::move(existing_value),
|
||||
operands, logger, statistics, clock,
|
||||
update_num_ops_stats, result_value, result_entity,
|
||||
op_failure_scope);
|
||||
}
|
||||
|
||||
// PRE: iter points to the first merge type entry
|
||||
// POST: iter points to the first entry beyond the merge process (or the end)
|
||||
// keys_, operands_ are updated to reflect the merge result.
|
||||
|
||||
@@ -85,6 +85,13 @@ class MergeHelper {
|
||||
std::string* result, Slice* result_operand, ValueType* result_type,
|
||||
MergeOperator::OpFailureScope* op_failure_scope);
|
||||
|
||||
static Status TimedFullMerge(
|
||||
const MergeOperator* merge_operator, const Slice& key, WideBaseValueTag,
|
||||
const WideColumns& columns, const std::vector<Slice>& operands,
|
||||
Logger* logger, Statistics* statistics, SystemClock* clock,
|
||||
bool update_num_ops_stats, std::string* result, Slice* result_operand,
|
||||
ValueType* result_type, MergeOperator::OpFailureScope* op_failure_scope);
|
||||
|
||||
// Variants that expose the merge result translated to the form requested by
|
||||
// the client. (For example, if the result is a wide-column structure but the
|
||||
// client requested the results in plain-value form, the value of the default
|
||||
@@ -112,6 +119,16 @@ class MergeHelper {
|
||||
std::string* result_value, PinnableWideColumns* result_entity,
|
||||
MergeOperator::OpFailureScope* op_failure_scope);
|
||||
|
||||
static Status TimedFullMerge(const MergeOperator* merge_operator,
|
||||
const Slice& key, WideBaseValueTag,
|
||||
const WideColumns& columns,
|
||||
const std::vector<Slice>& operands,
|
||||
Logger* logger, Statistics* statistics,
|
||||
SystemClock* clock, bool update_num_ops_stats,
|
||||
std::string* result_value,
|
||||
PinnableWideColumns* result_entity,
|
||||
MergeOperator::OpFailureScope* op_failure_scope);
|
||||
|
||||
// During compaction, merge entries until we hit
|
||||
// - a corrupted key
|
||||
// - a Put/Delete,
|
||||
|
||||
@@ -149,6 +149,7 @@ TEST_F(PerfContextTest, SeekIntoDeletion) {
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
StopWatchNano timer2(SystemClock::Default().get(), true);
|
||||
iter->Next();
|
||||
ASSERT_OK(iter->status());
|
||||
auto elapsed_nanos2 = timer2.ElapsedNanos();
|
||||
if (FLAGS_verbose) {
|
||||
std::cout << "next cmp: " << get_perf_context()->user_key_comparison_count
|
||||
@@ -1092,6 +1093,7 @@ TEST_F(PerfContextTest, MergeOperandCount) {
|
||||
|
||||
get_perf_context()->Reset();
|
||||
}
|
||||
ASSERT_OK(it->status());
|
||||
}
|
||||
|
||||
// Backward iteration
|
||||
@@ -1104,6 +1106,7 @@ TEST_F(PerfContextTest, MergeOperandCount) {
|
||||
|
||||
get_perf_context()->Reset();
|
||||
}
|
||||
ASSERT_OK(it->status());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -42,15 +42,16 @@ class PeriodicTaskScheduler {
|
||||
PeriodicTaskScheduler& operator=(const PeriodicTaskScheduler&) = delete;
|
||||
PeriodicTaskScheduler& operator=(PeriodicTaskScheduler&&) = delete;
|
||||
|
||||
// Register a task with its default repeat period
|
||||
// Register a task with its default repeat period. Thread safe call.
|
||||
Status Register(PeriodicTaskType task_type, const PeriodicTaskFunc& fn);
|
||||
|
||||
// Register a task with specified repeat period. 0 is an invalid argument
|
||||
// (kInvalidPeriodSec). To stop the task, please use Unregister() specifically
|
||||
// (kInvalidPeriodSec). To stop the task, please use Unregister().
|
||||
// Thread safe call.
|
||||
Status Register(PeriodicTaskType task_type, const PeriodicTaskFunc& fn,
|
||||
uint64_t repeat_period_seconds);
|
||||
|
||||
// Unregister the task
|
||||
// Unregister the task. Thread safe call.
|
||||
Status Unregister(PeriodicTaskType task_type);
|
||||
|
||||
#ifndef NDEBUG
|
||||
@@ -105,4 +106,3 @@ class PeriodicTaskScheduler {
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -897,6 +897,7 @@ TEST_P(PlainTableDBTest, IteratorLargeKeys) {
|
||||
}
|
||||
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
delete iter;
|
||||
}
|
||||
@@ -945,6 +946,7 @@ TEST_P(PlainTableDBTest, IteratorLargeKeysWithPrefix) {
|
||||
}
|
||||
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
delete iter;
|
||||
}
|
||||
|
||||
@@ -782,6 +782,7 @@ TEST_F(PrefixTest, PrefixSeekModePrev) {
|
||||
}
|
||||
}
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -471,7 +471,7 @@ class Repairer {
|
||||
0 /* file_creation_time */, "DB Repairer" /* db_id */, db_session_id_,
|
||||
0 /*target_file_size*/, meta.fd.GetNumber());
|
||||
|
||||
SeqnoToTimeMapping empty_seqno_time_mapping;
|
||||
SeqnoToTimeMapping empty_seqno_to_time_mapping;
|
||||
status = BuildTable(
|
||||
dbname_, /* versions */ nullptr, immutable_db_options_, tboptions,
|
||||
file_options_, read_options, table_cache_.get(), iter.get(),
|
||||
@@ -479,8 +479,9 @@ class Repairer {
|
||||
{}, kMaxSequenceNumber, kMaxSequenceNumber, snapshot_checker,
|
||||
false /* paranoid_file_checks*/, nullptr /* internal_stats */, &io_s,
|
||||
nullptr /*IOTracer*/, BlobFileCreationReason::kRecovery,
|
||||
empty_seqno_time_mapping, nullptr /* event_logger */, 0 /* job_id */,
|
||||
Env::IO_HIGH, nullptr /* table_properties */, write_hint);
|
||||
empty_seqno_to_time_mapping, nullptr /* event_logger */,
|
||||
0 /* job_id */, Env::IO_HIGH, nullptr /* table_properties */,
|
||||
write_hint);
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
"Log #%" PRIu64 ": %d ops saved to Table #%" PRIu64 " %s",
|
||||
log, counter, meta.fd.GetNumber(),
|
||||
|
||||
+454
-70
@@ -12,13 +12,13 @@
|
||||
#include "rocksdb/utilities/debug.h"
|
||||
#include "test_util/mock_time_env.h"
|
||||
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class SeqnoTimeTest : public DBTestBase {
|
||||
public:
|
||||
SeqnoTimeTest() : DBTestBase("seqno_time_test", /*env_do_fsync=*/false) {
|
||||
mock_clock_ = std::make_shared<MockSystemClock>(env_->GetSystemClock());
|
||||
mock_clock_->SetCurrentTime(kMockStartTime);
|
||||
mock_env_ = std::make_unique<CompositeEnvWrapper>(env_, mock_clock_);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,10 @@ class SeqnoTimeTest : public DBTestBase {
|
||||
std::unique_ptr<Env> mock_env_;
|
||||
std::shared_ptr<MockSystemClock> mock_clock_;
|
||||
|
||||
// Sufficient starting time that preserve time doesn't under-flow into
|
||||
// pre-history
|
||||
static constexpr uint32_t kMockStartTime = 10000000;
|
||||
|
||||
void SetUp() override {
|
||||
mock_clock_->InstallTimedWaitFixCallback();
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
@@ -34,6 +38,7 @@ class SeqnoTimeTest : public DBTestBase {
|
||||
reinterpret_cast<PeriodicTaskScheduler*>(arg);
|
||||
periodic_task_scheduler_ptr->TEST_OverrideTimer(mock_clock_.get());
|
||||
});
|
||||
mock_clock_->SetCurrentTime(kMockStartTime);
|
||||
}
|
||||
|
||||
// make sure the file is not in cache, otherwise it won't have IO info
|
||||
@@ -77,11 +82,6 @@ TEST_F(SeqnoTimeTest, TemperatureBasicUniversal) {
|
||||
options.num_levels = kNumLevels;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// pass some time first, otherwise the first a few keys write time are going
|
||||
// to be zero, and internally zero has special meaning: kUnknownSeqnoTime
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun(
|
||||
[&] { mock_clock_->MockSleepForSeconds(static_cast<int>(kKeyPerSec)); });
|
||||
|
||||
int sst_num = 0;
|
||||
// Write files that are overlap and enough to trigger compaction
|
||||
for (; sst_num < kNumTrigger; sst_num++) {
|
||||
@@ -189,11 +189,6 @@ TEST_F(SeqnoTimeTest, TemperatureBasicLevel) {
|
||||
options.disable_auto_compactions = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// pass some time first, otherwise the first a few keys write time are going
|
||||
// to be zero, and internally zero has special meaning: kUnknownSeqnoTime
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun(
|
||||
[&] { mock_clock_->MockSleepForSeconds(static_cast<int>(10)); });
|
||||
|
||||
int sst_num = 0;
|
||||
// Write files that are overlap
|
||||
for (; sst_num < 4; sst_num++) {
|
||||
@@ -320,7 +315,9 @@ TEST_P(SeqnoTimeTablePropTest, BasicSeqnoToTimeMapping) {
|
||||
DestroyAndReopen(options);
|
||||
|
||||
std::set<uint64_t> checked_file_nums;
|
||||
SequenceNumber start_seq = dbfull()->GetLatestSequenceNumber();
|
||||
SequenceNumber start_seq = dbfull()->GetLatestSequenceNumber() + 1;
|
||||
uint64_t start_time = mock_clock_->NowSeconds();
|
||||
|
||||
// Write a key every 10 seconds
|
||||
for (int i = 0; i < 200; i++) {
|
||||
ASSERT_OK(Put(Key(i), "value"));
|
||||
@@ -338,21 +335,20 @@ TEST_P(SeqnoTimeTablePropTest, BasicSeqnoToTimeMapping) {
|
||||
ASSERT_FALSE(tp_mapping.Empty());
|
||||
auto seqs = tp_mapping.TEST_GetInternalMapping();
|
||||
// about ~20 seqs->time entries, because the sample rate is 10000/100, and it
|
||||
// passes 2k time.
|
||||
ASSERT_GE(seqs.size(), 19);
|
||||
ASSERT_LE(seqs.size(), 21);
|
||||
SequenceNumber seq_end = dbfull()->GetLatestSequenceNumber();
|
||||
for (auto i = start_seq; i < start_seq + 10; i++) {
|
||||
ASSERT_LE(tp_mapping.GetOldestApproximateTime(i), (i + 1) * 10);
|
||||
}
|
||||
start_seq += 10;
|
||||
// passes 2k time. Add (roughly) one for starting entry.
|
||||
ASSERT_GE(seqs.size(), 20);
|
||||
ASSERT_LE(seqs.size(), 22);
|
||||
SequenceNumber seq_end = dbfull()->GetLatestSequenceNumber() + 1;
|
||||
for (auto i = start_seq; i < seq_end; i++) {
|
||||
// The result is within the range
|
||||
ASSERT_GE(tp_mapping.GetOldestApproximateTime(i), (i - 10) * 10);
|
||||
ASSERT_LE(tp_mapping.GetOldestApproximateTime(i), (i + 10) * 10);
|
||||
ASSERT_GE(tp_mapping.GetProximalTimeBeforeSeqno(i),
|
||||
start_time + (i - start_seq) * 10 - 100);
|
||||
ASSERT_LE(tp_mapping.GetProximalTimeBeforeSeqno(i),
|
||||
start_time + (i - start_seq) * 10);
|
||||
}
|
||||
checked_file_nums.insert(it->second->orig_file_number);
|
||||
start_seq = seq_end;
|
||||
start_time = mock_clock_->NowSeconds();
|
||||
|
||||
// Write a key every 1 seconds
|
||||
for (int i = 0; i < 200; i++) {
|
||||
@@ -360,7 +356,7 @@ TEST_P(SeqnoTimeTablePropTest, BasicSeqnoToTimeMapping) {
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun(
|
||||
[&] { mock_clock_->MockSleepForSeconds(static_cast<int>(1)); });
|
||||
}
|
||||
seq_end = dbfull()->GetLatestSequenceNumber();
|
||||
seq_end = dbfull()->GetLatestSequenceNumber() + 1;
|
||||
ASSERT_OK(Flush());
|
||||
tables_props.clear();
|
||||
ASSERT_OK(dbfull()->GetPropertiesOfAllTables(&tables_props));
|
||||
@@ -382,13 +378,14 @@ TEST_P(SeqnoTimeTablePropTest, BasicSeqnoToTimeMapping) {
|
||||
ASSERT_GE(seqs.size(), 1);
|
||||
ASSERT_LE(seqs.size(), 3);
|
||||
for (auto i = start_seq; i < seq_end; i++) {
|
||||
// The result is not very accurate, as there is more data write within small
|
||||
// range of time
|
||||
ASSERT_GE(tp_mapping.GetOldestApproximateTime(i), (i - start_seq) + 1000);
|
||||
ASSERT_LE(tp_mapping.GetOldestApproximateTime(i), (i - start_seq) + 3000);
|
||||
ASSERT_GE(tp_mapping.GetProximalTimeBeforeSeqno(i),
|
||||
start_time + (i - start_seq) - 100);
|
||||
ASSERT_LE(tp_mapping.GetProximalTimeBeforeSeqno(i),
|
||||
start_time + (i - start_seq));
|
||||
}
|
||||
checked_file_nums.insert(it->second->orig_file_number);
|
||||
start_seq = seq_end;
|
||||
start_time = mock_clock_->NowSeconds();
|
||||
|
||||
// Write a key every 200 seconds
|
||||
for (int i = 0; i < 200; i++) {
|
||||
@@ -396,7 +393,7 @@ TEST_P(SeqnoTimeTablePropTest, BasicSeqnoToTimeMapping) {
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun(
|
||||
[&] { mock_clock_->MockSleepForSeconds(static_cast<int>(200)); });
|
||||
}
|
||||
seq_end = dbfull()->GetLatestSequenceNumber();
|
||||
seq_end = dbfull()->GetLatestSequenceNumber() + 1;
|
||||
ASSERT_OK(Flush());
|
||||
tables_props.clear();
|
||||
ASSERT_OK(dbfull()->GetPropertiesOfAllTables(&tables_props));
|
||||
@@ -417,20 +414,18 @@ TEST_P(SeqnoTimeTablePropTest, BasicSeqnoToTimeMapping) {
|
||||
// The sequence number -> time entries should be maxed
|
||||
ASSERT_GE(seqs.size(), 99);
|
||||
ASSERT_LE(seqs.size(), 101);
|
||||
for (auto i = start_seq; i < seq_end - 99; i++) {
|
||||
// likely the first 100 entries reports 0
|
||||
ASSERT_LE(tp_mapping.GetOldestApproximateTime(i), (i - start_seq) + 3000);
|
||||
}
|
||||
start_seq += 101;
|
||||
|
||||
for (auto i = start_seq; i < seq_end; i++) {
|
||||
ASSERT_GE(tp_mapping.GetOldestApproximateTime(i),
|
||||
(i - start_seq) * 200 + 22200);
|
||||
ASSERT_LE(tp_mapping.GetOldestApproximateTime(i),
|
||||
(i - start_seq) * 200 + 22600);
|
||||
// aged out entries allowed to report time=0
|
||||
if ((seq_end - i) * 200 <= 10000) {
|
||||
ASSERT_GE(tp_mapping.GetProximalTimeBeforeSeqno(i),
|
||||
start_time + (i - start_seq) * 200 - 100);
|
||||
}
|
||||
ASSERT_LE(tp_mapping.GetProximalTimeBeforeSeqno(i),
|
||||
start_time + (i - start_seq) * 200);
|
||||
}
|
||||
checked_file_nums.insert(it->second->orig_file_number);
|
||||
start_seq = seq_end;
|
||||
start_time = mock_clock_->NowSeconds();
|
||||
|
||||
// Write a key every 100 seconds
|
||||
for (int i = 0; i < 200; i++) {
|
||||
@@ -438,7 +433,7 @@ TEST_P(SeqnoTimeTablePropTest, BasicSeqnoToTimeMapping) {
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun(
|
||||
[&] { mock_clock_->MockSleepForSeconds(static_cast<int>(100)); });
|
||||
}
|
||||
seq_end = dbfull()->GetLatestSequenceNumber();
|
||||
seq_end = dbfull()->GetLatestSequenceNumber() + 1;
|
||||
ASSERT_OK(Flush());
|
||||
tables_props.clear();
|
||||
ASSERT_OK(dbfull()->GetPropertiesOfAllTables(&tables_props));
|
||||
@@ -484,18 +479,15 @@ TEST_P(SeqnoTimeTablePropTest, BasicSeqnoToTimeMapping) {
|
||||
seqs = tp_mapping.TEST_GetInternalMapping();
|
||||
ASSERT_GE(seqs.size(), 99);
|
||||
ASSERT_LE(seqs.size(), 101);
|
||||
for (auto i = start_seq; i < seq_end - 99; i++) {
|
||||
// likely the first 100 entries reports 0
|
||||
ASSERT_LE(tp_mapping.GetOldestApproximateTime(i),
|
||||
(i - start_seq) * 100 + 50000);
|
||||
}
|
||||
start_seq += 101;
|
||||
|
||||
for (auto i = start_seq; i < seq_end; i++) {
|
||||
ASSERT_GE(tp_mapping.GetOldestApproximateTime(i),
|
||||
(i - start_seq) * 100 + 52200);
|
||||
ASSERT_LE(tp_mapping.GetOldestApproximateTime(i),
|
||||
(i - start_seq) * 100 + 52400);
|
||||
// aged out entries allowed to report time=0
|
||||
// FIXME: should be <=
|
||||
if ((seq_end - i) * 100 < 10000) {
|
||||
ASSERT_GE(tp_mapping.GetProximalTimeBeforeSeqno(i),
|
||||
start_time + (i - start_seq) * 100 - 100);
|
||||
}
|
||||
ASSERT_LE(tp_mapping.GetProximalTimeBeforeSeqno(i),
|
||||
start_time + (i - start_seq) * 100);
|
||||
}
|
||||
ASSERT_OK(db_->Close());
|
||||
}
|
||||
@@ -620,14 +612,12 @@ TEST_P(SeqnoTimeTablePropTest, MultiCFs) {
|
||||
ASSERT_GE(seqs.size(), 99);
|
||||
ASSERT_LE(seqs.size(), 101);
|
||||
|
||||
for (int j = 0; j < 2; j++) {
|
||||
for (int i = 0; i < 200; i++) {
|
||||
ASSERT_OK(Put(0, Key(i), "value"));
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun(
|
||||
[&] { mock_clock_->MockSleepForSeconds(static_cast<int>(100)); });
|
||||
}
|
||||
ASSERT_OK(Flush(0));
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
tables_props.clear();
|
||||
ASSERT_OK(dbfull()->GetPropertiesOfAllTables(handles_[0], &tables_props));
|
||||
@@ -735,8 +725,9 @@ TEST_P(SeqnoTimeTablePropTest, SeqnoToTimeMappingUniversal) {
|
||||
ASSERT_OK(tp_mapping.Sort());
|
||||
ASSERT_FALSE(tp_mapping.Empty());
|
||||
auto seqs = tp_mapping.TEST_GetInternalMapping();
|
||||
ASSERT_GE(seqs.size(), 10 - 1);
|
||||
ASSERT_LE(seqs.size(), 10 + 1);
|
||||
// Add (roughly) one for starting entry.
|
||||
ASSERT_GE(seqs.size(), 10);
|
||||
ASSERT_LE(seqs.size(), 10 + 2);
|
||||
}
|
||||
|
||||
// Trigger a compaction
|
||||
@@ -826,6 +817,203 @@ TEST_P(SeqnoTimeTablePropTest, SeqnoToTimeMappingUniversal) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_P(SeqnoTimeTablePropTest, PrePopulateInDB) {
|
||||
Options base_options = CurrentOptions();
|
||||
base_options.env = mock_env_.get();
|
||||
base_options.disable_auto_compactions = true;
|
||||
base_options.create_missing_column_families = true;
|
||||
Options track_options = base_options;
|
||||
constexpr uint32_t kPreserveSecs = 1234567;
|
||||
SetTrackTimeDurationOptions(kPreserveSecs, track_options);
|
||||
SeqnoToTimeMapping sttm;
|
||||
SequenceNumber latest_seqno;
|
||||
uint64_t start_time, end_time;
|
||||
|
||||
// #### DB#1, #2: No pre-population without preserve/preclude ####
|
||||
// #### But a single entry is added when preserve/preclude enabled ####
|
||||
for (bool with_write : {false, true}) {
|
||||
SCOPED_TRACE("with_write=" + std::to_string(with_write));
|
||||
DestroyAndReopen(base_options);
|
||||
sttm = dbfull()->TEST_GetSeqnoToTimeMapping();
|
||||
ASSERT_TRUE(sttm.Empty());
|
||||
ASSERT_EQ(db_->GetLatestSequenceNumber(), 0U);
|
||||
|
||||
if (with_write) {
|
||||
// Ensure that writes before new CF with preserve/preclude option don't
|
||||
// interfere with the seqno-to-time mapping getting a starting entry.
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_OK(Flush());
|
||||
} else {
|
||||
// FIXME: currently, starting entry after CreateColumnFamily requires
|
||||
// non-zero seqno
|
||||
ASSERT_OK(Delete("blah"));
|
||||
}
|
||||
|
||||
// Unfortunately, if we add a CF with preserve/preclude option after
|
||||
// open, that does not reserve seqnos with pre-populated time mappings.
|
||||
CreateColumnFamilies({"one"}, track_options);
|
||||
|
||||
// No pre-population (unfortunately), just a single starting entry
|
||||
sttm = dbfull()->TEST_GetSeqnoToTimeMapping();
|
||||
latest_seqno = db_->GetLatestSequenceNumber();
|
||||
start_time = mock_clock_->NowSeconds();
|
||||
ASSERT_EQ(sttm.Size(), 1);
|
||||
ASSERT_EQ(latest_seqno, 1U);
|
||||
// Current time maps to starting entry / seqno
|
||||
ASSERT_EQ(sttm.GetProximalSeqnoBeforeTime(start_time), 1U);
|
||||
// Any older times are unknown.
|
||||
ASSERT_EQ(sttm.GetProximalSeqnoBeforeTime(start_time - 1),
|
||||
kUnknownSeqnoBeforeAll);
|
||||
|
||||
// Now check that writes can proceed normally (passing about 20% of preserve
|
||||
// time)
|
||||
for (int i = 0; i < 20; i++) {
|
||||
ASSERT_OK(Put(Key(i), "value"));
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun([&] {
|
||||
mock_clock_->MockSleepForSeconds(static_cast<int>(kPreserveSecs / 99));
|
||||
});
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Check that mappings are getting populated
|
||||
sttm = dbfull()->TEST_GetSeqnoToTimeMapping();
|
||||
latest_seqno = db_->GetLatestSequenceNumber();
|
||||
end_time = mock_clock_->NowSeconds();
|
||||
ASSERT_EQ(sttm.Size(), 21);
|
||||
ASSERT_EQ(sttm.GetProximalSeqnoBeforeTime(end_time), latest_seqno);
|
||||
ASSERT_EQ(sttm.GetProximalSeqnoBeforeTime(start_time), 1U);
|
||||
ASSERT_EQ(sttm.GetProximalSeqnoBeforeTime(start_time - 1),
|
||||
kUnknownSeqnoBeforeAll);
|
||||
}
|
||||
|
||||
// ### DB#3, #4: Read-only DB with preserve/preclude after not ####
|
||||
// Make sure we don't hit issues with read-only DBs, which don't need
|
||||
// the mapping in the DB state (though it wouldn't hurt anything)
|
||||
for (bool with_write : {false, true}) {
|
||||
SCOPED_TRACE("with_write=" + std::to_string(with_write));
|
||||
DestroyAndReopen(base_options);
|
||||
if (with_write) {
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
ASSERT_OK(ReadOnlyReopen(base_options));
|
||||
if (with_write) {
|
||||
ASSERT_EQ(Get("foo"), "bar");
|
||||
}
|
||||
sttm = dbfull()->TEST_GetSeqnoToTimeMapping();
|
||||
ASSERT_EQ(sttm.Size(), 0);
|
||||
if (!with_write) {
|
||||
ASSERT_EQ(db_->GetLatestSequenceNumber(), 0);
|
||||
}
|
||||
|
||||
ASSERT_OK(ReadOnlyReopen(track_options));
|
||||
if (with_write) {
|
||||
ASSERT_EQ(Get("foo"), "bar");
|
||||
}
|
||||
sttm = dbfull()->TEST_GetSeqnoToTimeMapping();
|
||||
ASSERT_EQ(sttm.Size(), 0);
|
||||
if (!with_write) {
|
||||
ASSERT_EQ(db_->GetLatestSequenceNumber(), 0);
|
||||
|
||||
// And even if we re-open read-write, we do not get pre-population,
|
||||
// because that's only for new DBs.
|
||||
Reopen(track_options);
|
||||
sttm = dbfull()->TEST_GetSeqnoToTimeMapping();
|
||||
ASSERT_EQ(sttm.Size(), 0);
|
||||
ASSERT_EQ(db_->GetLatestSequenceNumber(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// #### DB#5: Destroy and open with preserve/preclude option ####
|
||||
DestroyAndReopen(track_options);
|
||||
|
||||
// Ensure pre-population
|
||||
constexpr auto kPrePopPairs = SeqnoToTimeMapping::kMaxSeqnoTimePairsPerSST;
|
||||
sttm = dbfull()->TEST_GetSeqnoToTimeMapping();
|
||||
latest_seqno = db_->GetLatestSequenceNumber();
|
||||
start_time = mock_clock_->NowSeconds();
|
||||
ASSERT_EQ(sttm.Size(), kPrePopPairs);
|
||||
// One nono-zero sequence number per pre-populated pair (this could be
|
||||
// revised if we want to use interpolation for better approximate time
|
||||
// mappings with no guarantee of erring in just one direction).
|
||||
ASSERT_EQ(latest_seqno, kPrePopPairs);
|
||||
// Current time maps to last pre-allocated seqno
|
||||
ASSERT_EQ(sttm.GetProximalSeqnoBeforeTime(start_time), latest_seqno);
|
||||
// Oldest tracking time maps to first pre-allocated seqno
|
||||
ASSERT_EQ(sttm.GetProximalSeqnoBeforeTime(start_time - kPreserveSecs), 1);
|
||||
|
||||
// In more detail, check that estimated seqnos (pre-allocated) are uniformly
|
||||
// spread over the tracked time.
|
||||
for (auto ratio : {0.0, 0.433, 0.678, 0.987, 1.0}) {
|
||||
// Round up query time
|
||||
uint64_t t = start_time - kPreserveSecs +
|
||||
static_cast<uint64_t>(ratio * kPreserveSecs + 0.9999999);
|
||||
// Round down estimated seqno
|
||||
SequenceNumber s =
|
||||
static_cast<SequenceNumber>(ratio * (latest_seqno - 1)) + 1;
|
||||
// Match
|
||||
ASSERT_EQ(sttm.GetProximalSeqnoBeforeTime(t), s);
|
||||
}
|
||||
|
||||
// Now check that writes can proceed normally (passing about 20% of preserve
|
||||
// time)
|
||||
for (int i = 0; i < 20; i++) {
|
||||
ASSERT_OK(Put(Key(i), "value"));
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun([&] {
|
||||
mock_clock_->MockSleepForSeconds(static_cast<int>(kPreserveSecs / 99));
|
||||
});
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Can still see some pre-populated mappings, though some displaced
|
||||
sttm = dbfull()->TEST_GetSeqnoToTimeMapping();
|
||||
latest_seqno = db_->GetLatestSequenceNumber();
|
||||
end_time = mock_clock_->NowSeconds();
|
||||
ASSERT_EQ(sttm.Size(), kPrePopPairs);
|
||||
ASSERT_EQ(sttm.GetProximalSeqnoBeforeTime(end_time), latest_seqno);
|
||||
ASSERT_EQ(sttm.GetProximalSeqnoBeforeTime(start_time - kPreserveSecs / 2),
|
||||
kPrePopPairs / 2);
|
||||
ASSERT_EQ(sttm.GetProximalSeqnoBeforeTime(start_time - kPreserveSecs),
|
||||
kUnknownSeqnoBeforeAll);
|
||||
|
||||
// Make sure we don't hit issues with read-only DBs, which don't need
|
||||
// the mapping in the DB state (though it wouldn't hurt anything)
|
||||
ASSERT_OK(ReadOnlyReopen(track_options));
|
||||
ASSERT_EQ(Get(Key(0)), "value");
|
||||
sttm = dbfull()->TEST_GetSeqnoToTimeMapping();
|
||||
ASSERT_EQ(sttm.Size(), 0);
|
||||
|
||||
// #### DB#6: Destroy and open+create an extra CF with preserve/preclude ####
|
||||
// (default CF does not have the option)
|
||||
Destroy(track_options);
|
||||
ReopenWithColumnFamilies({"default", "one"},
|
||||
List({base_options, track_options}));
|
||||
|
||||
// Ensure pre-population (not as exhaustive checking here)
|
||||
sttm = dbfull()->TEST_GetSeqnoToTimeMapping();
|
||||
latest_seqno = db_->GetLatestSequenceNumber();
|
||||
start_time = mock_clock_->NowSeconds();
|
||||
ASSERT_EQ(sttm.Size(), kPrePopPairs);
|
||||
// One nono-zero sequence number per pre-populated pair (this could be
|
||||
// revised if we want to use interpolation for better approximate time
|
||||
// mappings with no guarantee of erring in just one direction).
|
||||
ASSERT_EQ(latest_seqno, kPrePopPairs);
|
||||
// Current time maps to last pre-allocated seqno
|
||||
ASSERT_EQ(sttm.GetProximalSeqnoBeforeTime(start_time), latest_seqno);
|
||||
// Oldest tracking time maps to first pre-allocated seqno
|
||||
ASSERT_EQ(sttm.GetProximalSeqnoBeforeTime(start_time - kPreserveSecs), 1);
|
||||
|
||||
// Even after no writes and DB re-open without tracking options, sequence
|
||||
// numbers should not go backward into those that were pre-allocated.
|
||||
// (Future work: persist the mapping)
|
||||
ReopenWithColumnFamilies({"default", "one"},
|
||||
List({base_options, base_options}));
|
||||
ASSERT_EQ(latest_seqno, db_->GetLatestSequenceNumber());
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(SeqnoTimeTest, MappingAppend) {
|
||||
SeqnoToTimeMapping test(/*max_time_duration=*/100, /*max_capacity=*/10);
|
||||
|
||||
@@ -843,8 +1031,9 @@ TEST_F(SeqnoTimeTest, MappingAppend) {
|
||||
ASSERT_FALSE(test.Append(8, 12));
|
||||
ASSERT_EQ(size, test.Size());
|
||||
|
||||
// Append with the same seqno, newer time will be accepted
|
||||
ASSERT_TRUE(test.Append(10, 12));
|
||||
// Append with the same seqno, newer time is rejected because that makes
|
||||
// GetProximalSeqnoBeforeTime queries worse (see later test)
|
||||
ASSERT_FALSE(test.Append(10, 12));
|
||||
ASSERT_EQ(size, test.Size());
|
||||
// older time will be ignored
|
||||
ASSERT_FALSE(test.Append(10, 9));
|
||||
@@ -853,25 +1042,220 @@ TEST_F(SeqnoTimeTest, MappingAppend) {
|
||||
// new seqno with old time will be ignored
|
||||
ASSERT_FALSE(test.Append(12, 8));
|
||||
ASSERT_EQ(size, test.Size());
|
||||
|
||||
// new seqno with same time is accepted by replacing last entry
|
||||
// (improves GetProximalSeqnoBeforeTime queries without blowing up size)
|
||||
ASSERT_TRUE(test.Append(12, 11));
|
||||
ASSERT_EQ(size, test.Size());
|
||||
}
|
||||
|
||||
TEST_F(SeqnoTimeTest, GetOldestApproximateTime) {
|
||||
TEST_F(SeqnoTimeTest, ProximalFunctions) {
|
||||
SeqnoToTimeMapping test(/*max_time_duration=*/100, /*max_capacity=*/10);
|
||||
|
||||
ASSERT_EQ(test.GetOldestApproximateTime(10), kUnknownSeqnoTime);
|
||||
EXPECT_EQ(test.GetProximalTimeBeforeSeqno(1), kUnknownTimeBeforeAll);
|
||||
EXPECT_EQ(test.GetProximalTimeBeforeSeqno(1000000000000U),
|
||||
kUnknownTimeBeforeAll);
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(1), kUnknownSeqnoBeforeAll);
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(1000000000000U),
|
||||
kUnknownSeqnoBeforeAll);
|
||||
|
||||
test.Append(3, 10);
|
||||
// (Taken from example in SeqnoToTimeMapping class comment)
|
||||
// Time 500 is after seqno 10 and before seqno 11
|
||||
EXPECT_TRUE(test.Append(10, 500));
|
||||
|
||||
ASSERT_EQ(test.GetOldestApproximateTime(2), kUnknownSeqnoTime);
|
||||
ASSERT_EQ(test.GetOldestApproximateTime(3), 10);
|
||||
ASSERT_EQ(test.GetOldestApproximateTime(10), 10);
|
||||
// Seqno too early
|
||||
EXPECT_EQ(test.GetProximalTimeBeforeSeqno(9), kUnknownTimeBeforeAll);
|
||||
// We only know that 500 is after 10
|
||||
EXPECT_EQ(test.GetProximalTimeBeforeSeqno(10), kUnknownTimeBeforeAll);
|
||||
// Found
|
||||
EXPECT_EQ(test.GetProximalTimeBeforeSeqno(11), 500U);
|
||||
EXPECT_EQ(test.GetProximalTimeBeforeSeqno(1000000000000U), 500U);
|
||||
|
||||
test.Append(10, 100);
|
||||
// Time too early
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(499), kUnknownSeqnoBeforeAll);
|
||||
// Found
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(500), 10U);
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(501), 10U);
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(1000000000000U), 10U);
|
||||
|
||||
test.Append(100, 1000);
|
||||
ASSERT_EQ(test.GetOldestApproximateTime(10), 100);
|
||||
ASSERT_EQ(test.GetOldestApproximateTime(40), 100);
|
||||
ASSERT_EQ(test.GetOldestApproximateTime(111), 1000);
|
||||
// More samples
|
||||
EXPECT_TRUE(test.Append(20, 600));
|
||||
EXPECT_TRUE(test.Append(30, 700));
|
||||
|
||||
EXPECT_EQ(test.GetProximalTimeBeforeSeqno(10), kUnknownTimeBeforeAll);
|
||||
EXPECT_EQ(test.GetProximalTimeBeforeSeqno(11), 500U);
|
||||
EXPECT_EQ(test.GetProximalTimeBeforeSeqno(20), 500U);
|
||||
EXPECT_EQ(test.GetProximalTimeBeforeSeqno(21), 600U);
|
||||
EXPECT_EQ(test.GetProximalTimeBeforeSeqno(30), 600U);
|
||||
EXPECT_EQ(test.GetProximalTimeBeforeSeqno(31), 700U);
|
||||
EXPECT_EQ(test.GetProximalTimeBeforeSeqno(1000000000000U), 700U);
|
||||
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(499), kUnknownSeqnoBeforeAll);
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(500), 10U);
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(501), 10U);
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(599), 10U);
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(600), 20U);
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(601), 20U);
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(699), 20U);
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(700), 30U);
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(701), 30U);
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(1000000000000U), 30U);
|
||||
|
||||
// Redundant sample ignored
|
||||
EXPECT_EQ(test.Size(), 3U);
|
||||
EXPECT_FALSE(test.Append(30, 700));
|
||||
EXPECT_EQ(test.Size(), 3U);
|
||||
|
||||
EXPECT_EQ(test.GetProximalTimeBeforeSeqno(30), 600U);
|
||||
EXPECT_EQ(test.GetProximalTimeBeforeSeqno(31), 700U);
|
||||
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(699), 20U);
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(700), 30U);
|
||||
|
||||
// Later sample with same seqno is ignored, to provide best results
|
||||
// for GetProximalSeqnoBeforeTime function while saving entries
|
||||
// in SeqnoToTimeMapping.
|
||||
EXPECT_FALSE(test.Append(30, 800));
|
||||
|
||||
EXPECT_EQ(test.GetProximalTimeBeforeSeqno(30), 600U);
|
||||
// Could return 800, but saving space in SeqnoToTimeMapping instead.
|
||||
// Can reconsider if/when GetProximalTimeBeforeSeqno is used in
|
||||
// production.
|
||||
EXPECT_EQ(test.GetProximalTimeBeforeSeqno(31), 700U);
|
||||
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(699), 20U);
|
||||
// If the existing {30, 700} entry were replaced with {30, 800}, this
|
||||
// would return seqno 20 instead of 30, which would preclude more than
|
||||
// necessary for "preclude_last_level_data_seconds" feature.
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(700), 30U);
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(800), 30U);
|
||||
|
||||
// Still OK
|
||||
EXPECT_TRUE(test.Append(40, 900));
|
||||
|
||||
EXPECT_EQ(test.GetProximalTimeBeforeSeqno(30), 600U);
|
||||
EXPECT_EQ(test.GetProximalTimeBeforeSeqno(41), 900U);
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(899), 30U);
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(900), 40U);
|
||||
|
||||
// Burst of writes during a short time creates an opportunity
|
||||
// for better results from GetProximalSeqnoBeforeTime(), at the
|
||||
// expense of GetProximalTimeBeforeSeqno().
|
||||
EXPECT_TRUE(test.Append(50, 900));
|
||||
|
||||
// These are subject to later revision depending on priorities
|
||||
EXPECT_EQ(test.GetProximalTimeBeforeSeqno(49), 700U);
|
||||
EXPECT_EQ(test.GetProximalTimeBeforeSeqno(51), 900U);
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(899), 30U);
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(900), 50U);
|
||||
}
|
||||
|
||||
TEST_F(SeqnoTimeTest, PrePopulate) {
|
||||
SeqnoToTimeMapping test(/*max_time_duration=*/100, /*max_capacity=*/10);
|
||||
|
||||
EXPECT_EQ(test.Size(), 0U);
|
||||
|
||||
// Smallest case is like two Appends
|
||||
test.PrePopulate(10, 11, 500, 600);
|
||||
|
||||
EXPECT_EQ(test.GetProximalTimeBeforeSeqno(10), kUnknownTimeBeforeAll);
|
||||
EXPECT_EQ(test.GetProximalTimeBeforeSeqno(11), 500U);
|
||||
EXPECT_EQ(test.GetProximalTimeBeforeSeqno(12), 600U);
|
||||
|
||||
test.Clear();
|
||||
|
||||
// Populate a small range
|
||||
uint64_t kTimeIncrement = 1234567;
|
||||
test.PrePopulate(1, 12, kTimeIncrement, kTimeIncrement * 2);
|
||||
|
||||
for (uint64_t i = 0; i <= 12; ++i) {
|
||||
// NOTE: with 1 and 12 as the pre-populated end points, the duration is
|
||||
// broken into 11 equal(-ish) spans
|
||||
uint64_t t = kTimeIncrement + (i * kTimeIncrement) / 11 - 1;
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(t), i);
|
||||
}
|
||||
|
||||
test.Clear();
|
||||
|
||||
// Populate an excessively large range (in the future we might want to
|
||||
// interpolate estimated times for seqnos between entries)
|
||||
test.PrePopulate(1, 34567, kTimeIncrement, kTimeIncrement * 2);
|
||||
|
||||
for (auto ratio : {0.0, 0.433, 0.678, 0.987, 1.0}) {
|
||||
// Round up query time
|
||||
uint64_t t = kTimeIncrement +
|
||||
static_cast<uint64_t>(ratio * kTimeIncrement + 0.9999999);
|
||||
// Round down estimated seqno
|
||||
SequenceNumber s = static_cast<SequenceNumber>(ratio * (34567 - 1)) + 1;
|
||||
// Match
|
||||
// TODO: for now this is exact, but in the future might need approximation
|
||||
// bounds to account for limited samples.
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(t), s);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(SeqnoTimeTest, TruncateOldEntries) {
|
||||
constexpr uint64_t kMaxTimeDuration = 42;
|
||||
SeqnoToTimeMapping test(kMaxTimeDuration, /*max_capacity=*/10);
|
||||
|
||||
EXPECT_EQ(test.Size(), 0U);
|
||||
|
||||
// Safe on empty mapping
|
||||
test.TruncateOldEntries(500);
|
||||
|
||||
EXPECT_EQ(test.Size(), 0U);
|
||||
|
||||
// (Taken from example in SeqnoToTimeMapping class comment)
|
||||
// Time 500 is after seqno 10 and before seqno 11
|
||||
EXPECT_TRUE(test.Append(10, 500));
|
||||
EXPECT_TRUE(test.Append(20, 600));
|
||||
EXPECT_TRUE(test.Append(30, 700));
|
||||
EXPECT_TRUE(test.Append(40, 800));
|
||||
EXPECT_TRUE(test.Append(50, 900));
|
||||
|
||||
EXPECT_EQ(test.Size(), 5U);
|
||||
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(500), 10U);
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(599), 10U);
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(600), 20U);
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(699), 20U);
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(700), 30U);
|
||||
// etc.
|
||||
|
||||
// Must keep first entry
|
||||
test.TruncateOldEntries(500 + kMaxTimeDuration);
|
||||
EXPECT_EQ(test.Size(), 5U);
|
||||
test.TruncateOldEntries(599 + kMaxTimeDuration);
|
||||
EXPECT_EQ(test.Size(), 5U);
|
||||
|
||||
// Purges first entry
|
||||
test.TruncateOldEntries(600 + kMaxTimeDuration);
|
||||
EXPECT_EQ(test.Size(), 4U);
|
||||
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(500), kUnknownSeqnoBeforeAll);
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(599), kUnknownSeqnoBeforeAll);
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(600), 20U);
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(699), 20U);
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(700), 30U);
|
||||
|
||||
// No effect
|
||||
test.TruncateOldEntries(600 + kMaxTimeDuration);
|
||||
EXPECT_EQ(test.Size(), 4U);
|
||||
test.TruncateOldEntries(699 + kMaxTimeDuration);
|
||||
EXPECT_EQ(test.Size(), 4U);
|
||||
|
||||
// Purges next two
|
||||
test.TruncateOldEntries(899 + kMaxTimeDuration);
|
||||
EXPECT_EQ(test.Size(), 2U);
|
||||
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(799), kUnknownSeqnoBeforeAll);
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(899), 40U);
|
||||
|
||||
// Always keep last entry, to have a non-trivial seqno bound
|
||||
test.TruncateOldEntries(10000000);
|
||||
EXPECT_EQ(test.Size(), 1U);
|
||||
|
||||
EXPECT_EQ(test.GetProximalSeqnoBeforeTime(10000000), 50U);
|
||||
}
|
||||
|
||||
TEST_F(SeqnoTimeTest, Sort) {
|
||||
@@ -930,10 +1314,10 @@ TEST_F(SeqnoTimeTest, EncodeDecodeBasic) {
|
||||
for (SequenceNumber seq = 0; seq <= 1000; seq++) {
|
||||
// test has the more accurate time mapping, encode only pick
|
||||
// kMaxSeqnoTimePairsPerSST number of entries, which is less accurate
|
||||
uint64_t target_time = test.GetOldestApproximateTime(seq);
|
||||
ASSERT_GE(decoded.GetOldestApproximateTime(seq),
|
||||
uint64_t target_time = test.GetProximalTimeBeforeSeqno(seq);
|
||||
ASSERT_GE(decoded.GetProximalTimeBeforeSeqno(seq),
|
||||
target_time < 200 ? 0 : target_time - 200);
|
||||
ASSERT_LE(decoded.GetOldestApproximateTime(seq), target_time);
|
||||
ASSERT_LE(decoded.GetProximalTimeBeforeSeqno(seq), target_time);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+107
-71
@@ -11,14 +11,34 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
uint64_t SeqnoToTimeMapping::GetOldestApproximateTime(
|
||||
const SequenceNumber seqno) const {
|
||||
SeqnoToTimeMapping::pair_const_iterator SeqnoToTimeMapping::FindGreaterTime(
|
||||
uint64_t time) const {
|
||||
return std::upper_bound(pairs_.cbegin(), pairs_.cend(),
|
||||
SeqnoTimePair{0, time}, SeqnoTimePair::TimeLess);
|
||||
}
|
||||
|
||||
SeqnoToTimeMapping::pair_const_iterator SeqnoToTimeMapping::FindGreaterEqSeqno(
|
||||
SequenceNumber seqno) const {
|
||||
return std::lower_bound(pairs_.cbegin(), pairs_.cend(),
|
||||
SeqnoTimePair{seqno, 0}, SeqnoTimePair::SeqnoLess);
|
||||
}
|
||||
|
||||
SeqnoToTimeMapping::pair_const_iterator SeqnoToTimeMapping::FindGreaterSeqno(
|
||||
SequenceNumber seqno) const {
|
||||
return std::upper_bound(pairs_.cbegin(), pairs_.cend(),
|
||||
SeqnoTimePair{seqno, 0}, SeqnoTimePair::SeqnoLess);
|
||||
}
|
||||
|
||||
uint64_t SeqnoToTimeMapping::GetProximalTimeBeforeSeqno(
|
||||
SequenceNumber seqno) const {
|
||||
assert(is_sorted_);
|
||||
auto it = std::upper_bound(seqno_time_mapping_.begin(),
|
||||
seqno_time_mapping_.end(), seqno);
|
||||
if (it == seqno_time_mapping_.begin()) {
|
||||
return 0;
|
||||
// Find the last entry with a seqno strictly less than the given seqno.
|
||||
// First, find the first entry >= the given seqno (or end)
|
||||
auto it = FindGreaterEqSeqno(seqno);
|
||||
if (it == pairs_.cbegin()) {
|
||||
return kUnknownTimeBeforeAll;
|
||||
}
|
||||
// Then return data from previous.
|
||||
it--;
|
||||
return it->time;
|
||||
}
|
||||
@@ -28,44 +48,47 @@ void SeqnoToTimeMapping::Add(SequenceNumber seqno, uint64_t time) {
|
||||
return;
|
||||
}
|
||||
is_sorted_ = false;
|
||||
seqno_time_mapping_.emplace_back(seqno, time);
|
||||
pairs_.emplace_back(seqno, time);
|
||||
}
|
||||
|
||||
void SeqnoToTimeMapping::TruncateOldEntries(const uint64_t now) {
|
||||
assert(is_sorted_);
|
||||
|
||||
if (max_time_duration_ == 0) {
|
||||
// No cutoff time
|
||||
return;
|
||||
}
|
||||
|
||||
const uint64_t cut_off_time =
|
||||
now > max_time_duration_ ? now - max_time_duration_ : 0;
|
||||
assert(cut_off_time <= now); // no overflow
|
||||
|
||||
auto it = std::upper_bound(
|
||||
seqno_time_mapping_.begin(), seqno_time_mapping_.end(), cut_off_time,
|
||||
[](uint64_t target, const SeqnoTimePair& other) -> bool {
|
||||
return target < other.time;
|
||||
});
|
||||
if (it == seqno_time_mapping_.begin()) {
|
||||
if (now < max_time_duration_) {
|
||||
// Would under-flow
|
||||
return;
|
||||
}
|
||||
it--;
|
||||
seqno_time_mapping_.erase(seqno_time_mapping_.begin(), it);
|
||||
|
||||
const uint64_t cut_off_time = now - max_time_duration_;
|
||||
assert(cut_off_time <= now); // no under/overflow
|
||||
|
||||
auto it = FindGreaterTime(cut_off_time);
|
||||
if (it == pairs_.cbegin()) {
|
||||
return;
|
||||
}
|
||||
// Move back one, to the entry that would be used to return a good seqno from
|
||||
// GetProximalSeqnoBeforeTime(cut_off_time)
|
||||
--it;
|
||||
// Remove everything strictly before that entry
|
||||
pairs_.erase(pairs_.cbegin(), std::move(it));
|
||||
}
|
||||
|
||||
SequenceNumber SeqnoToTimeMapping::GetOldestSequenceNum(uint64_t time) {
|
||||
SequenceNumber SeqnoToTimeMapping::GetProximalSeqnoBeforeTime(uint64_t time) {
|
||||
assert(is_sorted_);
|
||||
|
||||
auto it = std::upper_bound(
|
||||
seqno_time_mapping_.begin(), seqno_time_mapping_.end(), time,
|
||||
[](uint64_t target, const SeqnoTimePair& other) -> bool {
|
||||
return target < other.time;
|
||||
});
|
||||
if (it == seqno_time_mapping_.begin()) {
|
||||
return 0;
|
||||
// Find the last entry with a time <= the given time.
|
||||
// First, find the first entry > the given time (or end).
|
||||
auto it = FindGreaterTime(time);
|
||||
if (it == pairs_.cbegin()) {
|
||||
return kUnknownSeqnoBeforeAll;
|
||||
}
|
||||
it--;
|
||||
// Then return data from previous.
|
||||
--it;
|
||||
return it->seqno;
|
||||
}
|
||||
|
||||
@@ -84,15 +107,13 @@ void SeqnoToTimeMapping::Encode(std::string& dest, const SequenceNumber start,
|
||||
return;
|
||||
}
|
||||
|
||||
auto start_it = std::upper_bound(seqno_time_mapping_.begin(),
|
||||
seqno_time_mapping_.end(), start);
|
||||
if (start_it != seqno_time_mapping_.begin()) {
|
||||
auto start_it = FindGreaterSeqno(start);
|
||||
if (start_it != pairs_.begin()) {
|
||||
start_it--;
|
||||
}
|
||||
|
||||
auto end_it = std::upper_bound(seqno_time_mapping_.begin(),
|
||||
seqno_time_mapping_.end(), end);
|
||||
if (end_it == seqno_time_mapping_.begin()) {
|
||||
auto end_it = FindGreaterSeqno(end);
|
||||
if (end_it == pairs_.begin()) {
|
||||
return;
|
||||
}
|
||||
if (start_it >= end_it) {
|
||||
@@ -108,7 +129,7 @@ void SeqnoToTimeMapping::Encode(std::string& dest, const SequenceNumber start,
|
||||
}
|
||||
}
|
||||
// to include the first element
|
||||
if (start_it != seqno_time_mapping_.begin()) {
|
||||
if (start_it != pairs_.begin()) {
|
||||
start_it--;
|
||||
}
|
||||
|
||||
@@ -166,14 +187,14 @@ void SeqnoToTimeMapping::Encode(std::string& dest, const SequenceNumber start,
|
||||
SeqnoTimePair base;
|
||||
for (auto it = start_it; it < end_it; it++) {
|
||||
assert(base < *it);
|
||||
SeqnoTimePair val = *it - base;
|
||||
SeqnoTimePair val = it->ComputeDelta(base);
|
||||
base = *it;
|
||||
val.Encode(dest);
|
||||
}
|
||||
}
|
||||
|
||||
Status SeqnoToTimeMapping::Add(const std::string& seqno_time_mapping_str) {
|
||||
Slice input(seqno_time_mapping_str);
|
||||
Status SeqnoToTimeMapping::Add(const std::string& pairs_str) {
|
||||
Slice input(pairs_str);
|
||||
if (input.empty()) {
|
||||
return Status::OK();
|
||||
}
|
||||
@@ -189,8 +210,8 @@ Status SeqnoToTimeMapping::Add(const std::string& seqno_time_mapping_str) {
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
val.Add(base);
|
||||
seqno_time_mapping_.emplace_back(val);
|
||||
val.ApplyDelta(base);
|
||||
pairs_.emplace_back(val);
|
||||
base = val;
|
||||
}
|
||||
return Status::OK();
|
||||
@@ -222,33 +243,58 @@ bool SeqnoToTimeMapping::Append(SequenceNumber seqno, uint64_t time) {
|
||||
return false;
|
||||
}
|
||||
if (seqno == Last().seqno) {
|
||||
Last().time = time;
|
||||
return true;
|
||||
}
|
||||
if (time == Last().time) {
|
||||
// new sequence has the same time as old one, no need to add new mapping
|
||||
// Updating Last() would hurt GetProximalSeqnoBeforeTime() queries, so
|
||||
// NOT doing it (for now)
|
||||
return false;
|
||||
}
|
||||
if (time == Last().time) {
|
||||
// Updating Last() here helps GetProximalSeqnoBeforeTime() queries, so
|
||||
// doing it (for now)
|
||||
Last().seqno = seqno;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
seqno_time_mapping_.emplace_back(seqno, time);
|
||||
pairs_.emplace_back(seqno, time);
|
||||
|
||||
if (seqno_time_mapping_.size() > max_capacity_) {
|
||||
seqno_time_mapping_.pop_front();
|
||||
if (pairs_.size() > max_capacity_) {
|
||||
// FIXME: be smarter about how we erase to avoid data falling off the
|
||||
// front prematurely.
|
||||
pairs_.pop_front();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SeqnoToTimeMapping::PrePopulate(SequenceNumber from_seqno,
|
||||
SequenceNumber to_seqno,
|
||||
uint64_t from_time, uint64_t to_time) {
|
||||
assert(Empty());
|
||||
assert(from_seqno > 0);
|
||||
assert(to_seqno > from_seqno);
|
||||
assert(from_time > kUnknownTimeBeforeAll);
|
||||
assert(to_time >= from_time);
|
||||
|
||||
// TODO: smartly limit this to max_capacity_ representative samples
|
||||
for (auto i = from_seqno; i <= to_seqno; i++) {
|
||||
uint64_t t = from_time + (to_time - from_time) * (i - from_seqno) /
|
||||
(to_seqno - from_seqno);
|
||||
pairs_.emplace_back(i, t);
|
||||
}
|
||||
|
||||
return /*success*/ true;
|
||||
}
|
||||
|
||||
bool SeqnoToTimeMapping::Resize(uint64_t min_time_duration,
|
||||
uint64_t max_time_duration) {
|
||||
uint64_t new_max_capacity =
|
||||
CalculateMaxCapacity(min_time_duration, max_time_duration);
|
||||
if (new_max_capacity == max_capacity_) {
|
||||
return false;
|
||||
} else if (new_max_capacity < seqno_time_mapping_.size()) {
|
||||
uint64_t delta = seqno_time_mapping_.size() - new_max_capacity;
|
||||
seqno_time_mapping_.erase(seqno_time_mapping_.begin(),
|
||||
seqno_time_mapping_.begin() + delta);
|
||||
} else if (new_max_capacity < pairs_.size()) {
|
||||
uint64_t delta = pairs_.size() - new_max_capacity;
|
||||
// FIXME: be smarter about how we erase to avoid data falling off the
|
||||
// front prematurely.
|
||||
pairs_.erase(pairs_.begin(), pairs_.begin() + delta);
|
||||
}
|
||||
max_capacity_ = new_max_capacity;
|
||||
return true;
|
||||
@@ -258,16 +304,16 @@ Status SeqnoToTimeMapping::Sort() {
|
||||
if (is_sorted_) {
|
||||
return Status::OK();
|
||||
}
|
||||
if (seqno_time_mapping_.empty()) {
|
||||
if (pairs_.empty()) {
|
||||
is_sorted_ = true;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
std::deque<SeqnoTimePair> copy = std::move(seqno_time_mapping_);
|
||||
std::deque<SeqnoTimePair> copy = std::move(pairs_);
|
||||
|
||||
std::sort(copy.begin(), copy.end());
|
||||
|
||||
seqno_time_mapping_.clear();
|
||||
pairs_.clear();
|
||||
|
||||
// remove seqno = 0, which may have special meaning, like zeroed out data
|
||||
while (copy.front().seqno == 0) {
|
||||
@@ -285,12 +331,12 @@ Status SeqnoToTimeMapping::Sort() {
|
||||
assert(it.seqno > prev.seqno);
|
||||
// If a larger sequence number has an older time which is not useful, skip
|
||||
if (it.time > prev.time) {
|
||||
seqno_time_mapping_.push_back(prev);
|
||||
pairs_.push_back(prev);
|
||||
prev = it;
|
||||
}
|
||||
}
|
||||
}
|
||||
seqno_time_mapping_.emplace_back(prev);
|
||||
pairs_.emplace_back(prev);
|
||||
|
||||
is_sorted_ = true;
|
||||
return Status::OK();
|
||||
@@ -298,7 +344,7 @@ Status SeqnoToTimeMapping::Sort() {
|
||||
|
||||
std::string SeqnoToTimeMapping::ToHumanString() const {
|
||||
std::string ret;
|
||||
for (const auto& seq_time : seqno_time_mapping_) {
|
||||
for (const auto& seq_time : pairs_) {
|
||||
AppendNumberTo(&ret, seq_time.seqno);
|
||||
ret.append("->");
|
||||
AppendNumberTo(&ret, seq_time.time);
|
||||
@@ -310,13 +356,11 @@ std::string SeqnoToTimeMapping::ToHumanString() const {
|
||||
SeqnoToTimeMapping SeqnoToTimeMapping::Copy(
|
||||
SequenceNumber smallest_seqno) const {
|
||||
SeqnoToTimeMapping ret;
|
||||
auto it = std::upper_bound(seqno_time_mapping_.begin(),
|
||||
seqno_time_mapping_.end(), smallest_seqno);
|
||||
if (it != seqno_time_mapping_.begin()) {
|
||||
auto it = FindGreaterSeqno(smallest_seqno);
|
||||
if (it != pairs_.begin()) {
|
||||
it--;
|
||||
}
|
||||
std::copy(it, seqno_time_mapping_.end(),
|
||||
std::back_inserter(ret.seqno_time_mapping_));
|
||||
std::copy(it, pairs_.end(), std::back_inserter(ret.pairs_));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -330,12 +374,4 @@ uint64_t SeqnoToTimeMapping::CalculateMaxCapacity(uint64_t min_time_duration,
|
||||
max_time_duration * kMaxSeqnoTimePairsPerCF / min_time_duration);
|
||||
}
|
||||
|
||||
SeqnoToTimeMapping::SeqnoTimePair SeqnoToTimeMapping::SeqnoTimePair::operator-(
|
||||
const SeqnoTimePair& other) const {
|
||||
SeqnoTimePair res;
|
||||
res.seqno = seqno - other.seqno;
|
||||
res.time = time - other.time;
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+80
-43
@@ -18,20 +18,32 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
constexpr uint64_t kUnknownSeqnoTime = 0;
|
||||
constexpr uint64_t kUnknownTimeBeforeAll = 0;
|
||||
constexpr SequenceNumber kUnknownSeqnoBeforeAll = 0;
|
||||
|
||||
// SeqnoToTimeMapping stores the sequence number to time mapping, so given a
|
||||
// sequence number it can estimate the oldest possible time for that sequence
|
||||
// number. For example:
|
||||
// 10 -> 100
|
||||
// 50 -> 300
|
||||
// then if a key has seqno 19, the OldestApproximateTime would be 100, for 51 it
|
||||
// would be 300.
|
||||
// As it's a sorted list, the new entry is inserted from the back. The old data
|
||||
// will be popped from the front if they're no longer used.
|
||||
// SeqnoToTimeMapping stores a sampled mapping from sequence numbers to
|
||||
// unix times (seconds since epoch). This information provides rough bounds
|
||||
// between sequence numbers and their write times, but is primarily designed
|
||||
// for getting a best lower bound on the sequence number of data written no
|
||||
// later than a specified time.
|
||||
//
|
||||
// Note: the data struct is not thread safe, both read and write need to be
|
||||
// synchronized by caller.
|
||||
// For ease of sampling, it is assumed that the recorded time in each pair
|
||||
// comes at or after the sequence number and before the next sequence number,
|
||||
// so this example:
|
||||
//
|
||||
// Seqno: 10, 11, ... 20, 21, ... 30, 31, ...
|
||||
// Time: ... 500 ... 600 ... 700 ...
|
||||
//
|
||||
// would be represented as
|
||||
// 10 -> 500
|
||||
// 20 -> 600
|
||||
// 30 -> 700
|
||||
//
|
||||
// In typical operation, the list is sorted, both among seqnos and among times,
|
||||
// with a bounded number of entries, but some public working states violate
|
||||
// these constraints.
|
||||
//
|
||||
// NOT thread safe - requires external synchronization.
|
||||
class SeqnoToTimeMapping {
|
||||
public:
|
||||
// Maximum number of entries can be encoded into SST. The data is delta encode
|
||||
@@ -63,28 +75,33 @@ class SeqnoToTimeMapping {
|
||||
// Decode the value from input Slice and remove it from the input
|
||||
Status Decode(Slice& input);
|
||||
|
||||
// subtraction of 2 SeqnoTimePair
|
||||
SeqnoTimePair operator-(const SeqnoTimePair& other) const;
|
||||
|
||||
// Add 2 values together
|
||||
void Add(const SeqnoTimePair& obj) {
|
||||
seqno += obj.seqno;
|
||||
time += obj.time;
|
||||
// For delta encoding
|
||||
SeqnoTimePair ComputeDelta(const SeqnoTimePair& base) const {
|
||||
return {seqno - base.seqno, time - base.time};
|
||||
}
|
||||
|
||||
// Compare SeqnoTimePair with a sequence number, used for binary search a
|
||||
// sequence number in a list of SeqnoTimePair
|
||||
bool operator<(const SequenceNumber& other) const { return seqno < other; }
|
||||
// For delta decoding
|
||||
void ApplyDelta(const SeqnoTimePair& delta_or_base) {
|
||||
seqno += delta_or_base.seqno;
|
||||
time += delta_or_base.time;
|
||||
}
|
||||
|
||||
// Compare 2 SeqnoTimePair
|
||||
// Ordering used for Sort()
|
||||
bool operator<(const SeqnoTimePair& other) const {
|
||||
return std::tie(seqno, time) < std::tie(other.seqno, other.time);
|
||||
}
|
||||
|
||||
// Check if 2 SeqnoTimePair is the same
|
||||
bool operator==(const SeqnoTimePair& other) const {
|
||||
return std::tie(seqno, time) == std::tie(other.seqno, other.time);
|
||||
}
|
||||
|
||||
static bool SeqnoLess(const SeqnoTimePair& a, const SeqnoTimePair& b) {
|
||||
return a.seqno < b.seqno;
|
||||
}
|
||||
|
||||
static bool TimeLess(const SeqnoTimePair& a, const SeqnoTimePair& b) {
|
||||
return a.time < b.time;
|
||||
}
|
||||
};
|
||||
|
||||
// constractor of SeqnoToTimeMapping
|
||||
@@ -99,20 +116,40 @@ class SeqnoToTimeMapping {
|
||||
uint64_t max_capacity = 0)
|
||||
: max_time_duration_(max_time_duration), max_capacity_(max_capacity) {}
|
||||
|
||||
// Both seqno range and time range are inclusive. ... TODO
|
||||
//
|
||||
bool PrePopulate(SequenceNumber from_seqno, SequenceNumber to_seqno,
|
||||
uint64_t from_time, uint64_t to_time);
|
||||
|
||||
// Append a new entry to the list. The new entry should be newer than the
|
||||
// existing ones. It maintains the internal sorted status.
|
||||
bool Append(SequenceNumber seqno, uint64_t time);
|
||||
|
||||
// Given a sequence number, estimate it's oldest time
|
||||
uint64_t GetOldestApproximateTime(SequenceNumber seqno) const;
|
||||
// Given a sequence number, return the best (largest / newest) known time
|
||||
// that is no later than the write time of that given sequence number.
|
||||
// If no such specific time is known, returns kUnknownTimeBeforeAll.
|
||||
// Using the example in the class comment above,
|
||||
// GetProximalTimeBeforeSeqno(10) -> kUnknownTimeBeforeAll
|
||||
// GetProximalTimeBeforeSeqno(11) -> 500
|
||||
// GetProximalTimeBeforeSeqno(20) -> 500
|
||||
// GetProximalTimeBeforeSeqno(21) -> 600
|
||||
uint64_t GetProximalTimeBeforeSeqno(SequenceNumber seqno) const;
|
||||
|
||||
// Truncate the old entries based on the current time and max_time_duration_
|
||||
// Remove any entries not needed for GetProximalSeqnoBeforeTime queries of
|
||||
// times older than `now - max_time_duration_`
|
||||
void TruncateOldEntries(uint64_t now);
|
||||
|
||||
// Given a time, return it's oldest possible sequence number
|
||||
SequenceNumber GetOldestSequenceNum(uint64_t time);
|
||||
// Given a time, return the best (largest) sequence number whose write time
|
||||
// is no later than that given time. If no such specific sequence number is
|
||||
// known, returns kUnknownSeqnoBeforeAll. Using the example in the class
|
||||
// comment above,
|
||||
// GetProximalSeqnoBeforeTime(499) -> kUnknownSeqnoBeforeAll
|
||||
// GetProximalSeqnoBeforeTime(500) -> 10
|
||||
// GetProximalSeqnoBeforeTime(599) -> 10
|
||||
// GetProximalSeqnoBeforeTime(600) -> 20
|
||||
SequenceNumber GetProximalSeqnoBeforeTime(uint64_t time);
|
||||
|
||||
// Encode to a binary string
|
||||
// Encode to a binary string. start and end seqno are both inclusive.
|
||||
void Encode(std::string& des, SequenceNumber start, SequenceNumber end,
|
||||
uint64_t now,
|
||||
uint64_t output_size = kMaxSeqnoTimePairsPerSST) const;
|
||||
@@ -122,10 +159,10 @@ class SeqnoToTimeMapping {
|
||||
void Add(SequenceNumber seqno, uint64_t time);
|
||||
|
||||
// Decode and add the entries to the current obj. The list will be unsorted
|
||||
Status Add(const std::string& seqno_time_mapping_str);
|
||||
Status Add(const std::string& pairs_str);
|
||||
|
||||
// Return the number of entries
|
||||
size_t Size() const { return seqno_time_mapping_.size(); }
|
||||
size_t Size() const { return pairs_.size(); }
|
||||
|
||||
// Reduce the size of internal list
|
||||
bool Resize(uint64_t min_time_duration, uint64_t max_time_duration);
|
||||
@@ -145,10 +182,10 @@ class SeqnoToTimeMapping {
|
||||
SeqnoToTimeMapping Copy(SequenceNumber smallest_seqno) const;
|
||||
|
||||
// If the internal list is empty
|
||||
bool Empty() const { return seqno_time_mapping_.empty(); }
|
||||
bool Empty() const { return pairs_.empty(); }
|
||||
|
||||
// clear all entries
|
||||
void Clear() { seqno_time_mapping_.clear(); }
|
||||
void Clear() { pairs_.clear(); }
|
||||
|
||||
// return the string for user message
|
||||
// Note: Not efficient, okay for print
|
||||
@@ -156,7 +193,7 @@ class SeqnoToTimeMapping {
|
||||
|
||||
#ifndef NDEBUG
|
||||
const std::deque<SeqnoTimePair>& TEST_GetInternalMapping() const {
|
||||
return seqno_time_mapping_;
|
||||
return pairs_;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -167,7 +204,7 @@ class SeqnoToTimeMapping {
|
||||
uint64_t max_time_duration_;
|
||||
uint64_t max_capacity_;
|
||||
|
||||
std::deque<SeqnoTimePair> seqno_time_mapping_;
|
||||
std::deque<SeqnoTimePair> pairs_;
|
||||
|
||||
bool is_sorted_ = true;
|
||||
|
||||
@@ -176,14 +213,14 @@ class SeqnoToTimeMapping {
|
||||
|
||||
SeqnoTimePair& Last() {
|
||||
assert(!Empty());
|
||||
return seqno_time_mapping_.back();
|
||||
return pairs_.back();
|
||||
}
|
||||
|
||||
using pair_const_iterator =
|
||||
std::deque<SeqnoToTimeMapping::SeqnoTimePair>::const_iterator;
|
||||
pair_const_iterator FindGreaterTime(uint64_t time) const;
|
||||
pair_const_iterator FindGreaterSeqno(SequenceNumber seqno) const;
|
||||
pair_const_iterator FindGreaterEqSeqno(SequenceNumber seqno) const;
|
||||
};
|
||||
|
||||
// for searching the sequence number from SeqnoToTimeMapping
|
||||
inline bool operator<(const SequenceNumber& seqno,
|
||||
const SeqnoToTimeMapping::SeqnoTimePair& other) {
|
||||
return seqno < other.seqno;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+10
-4
@@ -219,10 +219,16 @@ struct FileMetaData {
|
||||
// refers to. 0 is an invalid value; BlobDB numbers the files starting from 1.
|
||||
uint64_t oldest_blob_file_number = kInvalidBlobFileNumber;
|
||||
|
||||
// The file could be the compaction output from other SST files, which could
|
||||
// in turn be outputs for compact older SST files. We track the memtable
|
||||
// flush timestamp for the oldest SST file that eventually contribute data
|
||||
// to this file. 0 means the information is not available.
|
||||
// For flush output file, oldest ancestor time is the oldest key time in the
|
||||
// file. If the oldest key time is not available, flush time is used.
|
||||
//
|
||||
// For compaction output file, oldest ancestor time is the oldest
|
||||
// among all the oldest key time of its input files, since the file could be
|
||||
// the compaction output from other SST files, which could in turn be outputs
|
||||
// for compact older SST files. If that's not available, creation time of this
|
||||
// compaction output file is used.
|
||||
//
|
||||
// 0 means the information is not available.
|
||||
uint64_t oldest_ancester_time = kUnknownOldestAncesterTime;
|
||||
|
||||
// Unix time when the SST file is created.
|
||||
|
||||
+31
-28
@@ -3582,26 +3582,16 @@ void VersionStorageInfo::ComputeCompactionScore(
|
||||
}
|
||||
}
|
||||
ComputeFilesMarkedForCompaction(max_output_level);
|
||||
if (!immutable_options.allow_ingest_behind) {
|
||||
ComputeBottommostFilesMarkedForCompaction();
|
||||
}
|
||||
if (mutable_cf_options.ttl > 0 &&
|
||||
compaction_style_ == kCompactionStyleLevel) {
|
||||
ComputeExpiredTtlFiles(immutable_options, mutable_cf_options.ttl);
|
||||
}
|
||||
if (mutable_cf_options.periodic_compaction_seconds > 0) {
|
||||
ComputeFilesMarkedForPeriodicCompaction(
|
||||
immutable_options, mutable_cf_options.periodic_compaction_seconds,
|
||||
max_output_level);
|
||||
}
|
||||
|
||||
if (mutable_cf_options.enable_blob_garbage_collection &&
|
||||
mutable_cf_options.blob_garbage_collection_age_cutoff > 0.0 &&
|
||||
mutable_cf_options.blob_garbage_collection_force_threshold < 1.0) {
|
||||
ComputeFilesMarkedForForcedBlobGC(
|
||||
mutable_cf_options.blob_garbage_collection_age_cutoff,
|
||||
mutable_cf_options.blob_garbage_collection_force_threshold);
|
||||
}
|
||||
ComputeBottommostFilesMarkedForCompaction(
|
||||
immutable_options.allow_ingest_behind);
|
||||
ComputeExpiredTtlFiles(immutable_options, mutable_cf_options.ttl);
|
||||
ComputeFilesMarkedForPeriodicCompaction(
|
||||
immutable_options, mutable_cf_options.periodic_compaction_seconds,
|
||||
max_output_level);
|
||||
ComputeFilesMarkedForForcedBlobGC(
|
||||
mutable_cf_options.blob_garbage_collection_age_cutoff,
|
||||
mutable_cf_options.blob_garbage_collection_force_threshold,
|
||||
mutable_cf_options.enable_blob_garbage_collection);
|
||||
|
||||
EstimateCompactionBytesNeeded(mutable_cf_options);
|
||||
}
|
||||
@@ -3631,9 +3621,10 @@ void VersionStorageInfo::ComputeFilesMarkedForCompaction(int last_level) {
|
||||
|
||||
void VersionStorageInfo::ComputeExpiredTtlFiles(
|
||||
const ImmutableOptions& ioptions, const uint64_t ttl) {
|
||||
assert(ttl > 0);
|
||||
|
||||
expired_ttl_files_.clear();
|
||||
if (ttl == 0 || compaction_style_ != CompactionStyle::kCompactionStyleLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t _current_time;
|
||||
auto status = ioptions.clock->GetCurrentTime(&_current_time);
|
||||
@@ -3658,9 +3649,10 @@ void VersionStorageInfo::ComputeExpiredTtlFiles(
|
||||
void VersionStorageInfo::ComputeFilesMarkedForPeriodicCompaction(
|
||||
const ImmutableOptions& ioptions,
|
||||
const uint64_t periodic_compaction_seconds, int last_level) {
|
||||
assert(periodic_compaction_seconds > 0);
|
||||
|
||||
files_marked_for_periodic_compaction_.clear();
|
||||
if (periodic_compaction_seconds == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t temp_current_time;
|
||||
auto status = ioptions.clock->GetCurrentTime(&temp_current_time);
|
||||
@@ -3714,8 +3706,14 @@ void VersionStorageInfo::ComputeFilesMarkedForPeriodicCompaction(
|
||||
|
||||
void VersionStorageInfo::ComputeFilesMarkedForForcedBlobGC(
|
||||
double blob_garbage_collection_age_cutoff,
|
||||
double blob_garbage_collection_force_threshold) {
|
||||
double blob_garbage_collection_force_threshold,
|
||||
bool enable_blob_garbage_collection) {
|
||||
files_marked_for_forced_blob_gc_.clear();
|
||||
if (!(enable_blob_garbage_collection &&
|
||||
blob_garbage_collection_age_cutoff > 0.0 &&
|
||||
blob_garbage_collection_force_threshold < 1.0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (blob_files_.empty()) {
|
||||
return;
|
||||
@@ -4172,17 +4170,22 @@ void VersionStorageInfo::GenerateFileLocationIndex() {
|
||||
}
|
||||
}
|
||||
|
||||
void VersionStorageInfo::UpdateOldestSnapshot(SequenceNumber seqnum) {
|
||||
void VersionStorageInfo::UpdateOldestSnapshot(SequenceNumber seqnum,
|
||||
bool allow_ingest_behind) {
|
||||
assert(seqnum >= oldest_snapshot_seqnum_);
|
||||
oldest_snapshot_seqnum_ = seqnum;
|
||||
if (oldest_snapshot_seqnum_ > bottommost_files_mark_threshold_) {
|
||||
ComputeBottommostFilesMarkedForCompaction();
|
||||
ComputeBottommostFilesMarkedForCompaction(allow_ingest_behind);
|
||||
}
|
||||
}
|
||||
|
||||
void VersionStorageInfo::ComputeBottommostFilesMarkedForCompaction() {
|
||||
void VersionStorageInfo::ComputeBottommostFilesMarkedForCompaction(
|
||||
bool allow_ingest_behind) {
|
||||
bottommost_files_marked_for_compaction_.clear();
|
||||
bottommost_files_mark_threshold_ = kMaxSequenceNumber;
|
||||
if (allow_ingest_behind) {
|
||||
return;
|
||||
}
|
||||
// If a file's creation time is larger than creation_time_ub,
|
||||
// it is too new to be marked for compaction.
|
||||
int64_t creation_time_ub = 0;
|
||||
|
||||
+5
-3
@@ -228,7 +228,7 @@ class VersionStorageInfo {
|
||||
// eligible for compaction.
|
||||
//
|
||||
// REQUIRES: DB mutex held
|
||||
void ComputeBottommostFilesMarkedForCompaction();
|
||||
void ComputeBottommostFilesMarkedForCompaction(bool allow_ingest_behind);
|
||||
|
||||
// This computes files_marked_for_forced_blob_gc_ and is called by
|
||||
// ComputeCompactionScore()
|
||||
@@ -236,14 +236,16 @@ class VersionStorageInfo {
|
||||
// REQUIRES: DB mutex held
|
||||
void ComputeFilesMarkedForForcedBlobGC(
|
||||
double blob_garbage_collection_age_cutoff,
|
||||
double blob_garbage_collection_force_threshold);
|
||||
double blob_garbage_collection_force_threshold,
|
||||
bool enable_blob_garbage_collection);
|
||||
|
||||
bool level0_non_overlapping() const { return level0_non_overlapping_; }
|
||||
|
||||
// Updates the oldest snapshot and related internal state, like the bottommost
|
||||
// files marked for compaction.
|
||||
// REQUIRES: DB mutex held
|
||||
void UpdateOldestSnapshot(SequenceNumber oldest_snapshot_seqnum);
|
||||
void UpdateOldestSnapshot(SequenceNumber oldest_snapshot_seqnum,
|
||||
bool allow_ingest_behind);
|
||||
|
||||
int MaxInputLevel() const;
|
||||
int MaxOutputLevel(bool allow_ingest_behind) const;
|
||||
|
||||
+22
-11
@@ -584,7 +584,8 @@ TEST_F(VersionStorageInfoTest, ForcedBlobGCEmpty) {
|
||||
|
||||
constexpr double age_cutoff = 0.5;
|
||||
constexpr double force_threshold = 0.75;
|
||||
vstorage_.ComputeFilesMarkedForForcedBlobGC(age_cutoff, force_threshold);
|
||||
vstorage_.ComputeFilesMarkedForForcedBlobGC(
|
||||
age_cutoff, force_threshold, /*enable_blob_garbage_collection=*/true);
|
||||
|
||||
ASSERT_TRUE(vstorage_.FilesMarkedForForcedBlobGC().empty());
|
||||
}
|
||||
@@ -668,7 +669,8 @@ TEST_F(VersionStorageInfoTest, ForcedBlobGCSingleBatch) {
|
||||
{
|
||||
constexpr double age_cutoff = 0.1;
|
||||
constexpr double force_threshold = 0.0;
|
||||
vstorage_.ComputeFilesMarkedForForcedBlobGC(age_cutoff, force_threshold);
|
||||
vstorage_.ComputeFilesMarkedForForcedBlobGC(
|
||||
age_cutoff, force_threshold, /*enable_blob_garbage_collection=*/true);
|
||||
|
||||
ASSERT_TRUE(vstorage_.FilesMarkedForForcedBlobGC().empty());
|
||||
}
|
||||
@@ -679,7 +681,8 @@ TEST_F(VersionStorageInfoTest, ForcedBlobGCSingleBatch) {
|
||||
{
|
||||
constexpr double age_cutoff = 0.5;
|
||||
constexpr double force_threshold = 0.0;
|
||||
vstorage_.ComputeFilesMarkedForForcedBlobGC(age_cutoff, force_threshold);
|
||||
vstorage_.ComputeFilesMarkedForForcedBlobGC(
|
||||
age_cutoff, force_threshold, /*enable_blob_garbage_collection=*/true);
|
||||
|
||||
ASSERT_TRUE(vstorage_.FilesMarkedForForcedBlobGC().empty());
|
||||
}
|
||||
@@ -690,7 +693,8 @@ TEST_F(VersionStorageInfoTest, ForcedBlobGCSingleBatch) {
|
||||
{
|
||||
constexpr double age_cutoff = 1.0;
|
||||
constexpr double force_threshold = 0.6;
|
||||
vstorage_.ComputeFilesMarkedForForcedBlobGC(age_cutoff, force_threshold);
|
||||
vstorage_.ComputeFilesMarkedForForcedBlobGC(
|
||||
age_cutoff, force_threshold, /*enable_blob_garbage_collection=*/true);
|
||||
|
||||
ASSERT_TRUE(vstorage_.FilesMarkedForForcedBlobGC().empty());
|
||||
}
|
||||
@@ -701,7 +705,8 @@ TEST_F(VersionStorageInfoTest, ForcedBlobGCSingleBatch) {
|
||||
{
|
||||
constexpr double age_cutoff = 1.0;
|
||||
constexpr double force_threshold = 0.5;
|
||||
vstorage_.ComputeFilesMarkedForForcedBlobGC(age_cutoff, force_threshold);
|
||||
vstorage_.ComputeFilesMarkedForForcedBlobGC(
|
||||
age_cutoff, force_threshold, /*enable_blob_garbage_collection=*/true);
|
||||
|
||||
auto ssts_to_be_compacted = vstorage_.FilesMarkedForForcedBlobGC();
|
||||
ASSERT_EQ(ssts_to_be_compacted.size(), 1);
|
||||
@@ -815,7 +820,8 @@ TEST_F(VersionStorageInfoTest, ForcedBlobGCMultipleBatches) {
|
||||
{
|
||||
constexpr double age_cutoff = 0.1;
|
||||
constexpr double force_threshold = 0.0;
|
||||
vstorage_.ComputeFilesMarkedForForcedBlobGC(age_cutoff, force_threshold);
|
||||
vstorage_.ComputeFilesMarkedForForcedBlobGC(
|
||||
age_cutoff, force_threshold, /*enable_blob_garbage_collection=*/true);
|
||||
|
||||
ASSERT_TRUE(vstorage_.FilesMarkedForForcedBlobGC().empty());
|
||||
}
|
||||
@@ -826,7 +832,8 @@ TEST_F(VersionStorageInfoTest, ForcedBlobGCMultipleBatches) {
|
||||
{
|
||||
constexpr double age_cutoff = 0.25;
|
||||
constexpr double force_threshold = 0.0;
|
||||
vstorage_.ComputeFilesMarkedForForcedBlobGC(age_cutoff, force_threshold);
|
||||
vstorage_.ComputeFilesMarkedForForcedBlobGC(
|
||||
age_cutoff, force_threshold, /*enable_blob_garbage_collection=*/true);
|
||||
|
||||
ASSERT_TRUE(vstorage_.FilesMarkedForForcedBlobGC().empty());
|
||||
}
|
||||
@@ -837,7 +844,8 @@ TEST_F(VersionStorageInfoTest, ForcedBlobGCMultipleBatches) {
|
||||
{
|
||||
constexpr double age_cutoff = 0.5;
|
||||
constexpr double force_threshold = 0.6;
|
||||
vstorage_.ComputeFilesMarkedForForcedBlobGC(age_cutoff, force_threshold);
|
||||
vstorage_.ComputeFilesMarkedForForcedBlobGC(
|
||||
age_cutoff, force_threshold, /*enable_blob_garbage_collection=*/true);
|
||||
|
||||
ASSERT_TRUE(vstorage_.FilesMarkedForForcedBlobGC().empty());
|
||||
}
|
||||
@@ -848,7 +856,8 @@ TEST_F(VersionStorageInfoTest, ForcedBlobGCMultipleBatches) {
|
||||
{
|
||||
constexpr double age_cutoff = 0.5;
|
||||
constexpr double force_threshold = 0.5;
|
||||
vstorage_.ComputeFilesMarkedForForcedBlobGC(age_cutoff, force_threshold);
|
||||
vstorage_.ComputeFilesMarkedForForcedBlobGC(
|
||||
age_cutoff, force_threshold, /*enable_blob_garbage_collection=*/true);
|
||||
|
||||
auto ssts_to_be_compacted = vstorage_.FilesMarkedForForcedBlobGC();
|
||||
ASSERT_EQ(ssts_to_be_compacted.size(), 2);
|
||||
@@ -877,7 +886,8 @@ TEST_F(VersionStorageInfoTest, ForcedBlobGCMultipleBatches) {
|
||||
{
|
||||
constexpr double age_cutoff = 0.75;
|
||||
constexpr double force_threshold = 0.6;
|
||||
vstorage_.ComputeFilesMarkedForForcedBlobGC(age_cutoff, force_threshold);
|
||||
vstorage_.ComputeFilesMarkedForForcedBlobGC(
|
||||
age_cutoff, force_threshold, /*enable_blob_garbage_collection=*/true);
|
||||
|
||||
ASSERT_TRUE(vstorage_.FilesMarkedForForcedBlobGC().empty());
|
||||
}
|
||||
@@ -888,7 +898,8 @@ TEST_F(VersionStorageInfoTest, ForcedBlobGCMultipleBatches) {
|
||||
{
|
||||
constexpr double age_cutoff = 0.75;
|
||||
constexpr double force_threshold = 0.5;
|
||||
vstorage_.ComputeFilesMarkedForForcedBlobGC(age_cutoff, force_threshold);
|
||||
vstorage_.ComputeFilesMarkedForForcedBlobGC(
|
||||
age_cutoff, force_threshold, /*enable_blob_garbage_collection=*/true);
|
||||
|
||||
auto ssts_to_be_compacted = vstorage_.FilesMarkedForForcedBlobGC();
|
||||
ASSERT_EQ(ssts_to_be_compacted.size(), 2);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/overload.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -269,6 +270,134 @@ TEST_F(DBWideBasicTest, MultiCFMultiGetEntity) {
|
||||
ASSERT_EQ(results[1].columns(), second_columns);
|
||||
}
|
||||
|
||||
TEST_F(DBWideBasicTest, MultiCFMultiGetEntityAsPinnableAttributeGroups) {
|
||||
Options options = GetDefaultOptions();
|
||||
CreateAndReopenWithCF({"hot_cf", "cold_cf"}, options);
|
||||
|
||||
constexpr int DEFAULT_CF_HANDLE_INDEX = 0;
|
||||
constexpr int HOT_CF_HANDLE_INDEX = 1;
|
||||
constexpr int COLD_CF_HANDLE_INDEX = 2;
|
||||
|
||||
constexpr char first_key[] = "first";
|
||||
WideColumns first_default_columns{
|
||||
{"default_cf_col_1_name", "first_key_default_cf_col_1_value"},
|
||||
{"default_cf_col_2_name", "first_key_default_cf_col_2_value"}};
|
||||
WideColumns first_hot_columns{
|
||||
{"hot_cf_col_1_name", "first_key_hot_cf_col_1_value"},
|
||||
{"hot_cf_col_2_name", "first_key_hot_cf_col_2_value"}};
|
||||
WideColumns first_cold_columns{
|
||||
{"cold_cf_col_1_name", "first_key_cold_cf_col_1_value"}};
|
||||
constexpr char second_key[] = "second";
|
||||
WideColumns second_hot_columns{
|
||||
{"hot_cf_col_1_name", "second_key_hot_cf_col_1_value"}};
|
||||
WideColumns second_cold_columns{
|
||||
{"cold_cf_col_1_name", "second_key_cold_cf_col_1_value"}};
|
||||
|
||||
// TODO - update this to use the multi-attribute-group PutEntity when ready
|
||||
ASSERT_OK(db_->PutEntity(WriteOptions(), handles_[DEFAULT_CF_HANDLE_INDEX],
|
||||
first_key, first_default_columns));
|
||||
ASSERT_OK(db_->PutEntity(WriteOptions(), handles_[HOT_CF_HANDLE_INDEX],
|
||||
first_key, first_hot_columns));
|
||||
ASSERT_OK(db_->PutEntity(WriteOptions(), handles_[COLD_CF_HANDLE_INDEX],
|
||||
first_key, first_cold_columns));
|
||||
ASSERT_OK(db_->PutEntity(WriteOptions(), handles_[HOT_CF_HANDLE_INDEX],
|
||||
second_key, second_hot_columns));
|
||||
ASSERT_OK(db_->PutEntity(WriteOptions(), handles_[COLD_CF_HANDLE_INDEX],
|
||||
second_key, second_cold_columns));
|
||||
|
||||
constexpr size_t num_keys = 2;
|
||||
std::array<Slice, num_keys> keys = {first_key, second_key};
|
||||
std::vector<ColumnFamilyHandle*> all_cfs = handles_;
|
||||
std::vector<ColumnFamilyHandle*> default_and_hot_cfs{
|
||||
{handles_[DEFAULT_CF_HANDLE_INDEX], handles_[HOT_CF_HANDLE_INDEX]}};
|
||||
std::vector<ColumnFamilyHandle*> hot_and_cold_cfs{
|
||||
{handles_[HOT_CF_HANDLE_INDEX], handles_[COLD_CF_HANDLE_INDEX]}};
|
||||
auto create_result =
|
||||
[](const std::vector<ColumnFamilyHandle*>& column_families)
|
||||
-> PinnableAttributeGroups {
|
||||
PinnableAttributeGroups result;
|
||||
for (size_t i = 0; i < column_families.size(); ++i) {
|
||||
result.emplace_back(column_families[i]);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
{
|
||||
// Check for invalid argument
|
||||
ReadOptions read_options;
|
||||
read_options.io_activity = Env::IOActivity::kGetEntity;
|
||||
std::vector<PinnableAttributeGroups> results;
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
results.emplace_back(create_result(all_cfs));
|
||||
}
|
||||
db_->MultiGetEntity(read_options, num_keys, keys.data(), results.data());
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
for (size_t j = 0; j < all_cfs.size(); ++j) {
|
||||
ASSERT_NOK(results[i][j].status());
|
||||
ASSERT_TRUE(results[i][j].status().IsInvalidArgument());
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
// Case 1. Get first key from default cf and hot_cf and second key from
|
||||
// hot_cf and cold_cf
|
||||
std::vector<PinnableAttributeGroups> results;
|
||||
PinnableAttributeGroups first_key_result =
|
||||
create_result(default_and_hot_cfs);
|
||||
PinnableAttributeGroups second_key_result = create_result(hot_and_cold_cfs);
|
||||
results.emplace_back(std::move(first_key_result));
|
||||
results.emplace_back(std::move(second_key_result));
|
||||
|
||||
db_->MultiGetEntity(ReadOptions(), num_keys, keys.data(), results.data());
|
||||
ASSERT_EQ(2, results.size());
|
||||
// We expect to get values for all keys and CFs
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
for (size_t j = 0; j < 2; ++j) {
|
||||
ASSERT_OK(results[i][j].status());
|
||||
}
|
||||
}
|
||||
// verify values for first key (default cf and hot cf)
|
||||
ASSERT_EQ(2, results[0].size());
|
||||
ASSERT_EQ(first_default_columns, results[0][0].columns());
|
||||
ASSERT_EQ(first_hot_columns, results[0][1].columns());
|
||||
|
||||
// verify values for second key (hot cf and cold cf)
|
||||
ASSERT_EQ(2, results[1].size());
|
||||
ASSERT_EQ(second_hot_columns, results[1][0].columns());
|
||||
ASSERT_EQ(second_cold_columns, results[1][1].columns());
|
||||
}
|
||||
{
|
||||
// Case 2. Get first key and second key from all cfs. For the second key, we
|
||||
// don't expect to get columns from default cf.
|
||||
std::vector<PinnableAttributeGroups> results;
|
||||
PinnableAttributeGroups first_key_result = create_result(all_cfs);
|
||||
PinnableAttributeGroups second_key_result = create_result(all_cfs);
|
||||
results.emplace_back(std::move(first_key_result));
|
||||
results.emplace_back(std::move(second_key_result));
|
||||
|
||||
db_->MultiGetEntity(ReadOptions(), num_keys, keys.data(), results.data());
|
||||
// verify first key
|
||||
for (size_t i = 0; i < all_cfs.size(); ++i) {
|
||||
ASSERT_OK(results[0][i].status());
|
||||
}
|
||||
ASSERT_EQ(3, results[0].size());
|
||||
ASSERT_EQ(first_default_columns, results[0][0].columns());
|
||||
ASSERT_EQ(first_hot_columns, results[0][1].columns());
|
||||
ASSERT_EQ(first_cold_columns, results[0][2].columns());
|
||||
|
||||
// verify second key
|
||||
// key does not exist in default cf
|
||||
ASSERT_NOK(results[1][0].status());
|
||||
ASSERT_TRUE(results[1][0].status().IsNotFound());
|
||||
ASSERT_TRUE(results[1][0].columns().empty());
|
||||
|
||||
// key exists in hot_cf and cold_cf
|
||||
ASSERT_OK(results[1][1].status());
|
||||
ASSERT_EQ(second_hot_columns, results[1][1].columns());
|
||||
ASSERT_OK(results[1][2].status());
|
||||
ASSERT_EQ(second_cold_columns, results[1][2].columns());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBWideBasicTest, MergePlainKeyValue) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
@@ -690,6 +819,397 @@ TEST_F(DBWideBasicTest, MergeEntity) {
|
||||
verify_merge_ops_post_compaction();
|
||||
}
|
||||
|
||||
class DBWideMergeV3Test : public DBWideBasicTest {
|
||||
protected:
|
||||
void RunTest(const WideColumns& first_expected,
|
||||
const WideColumns& second_expected,
|
||||
const WideColumns& third_expected) {
|
||||
// Note: we'll take some snapshots to prevent merging during flush
|
||||
snapshots_.reserve(6);
|
||||
|
||||
// Test reading from memtables
|
||||
WriteKeyValues();
|
||||
VerifyKeyValues(first_expected, second_expected, third_expected);
|
||||
VerifyMergeOperandCount(first_key, 2);
|
||||
VerifyMergeOperandCount(second_key, 3);
|
||||
VerifyMergeOperandCount(third_key, 3);
|
||||
|
||||
// Test reading from SST files
|
||||
ASSERT_OK(Flush());
|
||||
VerifyKeyValues(first_expected, second_expected, third_expected);
|
||||
VerifyMergeOperandCount(first_key, 2);
|
||||
VerifyMergeOperandCount(second_key, 3);
|
||||
VerifyMergeOperandCount(third_key, 3);
|
||||
|
||||
// Test reading from SSTs after compaction. Note that we write the same KVs
|
||||
// and flush again so we have two overlapping files. We also release the
|
||||
// snapshots so that the compaction can merge all keys.
|
||||
WriteKeyValues();
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
snapshots_.clear();
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), /* begin */ nullptr,
|
||||
/* end */ nullptr));
|
||||
VerifyKeyValues(first_expected, second_expected, third_expected);
|
||||
VerifyMergeOperandCount(first_key, 1);
|
||||
VerifyMergeOperandCount(second_key, 1);
|
||||
VerifyMergeOperandCount(third_key, 1);
|
||||
}
|
||||
|
||||
void WriteKeyValues() {
|
||||
// Base values
|
||||
ASSERT_OK(db_->Delete(WriteOptions(), db_->DefaultColumnFamily(),
|
||||
first_key)); // no base value
|
||||
ASSERT_OK(db_->Put(WriteOptions(), db_->DefaultColumnFamily(), second_key,
|
||||
second_base_value)); // plain base value
|
||||
ASSERT_OK(db_->PutEntity(WriteOptions(), db_->DefaultColumnFamily(),
|
||||
third_key,
|
||||
third_columns)); // wide-column base value
|
||||
|
||||
snapshots_.emplace_back(db_);
|
||||
|
||||
// First round of merge operands
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), db_->DefaultColumnFamily(), first_key,
|
||||
first_merge_op1));
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), db_->DefaultColumnFamily(), second_key,
|
||||
second_merge_op1));
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), db_->DefaultColumnFamily(), third_key,
|
||||
third_merge_op1));
|
||||
|
||||
snapshots_.emplace_back(db_);
|
||||
|
||||
// Second round of merge operands
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), db_->DefaultColumnFamily(), first_key,
|
||||
first_merge_op2));
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), db_->DefaultColumnFamily(), second_key,
|
||||
second_merge_op2));
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), db_->DefaultColumnFamily(), third_key,
|
||||
third_merge_op2));
|
||||
|
||||
snapshots_.emplace_back(db_);
|
||||
}
|
||||
|
||||
void VerifyKeyValues(const WideColumns& first_expected,
|
||||
const WideColumns& second_expected,
|
||||
const WideColumns& third_expected) {
|
||||
assert(!first_expected.empty() &&
|
||||
first_expected[0].name() == kDefaultWideColumnName);
|
||||
assert(!second_expected.empty() &&
|
||||
second_expected[0].name() == kDefaultWideColumnName);
|
||||
assert(!third_expected.empty() &&
|
||||
third_expected[0].name() == kDefaultWideColumnName);
|
||||
|
||||
// Get
|
||||
{
|
||||
PinnableSlice result;
|
||||
ASSERT_OK(db_->Get(ReadOptions(), db_->DefaultColumnFamily(), first_key,
|
||||
&result));
|
||||
ASSERT_EQ(result, first_expected[0].value());
|
||||
}
|
||||
|
||||
{
|
||||
PinnableSlice result;
|
||||
ASSERT_OK(db_->Get(ReadOptions(), db_->DefaultColumnFamily(), second_key,
|
||||
&result));
|
||||
ASSERT_EQ(result, second_expected[0].value());
|
||||
}
|
||||
|
||||
{
|
||||
PinnableSlice result;
|
||||
ASSERT_OK(db_->Get(ReadOptions(), db_->DefaultColumnFamily(), third_key,
|
||||
&result));
|
||||
ASSERT_EQ(result, third_expected[0].value());
|
||||
}
|
||||
|
||||
// MultiGet
|
||||
{
|
||||
std::array<Slice, num_keys> keys{{first_key, second_key, third_key}};
|
||||
std::array<PinnableSlice, num_keys> values;
|
||||
std::array<Status, num_keys> statuses;
|
||||
|
||||
db_->MultiGet(ReadOptions(), db_->DefaultColumnFamily(), num_keys,
|
||||
keys.data(), values.data(), statuses.data());
|
||||
ASSERT_OK(statuses[0]);
|
||||
ASSERT_EQ(values[0], first_expected[0].value());
|
||||
ASSERT_OK(statuses[1]);
|
||||
ASSERT_EQ(values[1], second_expected[0].value());
|
||||
ASSERT_OK(statuses[2]);
|
||||
ASSERT_EQ(values[2], third_expected[0].value());
|
||||
}
|
||||
|
||||
// GetEntity
|
||||
{
|
||||
PinnableWideColumns result;
|
||||
|
||||
ASSERT_OK(db_->GetEntity(ReadOptions(), db_->DefaultColumnFamily(),
|
||||
first_key, &result));
|
||||
ASSERT_EQ(result.columns(), first_expected);
|
||||
}
|
||||
|
||||
{
|
||||
PinnableWideColumns result;
|
||||
|
||||
ASSERT_OK(db_->GetEntity(ReadOptions(), db_->DefaultColumnFamily(),
|
||||
second_key, &result));
|
||||
ASSERT_EQ(result.columns(), second_expected);
|
||||
}
|
||||
|
||||
{
|
||||
PinnableWideColumns result;
|
||||
|
||||
ASSERT_OK(db_->GetEntity(ReadOptions(), db_->DefaultColumnFamily(),
|
||||
third_key, &result));
|
||||
ASSERT_EQ(result.columns(), third_expected);
|
||||
}
|
||||
|
||||
// MultiGetEntity
|
||||
{
|
||||
std::array<Slice, num_keys> keys{{first_key, second_key, third_key}};
|
||||
std::array<PinnableWideColumns, num_keys> results;
|
||||
std::array<Status, num_keys> statuses;
|
||||
|
||||
db_->MultiGetEntity(ReadOptions(), db_->DefaultColumnFamily(), num_keys,
|
||||
keys.data(), results.data(), statuses.data());
|
||||
ASSERT_OK(statuses[0]);
|
||||
ASSERT_EQ(results[0].columns(), first_expected);
|
||||
ASSERT_OK(statuses[1]);
|
||||
ASSERT_EQ(results[1].columns(), second_expected);
|
||||
ASSERT_OK(statuses[2]);
|
||||
ASSERT_EQ(results[2].columns(), third_expected);
|
||||
}
|
||||
|
||||
// Iterator
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
|
||||
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(iter->key(), first_key);
|
||||
ASSERT_EQ(iter->value(), first_expected[0].value());
|
||||
ASSERT_EQ(iter->columns(), first_expected);
|
||||
|
||||
iter->Next();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(iter->key(), second_key);
|
||||
ASSERT_EQ(iter->value(), second_expected[0].value());
|
||||
ASSERT_EQ(iter->columns(), second_expected);
|
||||
|
||||
iter->Next();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(iter->key(), third_key);
|
||||
ASSERT_EQ(iter->value(), third_expected[0].value());
|
||||
ASSERT_EQ(iter->columns(), third_expected);
|
||||
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
iter->SeekToLast();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(iter->key(), third_key);
|
||||
ASSERT_EQ(iter->value(), third_expected[0].value());
|
||||
ASSERT_EQ(iter->columns(), third_expected);
|
||||
|
||||
iter->Prev();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(iter->key(), second_key);
|
||||
ASSERT_EQ(iter->value(), second_expected[0].value());
|
||||
ASSERT_EQ(iter->columns(), second_expected);
|
||||
|
||||
iter->Prev();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(iter->key(), first_key);
|
||||
ASSERT_EQ(iter->value(), first_expected[0].value());
|
||||
ASSERT_EQ(iter->columns(), first_expected);
|
||||
|
||||
iter->Prev();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
}
|
||||
|
||||
void VerifyMergeOperandCount(const Slice& key, int expected_merge_ops) {
|
||||
GetMergeOperandsOptions get_merge_opts;
|
||||
get_merge_opts.expected_max_number_of_operands = expected_merge_ops;
|
||||
|
||||
std::vector<PinnableSlice> merge_operands(expected_merge_ops);
|
||||
int number_of_operands = 0;
|
||||
|
||||
ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(),
|
||||
key, merge_operands.data(), &get_merge_opts,
|
||||
&number_of_operands));
|
||||
ASSERT_EQ(number_of_operands, expected_merge_ops);
|
||||
}
|
||||
|
||||
std::vector<ManagedSnapshot> snapshots_;
|
||||
|
||||
static constexpr size_t num_keys = 3;
|
||||
|
||||
static constexpr char first_key[] = "first";
|
||||
static constexpr char first_merge_op1[] = "hello";
|
||||
static constexpr char first_merge_op1_upper[] = "HELLO";
|
||||
static constexpr char first_merge_op2[] = "world";
|
||||
static constexpr char first_merge_op2_upper[] = "WORLD";
|
||||
|
||||
static constexpr char second_key[] = "second";
|
||||
static constexpr char second_base_value[] = "foo";
|
||||
static constexpr char second_base_value_upper[] = "FOO";
|
||||
static constexpr char second_merge_op1[] = "bar";
|
||||
static constexpr char second_merge_op1_upper[] = "BAR";
|
||||
static constexpr char second_merge_op2[] = "baz";
|
||||
static constexpr char second_merge_op2_upper[] = "BAZ";
|
||||
|
||||
static constexpr char third_key[] = "third";
|
||||
static const WideColumns third_columns;
|
||||
static constexpr char third_merge_op1[] = "three";
|
||||
static constexpr char third_merge_op1_upper[] = "THREE";
|
||||
static constexpr char third_merge_op2[] = "four";
|
||||
static constexpr char third_merge_op2_upper[] = "FOUR";
|
||||
};
|
||||
|
||||
const WideColumns DBWideMergeV3Test::third_columns{{"one", "ONE"},
|
||||
{"two", "TWO"}};
|
||||
|
||||
TEST_F(DBWideMergeV3Test, MergeV3WideColumnOutput) {
|
||||
// A test merge operator that always returns a wide-column result. It adds any
|
||||
// base values and merge operands to a single wide-column entity, and converts
|
||||
// all column values to uppercase. In addition, it puts "none", "plain", or
|
||||
// "wide" into the value of the default column depending on the type of the
|
||||
// base value (if any).
|
||||
static constexpr char kNone[] = "none";
|
||||
static constexpr char kPlain[] = "plain";
|
||||
static constexpr char kWide[] = "wide";
|
||||
|
||||
class WideColumnOutputMergeOperator : public MergeOperator {
|
||||
public:
|
||||
bool FullMergeV3(const MergeOperationInputV3& merge_in,
|
||||
MergeOperationOutputV3* merge_out) const override {
|
||||
assert(merge_out);
|
||||
|
||||
merge_out->new_value = MergeOperationOutputV3::NewColumns();
|
||||
auto& new_columns =
|
||||
std::get<MergeOperationOutputV3::NewColumns>(merge_out->new_value);
|
||||
|
||||
auto upper = [](std::string str) {
|
||||
for (char& c : str) {
|
||||
c = static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
|
||||
}
|
||||
|
||||
return str;
|
||||
};
|
||||
|
||||
std::visit(overload{[&](const std::monostate&) {
|
||||
new_columns.emplace_back(
|
||||
kDefaultWideColumnName.ToString(), kNone);
|
||||
},
|
||||
[&](const Slice& value) {
|
||||
new_columns.emplace_back(
|
||||
kDefaultWideColumnName.ToString(), kPlain);
|
||||
|
||||
const std::string val = value.ToString();
|
||||
new_columns.emplace_back(val, upper(val));
|
||||
},
|
||||
[&](const WideColumns& columns) {
|
||||
new_columns.emplace_back(
|
||||
kDefaultWideColumnName.ToString(), kWide);
|
||||
|
||||
for (const auto& column : columns) {
|
||||
new_columns.emplace_back(
|
||||
column.name().ToString(),
|
||||
upper(column.value().ToString()));
|
||||
}
|
||||
}},
|
||||
merge_in.existing_value);
|
||||
|
||||
for (const auto& operand : merge_in.operand_list) {
|
||||
const std::string op = operand.ToString();
|
||||
new_columns.emplace_back(op, upper(op));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* Name() const override {
|
||||
return "WideColumnOutputMergeOperator";
|
||||
}
|
||||
};
|
||||
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.merge_operator = std::make_shared<WideColumnOutputMergeOperator>();
|
||||
Reopen(options);
|
||||
|
||||
// Expected results
|
||||
// Lexicographical order: [default] < hello < world
|
||||
const WideColumns first_expected{{kDefaultWideColumnName, kNone},
|
||||
{first_merge_op1, first_merge_op1_upper},
|
||||
{first_merge_op2, first_merge_op2_upper}};
|
||||
// Lexicographical order: [default] < bar < baz < foo
|
||||
const WideColumns second_expected{
|
||||
{kDefaultWideColumnName, kPlain},
|
||||
{second_merge_op1, second_merge_op1_upper},
|
||||
{second_merge_op2, second_merge_op2_upper},
|
||||
{second_base_value, second_base_value_upper}};
|
||||
// Lexicographical order: [default] < four < one < three < two
|
||||
const WideColumns third_expected{
|
||||
{kDefaultWideColumnName, kWide},
|
||||
{third_merge_op2, third_merge_op2_upper},
|
||||
{third_columns[0].name(), third_columns[0].value()},
|
||||
{third_merge_op1, third_merge_op1_upper},
|
||||
{third_columns[1].name(), third_columns[1].value()}};
|
||||
|
||||
RunTest(first_expected, second_expected, third_expected);
|
||||
}
|
||||
|
||||
TEST_F(DBWideMergeV3Test, MergeV3PlainOutput) {
|
||||
// A test merge operator that always returns a plain value as result, namely
|
||||
// the total number of operands serialized as a string. Base values are also
|
||||
// counted as operands; specifically, a plain base value is counted as one
|
||||
// operand, while a wide-column base value is counted as as many operands as
|
||||
// the number of columns.
|
||||
class PlainOutputMergeOperator : public MergeOperator {
|
||||
public:
|
||||
bool FullMergeV3(const MergeOperationInputV3& merge_in,
|
||||
MergeOperationOutputV3* merge_out) const override {
|
||||
assert(merge_out);
|
||||
|
||||
size_t count = 0;
|
||||
std::visit(
|
||||
overload{[&](const std::monostate&) {},
|
||||
[&](const Slice&) { count = 1; },
|
||||
[&](const WideColumns& columns) { count = columns.size(); }},
|
||||
merge_in.existing_value);
|
||||
|
||||
count += merge_in.operand_list.size();
|
||||
|
||||
merge_out->new_value = std::string();
|
||||
std::get<std::string>(merge_out->new_value) = std::to_string(count);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* Name() const override { return "PlainOutputMergeOperator"; }
|
||||
};
|
||||
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.merge_operator = std::make_shared<PlainOutputMergeOperator>();
|
||||
Reopen(options);
|
||||
|
||||
const WideColumns first_expected{{kDefaultWideColumnName, "2"}};
|
||||
const WideColumns second_expected{{kDefaultWideColumnName, "3"}};
|
||||
const WideColumns third_expected{{kDefaultWideColumnName, "4"}};
|
||||
|
||||
RunTest(first_expected, second_expected, third_expected);
|
||||
}
|
||||
|
||||
TEST_F(DBWideBasicTest, CompactionFilter) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
|
||||
@@ -24,6 +24,11 @@ class WideColumnsHelper {
|
||||
return !columns.empty() && columns.front().name() == kDefaultWideColumnName;
|
||||
}
|
||||
|
||||
static bool HasDefaultColumnOnly(const WideColumns& columns) {
|
||||
return columns.size() == 1 &&
|
||||
columns.front().name() == kDefaultWideColumnName;
|
||||
}
|
||||
|
||||
static const Slice& GetDefaultColumn(const WideColumns& columns) {
|
||||
assert(HasDefaultColumn(columns));
|
||||
return columns.front().value();
|
||||
|
||||
+38
-18
@@ -2483,15 +2483,15 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
}
|
||||
|
||||
if (perform_merge) {
|
||||
// TODO: support wide-column base values for max_successive_merges
|
||||
|
||||
// 1) Get the existing value
|
||||
std::string get_value;
|
||||
// 1) Get the existing value. Use the wide column APIs to make sure we
|
||||
// don't lose any columns in the process.
|
||||
PinnableWideColumns existing;
|
||||
|
||||
// Pass in the sequence number so that we also include previous merge
|
||||
// operations in the same batch.
|
||||
SnapshotImpl read_from_snapshot;
|
||||
read_from_snapshot.number_ = sequence_;
|
||||
|
||||
// TODO: plumb Env::IOActivity
|
||||
ReadOptions read_options;
|
||||
read_options.snapshot = &read_from_snapshot;
|
||||
@@ -2500,28 +2500,47 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
if (cf_handle == nullptr) {
|
||||
cf_handle = db_->DefaultColumnFamily();
|
||||
}
|
||||
Status get_status = db_->Get(read_options, cf_handle, key, &get_value);
|
||||
|
||||
Status get_status =
|
||||
db_->GetEntity(read_options, cf_handle, key, &existing);
|
||||
if (!get_status.ok()) {
|
||||
// Failed to read a key we know exists. Store the delta in memtable.
|
||||
perform_merge = false;
|
||||
} else {
|
||||
Slice get_value_slice = Slice(get_value);
|
||||
|
||||
// 2) Apply this merge
|
||||
auto merge_operator = moptions->merge_operator;
|
||||
assert(merge_operator);
|
||||
|
||||
const auto& columns = existing.columns();
|
||||
|
||||
Status merge_status;
|
||||
std::string new_value;
|
||||
ValueType new_value_type;
|
||||
// `op_failure_scope` (an output parameter) is not provided (set to
|
||||
// nullptr) since a failure must be propagated regardless of its value.
|
||||
Status merge_status = MergeHelper::TimedFullMerge(
|
||||
merge_operator, key, MergeHelper::kPlainBaseValue, get_value_slice,
|
||||
{value}, moptions->info_log, moptions->statistics,
|
||||
SystemClock::Default().get(),
|
||||
/* update_num_ops_stats */ false, &new_value,
|
||||
/* result_operand */ nullptr, &new_value_type,
|
||||
/* op_failure_scope */ nullptr);
|
||||
|
||||
if (WideColumnsHelper::HasDefaultColumnOnly(columns)) {
|
||||
// `op_failure_scope` (an output parameter) is not provided (set to
|
||||
// nullptr) since a failure must be propagated regardless of its
|
||||
// value.
|
||||
merge_status = MergeHelper::TimedFullMerge(
|
||||
merge_operator, key, MergeHelper::kPlainBaseValue,
|
||||
WideColumnsHelper::GetDefaultColumn(columns), {value},
|
||||
moptions->info_log, moptions->statistics,
|
||||
SystemClock::Default().get(),
|
||||
/* update_num_ops_stats */ false, &new_value,
|
||||
/* result_operand */ nullptr, &new_value_type,
|
||||
/* op_failure_scope */ nullptr);
|
||||
} else {
|
||||
// `op_failure_scope` (an output parameter) is not provided (set to
|
||||
// nullptr) since a failure must be propagated regardless of its
|
||||
// value.
|
||||
merge_status = MergeHelper::TimedFullMerge(
|
||||
merge_operator, key, MergeHelper::kWideBaseValue, columns,
|
||||
{value}, moptions->info_log, moptions->statistics,
|
||||
SystemClock::Default().get(),
|
||||
/* update_num_ops_stats */ false, &new_value,
|
||||
/* result_operand */ nullptr, &new_value_type,
|
||||
/* op_failure_scope */ nullptr);
|
||||
}
|
||||
|
||||
if (!merge_status.ok()) {
|
||||
// Failed to merge!
|
||||
@@ -2530,12 +2549,13 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
} else {
|
||||
// 3) Add value to memtable
|
||||
assert(!concurrent_memtable_writes_);
|
||||
assert(new_value_type == kTypeValue ||
|
||||
new_value_type == kTypeWideColumnEntity);
|
||||
|
||||
if (kv_prot_info != nullptr) {
|
||||
auto merged_kv_prot_info =
|
||||
kv_prot_info->StripC(column_family_id).ProtectS(sequence_);
|
||||
merged_kv_prot_info.UpdateV(value, new_value);
|
||||
assert(new_value_type == kTypeValue ||
|
||||
new_value_type == kTypeWideColumnEntity);
|
||||
merged_kv_prot_info.UpdateO(kTypeMerge, new_value_type);
|
||||
ret_status = mem->Add(sequence_, new_value_type, key, new_value,
|
||||
&merged_kv_prot_info);
|
||||
|
||||
@@ -9,6 +9,7 @@ add_executable(db_stress${ARTIFACT_SUFFIX}
|
||||
db_stress_shared_state.cc
|
||||
db_stress_stat.cc
|
||||
db_stress_test_base.cc
|
||||
db_stress_wide_merge_operator.cc
|
||||
db_stress_tool.cc
|
||||
expected_state.cc
|
||||
expected_value.cc
|
||||
|
||||
@@ -52,11 +52,11 @@ class BatchedOpsStressTest : public StressTest {
|
||||
const std::string k = num + key_body;
|
||||
const std::string v = value_body + num;
|
||||
|
||||
if (FLAGS_use_merge) {
|
||||
batch.Merge(cfh, k, v);
|
||||
} else if (FLAGS_use_put_entity_one_in > 0 &&
|
||||
(value_base % FLAGS_use_put_entity_one_in) == 0) {
|
||||
if (FLAGS_use_put_entity_one_in > 0 &&
|
||||
(value_base % FLAGS_use_put_entity_one_in) == 0) {
|
||||
batch.PutEntity(cfh, k, GenerateWideColumns(value_base, v));
|
||||
} else if (FLAGS_use_merge) {
|
||||
batch.Merge(cfh, k, v);
|
||||
} else {
|
||||
batch.Put(cfh, k, v);
|
||||
}
|
||||
|
||||
@@ -36,18 +36,15 @@ class CfConsistencyStressTest : public StressTest {
|
||||
|
||||
WriteBatch batch;
|
||||
|
||||
const bool use_put_entity = !FLAGS_use_merge &&
|
||||
FLAGS_use_put_entity_one_in > 0 &&
|
||||
(value_base % FLAGS_use_put_entity_one_in) == 0;
|
||||
|
||||
for (auto cf : rand_column_families) {
|
||||
ColumnFamilyHandle* const cfh = column_families_[cf];
|
||||
assert(cfh);
|
||||
|
||||
if (FLAGS_use_merge) {
|
||||
batch.Merge(cfh, k, v);
|
||||
} else if (use_put_entity) {
|
||||
if (FLAGS_use_put_entity_one_in > 0 &&
|
||||
(value_base % FLAGS_use_put_entity_one_in) == 0) {
|
||||
batch.PutEntity(cfh, k, GenerateWideColumns(value_base, v));
|
||||
} else if (FLAGS_use_merge) {
|
||||
batch.Merge(cfh, k, v);
|
||||
} else {
|
||||
batch.Put(cfh, k, v);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
#include "util/file_checksum_helper.h"
|
||||
#include "util/xxhash.h"
|
||||
|
||||
@@ -21,6 +22,8 @@ ROCKSDB_NAMESPACE::Env* db_stress_env = nullptr;
|
||||
// If non-null, injects read error at a rate specified by the
|
||||
// read_fault_one_in or write_fault_one_in flag
|
||||
std::shared_ptr<ROCKSDB_NAMESPACE::FaultInjectionTestFS> fault_fs_guard;
|
||||
std::shared_ptr<ROCKSDB_NAMESPACE::SecondaryCache> compressed_secondary_cache;
|
||||
std::shared_ptr<ROCKSDB_NAMESPACE::Cache> block_cache;
|
||||
enum ROCKSDB_NAMESPACE::CompressionType compression_type_e =
|
||||
ROCKSDB_NAMESPACE::kSnappyCompression;
|
||||
enum ROCKSDB_NAMESPACE::CompressionType bottommost_compression_type_e =
|
||||
@@ -148,6 +151,88 @@ void DbVerificationThread(void* v) {
|
||||
}
|
||||
}
|
||||
|
||||
void CompressedCacheSetCapacityThread(void* v) {
|
||||
assert(FLAGS_compressed_secondary_cache_size > 0 ||
|
||||
FLAGS_compressed_secondary_cache_ratio > 0.0);
|
||||
auto* thread = reinterpret_cast<ThreadState*>(v);
|
||||
SharedState* shared = thread->shared;
|
||||
while (true) {
|
||||
{
|
||||
MutexLock l(shared->GetMutex());
|
||||
if (shared->ShouldStopBgThread()) {
|
||||
shared->IncBgThreadsFinished();
|
||||
if (shared->BgThreadsFinished()) {
|
||||
shared->GetCondVar()->SignalAll();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
db_stress_env->SleepForMicroseconds(FLAGS_secondary_cache_update_interval);
|
||||
if (FLAGS_compressed_secondary_cache_size > 0) {
|
||||
Status s = compressed_secondary_cache->SetCapacity(0);
|
||||
size_t capacity;
|
||||
if (s.ok()) {
|
||||
s = compressed_secondary_cache->GetCapacity(capacity);
|
||||
assert(capacity == 0);
|
||||
}
|
||||
db_stress_env->SleepForMicroseconds(10 * 1000 * 1000);
|
||||
if (s.ok()) {
|
||||
s = compressed_secondary_cache->SetCapacity(
|
||||
FLAGS_compressed_secondary_cache_size);
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = compressed_secondary_cache->GetCapacity(capacity);
|
||||
assert(capacity == FLAGS_compressed_secondary_cache_size);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "Compressed cache Set/GetCapacity returned error: %s\n",
|
||||
s.ToString().c_str());
|
||||
}
|
||||
} else if (FLAGS_compressed_secondary_cache_ratio > 0.0) {
|
||||
if (thread->rand.OneIn(2)) { // if (thread->rand.OneIn(2)) {
|
||||
size_t capacity = block_cache->GetCapacity();
|
||||
size_t adjustment;
|
||||
if (FLAGS_use_write_buffer_manager && FLAGS_db_write_buffer_size > 0) {
|
||||
adjustment = (capacity - FLAGS_db_write_buffer_size);
|
||||
} else {
|
||||
adjustment = capacity;
|
||||
}
|
||||
// Lower by upto 50% of usable block cache capacity
|
||||
adjustment = (adjustment * thread->rand.Uniform(50)) / 100;
|
||||
block_cache->SetCapacity(capacity - adjustment);
|
||||
fprintf(stderr, "New cache capacity = %lu\n",
|
||||
block_cache->GetCapacity());
|
||||
db_stress_env->SleepForMicroseconds(10 * 1000 * 1000);
|
||||
block_cache->SetCapacity(capacity);
|
||||
} else {
|
||||
Status s;
|
||||
double new_comp_cache_ratio =
|
||||
(double)thread->rand.Uniform(
|
||||
FLAGS_compressed_secondary_cache_ratio * 100) /
|
||||
100;
|
||||
if (new_comp_cache_ratio == 0.0) {
|
||||
new_comp_cache_ratio = 0.05;
|
||||
}
|
||||
fprintf(stderr, "New comp cache ratio = %f\n", new_comp_cache_ratio);
|
||||
|
||||
s = UpdateTieredCache(block_cache, /*capacity*/ -1,
|
||||
new_comp_cache_ratio);
|
||||
if (s.ok()) {
|
||||
db_stress_env->SleepForMicroseconds(10 * 1000 * 1000);
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = UpdateTieredCache(block_cache, /*capacity*/ -1,
|
||||
FLAGS_compressed_secondary_cache_ratio);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "UpdateTieredCache returned error: %s\n",
|
||||
s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PrintKeyValue(int cf, uint64_t key, const char* value, size_t sz) {
|
||||
if (!FLAGS_verbose) {
|
||||
return;
|
||||
|
||||
@@ -108,11 +108,14 @@ DECLARE_int32(max_write_buffer_number);
|
||||
DECLARE_int32(min_write_buffer_number_to_merge);
|
||||
DECLARE_int32(max_write_buffer_number_to_maintain);
|
||||
DECLARE_int64(max_write_buffer_size_to_maintain);
|
||||
DECLARE_bool(use_write_buffer_manager);
|
||||
DECLARE_double(memtable_prefix_bloom_size_ratio);
|
||||
DECLARE_bool(memtable_whole_key_filtering);
|
||||
DECLARE_int32(open_files);
|
||||
DECLARE_int64(compressed_cache_size);
|
||||
DECLARE_int32(compressed_cache_numshardbits);
|
||||
DECLARE_uint64(compressed_secondary_cache_size);
|
||||
DECLARE_int32(compressed_secondary_cache_numshardbits);
|
||||
DECLARE_int32(secondary_cache_update_interval);
|
||||
DECLARE_double(compressed_secondary_cache_ratio);
|
||||
DECLARE_int32(compaction_style);
|
||||
DECLARE_int32(compaction_pri);
|
||||
DECLARE_int32(num_levels);
|
||||
@@ -358,6 +361,9 @@ constexpr int kValueMaxLen = 100;
|
||||
extern ROCKSDB_NAMESPACE::Env* db_stress_env;
|
||||
extern ROCKSDB_NAMESPACE::Env* db_stress_listener_env;
|
||||
extern std::shared_ptr<ROCKSDB_NAMESPACE::FaultInjectionTestFS> fault_fs_guard;
|
||||
extern std::shared_ptr<ROCKSDB_NAMESPACE::SecondaryCache>
|
||||
compressed_secondary_cache;
|
||||
extern std::shared_ptr<ROCKSDB_NAMESPACE::Cache> block_cache;
|
||||
|
||||
extern enum ROCKSDB_NAMESPACE::CompressionType compression_type_e;
|
||||
extern enum ROCKSDB_NAMESPACE::CompressionType bottommost_compression_type_e;
|
||||
@@ -650,6 +656,8 @@ extern void PoolSizeChangeThread(void* v);
|
||||
|
||||
extern void DbVerificationThread(void* v);
|
||||
|
||||
extern void CompressedCacheSetCapacityThread(void* v);
|
||||
|
||||
extern void TimestampedSnapshotsThread(void* v);
|
||||
|
||||
extern void PrintKeyValue(int cf, uint64_t key, const char* value, size_t sz);
|
||||
|
||||
@@ -121,6 +121,11 @@ bool RunStressTestImpl(SharedState* shared) {
|
||||
shared->IncBgThreads();
|
||||
}
|
||||
|
||||
if (FLAGS_compressed_secondary_cache_size > 0 ||
|
||||
FLAGS_compressed_secondary_cache_ratio > 0.0) {
|
||||
shared->IncBgThreads();
|
||||
}
|
||||
|
||||
std::vector<ThreadState*> threads(n);
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
threads[i] = new ThreadState(i, shared);
|
||||
@@ -138,6 +143,13 @@ bool RunStressTestImpl(SharedState* shared) {
|
||||
&continuous_verification_thread);
|
||||
}
|
||||
|
||||
ThreadState compressed_cache_set_capacity_thread(0, shared);
|
||||
if (FLAGS_compressed_secondary_cache_size > 0 ||
|
||||
FLAGS_compressed_secondary_cache_ratio > 0.0) {
|
||||
db_stress_env->StartThread(CompressedCacheSetCapacityThread,
|
||||
&compressed_cache_set_capacity_thread);
|
||||
}
|
||||
|
||||
// Each thread goes through the following states:
|
||||
// initializing -> wait for others to init -> read/populate/depopulate
|
||||
// wait for others to operate -> verify -> done
|
||||
@@ -230,7 +242,9 @@ bool RunStressTestImpl(SharedState* shared) {
|
||||
}
|
||||
|
||||
if (FLAGS_compaction_thread_pool_adjust_interval > 0 ||
|
||||
FLAGS_continuous_verification_interval > 0) {
|
||||
FLAGS_continuous_verification_interval > 0 ||
|
||||
FLAGS_compressed_secondary_cache_size > 0 ||
|
||||
FLAGS_compressed_secondary_cache_ratio > 0.0) {
|
||||
MutexLock l(shared->GetMutex());
|
||||
shared->SetShouldStopBgThread();
|
||||
while (!shared->BgThreadsFinished()) {
|
||||
|
||||
@@ -136,6 +136,9 @@ DEFINE_uint64(db_write_buffer_size,
|
||||
ROCKSDB_NAMESPACE::Options().db_write_buffer_size,
|
||||
"Number of bytes to buffer in all memtables before compacting");
|
||||
|
||||
DEFINE_bool(use_write_buffer_manager, false,
|
||||
"Charge WriteBufferManager memory to the block cache");
|
||||
|
||||
DEFINE_int32(
|
||||
write_buffer_size,
|
||||
static_cast<int32_t>(ROCKSDB_NAMESPACE::Options().write_buffer_size),
|
||||
@@ -198,15 +201,23 @@ DEFINE_int32(open_files, ROCKSDB_NAMESPACE::Options().max_open_files,
|
||||
"Maximum number of files to keep open at the same time "
|
||||
"(use default if == 0)");
|
||||
|
||||
DEFINE_int64(compressed_cache_size, 0,
|
||||
"Number of bytes to use as a cache of compressed data."
|
||||
" 0 means use default settings.");
|
||||
DEFINE_uint64(compressed_secondary_cache_size, 0,
|
||||
"Number of bytes to use as a cache of compressed data."
|
||||
" 0 means use default settings.");
|
||||
|
||||
DEFINE_int32(
|
||||
compressed_cache_numshardbits, -1,
|
||||
"Number of shards for the compressed block cache is 2 ** "
|
||||
"compressed_cache_numshardbits. Negative value means default settings. "
|
||||
"This is applied only if compressed_cache_size is greater than 0.");
|
||||
DEFINE_int32(compressed_secondary_cache_numshardbits, -1,
|
||||
"Number of shards for the compressed secondary cache is 2 ** "
|
||||
"compressed_secondary_cache_numshardbits. "
|
||||
"Negative value means default settings. This is applied only "
|
||||
"if compressed_secondary_cache_size is greater than 0.");
|
||||
|
||||
DEFINE_double(compressed_secondary_cache_ratio, 0.0,
|
||||
"Fraction of block cache memory budget to use for compressed "
|
||||
"secondary cache");
|
||||
|
||||
DEFINE_int32(secondary_cache_update_interval, 30 * 1000 * 1000,
|
||||
"Interval between modification of secondary cache parameters, in "
|
||||
"microseconds");
|
||||
|
||||
DEFINE_int32(compaction_style, ROCKSDB_NAMESPACE::Options().compaction_style,
|
||||
"");
|
||||
@@ -1023,6 +1034,9 @@ DEFINE_string(secondary_cache_uri, "",
|
||||
DEFINE_int32(secondary_cache_fault_one_in, 0,
|
||||
"On non-zero, enables fault injection in secondary cache inserts"
|
||||
" and lookups");
|
||||
DEFINE_double(tiered_cache_percent_compressed, 0.0,
|
||||
"Percentage of total block cache budget to allocate to the "
|
||||
"compressed cache");
|
||||
DEFINE_int32(open_write_fault_one_in, 0,
|
||||
"On non-zero, enables fault injection on file writes "
|
||||
"during DB reopen.");
|
||||
|
||||
@@ -71,11 +71,23 @@ class DbStressListener : public EventListener {
|
||||
VerifyFilePath(info.file_path);
|
||||
// pretending doing some work here
|
||||
RandomSleep();
|
||||
if (FLAGS_read_fault_one_in) {
|
||||
(void)fault_fs_guard->GetAndResetErrorCount();
|
||||
fault_fs_guard->DisableErrorInjection();
|
||||
}
|
||||
}
|
||||
|
||||
void OnFlushBegin(DB* /*db*/,
|
||||
const FlushJobInfo& /*flush_job_info*/) override {
|
||||
RandomSleep();
|
||||
if (FLAGS_read_fault_one_in) {
|
||||
// Hardcoded to inject retryable error as a non-retryable error would put
|
||||
// the DB in read-only mode and then it would crash on the next write.
|
||||
fault_fs_guard->SetThreadLocalReadErrorContext(
|
||||
static_cast<uint32_t>(FLAGS_seed), FLAGS_read_fault_one_in,
|
||||
true /* retryable */);
|
||||
fault_fs_guard->EnableErrorInjection();
|
||||
}
|
||||
}
|
||||
|
||||
void OnTableFileDeleted(const TableFileDeletionInfo& /*info*/) override {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "db_stress_tool/db_stress_compaction_filter.h"
|
||||
#include "db_stress_tool/db_stress_driver.h"
|
||||
#include "db_stress_tool/db_stress_table_properties_collector.h"
|
||||
#include "db_stress_tool/db_stress_wide_merge_operator.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/filter_policy.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
@@ -111,6 +112,11 @@ std::shared_ptr<Cache> StressTest::NewCache(size_t capacity,
|
||||
|
||||
std::shared_ptr<SecondaryCache> secondary_cache;
|
||||
if (!FLAGS_secondary_cache_uri.empty()) {
|
||||
assert(!strstr(FLAGS_secondary_cache_uri.c_str(),
|
||||
"compressed_secondary_cache") ||
|
||||
(FLAGS_compressed_secondary_cache_size == 0 &&
|
||||
FLAGS_compressed_secondary_cache_ratio == 0.0 &&
|
||||
!StartsWith(FLAGS_cache_type, "tiered_")));
|
||||
Status s = SecondaryCache::CreateFromString(
|
||||
config_options, FLAGS_secondary_cache_uri, &secondary_cache);
|
||||
if (secondary_cache == nullptr) {
|
||||
@@ -124,36 +130,81 @@ std::shared_ptr<Cache> StressTest::NewCache(size_t capacity,
|
||||
secondary_cache, static_cast<uint32_t>(FLAGS_seed),
|
||||
FLAGS_secondary_cache_fault_one_in);
|
||||
}
|
||||
} else if (FLAGS_compressed_secondary_cache_size > 0) {
|
||||
if (StartsWith(FLAGS_cache_type, "tiered_")) {
|
||||
fprintf(stderr,
|
||||
"Cannot specify both compressed_secondary_cache_size and %s\n",
|
||||
FLAGS_cache_type.c_str());
|
||||
exit(1);
|
||||
}
|
||||
CompressedSecondaryCacheOptions opts;
|
||||
opts.capacity = FLAGS_compressed_secondary_cache_size;
|
||||
secondary_cache = NewCompressedSecondaryCache(opts);
|
||||
if (secondary_cache == nullptr) {
|
||||
fprintf(stderr, "Failed to allocate compressed secondary cache\n");
|
||||
exit(1);
|
||||
}
|
||||
compressed_secondary_cache = secondary_cache;
|
||||
}
|
||||
|
||||
if (FLAGS_cache_type == "clock_cache") {
|
||||
std::string cache_type = FLAGS_cache_type;
|
||||
size_t cache_size = FLAGS_cache_size;
|
||||
bool tiered = false;
|
||||
if (StartsWith(cache_type, "tiered_")) {
|
||||
tiered = true;
|
||||
cache_type.erase(0, strlen("tiered_"));
|
||||
}
|
||||
if (FLAGS_use_write_buffer_manager) {
|
||||
cache_size += FLAGS_db_write_buffer_size;
|
||||
}
|
||||
if (cache_type == "clock_cache") {
|
||||
fprintf(stderr, "Old clock cache implementation has been removed.\n");
|
||||
exit(1);
|
||||
} else if (EndsWith(FLAGS_cache_type, "hyper_clock_cache")) {
|
||||
} else if (EndsWith(cache_type, "hyper_clock_cache")) {
|
||||
size_t estimated_entry_charge;
|
||||
if (FLAGS_cache_type == "fixed_hyper_clock_cache" ||
|
||||
FLAGS_cache_type == "hyper_clock_cache") {
|
||||
if (cache_type == "fixed_hyper_clock_cache" ||
|
||||
cache_type == "hyper_clock_cache") {
|
||||
estimated_entry_charge = FLAGS_block_size;
|
||||
} else if (FLAGS_cache_type == "auto_hyper_clock_cache") {
|
||||
} else if (cache_type == "auto_hyper_clock_cache") {
|
||||
estimated_entry_charge = 0;
|
||||
} else {
|
||||
fprintf(stderr, "Cache type not supported.");
|
||||
exit(1);
|
||||
}
|
||||
HyperClockCacheOptions opts(FLAGS_cache_size, estimated_entry_charge,
|
||||
HyperClockCacheOptions opts(cache_size, estimated_entry_charge,
|
||||
num_shard_bits);
|
||||
opts.hash_seed = BitwiseAnd(FLAGS_seed, INT32_MAX);
|
||||
return opts.MakeSharedCache();
|
||||
} else if (FLAGS_cache_type == "lru_cache") {
|
||||
if (tiered) {
|
||||
TieredCacheOptions tiered_opts;
|
||||
tiered_opts.cache_opts = &opts;
|
||||
tiered_opts.cache_type = PrimaryCacheType::kCacheTypeHCC;
|
||||
tiered_opts.total_capacity = cache_size;
|
||||
tiered_opts.compressed_secondary_ratio = 0.5;
|
||||
block_cache = NewTieredCache(tiered_opts);
|
||||
} else {
|
||||
opts.secondary_cache = std::move(secondary_cache);
|
||||
block_cache = opts.MakeSharedCache();
|
||||
}
|
||||
} else if (EndsWith(cache_type, "lru_cache")) {
|
||||
LRUCacheOptions opts;
|
||||
opts.capacity = capacity;
|
||||
opts.num_shard_bits = num_shard_bits;
|
||||
opts.secondary_cache = std::move(secondary_cache);
|
||||
return NewLRUCache(opts);
|
||||
if (tiered) {
|
||||
TieredCacheOptions tiered_opts;
|
||||
tiered_opts.cache_opts = &opts;
|
||||
tiered_opts.cache_type = PrimaryCacheType::kCacheTypeLRU;
|
||||
tiered_opts.total_capacity = cache_size;
|
||||
tiered_opts.compressed_secondary_ratio = 0.5;
|
||||
block_cache = NewTieredCache(tiered_opts);
|
||||
} else {
|
||||
opts.secondary_cache = std::move(secondary_cache);
|
||||
block_cache = NewLRUCache(opts);
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Cache type not supported.");
|
||||
exit(1);
|
||||
}
|
||||
return block_cache;
|
||||
}
|
||||
|
||||
std::vector<std::string> StressTest::GetBlobCompressionTags() {
|
||||
@@ -414,10 +465,22 @@ Status StressTest::AssertSame(DB* db, ColumnFamilyHandle* cf,
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void StressTest::VerificationAbort(SharedState* shared, std::string msg,
|
||||
Status s) const {
|
||||
fprintf(stderr, "Verification failed: %s. Status is %s\n", msg.c_str(),
|
||||
s.ToString().c_str());
|
||||
void StressTest::ProcessStatus(SharedState* shared, std::string opname,
|
||||
Status s) const {
|
||||
if (s.ok()) {
|
||||
return;
|
||||
}
|
||||
if (!s.IsIOError() || !std::strstr(s.getState(), "injected")) {
|
||||
std::ostringstream oss;
|
||||
oss << opname << " failed: " << s.ToString();
|
||||
VerificationAbort(shared, oss.str());
|
||||
assert(false);
|
||||
}
|
||||
fprintf(stdout, "%s failed: %s\n", opname.c_str(), s.ToString().c_str());
|
||||
}
|
||||
|
||||
void StressTest::VerificationAbort(SharedState* shared, std::string msg) const {
|
||||
fprintf(stderr, "Verification failed: %s\n", msg.c_str());
|
||||
shared->SetVerificationFailure();
|
||||
}
|
||||
|
||||
@@ -511,7 +574,11 @@ void StressTest::PreloadDbAndReopenAsReadOnly(int64_t number_of_keys,
|
||||
ts = GetNowNanos();
|
||||
}
|
||||
|
||||
if (FLAGS_use_merge) {
|
||||
if (FLAGS_use_put_entity_one_in > 0 &&
|
||||
(value_base % FLAGS_use_put_entity_one_in) == 0) {
|
||||
s = db_->PutEntity(write_opts, cfh, key,
|
||||
GenerateWideColumns(value_base, v));
|
||||
} else if (FLAGS_use_merge) {
|
||||
if (!FLAGS_use_txn) {
|
||||
if (FLAGS_user_timestamp_size > 0) {
|
||||
s = db_->Merge(write_opts, cfh, key, ts, v);
|
||||
@@ -523,9 +590,6 @@ void StressTest::PreloadDbAndReopenAsReadOnly(int64_t number_of_keys,
|
||||
write_opts, /*thread=*/nullptr,
|
||||
[&](Transaction& txn) { return txn.Merge(cfh, key, v); });
|
||||
}
|
||||
} else if (FLAGS_use_put_entity_one_in > 0) {
|
||||
s = db_->PutEntity(write_opts, cfh, key,
|
||||
GenerateWideColumns(value_base, v));
|
||||
} else {
|
||||
if (!FLAGS_use_txn) {
|
||||
if (FLAGS_user_timestamp_size > 0) {
|
||||
@@ -908,35 +972,24 @@ void StressTest::OperateDb(ThreadState* thread) {
|
||||
if (thread->rand.OneInOpt(FLAGS_get_live_files_one_in) &&
|
||||
!FLAGS_write_fault_one_in) {
|
||||
Status status = VerifyGetLiveFiles();
|
||||
if (!status.ok()) {
|
||||
VerificationAbort(shared, "VerifyGetLiveFiles status not OK", status);
|
||||
}
|
||||
ProcessStatus(shared, "VerifyGetLiveFiles", status);
|
||||
}
|
||||
|
||||
// Verify GetSortedWalFiles with a 1 in N chance.
|
||||
if (thread->rand.OneInOpt(FLAGS_get_sorted_wal_files_one_in)) {
|
||||
Status status = VerifyGetSortedWalFiles();
|
||||
if (!status.ok()) {
|
||||
VerificationAbort(shared, "VerifyGetSortedWalFiles status not OK",
|
||||
status);
|
||||
}
|
||||
ProcessStatus(shared, "VerifyGetSortedWalFiles", status);
|
||||
}
|
||||
|
||||
// Verify GetCurrentWalFile with a 1 in N chance.
|
||||
if (thread->rand.OneInOpt(FLAGS_get_current_wal_file_one_in)) {
|
||||
Status status = VerifyGetCurrentWalFile();
|
||||
if (!status.ok()) {
|
||||
VerificationAbort(shared, "VerifyGetCurrentWalFile status not OK",
|
||||
status);
|
||||
}
|
||||
ProcessStatus(shared, "VerifyGetCurrentWalFile", status);
|
||||
}
|
||||
|
||||
if (thread->rand.OneInOpt(FLAGS_pause_background_one_in)) {
|
||||
Status status = TestPauseBackground(thread);
|
||||
if (!status.ok()) {
|
||||
VerificationAbort(
|
||||
shared, "Pause/ContinueBackgroundWork status not OK", status);
|
||||
}
|
||||
ProcessStatus(shared, "Pause/ContinueBackgroundWork", status);
|
||||
}
|
||||
|
||||
if (thread->rand.OneInOpt(FLAGS_verify_checksum_one_in)) {
|
||||
@@ -945,9 +998,7 @@ void StressTest::OperateDb(ThreadState* thread) {
|
||||
ThreadStatus::OperationType::OP_VERIFY_DB_CHECKSUM);
|
||||
Status status = db_->VerifyChecksum();
|
||||
ThreadStatusUtil::ResetThreadStatus();
|
||||
if (!status.ok()) {
|
||||
VerificationAbort(shared, "VerifyChecksum status not OK", status);
|
||||
}
|
||||
ProcessStatus(shared, "VerifyChecksum", status);
|
||||
}
|
||||
|
||||
if (thread->rand.OneInOpt(FLAGS_verify_file_checksums_one_in)) {
|
||||
@@ -956,10 +1007,7 @@ void StressTest::OperateDb(ThreadState* thread) {
|
||||
ThreadStatus::OperationType::OP_VERIFY_FILE_CHECKSUMS);
|
||||
Status status = db_->VerifyFileChecksums(read_opts);
|
||||
ThreadStatusUtil::ResetThreadStatus();
|
||||
if (!status.ok()) {
|
||||
VerificationAbort(shared, "VerifyFileChecksums status not OK",
|
||||
status);
|
||||
}
|
||||
ProcessStatus(shared, "VerifyFileChecksums", status);
|
||||
}
|
||||
|
||||
if (thread->rand.OneInOpt(FLAGS_get_property_one_in)) {
|
||||
@@ -986,35 +1034,19 @@ void StressTest::OperateDb(ThreadState* thread) {
|
||||
|
||||
if (total_size <= FLAGS_backup_max_size) {
|
||||
Status s = TestBackupRestore(thread, rand_column_families, rand_keys);
|
||||
if (!s.ok()) {
|
||||
if (!s.IsIOError() || !std::strstr(s.getState(), "injected")) {
|
||||
VerificationAbort(shared,
|
||||
"Backup/restore gave inconsistent state", s);
|
||||
} else {
|
||||
fprintf(stdout, "Backup/restore failed: %s\n",
|
||||
s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
ProcessStatus(shared, "Backup/restore", s);
|
||||
}
|
||||
}
|
||||
|
||||
if (thread->rand.OneInOpt(FLAGS_checkpoint_one_in)) {
|
||||
Status s = TestCheckpoint(thread, rand_column_families, rand_keys);
|
||||
if (!s.ok()) {
|
||||
if (!s.IsIOError() || !std::strstr(s.getState(), "injected")) {
|
||||
VerificationAbort(shared, "Checkpoint gave inconsistent state", s);
|
||||
} else {
|
||||
fprintf(stdout, "Checkpoint failed: %s\n", s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
ProcessStatus(shared, "Checkpoint", s);
|
||||
}
|
||||
|
||||
if (thread->rand.OneInOpt(FLAGS_approximate_size_one_in)) {
|
||||
Status s =
|
||||
TestApproximateSize(thread, i, rand_column_families, rand_keys);
|
||||
if (!s.ok()) {
|
||||
VerificationAbort(shared, "ApproximateSize Failed", s);
|
||||
}
|
||||
ProcessStatus(shared, "ApproximateSize", s);
|
||||
}
|
||||
if (thread->rand.OneInOpt(FLAGS_acquire_snapshot_one_in)) {
|
||||
TestAcquireSnapshot(thread, rand_column_family, keystr, i);
|
||||
@@ -1022,9 +1054,7 @@ void StressTest::OperateDb(ThreadState* thread) {
|
||||
|
||||
/*always*/ {
|
||||
Status s = MaybeReleaseSnapshots(thread, i);
|
||||
if (!s.ok()) {
|
||||
VerificationAbort(shared, "Snapshot gave inconsistent state", s);
|
||||
}
|
||||
ProcessStatus(shared, "Snapshot", s);
|
||||
}
|
||||
|
||||
// Assign timestamps if necessary.
|
||||
@@ -1235,7 +1265,6 @@ Status StressTest::TestIterate(ThreadState* thread,
|
||||
} else if (options_.prefix_extractor.get() == nullptr) {
|
||||
expect_total_order = true;
|
||||
}
|
||||
|
||||
std::string upper_bound_str;
|
||||
Slice upper_bound;
|
||||
if (thread->rand.OneIn(16)) {
|
||||
@@ -1246,6 +1275,7 @@ Status StressTest::TestIterate(ThreadState* thread,
|
||||
upper_bound = Slice(upper_bound_str);
|
||||
ro.iterate_upper_bound = &upper_bound;
|
||||
}
|
||||
|
||||
std::string lower_bound_str;
|
||||
Slice lower_bound;
|
||||
if (thread->rand.OneIn(16)) {
|
||||
@@ -1375,8 +1405,7 @@ Status StressTest::TestIterate(ThreadState* thread,
|
||||
key, op_logs, &diverged);
|
||||
|
||||
const bool no_reverse =
|
||||
(FLAGS_memtablerep == "prefix_hash" && !expect_total_order) ||
|
||||
FLAGS_auto_readahead_size;
|
||||
(FLAGS_memtablerep == "prefix_hash" && !expect_total_order);
|
||||
for (uint64_t i = 0; i < FLAGS_num_iterations && iter->Valid(); ++i) {
|
||||
if (no_reverse || thread->rand.OneIn(2)) {
|
||||
iter->Next();
|
||||
@@ -1564,7 +1593,8 @@ void StressTest::VerifyIterator(ThreadState* thread,
|
||||
fprintf(stderr, "iterator has value %s\n",
|
||||
iter->key().ToString(true).c_str());
|
||||
} else {
|
||||
fprintf(stderr, "iterator is not valid\n");
|
||||
fprintf(stderr, "iterator is not valid with status: %s\n",
|
||||
iter->status().ToString().c_str());
|
||||
}
|
||||
*diverged = true;
|
||||
}
|
||||
@@ -1852,7 +1882,7 @@ Status StressTest::TestBackupRestore(
|
||||
from = "Destroy restore dir";
|
||||
}
|
||||
}
|
||||
if (!s.ok()) {
|
||||
if (!s.ok() && (!s.IsIOError() || !std::strstr(s.getState(), "injected"))) {
|
||||
fprintf(stderr, "Failure in %s with: %s\n", from.c_str(),
|
||||
s.ToString().c_str());
|
||||
}
|
||||
@@ -1928,17 +1958,19 @@ Status StressTest::TestCheckpoint(ThreadState* thread,
|
||||
if (s.ok()) {
|
||||
s = checkpoint->CreateCheckpoint(checkpoint_dir);
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "Fail to create checkpoint to %s\n",
|
||||
checkpoint_dir.c_str());
|
||||
std::vector<std::string> files;
|
||||
Status my_s = db_stress_env->GetChildren(checkpoint_dir, &files);
|
||||
if (my_s.ok()) {
|
||||
for (const auto& f : files) {
|
||||
fprintf(stderr, " %s\n", f.c_str());
|
||||
if (!s.IsIOError() || !std::strstr(s.getState(), "injected")) {
|
||||
fprintf(stderr, "Fail to create checkpoint to %s\n",
|
||||
checkpoint_dir.c_str());
|
||||
std::vector<std::string> files;
|
||||
Status my_s = db_stress_env->GetChildren(checkpoint_dir, &files);
|
||||
if (my_s.ok()) {
|
||||
for (const auto& f : files) {
|
||||
fprintf(stderr, " %s\n", f.c_str());
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Fail to get files under the directory to %s\n",
|
||||
my_s.ToString().c_str());
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Fail to get files under the directory to %s\n",
|
||||
my_s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2014,8 +2046,10 @@ Status StressTest::TestCheckpoint(ThreadState* thread,
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "A checkpoint operation failed with: %s\n",
|
||||
s.ToString().c_str());
|
||||
if (!s.IsIOError() || !std::strstr(s.getState(), "injected")) {
|
||||
fprintf(stderr, "A checkpoint operation failed with: %s\n",
|
||||
s.ToString().c_str());
|
||||
}
|
||||
} else {
|
||||
DestroyDB(checkpoint_dir, tmp_opts);
|
||||
}
|
||||
@@ -2755,8 +2789,7 @@ void StressTest::Open(SharedState* shared, bool reopen) {
|
||||
if (s.ok()) {
|
||||
db_ = blob_db;
|
||||
}
|
||||
} else
|
||||
{
|
||||
} else {
|
||||
if (db_preload_finished_.load() && FLAGS_read_only) {
|
||||
s = DB::OpenForReadOnly(DBOptions(options_), FLAGS_db,
|
||||
cf_descriptors, &column_families_, &db_);
|
||||
@@ -3174,6 +3207,10 @@ void InitializeOptionsFromFlags(
|
||||
FLAGS_max_write_buffer_size_to_maintain;
|
||||
options.memtable_prefix_bloom_size_ratio =
|
||||
FLAGS_memtable_prefix_bloom_size_ratio;
|
||||
if (FLAGS_use_write_buffer_manager) {
|
||||
options.write_buffer_manager.reset(
|
||||
new WriteBufferManager(FLAGS_db_write_buffer_size, block_cache));
|
||||
}
|
||||
options.memtable_whole_key_filtering = FLAGS_memtable_whole_key_filtering;
|
||||
options.disable_auto_compactions = FLAGS_disable_auto_compactions;
|
||||
options.max_background_compactions = FLAGS_max_background_compactions;
|
||||
@@ -3337,7 +3374,11 @@ void InitializeOptionsFromFlags(
|
||||
if (FLAGS_use_full_merge_v1) {
|
||||
options.merge_operator = MergeOperators::CreateDeprecatedPutOperator();
|
||||
} else {
|
||||
options.merge_operator = MergeOperators::CreatePutOperator();
|
||||
if (FLAGS_use_put_entity_one_in > 0) {
|
||||
options.merge_operator = std::make_shared<DBStressWideMergeOperator>();
|
||||
} else {
|
||||
options.merge_operator = MergeOperators::CreatePutOperator();
|
||||
}
|
||||
}
|
||||
|
||||
if (FLAGS_enable_compaction_filter) {
|
||||
|
||||
@@ -224,7 +224,9 @@ class StressTest {
|
||||
return Status::NotSupported("TestCustomOperations() must be overridden");
|
||||
}
|
||||
|
||||
void VerificationAbort(SharedState* shared, std::string msg, Status s) const;
|
||||
void ProcessStatus(SharedState* shared, std::string msg, Status s) const;
|
||||
|
||||
void VerificationAbort(SharedState* shared, std::string msg) const;
|
||||
|
||||
void VerificationAbort(SharedState* shared, std::string msg, int cf,
|
||||
int64_t key) const;
|
||||
|
||||
@@ -88,11 +88,11 @@ int db_stress_tool(int argc, char** argv) {
|
||||
FaultInjectionTestFS* fs =
|
||||
new FaultInjectionTestFS(raw_env->GetFileSystem());
|
||||
fault_fs_guard.reset(fs);
|
||||
if (FLAGS_write_fault_one_in) {
|
||||
fault_fs_guard->SetFilesystemDirectWritable(false);
|
||||
} else {
|
||||
fault_fs_guard->SetFilesystemDirectWritable(true);
|
||||
}
|
||||
// Set it to direct writable here to not lose files created during DB open
|
||||
// when no open fault injection is not enabled.
|
||||
// This will be overwritten in StressTest::Open() for open fault injection
|
||||
// and in RunStressTestImpl() for proper write fault injection setup.
|
||||
fault_fs_guard->SetFilesystemDirectWritable(true);
|
||||
fault_env_guard =
|
||||
std::make_shared<CompositeEnvWrapper>(raw_env, fault_fs_guard);
|
||||
raw_env = fault_env_guard.get();
|
||||
@@ -240,10 +240,10 @@ int db_stress_tool(int argc, char** argv) {
|
||||
FLAGS_secondaries_base = default_secondaries_path;
|
||||
}
|
||||
|
||||
if (FLAGS_best_efforts_recovery && !FLAGS_skip_verifydb &&
|
||||
!FLAGS_disable_wal) {
|
||||
if (FLAGS_best_efforts_recovery &&
|
||||
!(FLAGS_skip_verifydb && FLAGS_disable_wal)) {
|
||||
fprintf(stderr,
|
||||
"With best-efforts recovery, either skip_verifydb or disable_wal "
|
||||
"With best-efforts recovery, skip_verifydb and disable_wal "
|
||||
"should be set to true.\n");
|
||||
exit(1);
|
||||
}
|
||||
@@ -308,11 +308,11 @@ int db_stress_tool(int argc, char** argv) {
|
||||
}
|
||||
|
||||
if (FLAGS_use_put_entity_one_in > 0 &&
|
||||
(FLAGS_use_merge || FLAGS_use_full_merge_v1 || FLAGS_use_txn ||
|
||||
FLAGS_test_multi_ops_txns || FLAGS_user_timestamp_size > 0)) {
|
||||
(FLAGS_use_full_merge_v1 || FLAGS_use_txn || FLAGS_test_multi_ops_txns ||
|
||||
FLAGS_user_timestamp_size > 0)) {
|
||||
fprintf(stderr,
|
||||
"PutEntity is currently incompatible with Merge,"
|
||||
" transactions, and user-defined timestamps\n");
|
||||
"Wide columns are incompatible with V1 Merge, transactions, and "
|
||||
"user-defined timestamps\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#ifdef GFLAGS
|
||||
|
||||
#include "db_stress_tool/db_stress_wide_merge_operator.h"
|
||||
|
||||
#include "db_stress_tool/db_stress_common.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
bool DBStressWideMergeOperator::FullMergeV3(
|
||||
const MergeOperationInputV3& merge_in,
|
||||
MergeOperationOutputV3* merge_out) const {
|
||||
assert(!merge_in.operand_list.empty());
|
||||
assert(merge_out);
|
||||
|
||||
const Slice& latest = merge_in.operand_list.back();
|
||||
|
||||
if (latest.size() < sizeof(uint32_t)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t value_base = GetValueBase(latest);
|
||||
|
||||
if (FLAGS_use_put_entity_one_in == 0 ||
|
||||
(value_base % FLAGS_use_put_entity_one_in) != 0) {
|
||||
merge_out->new_value = latest;
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto columns = GenerateWideColumns(value_base, latest);
|
||||
|
||||
merge_out->new_value = MergeOperationOutputV3::NewColumns();
|
||||
auto& new_columns =
|
||||
std::get<MergeOperationOutputV3::NewColumns>(merge_out->new_value);
|
||||
new_columns.reserve(columns.size());
|
||||
|
||||
for (const auto& column : columns) {
|
||||
new_columns.emplace_back(column.name().ToString(),
|
||||
column.value().ToString());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#endif // GFLAGS
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocksdb/merge_operator.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// A test merge operator that implements the wide-column aware FullMergeV3
|
||||
// interface. Similarly to the simple "put" type merge operators, the merge
|
||||
// result is based on the last merge operand; however, the merge result can
|
||||
// potentially be a wide-column entity, depending on the value base encoded into
|
||||
// the merge operand and the value of the "use_put_entity_one_in" stress test
|
||||
// option. Following the same rule as for writes ensures that the queries
|
||||
// issued by the validation logic receive the expected results.
|
||||
class DBStressWideMergeOperator : public MergeOperator {
|
||||
public:
|
||||
bool FullMergeV3(const MergeOperationInputV3& merge_in,
|
||||
MergeOperationOutputV3* merge_out) const override;
|
||||
|
||||
const char* Name() const override { return "DBStressWideMergeOperator"; }
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Loaded 100 of 278 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user