Compare commits

...
84 Commits
Author SHA1 Message Date
anand76 986b8b9f20 Update version to 8.8.1 2023-11-17 15:25:29 -08:00
anand76 35b30e4c3d Make CacheWithSecondaryAdapter reservation accounting more robust (#12059)
Summary:
`CacheWithSecondaryAdapter` can distribute placeholder reservations across the primary and secondary caches. The current implementation of the accounting is quite complicated in order to avoid using a mutex. This may cause the accounting to be slightly off after changes to the cache capacity and ratio, resulting in assertion failures. There's also a bug in the unlikely event that the total reservation exceeds the cache capacity. Furthermore, the current implementation is difficult to reason about.

This PR simplifies it by doing the accounting while holding a mutex. The reservations are processed in 1MB chunks in order to avoid taking a lock too frequently. As a side effect, this also removes the restriction of not allowing to increase the compressed secondary cache capacity after decreasing it to 0.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/12059

Test Plan: Existing unit tests, and a new test for capacity increase from 0

Reviewed By: pdillinger

Differential Revision: D51278686

Pulled By: anand1976

fbshipit-source-id: 7e1ad2c50694772997072dd59cab35c93c12ba4f
2023-11-17 12:55:45 -08:00
Yu Zhang 4d706dc4db Update HISTORY.md 2023-10-31 22:03:03 -07:00
Yu Zhang b375f596ec Rate limiting stale sst files' deletion during recovery (#12016)
Summary:
As titled. If SstFileManager is available, deleting stale sst files will be delegated to it so it can be rate limited.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/12016

Reviewed By: hx235

Differential Revision: D50670482

Pulled By: jowlyzhang

fbshipit-source-id: bde5b76ea1d98e67f6b4f08bfba3db48e46aab4e
2023-10-31 21:59:19 -07:00
Adam Retter 54bb0183e0 Fix the ZStd checksum (#12005)
Summary:
Somehow we had the wrong checksum when validating the ZStd 1.5.5 download for RocksJava in the previous Pull Request - https://github.com/facebook/rocksdb/pull/9304. This PR fixes that.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/12005

Reviewed By: jaykorean

Differential Revision: D50840338

Pulled By: cbi42

fbshipit-source-id: 8a92779d3bef013d812eecb89aaaf33fc73991ec
2023-10-31 18:26:24 -07:00
anand76 8d08b843eb Update HISTORY.md 2023-10-27 12:41:43 -07:00
anand76 8767167425 Add APIs to query secondary cache capacity and usage for TieredCache (#12011)
Summary:
In `TieredCache`, the underlying compressed secondary cache is hidden from the user. So we need a way to query the capacity, as well as the portion of cache reservation charged to the compressed secondary cache.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/12011

Test Plan: Update the unit tests

Reviewed By: akankshamahajan15

Differential Revision: D50651943

Pulled By: anand1976

fbshipit-source-id: 06d1cb5edb75a790c919bce718e2ff65f5908220
2023-10-27 12:40:37 -07:00
Yu Zhang 0d13952bcc Remove extra semicolon (#12017)
Summary:
As titled.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/12017

Reviewed By: hx235

Differential Revision: D50670406

Pulled By: jowlyzhang

fbshipit-source-id: 28b3acd930ee676d78ebb47144047ce233fc11c5
2023-10-25 17:52:23 -07:00
Peter Dillinger b5fed4a509 Fix windows build errors (rdtsc and fnptr) (#12008)
Summary:
Combining best parts of https://github.com/facebook/rocksdb/issues/11794 and https://github.com/facebook/rocksdb/issues/11766, fixing the CircleCI config in the latter. I was going to amend the latter but wasn't granted access.

Ideally this would be ported back to 8.4 branch and crc32c part into 8.3 branch.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/12008

Test Plan: CI

Reviewed By: hx235

Differential Revision: D50616172

Pulled By: pdillinger

fbshipit-source-id: fa7f778bc281e881a140522e774f480c6d1e5f48
2023-10-25 09:35:38 -07:00
Hui Xiao 366a61aee6 Update 8.8.fb release note 2023-10-23 20:20:34 -07:00
Peter Dillinger 519f2a41fb Add cache_bench to buck build (#11990)
Summary:
as title

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11990

Test Plan: buck build in fbcode

Reviewed By: hx235

Differential Revision: D50502851

Pulled By: pdillinger

fbshipit-source-id: b046e4d8b90f1496e5a134faf2b936dec10922de
2023-10-23 15:12:36 -07:00
anand76 e81393e81e Add some stats to observe the usefulness of scan prefetching (#11981)
Summary:
Add stats for better observability of scan prefetching. Its only implemented for sync scan right now. These stats can help inform future improvements in scan prefetching.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11981

Test Plan: Add a new unit test

Reviewed By: akankshamahajan15

Differential Revision: D50516505

Pulled By: anand1976

fbshipit-source-id: cb1cc6cf02df8295930a49c62b11870020df3f97
2023-10-23 14:42:44 -07:00
Peter Dillinger 4d9f9733b2 Disable AutoHCC in crash test (#12000)
Summary:
... until I can reproduce and resolve assertion failures (mostly in PurgeImplLocked) seen in crash test.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/12000

Test Plan: make blackbox_crash_test

Reviewed By: hx235

Differential Revision: D50565984

Pulled By: pdillinger

fbshipit-source-id: 5eea1638ff2683c41b4f65ee1ffc2398071911e7
2023-10-23 12:23:13 -07:00
Peter Dillinger 4155087746 Use manifest to persist pre-allocated seqnos (#11995)
Summary:
... and other fixes for crash test after https://github.com/facebook/rocksdb/issues/11922.
* When pre-allocating sequence numbers for establishing a time history, record that last sequence number in the manifest so that it is (most likely) restored on recovery even if no user writes were made or were recovered (e.g. no WAL).
* When pre-allocating sequence numbers for establishing a time history, only do this for actually new DBs.
* Remove the feature that ensures non-zero sequence number on creating the first column family with preserve/preclude option after initial DB::Open. Until fixed in a way compatible with the crash test, this creates a gap where some data written with active preserve/preclude option won't have a known associated time.

Together, these ensure we don't upset the crash test by manipulating sequence numbers after initial DB creation (esp when re-opening with different options). (The crash test expects that the seqno after re-open corresponds to a known point in time from previous crash test operation, matching an expected DB state.)

Follow-up work:
* Re-fill the gap to ensure all data written under preserve/preclude settings have a known time estimate.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11995

Test Plan:
Added to unit test SeqnoTimeTablePropTest.PrePopulateInDB

Verified fixes two crash test scenarios:
## 1st reproducer
First apply
```
 diff --git a/db_stress_tool/expected_state.cc b/db_stress_tool/expected_state.cc
index b483e154c..ef63b8d6c 100644
 --- a/db_stress_tool/expected_state.cc
+++ b/db_stress_tool/expected_state.cc
@@ -333,6 +333,7 @@ Status FileExpectedStateManager::SaveAtAndAfter(DB* db) {
     s = NewFileTraceWriter(Env::Default(), soptions, trace_file_path,
                            &trace_writer);
   }
+  if (getenv("CRASH")) assert(false);
   if (s.ok()) {
     TraceOptions trace_opts;
     trace_opts.filter |= kTraceFilterGet;
```

Then
```
mkdir -p /dev/shm/rocksdb_test/rocksdb_crashtest_expected
mkdir -p /dev/shm/rocksdb_test/rocksdb_crashtest_whitebox
rm -rf /dev/shm/rocksdb_test/rocksdb_crashtest_*/*
CRASH=1 ./db_stress --db=/dev/shm/rocksdb_test/rocksdb_crashtest_whitebox --expected_values_dir=/dev/shm/rocksdb_test/rocksdb_crashtest_expected --destroy_db_initially=1 --manual_wal_flush_one_in=1000000 --clear_column_family_one_in=0 --preserve_internal_time_seconds=36000
./db_stress --db=/dev/shm/rocksdb_test/rocksdb_crashtest_whitebox --expected_values_dir=/dev/shm/rocksdb_test/rocksdb_crashtest_expected --destroy_db_initially=0 --manual_wal_flush_one_in=1000000 --clear_column_family_one_in=0 --preserve_internal_time_seconds=0
```

Without the fix you get
```
...
DB path: [/dev/shm/rocksdb_test/rocksdb_crashtest_whitebox]
(Re-)verified 34 unique IDs
Error restoring historical expected values: Corruption: DB is older than any restorable expected state
```

## 2nd reproducer
First apply
```
 diff --git a/db_stress_tool/db_stress_test_base.cc b/db_stress_tool/db_stress_test_base.cc
index 62ddead7b..f2654980f 100644
 --- a/db_stress_tool/db_stress_test_base.cc
+++ b/db_stress_tool/db_stress_test_base.cc
@@ -1126,6 +1126,7 @@ void StressTest::OperateDb(ThreadState* thread) {
         // OPERATION write
         TestPut(thread, write_opts, read_opts, rand_column_families, rand_keys,
                 value);
+        if (getenv("CRASH")) assert(false);
       } else if (prob_op < del_bound) {
         assert(write_bound <= prob_op);
         // OPERATION delete
```

Then
```
rm -rf /dev/shm/rocksdb_test/rocksdb_crashtest_*/*
CRASH=1 ./db_stress --db=/dev/shm/rocksdb_test/rocksdb_crashtest_whitebox --expected_values_dir=/dev/shm/rocksdb_test/rocksdb_crashtest_expected --destroy_db_initially=1 --manual_wal_flush_one_in=1000000 --clear_column_family_one_in=0 --disable_wal=1 --reopen=0 --preserve_internal_time_seconds=0
./db_stress --db=/dev/shm/rocksdb_test/rocksdb_crashtest_whitebox --expected_values_dir=/dev/shm/rocksdb_test/rocksdb_crashtest_expected --destroy_db_initially=0 --manual_wal_flush_one_in=1000000 --clear_column_family_one_in=0 --disable_wal=1 --reopen=0 --preserve_internal_time_seconds=3600
```

Without the fix you get
```
DB path: [/dev/shm/rocksdb_test/rocksdb_crashtest_whitebox]
(Re-)verified 34 unique IDs
db_stress: db_stress_tool/expected_state.cc:380: virtual rocksdb::{anonymous}::ExpectedStateTraceRecordHandler::~
ExpectedStateTraceRecordHandler(): Assertion `IsDone()' failed.
```

Reviewed By: jowlyzhang

Differential Revision: D50533346

Pulled By: pdillinger

fbshipit-source-id: 1056be45c5b9e537c8c601b28c4b27431a782477
2023-10-23 09:20:59 -07:00
rogertyang 543191f2ea Add bounds checking to WBWIIteratorImpl and respect bounds of ReadOptions in Transaction (#11680)
Summary:
Fix https://github.com/facebook/rocksdb/issues/11607
Fix https://github.com/facebook/rocksdb/issues/11679
Fix https://github.com/facebook/rocksdb/issues/11606
Fix https://github.com/facebook/rocksdb/issues/2343

Add bounds checking to `WBWIIteratorImpl`, which will be reflected in `BaseDeltaIterator::delta_iterator_::Valid()`, just like `BaseDeltaIterator::base_iterator_::Valid()`. In this way, the two sub itertors become more aligned from `BaseDeltaIterator`'s perspective. Like `DBIter`, the added bounds checking caps in either bound when seeking and disvalidates the `WBWIIteratorImpl` iterator when the lower bound is past or the upper bound is reached.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11680

Test Plan:
- A simple test added to write_batch_with_index_test.cc to exercise the bounds checking in `WBWIIteratorImpl`.
- A sophisticated test added to transaction_test.cc to assert that `Transaction` with different write policies honor bounds in `ReadOptions`. It should be so as long as the  `BaseDeltaIterator` is correctly coordinating the two sub iterators to perform iterating and bounds checking.

Reviewed By: ajkr

Differential Revision: D48125229

Pulled By: cbi42

fbshipit-source-id: c9acea52595aed1471a63d7ca6ef15d2a2af1367
2023-10-20 13:28:28 -07:00
Adam Retter d7567d5eee Update libs for RocksJava Static build (#9304)
Summary:
Updates ZStd and Snappy to the latest versions.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/9304

Reviewed By: ajkr

Differential Revision: D33176708

Pulled By: cbi42

fbshipit-source-id: eb50db50557c433e19fcc7c2874329d1d6cba93f
2023-10-20 10:38:27 -07:00
Peter Dillinger ef0c3f08fa Fix rare destructor bug in AutoHCC (#11988)
Summary:
and some other small enhancements/fixes:
* The main bug fixed is that in some rare cases, the "published" table size might be smaller than the actual table size. This is a transient state that can happen with concurrent growth that is normally fixed after enough insertions, but if the cache is destroyed soon enough after growth, it could fail to fully destroy some entries and cause assertion failures. We can fix this by detecting the true table size in the destructor.
* Change the "too many iterations" debug threshold from 512 to 768. We might have hit at least one false positive failure. (Failed despite legitimate operation.)
* Added some stronger assertions in some places to aid in debugging.
* Use COERCE_CONTEXT_SWITCH to make behavior of Grow less predictable in terms of thread interleaving. (Might add in more places.) This was useful in reproducing the destructor bug.
* Fix some comments with typos or that were based on earlier revisions of the code.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11988

Test Plan:
Variants of this bug-finding command:
```
USE_CLANG=1 COMPILE_WITH_ASAN=1 COMPILE_WITH_UBSAN=1 COERCE_CONTEXT_SWITCH=1 DEBUG_LEVEL=2 make -j32 cache_bench && while ROCKSDB_DEBUG=1 ./cache_bench -cache_type=auto_hyper_clock_cache -histograms=0 -cache_size=80000000 -threads=32 -populate_cache=0 -ops_per_thread=1000 -num_shard_bits=0; do :; done
```

Reviewed By: jowlyzhang

Differential Revision: D50470318

Pulled By: pdillinger

fbshipit-source-id: d407a8bb0b6d2ddc598a954c319a1640136f12f2
2023-10-19 14:51:22 -07:00
Jay Huh 2e514e4b4a Fix copyright header (#11986)
Summary:
Add missing copyright header

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11986

Test Plan: N/A

Reviewed By: hx235

Differential Revision: D50461904

Pulled By: jaykorean

fbshipit-source-id: b1b2704890f4a0bb3c9b464b01468e85a5365a8e
2023-10-19 10:30:54 -07:00
Hui Xiao 0836a2b26d New tickers on deletion compactions grouped by reasons (#11957)
Summary:
Context/Summary: as titled

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11957

Test Plan: piggyback on existing tests; fixed a failed test due to adding new stats

Reviewed By: ajkr, cbi42

Differential Revision: D50294310

Pulled By: hx235

fbshipit-source-id: d99b97ebac41efc1bdeaf9ca7a1debd2927d54cd
2023-10-18 18:00:07 -07:00
Radek Hubner 55590012ae Add RocksJava tests to CMake (#11756)
Summary:
Refactor CMake build to allow run java tests via CMake, including Windows.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11756

Reviewed By: hx235

Differential Revision: D50370739

Pulled By: akankshamahajan15

fbshipit-source-id: ae05cc08a0f9bb2a0a4f1ece02c523fb465bb817
2023-10-18 12:51:50 -07:00
Radek Hubner a80e3f6c57 Add keyExists Java API (#11705)
Summary:
Add a new method to check if a key exists in the database. It avoids copying data between C++ and Java code.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11705

Reviewed By: ajkr

Differential Revision: D50370934

Pulled By: akankshamahajan15

fbshipit-source-id: ab2d42213fbebcaff919b0ffbbef9d45e88ca365
2023-10-18 12:46:35 -07:00
Radek Hubner 0bb3a26d89 Lazy load native library in Statistics constructor. (#11953)
Summary:
Should fix https://github.com/facebook/rocksdb/issues/9667 and follow the same appropoach as https://github.com/facebook/rocksdb/issues/11919

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11953

Reviewed By: hx235

Differential Revision: D50307456

Pulled By: ajkr

fbshipit-source-id: 43e7e671e8b04875185b38284cefd4c3e11981fa
2023-10-18 11:01:04 -07:00
Changyu Bi d5bc30befa Enforce status checking after Valid() returns false for IteratorWrapper (#11975)
Summary:
... when compiled with ASSERT_STATUS_CHECKED = 1.

The main change is in iterator_wrapper.h. The remaining changes are just fixing existing unit tests. Adding this check to IteratorWrapper gives a good coverage as the class is used in many places, including child iterators under merging iterator, merging iterator under DB iter, file_iter under level iterator, etc. This change can catch the bug fixed in https://github.com/facebook/rocksdb/issues/11782.

Future follow up: enable `ASSERT_STATUS_CHECKED=1` for stress test and for DEBUG_LEVEL=0.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11975

Test Plan:
* `ASSERT_STATUS_CHECKED=1 DEBUG_LEVEL=2 make -j32 J=32 check`
* I tried to run stress test with `ASSERT_STATUS_CHECKED=1`, but there are a lot of existing stress code that ignore status checking, and fail without the change in this PR. So defer that to a follow up task.

Reviewed By: ajkr

Differential Revision: D50383790

Pulled By: cbi42

fbshipit-source-id: 1a28ce0f5fdf1890f93400b26b3b1b3a287624ce
2023-10-18 09:38:38 -07:00
Yu Zhang 42266939ab Remove documentation that marks user-defined timestamps feature as experimental (#11974)
Summary:
As titled. The most notable place that marks the feature as experimental is its wiki page. That was updated. And this PR removes the experimental marker from a few places for this feature.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11974

Reviewed By: ltamasi

Differential Revision: D50383640

Pulled By: jowlyzhang

fbshipit-source-id: 0bfe26ceda0793515f54b602cf3cd13d0737ec25
2023-10-17 15:25:40 -07:00
Yu Zhang 933ee295f4 Fix a race condition between recovery and backup (#11955)
Summary:
A race condition between recovery and backup can happen with error messages like this:
```Failure in BackupEngine::CreateNewBackup with: IO error: No such file or directory: While opening a file for sequentially reading: /dev/shm/rocksdb_test/rocksdb_crashtest_whitebox/002653.log: No such file or directory```

PR https://github.com/facebook/rocksdb/issues/6949  introduced disabling file deletion during error handling of manifest IO errors. Aformentioned race condition is caused by this chain of event:

[Backup engine]     disable file deletion
[Recovery]              disable file deletion <= this is optional for the race condition, it may or may not get called
[Backup engine]     get list of file to copy/link
[Recovery]              force enable file deletion
....                            some files refered by backup engine get deleted
[Backup engine]    copy/link file <= error no file found

This PR fixes this with:
1) Recovery thread is currently forcing enabling file deletion as long as file deletion is disabled. Regardless of whether the previous error handling is for manifest IO error and that disabled it in the first place. This means it could incorrectly enabling file deletions intended by other threads like backup threads, file snapshotting threads. This PR does this check explicitly before making the call.

2) `disable_delete_obsolete_files_` is designed as a counter to allow different threads to enable and disable file deletion separately. The recovery thread currently does a force enable file deletion, because `ErrorHandler::SetBGError()` can be called multiple times by different threads when they receive a manifest IO error(details per PR https://github.com/facebook/rocksdb/issues/6949), resulting in `DBImpl::DisableFileDeletions` to be called multiple times too. Making a force enable file deletion call that resets the counter `disable_delete_obsolete_files_` to zero is a workaround for this. However, as it shows in the race condition, it can incorrectly suppress other threads like a backup thread's intention to keep the file deletion disabled. <strike>This PR adds a `std::atomic<int> disable_file_deletion_count_` to the error handler to track the needed counter decrease more precisely</strike>. This PR tracks and caps file deletion enabling/disabling in error handler.

3) for recovery, the section to find obsolete files and purge them was moved to be done after the attempt to enable file deletion. The actual finding and purging is more likely to happen if file deletion was previously disabled and get re-enabled now. An internal function `DBImpl::EnableFileDeletionsWithLock` was added to support change 2) and 3). Some useful logging was explicitly added to keep those log messages around.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11955

Test Plan: existing unit tests

Reviewed By: anand1976

Differential Revision: D50290592

Pulled By: jowlyzhang

fbshipit-source-id: 73aa8331ca4d636955a5b0324b1e104a26e00c9b
2023-10-17 13:18:04 -07:00
akankshamahajan 9135a61ec6 Fix corruption error in stress test for auto_readahead_size enabled (#11961)
Summary:
Fix corruption error - "Corruption: first key in index doesn't match first key in block". when auto_readahead_size is enabled. Error is because of bug when index_iter_ moves forward, first_internal_key of that index_iter_ is not copied. So the Slice points to a different key resulting in wrong comparison when doing comparison.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11961

Test Plan: Ran stress test which reproduced this error.

Reviewed By: anand1976

Differential Revision: D50310589

Pulled By: akankshamahajan15

fbshipit-source-id: 95d8320b8388f1e3822c32024f84754f3a20a631
2023-10-17 12:21:08 -07:00
Alan Paxton 2296c624fa Perform java static checks in CI (#11221)
Summary:
Integrate pmd on the Java API to catch and report common Java coding problems; fix or suppress a basic set of PMD checks.

Link pmd into java build / CI

Add a pmd dependency to maven
Add a jpmd target to Makefile which runs pmd
Add a workflow to Circle CI which runs pmd
Configure an initial default pmd for CI

Repair lots of very simple PMD reports generated when we apply pmd-rules.xml

Repair or exception for PMD rules in the CloseResource category, which finds unclosed AutoCloseable resources.
We special-case the configuration of CloseResource and use the // NOPMD comment in source the avoid reports where we are the API creating an AutoCloseable, and therefore returning an unclosed resource is correct behaviour.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11221

Reviewed By: akankshamahajan15

Differential Revision: D50369930

Pulled By: jowlyzhang

fbshipit-source-id: a41c36b44b3bab7644df3e9cc16afbdf33b84f6b
2023-10-17 10:04:35 -07:00
anand76 84af7cf0bd Sanitize db_stress arguments when secondary_cache_uri is not empty (#11967)
Summary:
When `secondary_cache_uri` is non-empty and the `cache_type` is not a tiered cache, then sanitize `compressed_secondary_cache_size` to 0.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11967

Test Plan: Run crash test

Reviewed By: akankshamahajan15

Differential Revision: D50346157

Pulled By: anand1976

fbshipit-source-id: 57bcbad2ec81fa736f1539a0a41ed6854ded2077
2023-10-16 17:28:36 -07:00
Akanksha Mahajan 018eede679 Remove assertion from PrefetchAsync (#11965)
Summary:
Remove assertion from PrefetchAsync (roundup_len2 >= alignment) as for non direct_io, buffer size can be less than alignment resulting in assertion.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11965

Test Plan: Ran the issue causing db_stress without this assertion and the verification completes successfully.

Reviewed By: anand1976

Differential Revision: D50328955

Pulled By: akankshamahajan15

fbshipit-source-id: 65f55ca230d2bbc63f4e2cc34c7273b22b515879
2023-10-16 15:14:58 -07:00
Hui Xiao 25d4379cc8 Make rate limiter single burst bytes runtime changeable (#11923)
Summary:
Context/Summary: as titled

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11923

Test Plan: new UT

Reviewed By: ajkr

Differential Revision: D49941161

Pulled By: hx235

fbshipit-source-id: f75a4d07f3cdd86863ea22c57f2bcd3a621baaf3
2023-10-16 10:21:35 -07:00
Peter Dillinger 2fd850c7eb Remove write queue synchronization from WriteOptionsFile (#11951)
Summary:
This has become obsolete with the new `options_mutex_` in https://github.com/facebook/rocksdb/pull/11929

* Remove now-unnecessary parameter from WriteOptionsFile
* Rename (and negate) other parameter for better clarity (the caller shouldn't tell the callee what the callee needs, just what the caller knows, provides, and requests)
* Move a ROCKS_LOG_WARN (I/O) in WriteOptionsFile to outside of holding DB mutex.
* Also *avoid* (but not always eliminate) write queue synchronization in SetDBOptions. Still needed if there was a change to WAL size limit or other configuration.
* Improve some comments

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11951

Test Plan: existing unit tests and TSAN crash test local run

Reviewed By: ajkr

Differential Revision: D50247904

Pulled By: pdillinger

fbshipit-source-id: 7dfe445c705ec013886a2adb7c50abe50d83af69
2023-10-16 08:58:47 -07:00
Changyu Bi 9ded9f789f Fix db_stress FaultInjectionTestFS set up before DB open (#11958)
Summary:
We saw frequent stress test failures with error messages like:
```
Verification failed for column family 0 key ...: value_from_db: , value_from_expected: ..., msg: GetEntity verification: Value not found: NotFound:
```
One cause for this is that data in WAL is lost after a crash. We initialize FaultInjectionTestFS to be not direct writable when write_fault_injection is enabled (see code change). This can cause the first WAL created during DB open to be lost if a db_stress is killed before the first WAL is synced. This PR initializes FaultInjectionTestFS to be direct writable. Note that FaultInjectionTestFS will be configured propertly for write fault injection after DB open in `RunStressTestImpl()`. So this change should not affect write fault injection coverage.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11958

Test Plan:
a repro for the above bug:
```
Simulate crash before first WAL is sealed:

 --- a/db_stress_tool/db_stress_driver.cc
+++ b/db_stress_tool/db_stress_driver.cc
@@ -256,6 +256,7 @@ bool RunStressTestImpl(SharedState* shared) {
     fprintf(stderr, "Verification failed :(\n");
     return false;
   }
+  exit(1);
   return true;
 }

./db_stress --clear_column_family_one_in=0 --column_families=1 --preserve_internal_time_seconds=60 --destroy_db_initially=0 --db=/dev/shm/rocksdb_crashtest_blackbox --db_write_buffer_size=2097152 --destroy_db_initially=0 --expected_values_dir=/dev/shm/rocksdb_crashtest_expected --reopen=0 --test_batches_snapshots=0 --threads=1 --ops_per_thread=100 --write_fault_one_in=1000 --sync_fault_injection=0

./db_stress_main  --clear_column_family_one_in=0 --column_families=1 --preserve_internal_time_seconds=60 --destroy_db_initially=0 --db=/dev/shm/rocksdb_crashtest_blackbox --db_write_buffer_size=2097152 --destroy_db_initially=0 --expected_values_dir=/dev/shm/rocksdb_crashtest_expected --reopen=0 --test_batches_snapshots=0 --sync_fault_injection=1
```

Reviewed By: akankshamahajan15

Differential Revision: D50300347

Pulled By: cbi42

fbshipit-source-id: 3a4881d72197f5ece82364382a0100912e16c2d6
2023-10-14 13:33:55 -07:00
Changyu Bi f3aef8cad7 Add write operation to tracer only after successful callback (#11954)
Summary:
We saw optimistic transaction stress test failures like the following:
```
Verification failed for column family 0 key 000000000001E9AF000000000000012B00000000000000B5 (12535491): value_from_db: 010000000504070609080B0A0D0C0F0E111013121514171619181B1A1D1C1F1E212023222524272629282B2A2D2C2F2E313033323534373639383B3A3D3C3F3E, value_from_expected: , msg: Iterator verification: Unexpected value found```
```
With ajkr's repro (see test plan), I found that we record duplicated writes to tracer when an optimistic transaction conflict checking fails. This PR fixes it by checking callback status before record a write operation to tracer.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11954

Test Plan:
this reproduces the failure consistently
```
#!/bin/bash
db=/dev/shm/rocksdb_crashtest_blackbox exp=/dev/shm/rocksdb_crashtest_expected
rm -rf $db $exp && mkdir -p $exp && while ./db_stress \
        --atomic_flush=1 \
        --clear_column_family_one_in=0 \
        --db=$db \
        --db_write_buffer_size=2097152 \
        --delpercent=0 \
        --delrangepercent=0 \
        --destroy_db_initially=0 \
        --disable_wal=1 \
        --expected_values_dir=$exp \
        --iterpercent=0 \
        --max_bytes_for_level_base=2097152 \
        --max_key=250000 \
        --memtable_prefix_bloom_size_ratio=0.5 \
        --memtable_whole_key_filtering=1 \
        --occ_lock_bucket_count=100 \
        --occ_validation_policy=0 \
        --ops_per_thread=10 \
        --prefixpercent=0 \
        --readpercent=0 \
        --reopen=0 \
        --target_file_size_base=524288 \
        --test_batches_snapshots=0 \
        --use_optimistic_txn=1 \
        --use_txn=1 \
        --value_size_mult=32 \
        --write_buffer_size=524288 \
        --writepercent=100 ; do : ; done
```

Reviewed By: akankshamahajan15

Differential Revision: D50284976

Pulled By: cbi42

fbshipit-source-id: 793e3cee186c8b4f406b29166efd8d9028695206
2023-10-14 12:00:31 -07:00
Changyu Bi 50b0879d50 Do not fail stress test when file ingestion return injected error (#11956)
Summary:
Currently, if file ingestion hit injected error, stress test is considered failed since it prints a message to stderr containing the keyword "error" and db_crashtest.py looks for it in stderr. This PR fixes it by print injected error to stdout.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11956

Test Plan: Check future stress test runs.

Reviewed By: akankshamahajan15

Differential Revision: D50293537

Pulled By: cbi42

fbshipit-source-id: e74915b1b3c6876a61ab6933c4529780362ec02b
2023-10-14 10:08:03 -07:00
Jay Huh c9d8e6a5bf AttributeGroups - MultiGetEntity Implementation (#11925)
Summary:
Introducing the notion of AttributeGroup by adding the `MultiGetEntity()` API retrieving `PinnableAttributeGroups`.
An "attribute group" refers to a logical grouping of wide-column entities within RocksDB. These attribute groups are implemented using column families.

Users can store WideColumns in different CFs for various reasons (e.g. similar access patterns, same types, etc.). This new API `MultiGetEntity()` takes keys and `PinnableAttributeGroups` per key. `PinnableAttributeGroups` is just a list of `PinnableAttributeGroup`s in which we have `ColumnFamilyHandle*`, `Status`, and `PinnableWideColumns`.

Let's say a user stored "hot" wide columns in column family "hot_data_cf" and "cold" wide columns in column family "cold_data_cf" and all other columns in "common_cf".

Prior to this PR, if the user wants to query for two keys, "key_1" and "key_2" and but only interested in "common_cf" and "hot_data_cf" for "key_1", and "common_cf" and "cold_data_cf" for "key_2", the user would have to construct input like `keys = ["key_1", "key_1", "key_2", "key_2"]`, `column_families = ["common_cf", "hot_data_cf", "common_cf", "cold_data_cf"]` and get the flat list of `PinnableWideColumns` to find the corresponding <key,CF> combo.

With the new `MultiGetEntity()` introduced in this PR, users can now query only `["common_cf", "hot_data_cf"]` for `"key_1"`, and only `["common_cf", "cold_data_cf"]` for `"key_2"`. The user will get `PinnableAttributeGroups` for each key, and `PinnableAttributeGroups` gives a list of `PinnableAttributeGroup`s where the user can find column family and corresponding `PinnableWideColumns` and the `Status`.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11925

Test Plan:
- `DBWideBasicTest::MultiCFMultiGetEntityAsPinnableAttributeGroups` added

will enable this new API in the `db_stress` in a separate PR

Reviewed By: ltamasi

Differential Revision: D50017414

Pulled By: jaykorean

fbshipit-source-id: 643611d1273c574bc81b94c6f5aeea24b40c4586
2023-10-13 15:58:03 -07:00
Peter Dillinger dc576af0fd AutoHCC - fix a rare loop condition in Lookup (#11948)
Summary:
Saw this in stress test:
```
db_stress: cache/clock_cache.cc:3152:[...] Assertion `i < 0x2000' failed.
```

The problem is related to Lookups on a chain currently involved in a Grow operation. To avoid Lookup waiting on Grow, Lookup is able to walk a chain whose first part is already migrated and tail is not yet migrated, so is mixed with entries with a different destination home (according to `home_shift`) than what we're looking for. This is fine until we save one of these entries as a safe point in the chain to backtrack to (`read_ref_on_chain`) in case of concurrent modification and end up backtracking to it. In that case, we can get stuck on the wrong destination chain and keep trying to backtrack to an entry that is supposed to be on the correct chain but is not (anymore).

For some reason I haven't quite worked out, I believe it's usually able to recover after some 1000+ looop iterations, so reproducibility depends on the threshold at which we consider a Lookup loop to be too many iterations for a plausibly valid Lookup.

Detecting and working around this case is relatively simple. We can (and must) keep going on the chain but ensure we don't save it as a safe entry to backtrack to.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11948

Test Plan:
The problem could be reproduced in a few minutes with this (debug build):
```
$ while ./cache_bench -cache_type=auto_hyper_clock_cache -histograms=0 -cache_size=80000000 -threads=32 -populate_cache=0 -ops_per_thread=10000 -degenerate_hash_bits=6 -num_shard_bits=0; do :; done
```

At least with a lower threshold on suspiciously high number of iterations. I've lowered the thresholds quite a bit and no longer able to reproduce a failure.

Reviewed By: jowlyzhang

Differential Revision: D50236574

Pulled By: pdillinger

fbshipit-source-id: 2cb54a4e02bb51d5933eea41fcd489ab9d34aa96
2023-10-13 09:52:33 -07:00
Changyu Bi 6e3429b8a6 Fix data race in accessing recovery_in_prog_ (#11950)
Summary:
We saw the following TSAN stress test failure:
```
WARNING: ThreadSanitizer: data race (pid=17523)
  Write of size 1 at 0x7b8c000008b9 by thread T4243 (mutexes: write M0):
    #0 rocksdb::ErrorHandler::RecoverFromRetryableBGIOError() fbcode/internal_repo_rocksdb/repo/db/error_handler.cc:742 (db_stress+0x95f954) (BuildId: 35795dfb86ddc9c4f20ddf08a491f24d)
    https://github.com/facebook/rocksdb/issues/1 std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (rocksdb::ErrorHandler::*)(), rocksdb::ErrorHandler*>>>::_M_run() fbcode/third-party-buck/platform010/build/libgcc/include/c++/trunk/bits/invoke.h:74 (db_stress+0x95fc2b) (BuildId: 35795dfb86ddc9c4f20ddf08a491f24d)
    https://github.com/facebook/rocksdb/issues/2 execute_native_thread_routine /home/engshare/third-party2/libgcc/11.x/src/gcc-11.x/x86_64-facebook-linux/libstdc++-v3/src/c++11/../../../.././libstdc++-v3/src/c++11/thread.cc:82:18 (libstdc++.so.6+0xdf4e4) (BuildId: 452d1cdae868baeeb2fdf1ab140f1c219bf50c6e)

  Previous read of size 1 at 0x7b8c000008b9 by thread T22:
    #0 rocksdb::DBImpl::SyncClosedLogs(rocksdb::JobContext*, rocksdb::VersionEdit*) fbcode/internal_repo_rocksdb/repo/db/error_handler.h:76 (db_stress+0x84f69c) (BuildId: 35795dfb86ddc9c4f20ddf08a491f24d)
```

This is due to a data race in accessing `recovery_in_prog_`. This PR fixes it by accessing `recovery_in_prog_` under db mutex before calling `SyncClosedLogs()`. I think the original PR https://github.com/facebook/rocksdb/pull/10489 intended to clear the error if it's a recovery flush. So ideally we can also just check flush reason. I plan to keep a safer change in this PR and make that change in the future if needed.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11950

Test Plan: check future TSAN stress test results.

Reviewed By: anand1976

Differential Revision: D50242255

Pulled By: cbi42

fbshipit-source-id: 0d487948ef9546b038a34460f3bb037f6e5bfc58
2023-10-12 16:55:25 -07:00
Levi Tamasi 261e9be7b3 Resolve BaseDeltaIterator's value in UpdateCurrent (#11947)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/11947

The patch is a small refactoring of `BaseDeltaIterator`: instead of determining the iterator's value during the `value()` call, it is resolved up front in `UpdateCurrent()`. This has multiple benefits: the value is now computed only once even if `value()` is called multiple times for the same iterator position (note that with the previous code, merges for example would get performed multiple times in this case), it makes it possible to remove the `mutable` modifiers from the `status_` and `merge_result_` members, and it also serves as groundwork for adding wide-column support to `WriteBatchWithIndex`.

Reviewed By: jaykorean

Differential Revision: D50236117

fbshipit-source-id: ae3d05863f811e9bac4c09edc49eca5f37e072a5
2023-10-12 16:22:49 -07:00
Changyu Bi 648fe25bc0 Always clear files marked for compaction in ComputeCompactionScore() (#11946)
Summary:
We were seeing the following stress test failures:
```LevelCompactionBuilder::PickFileToCompact(const rocksdb::autovector<std::pair<int, rocksdb::FileMetaData*> >&, bool): Assertion `!level_file.second->being_compacted' failed```

This can happen when we are picking a file to be compacted from some files marked for compaction, but that file is already being_compacted. We prevent this by always calling `ComputeCompactionScore()` after we pick a compaction and mark some files as being_compacted. However, if SetOptions() is called to disable marking certain files to be compacted, say `enable_blob_garbage_collection`, we currently just skip the relevant logic in `ComputeCompactionScore()` without clearing the existing files already marked for compaction. This PR fixes this issue by already clearing these files.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11946

Test Plan: existing tests.

Reviewed By: akankshamahajan15

Differential Revision: D50232608

Pulled By: cbi42

fbshipit-source-id: 11e4fb5e9d48b0f946ad33b18f7c005f0161f496
2023-10-12 15:26:10 -07:00
anand76 90e160733e Fix runtime error in UpdateTieredCache due to integer underflow (#11949)
Summary:
With the introduction of the `UpdateTieredCache` API, its possible to dynamically change the compressed secondary cache ratio of the total cache capacity. In order to optimize performance, we avoid using a mutex when inserting/releasing placeholder entries, which can result in some inaccuracy in the accounting during the dynamic update. This inaccuracy was causing a runtime error due to an integer underflow in `UpdateCacheReservationRatio`, causing ubsan crash tests to fail. This PR fixes it by explicitly checking for the underflow.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11949

Test Plan:
1. Added a unit test that fails without the fix
2. Run ubsan_crash

Reviewed By: akankshamahajan15

Differential Revision: D50240217

Pulled By: anand1976

fbshipit-source-id: d2f7b79da54eec8b61aec2cc1f2943da5d5847ac
2023-10-12 15:09:40 -07:00
Peter Dillinger d010b02e86 Fix race in options taking effect (#11929)
Summary:
In follow-up to https://github.com/facebook/rocksdb/issues/11922, fix a race in functions like CreateColumnFamily and SetDBOptions where the DB reports one option setting but a different one is left in effect.

To fix, we can add an extra mutex around these rare operations. We don't want to hold the DB mutex during I/O or other slow things because of the many purposes it serves, but a mutex more limited to these cases should be fine.

I believe this would fix a write-write race in https://github.com/facebook/rocksdb/issues/10079 but not the read-write race.

Intended follow-up to this:
* Should be able to remove write thread synchronization from DBImpl::WriteOptionsFile

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11929

Test Plan:
Added two mini-stress style regression tests that fail with >1% probability before this change:
DBOptionsTest::SetStatsDumpPeriodSecRace
ColumnFamilyTest::CreateAndDropPeriodicRace

I haven't reproduced such an inconsistency between in-memory options and on disk latest options, but this change at least improves safety and adds a test anyway:
DBOptionsTest::SetStatsDumpPeriodSecRace

Reviewed By: ajkr

Differential Revision: D50024506

Pulled By: pdillinger

fbshipit-source-id: 1e99a9ed4d96fdcf3ac5061ec6b3cee78aecdda4
2023-10-12 10:05:23 -07:00
Alan Paxton b2fe14817e java API - load block based table config (#10826)
Summary:
Closes https://github.com/facebook/rocksdb/issues/5297

The BlockBasedTableConfig (or more generally, the TableFormatConfig) of ColumnFamilyOptions, isn't being constructed when column family options are loaded. This happens in `OptionsUtil` which implements the loading.

In `OptionsUtil` we add the method `private native static TableFormatConfig readTableFormatConfig(final long nativeHandle_)` which defers to a JNI method which creates a `TableFormatConfig` (specifically a `BlockBasedTableConfig`) for the supplied `ColumnFamilyOptions`, by copying the table format attached to the C++ column family options. A new Java constructor for `BlockBasedTableConfig` is implemented which is called from C++ with the parameters retrieved from the table format, and then returned to the calling `readTableFormatConfig`.

At the Java side in `OptionsUtil`, the new `TableFormatConfig` is added as the `tableFormatConfig_` field of the `ColumnFamilyOptions`.

To support this, the new class `BlockBasedTableOptionsJni` and associated support methods are added to 'portal.h'.

`BloomFilter.java` has a constructor and field added so that the filter in use can be read back and inspected.

`FilterPolicyType.java` implements an enum (shadowed in C++) to support transfer of filter policy information back to Java from being read at the C++ side.

 Tests written to cover the block based table config, and cleaned up and generalised a bit as some of the methods on OptionsUtil weren't tested; and these had their own unique JNI method variants which in turn were never exercised in test.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/10826

Reviewed By: ajkr

Differential Revision: D50136247

Pulled By: jowlyzhang

fbshipit-source-id: 39387448147abc574e99f43979d89b0900e5f81d
2023-10-12 09:39:01 -07:00
Jay Huh d2daa10afc Fix crash_test_with_best_efforts_recovery (#11938)
Summary:
Thanks ltamasi and ajkr  for initial investigations on the test failure. Per the investigations, the following scenario is likely causing the test to fail.

1. Recovery is needed (could be any reason during crash test)
2. Trying to recover from the latest manifest fails (likely due to read error injection)
3. DB opens with recovery from the next manifest which is different from step 2.
4. Expected state is based on the manifest we tried and failed in step 2.
5. Two manifests used in step 2 and 3 are confirmed to have difference in LSM trees (Thanks ltamasi  again for the finding).

```
2023/10/05-11:24:18.942189 56341 [db/version_set.cc:6079] Trying to recover from manifest: /dev/shm/rocksdb_test/rocksdb_crashtest_blackbox/MANIFEST-007184
...
2023/10/05-11:24:18.978007 56341 [db/version_set.cc:6079] Trying to recover from manifest: /dev/shm/rocksdb_test/rocksdb_crashtest_blackbox/MANIFEST-007180
```
```
[ltamasi@devbig1024.prn1 /tmp/x]$ ldb manifest_dump --hex --path=MANIFEST-007184_renamed_ > 2
[ltamasi@devbig1024.prn1 /tmp/x]$ ldb manifest_dump --hex --path=MANIFEST-007180_renamed_ > 1
[ltamasi@devbig1024.prn1 /tmp/x]$ diff 1 2
 --- 1   2023-10-09 10:29:16.966215207 -0700
+++ 2   2023-10-09 10:29:11.984241645 -0700
@@ -13,7 +13,7 @@
  7174:3950254[1875617 .. 2203952]['000000000003415B000000000000012B000000000000007D' seq:1906214, type:1 .. '000000000003CA59000000000000012B000000000000005C' seq:2039838, type:1]
  7175:88060[2074748 .. 2203892]['000000000003CA6300000000000000CF78787878787878' seq:2167539, type:2 .. '000000000003D08F000000000000012B0000000000000130' seq:2112478, type:0]
 --- level 6 --- version# 1 ---
- 7057:3132633[0 .. 2046144]['0000000000000009000000000000000978' seq:0, type:1 .. '0000000000005F8B000000000000012B00000000000002AC' seq:0, type:1]
+ 7219:2135565[0 .. 2046144]['0000000000000009000000000000000978' seq:0, type:1 .. '0000000000005F8B000000000000012B00000000000002AC' seq:0, type:1]
  7061:827724[0 .. 2046131]['0000000000005F95000000000000000778787878787878' seq:0, type:1 .. '000000000000784F000000000000012B0000000000000113' seq:0, type:1]
  6763:1352[0 .. 0]['000000000000784F000000000000012B0000000000000129' seq:0, type:1 .. '000000000000784F000000000000012B0000000000000129' seq:0, type:1]
  7173:4812291[0 .. 2203957]['000000000000784F000000000000012B0000000000000138' seq:0, type:1 .. '0000000000020FAE787878787878' seq:0, type:1]
@@ -77,4 +77,4 @@
 --- level 61 --- version# 1 ---
 --- level 62 --- version# 1 ---
 --- level 63 --- version# 1 ---
-next_file_number 7182 last_sequence 2203963  prev_log_number 0 max_column_family 0 min_log_number_to_keep 7015
+next_file_number 7221 last_sequence 2203963  prev_log_number 0 max_column_family 0 min_log_number_to_keep 7015
```

We have two options to fix this. Either skip verification against expected state or disable read injection when BE recovery is enabled. I chose to skip verification against expected state per discussion. (See comments in this PR)

Please note that some linter changes were included in this PR.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11938

Test Plan:
```
TEST_TMPDIR=/dev/shm/rocksdb make crash_test_with_best_efforts_recovery
```

Reviewed By: ltamasi

Differential Revision: D50136341

Pulled By: jaykorean

fbshipit-source-id: ac7434d592aebc148bfc3a4fcaa34936f136b95c
2023-10-11 14:26:10 -07:00
anand76 d367b34cc9 Fix TSAN crash test false positive (#11941)
Summary:
Fix the TSAN false positive caused by reading a bool flag without synchronization.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11941

Test Plan: Run tsan crash test locally

Reviewed By: akankshamahajan15

Differential Revision: D50181799

Pulled By: anand1976

fbshipit-source-id: 889e7237e9f3c9452a9df94a0d949db5fe13bb57
2023-10-11 13:28:10 -07:00
Levi Tamasi 5e2906c288 Add missing copyright headers to files added in PR 11805 (#11942)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/11942

Reviewed By: akankshamahajan15

Differential Revision: D50188986

fbshipit-source-id: 56a8e72eac085470824e33f2126d2a6ec3880400
2023-10-11 12:56:39 -07:00
anand76 20b4f1356e Enable write fault injection in db_stress (#11924)
Summary:
This PR depends on https://github.com/facebook/rocksdb/issues/11879 . Enable write fault injection for the basic whitebox, blackbox, and cf_consistency modes. For other test modes like multiops_txn, best_efforts_recovery etc., leave it disabled for now until we can do more testing.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11924

Reviewed By: ajkr

Differential Revision: D50178252

Pulled By: anand1976

fbshipit-source-id: 5794f81c14cded1eb28762b2de818dfff1c1a34c
2023-10-11 11:28:00 -07:00
Andrew Kryczka 4bd5aa4f55 Fix two ErrorHandler race conditions (#11939)
Summary:
1. Prevent a double join on a `port::Thread`
2. Ensure `recovery_in_prog_` and `bg_error_` are both set under same lock hold. This is useful for writers who see a non-OK `bg_error_` and are deciding whether to stall based on whether the error will be auto-recovered.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11939

Reviewed By: cbi42

Differential Revision: D50155484

Pulled By: ajkr

fbshipit-source-id: fbc1f85c50e7eaee27ee0e376aee688d8a06c93b
2023-10-11 09:42:48 -07:00
anand76 5b11f5a3a2 Add TieredCache and compressed cache capacity change to db_stress (#11935)
Summary:
Add `TieredCache` to the cache types tested by db_stress. Also add compressed secondary cache capacity change, and `WriteBufferManager` integration with `TieredCache` for memory charging.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11935

Test Plan: Run whitebox/blackbox crash tests locally

Reviewed By: akankshamahajan15

Differential Revision: D50135365

Pulled By: anand1976

fbshipit-source-id: 7d73ed00c00a0953d86e49f35cce6bd550ba00f1
2023-10-10 13:12:18 -07:00
Radek Hubner 98ab2d80fa Add PerfContext API in Java (#11805)
Summary:
This PR expose RocksDB C++ API for performance measurement in Java.
It's initial implementation and it doesn't support ```level_to_perf_context```

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11805

Reviewed By: akankshamahajan15

Differential Revision: D50128356

Pulled By: ltamasi

fbshipit-source-id: afb35980a89129a30d4a6b4cce12352c9de186b6
2023-10-10 11:07:33 -07:00
Radek Hubner f1aa17c73f Lazy load java native library (#11919)
Summary:
This address https://github.com/facebook/rocksdb/issues/11277. Java native library is not anymore loaded until the code is first used.
It should allow to manually load native library from different location with `RocksDB#loadLibrary(List<java.lang.String>)`

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11919

Reviewed By: jaykorean

Differential Revision: D50103182

Pulled By: ltamasi

fbshipit-source-id: 6090b529c7299b032f4e93cd0c3025a60f58652f
2023-10-10 08:40:07 -07:00
Andrew Kryczka 77d160ef47 Consolidate ErrorHandler's recovery status variables (#11937)
Summary:
cbi42 pointed out a race condition in which `recovery_io_error_` and `recovery_error_` could be updated inconsistently due to releasing the DB mutex in `EventHelpers::NotifyOnBackgroundError()`. There doesn't seem to be a point to having two status objects, so this PR consolidates them.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11937

Reviewed By: cbi42

Differential Revision: D50105793

Pulled By: ajkr

fbshipit-source-id: 3de95baccfa44351a49a5c2aa0986c9bc81baa8f
2023-10-10 06:31:45 -07:00
Andrew Kryczka 8a9cfd5292 Make stopped writes block on recovery (#11879)
Summary:
Relaxed the constraints for blocking when writes are stopped. When a recovery is already being attempted, we might as well let `!no_slowdown` writes wait on it in case it succeeds. This makes the user-visible behavior consistent across recovery flush and non-recovery flush.

This enables `db_stress` to inject retryable (soft) flush read errors without having to handle user write failures. I changed `db_stress` a bit to permit injected errors in much more foreground operations as more admin operations (like `GetLiveFiles()`) can fail on a retryable error during flush.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11879

Reviewed By: anand1976

Differential Revision: D49571196

Pulled By: ajkr

fbshipit-source-id: 5d516d6faf20d2c6bfe0594ab4f2706bca6d69b0
2023-10-10 06:29:01 -07:00
darionyaphet ee0829ba76 fix typo snapshto (#11817)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/11817

Reviewed By: jaykorean

Differential Revision: D50103497

Pulled By: ltamasi

fbshipit-source-id: 77c5cf86ff7eb5021fc91b03225882536163af7b
2023-10-09 19:10:06 -07:00
darionyaphet 229a6e5f55 Remove unnecessary comments (#11833)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/11833

Reviewed By: jaykorean

Differential Revision: D50103376

Pulled By: ltamasi

fbshipit-source-id: 0da49252c3e584b9d77e9fd3f27453d4b24afe6e
2023-10-09 19:05:48 -07:00
Levi Tamasi 51d7e6a49e Clean up WriteBatchWithIndexInternal a bit (#11930)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/11930

The patch cleans up and refactors the logic in/around `WriteBatchWithIndexInternal` a bit as groundwork for further changes. Specifically, the class is turned back into a stateless collection of static helpers (which is the way it was before PR 6851). Note that there were two apparent reasons for introducing this instance state in PR 6851: a) encapsulating `MergeContext` and b) resolving objects like `Logger` and `Statistics` based on a variety of handles. However, neither reason seems justified at this point. Regarding a), the `MultiGetFromBatchAndDB` logic passes in its own `MergeContext` objects via a second set of methods that do not use the member `MergeContext`. As for b), `Logger` and friends are only needed for Merge, which is only supported if a column family handle is provided; in turn, the column family handle enables us to resolve all the necessary objects without the need for any other handles like `DB` or `DBOptions`. In addition to the above, the patch changes the type of `BaseDeltaIterator::merge_result_` to `std::string` from `PinnableSlice` (since no pinning is ever done) and makes some other small code quality improvements.

Reviewed By: jaykorean

Differential Revision: D50038302

fbshipit-source-id: 5f34abe2e808bdaea0f3a8033b5764ebd446b85d
2023-10-09 15:25:35 -07:00
Hui Xiao 21a12363e1 Add EXPERIMENTAL comments about XXOptions::io_activity (#11926)
Summary:
Context/Summary: this option is experimental right now

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11926

Test Plan: no code change

Reviewed By: jaykorean

Differential Revision: D49985000

Pulled By: hx235

fbshipit-source-id: a5b439ed35e3d6bb04c125f222ac29cd3842d1a1
2023-10-06 12:37:51 -07:00
Yu Zhang 2dc63c8911 Add the default WritableFile::GetFileSize implementation back for com… (#11927)
Summary:
As mentioned in https://github.com/facebook/rocksdb/issues/11726, we should defer user feasible API changes to major release.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11927

Reviewed By: anand1976

Differential Revision: D50016723

Pulled By: jowlyzhang

fbshipit-source-id: 59781442602fadb9906e37aad2021e3178723db5
2023-10-06 10:34:44 -07:00
Peter Dillinger 1d5bddbc58 Bootstrap, pre-populate seqno_to_time_mapping (#11922)
Summary:
This change has two primary goals (follow-up to https://github.com/facebook/rocksdb/issues/11917, https://github.com/facebook/rocksdb/issues/11920):
* Ensure the DB seqno_to_time_mapping has entries that allow us to put a good time lower bound on any writes that happen after setting up preserve/preclude options (either in a new DB, new CF, SetOptions, etc.) and haven't yet aged out of that time window. This allows us to remove a bunch of work-arounds in tests.
* For new DBs using preserve/preclude options, automatically reserve some sequence numbers and pre-map them to cover the time span back to the preserve/preclude cut-off time. In the future, this will allow us to import data from another DB by key, value, and write time by assigning an appropriate seqno in this DB for that write time.

Note that the pre-population (historical mappings) does not happen if the original options at DB Open time do not have preserve/preclude, so it is recommended to create initial column families at that time with create_missing_column_families, to take advantage of this (future) feature. (Adding these historical mappings after DB Open would risk non-monotonic seqno_to_time_mapping, which is dubious if not dangerous.)

Recommended follow-up:
* Solve existing race conditions (not memory safety) where parallel operations like CreateColumnFamily or SetDBOptions could leave the wrong setting in effect.
* Make SeqnoToTimeMapping more gracefully handle a possible case in which too many mappings are added for the time range of concern. It seems like there could be cases where data is massively excluded from the cold tier because of entries falling off the front of the mapping list (causing GetProximalSeqnoBeforeTime() to return 0). (More investigation needed.)

No release note for the minor bug fix because this is still an experimental feature with limited usage.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11922

Test Plan: tests added / updated

Reviewed By: jowlyzhang

Differential Revision: D49956563

Pulled By: pdillinger

fbshipit-source-id: 92beb918c3a298fae9ca8e509717b1067caa1519
2023-10-06 08:21:21 -07:00
Hui Xiao 8e949116f7 Fix comments about creation_time/oldest_ancester_time/oldest_key_time (#11921)
Summary:
Code reference for the comments change:

https://github.com/facebook/rocksdb/blob/40b618f2349b509eabdd175f75faf7ce84cf0696/table/block_based/block_based_table_builder.cc?fbclid=IwAR0JlfnG8wysclFP5wv0fSngFbi_j32BUCKbFayeGdr10tzDhyyk5QqpclA#L2093

https://github.com/facebook/rocksdb/blob/40b618f2349b509eabdd175f75faf7ce84cf0696/db/flush_job.cc?fbclid=IwAR1ri6eTX3wyD_2fAEBRzFSwZItcbmDS8LaB11k1letDMQmB2L8nF6TfXDs#L945-L949

https://github.com/facebook/rocksdb/blob/40b618f2349b509eabdd175f75faf7ce84cf0696/db/compaction/compaction_job.cc#L1882-L1904

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11921

Reviewed By: cbi42

Differential Revision: D49921304

Pulled By: hx235

fbshipit-source-id: 2ae17e43c0fd52044404d7b63fea254d2d1f3595
2023-10-04 14:42:35 -07:00
Peter Dillinger 141b872bd4 Improve efficiency of create_missing_column_families, light refactor (#11920)
Summary:
In preparing some seqno_to_time_mapping improvements, I found that some of the wrap-up work for creating column families was unnecessarily repeated in the case of DB::Open with create_missing_column_families. This change fixes that (`CreateColumnFamily()` -> `CreateColumnFamilyImpl()` in `DBImpl::Open()`), motivated by avoiding repeated calls to `RegisterRecordSeqnoTimeWorker()` but with the side benefit of avoiding repeated calls to `WriteOptionsFile()` for each CF.

Also in this change:
* Add a `Status::UpdateIfOk()` function for combining statuses in a common pattern
* Rename `max_time_duration` -> `min_preserve_seconds` (include units as much as possible)
* Improved comments in several places

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11920

Test Plan: tests added / updated

Reviewed By: jaykorean

Differential Revision: D49919147

Pulled By: pdillinger

fbshipit-source-id: 3d0318c1d070c842c5331da0a5b415caedc104f1
2023-10-04 14:14:22 -07:00
akankshamahajan 40b618f234 Enable auto_readahead_size in db_stress (#11916)
Summary:
Depends on https://github.com/facebook/rocksdb/pull/11884
This PR only enables the option in db_stress.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11916

Reviewed By: anand1976

Differential Revision: D49834479

Pulled By: akankshamahajan15

fbshipit-source-id: 103a64fd7b23236493a8f3064d4c5af83656bd18
2023-10-03 14:41:26 -07:00
Adam Retter c13569e41d RocksDB now requires gflags v2.2.0 (#10933)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/10933

Reviewed By: jaykorean

Differential Revision: D49872302

Pulled By: jowlyzhang

fbshipit-source-id: 15f4e177bed59455ff58a0b48a3f6a55973d0b38
2023-10-03 09:58:49 -07:00
akankshamahajan 97f6f475bc Fix various failures in auto_readahead_size (#11884)
Summary:
1. **Error** in TestIterateAgainstExpected API - `Assertion index < pre_read_expected_values.size() && index < post_read_expected_values.size() failed.`
 **Fix** - `Prev` op is not supported with `auto_readahead_size`. So added support to Reseek in db_iter, if Prev is called. In BlockBasedTableIterator, index_iter_ already moves forward. So there is no way to do Prev from BlockBasedTableIterator.

2. **Error** - `void rocksdb::BlockBasedTableIterator::BlockCacheLookupForReadAheadSize(uint64_t, size_t, size_t&): Assertion index_iter_->value().handle.offset() == offset`
   **Fix** - Remove prefetch_buffer to be used when uncompressed dict is read.

3. ** Error in TestPrefixScan API - `db_stress: db/db_iter.cc:369: bool rocksdb::DBIter::FindNextUserEntryInternal(bool, const rocksdb::Slice*): Assertion !skipping_saved_key || CompareKeyForSkip(ikey_.user_key, saved_key_.GetUserKey()) > 0 failed.
Received signal 6 (Aborted)
Invoking GDB for stack trace...
db_stress: table/merging_iterator.cc:1036: bool rocksdb::MergingIterator::SkipNextDeleted(): Assertion comparator_->Compare(range_tombstone_iters_[i]->start_key(), pik) <= 0 failed`
  **Fix** -  SeekPrev also calls 1) SeekPrev , 2)Seek and then 3)Prev in some cases in db_iter.cc leading to failure of Prev operation. These backward operations also call Seek.  Added direction to disable lookup once direction is backwards in BlockBasedTableIterator.cc

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11884

Test Plan: Ran various flavors of crash tests locally for the whole duration

Reviewed By: anand1976

Differential Revision: D49834201

Pulled By: akankshamahajan15

fbshipit-source-id: 9a007b4d46a48002c43dc4623a400ecf47d997fe
2023-10-02 17:47:24 -07:00
Jay Huh 5fbea87859 Disallow start_time == end_time in offpeak time and compare at minute level to allow 24hr offpeak (#11911)
Summary:
Since allowing 24hr peak by setting start_time = end_time is not so intuitive, we are not going to allow it (e.g. `00:00-00:00` doesn't looks like a value that would cover 24hr.). Instead, we are going to compare at minute level (i.e. dropping the seconds to the nearest minute) so that `00:00-23:59` will cover 24hrs. The entire minute from 23:59:00 23:59:59 will be covered with this change.

Minor fixes from previous PR
- release build error
- fixed random seed in test

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11911

Test Plan:
`DBOptionsTest::OffPeakTimes`
`make -j64 static_lib` to test release build issue that was fixed

Reviewed By: pdillinger

Differential Revision: D49787795

Pulled By: jaykorean

fbshipit-source-id: e8d045b95f54f61d5dd5f1bb473579f8d55c18b3
2023-10-02 16:52:39 -07:00
Andrew Kryczka 10fd05e394 Give retry flushes their own functions (#11903)
Summary:
Recovery triggers flushes for very different scenarios:

(1) `FlushReason::kErrorRecoveryRetryFlush`: a flush failed
(2) `FlushReason::kErrorRecovery`: a WAL may be corrupted
(3) `FlushReason::kCatchUpAfterErrorRecovery`: immutable memtables may have accumulated

The old code called called `FlushAllColumnFamilies()` in all cases, which uses manual flush functions: `AtomicFlushMemTables()` and `FlushMemTable()`. Forcing flushing the latest data on all CFs was useful for (2) because it ensures all CFs move past the corrupted WAL.

However, those code paths were overkill for (1) and (3), where only already-immutable memtables need to be flushed. There were conditionals to exclude some of the extraneous logic but I found there was still too much happening. For example, both of the manual flush functions enter the write thread. Entering the write thread is inconvenient because then we can't allow stalled writes to wait on a retrying flush to finish.

Instead of continuing down the path of adding more conditionals to the manual flush functions, this PR introduces a dedicated function for cases (1) and (3): `RetryFlushesForErrorRecovery()`. Also I cleaned up the manual flush functions to remove existing conditionals for these cases as they're no longer needed.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11903

Reviewed By: cbi42

Differential Revision: D49693812

Pulled By: ajkr

fbshipit-source-id: 7630ac539b9d6c92052c13a3cdce53256134d990
2023-10-02 16:26:24 -07:00
Levi Tamasi b00fa5597e Fix the handling of wide-column base values in the max_successive_merges logic (#11913)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/11913

The `max_successive_merges` logic currently does not handle wide-column base values correctly, since it uses the `Get` API, which only returns the value of the default column. The patch fixes this by switching to `GetEntity` and passing all columns (if applicable) to the merge operator.

Reviewed By: jaykorean

Differential Revision: D49795097

fbshipit-source-id: 75eb7cc9476226255062cdb3d43ab6bd1cc2faa3
2023-10-02 16:25:25 -07:00
Peter Dillinger 7bebd3036d Update tiered storage tests (ahead of next change) (#11917)
Summary:
After https://github.com/facebook/rocksdb/issues/11905, I am preparing a DBImpl change to ensure all sufficiently recent sequence numbers since Open are covered by SeqnoToTimeMapping. **Intended follow-up**

However, there are a number of test changes I want to make prior to that to make it clear that I am not regressing the tests and production behavior at the same time.

* Start mock time in the tests well beyond epoch (time 0) so that we aren't normally reaching into pre-history for current time minus the preserve/preclude duration.
* Majorly clean up BasicSeqnoToTimeMapping to avoid confusing hard-coded bounds on GetProximalTimeBeforeSeqno() results.
  * There is an unresolved/unexplained issue marked with FIXME that should be investigated when GetProximalTimeBeforeSeqno() is put into production.
* MultiCFs test was strangely generating 5 L0 files, four of which would be compacted into an L1, and then letting TTL compaction compact 1@L0+1@L1. Changing the starting time of the tests seemed to mess up the TTL compaction. But I suspect the TTL compaction was unintentional, so I've cut it down to just 4 L0 files, which compacts predictably.
* Unrelated: allow ROCKSDB_NO_STACK=1 to skip printing a stack trace on assertion failures.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11917

Test Plan: no changes to production code

Reviewed By: jowlyzhang

Differential Revision: D49841436

Pulled By: pdillinger

fbshipit-source-id: 753348ace9c548e82bcb77fcc8b2ffb7a6beeb0a
2023-10-02 16:19:05 -07:00
Andrew Kryczka be879cc56b stress test verification value mismatch message (#11912)
Summary:
Separate the message for value mismatch from the message for an extra value in the DB

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11912

Reviewed By: hx235

Differential Revision: D49792137

Pulled By: ajkr

fbshipit-source-id: 311bc1801843a15367f409ead88ef755acbde468
2023-10-02 16:07:39 -07:00
leipeng d98a9cfb27 test: WritableFile derived class: add missing GetFileSize() override (#11726)
Summary:
Missed `GetFileSize()` forwarding , this PR fix this issue, and mark `WritableFile::GetFileSize()` as pure virtual to detect such issue in compile time.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11726

Reviewed By: ajkr

Differential Revision: D49791240

Pulled By: jowlyzhang

fbshipit-source-id: ef219508d6b15c9a24df9b706a9fdc33cc6a286e
2023-09-29 15:58:08 -07:00
Andrew Kryczka 3c4cc6c2cc flip default DBOptions::fail_if_options_file_error (#11800)
Summary:
Changed `DBOptions::fail_if_options_file_error` default from `false` to
`true`. It is safer to fail an operation by default when it encounters
an error.

Also changed the API doc to list items in the conventional way for listing items in a sentence. The slashes weren't working well as one got dropped, probably because it looked like a typo.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11800

Test Plan: rely on CI

Reviewed By: jowlyzhang

Differential Revision: D49030532

Pulled By: ajkr

fbshipit-source-id: e606062aa25f9063d8c6fb0d03aebca5c2bc56d3
2023-09-29 15:15:32 -07:00
Jay Huh 63ed868840 Offpeak in db option (#11893)
Summary:
RocksDB's primary function is to facilitate read and write operations. Compactions, while essential for minimizing read amplifications and optimizing storage, can sometimes compete with these primary tasks. Especially during periods of high read/write traffic, it's vital to ensure that primary operations receive priority, avoiding any potential disruptions or slowdowns. Conversely, during off-peak times when traffic is minimal, it's an opportune moment to tackle low-priority tasks like TTL based compactions, optimizing resource usage.

In this PR, we are incorporating the concept of off-peak time into RocksDB by introducing `daily_offpeak_time_utc` within the DBOptions. This setting is formatted as "HH:mm-HH:mm" where the first one before "-" is the start time and the second one is the end time, inclusive. It will be later used for resource optimization in subsequent PRs.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11893

Test Plan:
- New Unit Test Added - `DBOptionsTest::OffPeakTimes`
- Existing Unit Test Updated - `OptionsTest`, `OptionsSettableTest`

Reviewed By: pdillinger

Differential Revision: D49714553

Pulled By: jaykorean

fbshipit-source-id: fef51ea7c0fede6431c715bff116ddbb567c8752
2023-09-29 13:03:39 -07:00
Peter Dillinger 02443dd93f Refactor, clean up, fixes, and more testing for SeqnoToTimeMapping (#11905)
Summary:
This change is before a planned DBImpl change to ensure all sufficiently recent sequence numbers since Open are covered by SeqnoToTimeMapping (bug fix with existing test work-arounds). **Intended follow-up**

However, I found enough issues with SeqnoToTimeMapping to warrant this PR first, including very small fixes in DB implementation related to API contract of SeqnoToTimeMapping.

Functional fixes / changes:
* This fixes some mishandling of boundary cases. For example, if the user decides to stop writing to DB, the last written sequence number would perpetually have its write time updated to "now" and would always be ineligible for migration to cold tier. Part of the problem is that the SeqnoToTimeMapping would return a seqno known to have been written before (immediately or otherwise) the requested time, but compaction_job.cc would include that seqno in the preserve/exclude set. That is fixed (in part) by adding one in compaction_job.cc
* That problem was worse because a whole range of seqnos could be updated perpetually with new times in SeqnoToTimeMapping::Append (if no writes to DB). That logic was apparently optimized for GetOldestApproximateTime (now GetProximalTimeBeforeSeqno), which is not used in production, to the detriment of GetOldestSequenceNum (now GetProximalSeqnoBeforeTime), which is used in production. (Perhaps plans changed during development?) This is fixed in Append to optimize for accuracy of GetProximalSeqnoBeforeTime. (Unit tests added and updated.)
* Related: SeqnoToTimeMapping did not have a clear contract about the relationships between seqnos and times, just the idea of a rough correspondence. Now the class description makes it clear that the write time of each recorded seqno comes before or at the associated time, to support getting best results for GetProximalSeqnoBeforeTime. And this makes it easier to make clear the contract of each API function.
  * Update `DBImpl::RecordSeqnoToTimeMapping()` to follow this ordering in gathering samples.

Some part of these changes has required an expanded test work-around for the problem (see intended follow-up above) that the DB does not immediately ensure recent seqnos are covered by its mapping. These work-arounds will be removed with that planned work.

An apparent compaction bug is revealed in
PrecludeLastLevelTest::RangeDelsCauseFileEndpointsToOverlap, so that test is disabled. Filed GitHub issue #11909

Cosmetic / code safety things (not exhaustive):
* Fix some confusing names.
  * `seqno_time_mapping` was used inconsistently in places. Now just `seqno_to_time_mapping` to correspond to class name.
  * Rename confusing `GetOldestSequenceNum` -> `GetProximalSeqnoBeforeTime` and `GetOldestApproximateTime` -> `GetProximalTimeBeforeSeqno`. Part of the motivation is that our times and seqnos here have the same underlying type, so we want to be clear about which is expected where to avoid mixing.
  * Rename `kUnknownSeqnoTime` to `kUnknownTimeBeforeAll` because the value is a bad choice for unknown if we ever add ProximalAfterBlah functions.
  * Arithmetic on SeqnoTimePair doesn't make sense except for delta encoding, so use better names / APIs with that in mind.
  * (OMG) Don't allow direct comparison between SeqnoTimePair and SequenceNumber. (There is no checking that it isn't compared against time by accident.)
  * A field name essentially matching the containing class name is a confusing pattern (`seqno_time_mapping_`).
  * Wrap calls to confusing (but useful) upper_bound and lower_bound functions to have clearer names and more code reuse.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11905

Test Plan: GetOldestSequenceNum (now GetProximalSeqnoBeforeTime) and TruncateOldEntries were lacking unit tests, despite both being used in production (experimental feature). Added those and expanded others.

Reviewed By: jowlyzhang

Differential Revision: D49755592

Pulled By: pdillinger

fbshipit-source-id: f72a3baac74d24b963c77e538bba89a7fc8dce51
2023-09-29 11:21:59 -07:00
Jay Huh 2cfe53ec05 Add helpful message for ldb when unknown option found (#11907)
Summary:
Users may run into an issue when running ldb on db that's in a different version and they have different set of options: `Failed: Invalid argument: Could not find option: <MISSING_OPTION>`

They can work around this by setting `--ignore_unknown_options`, but the error message is not clear for users to find why the option is missing. It's also hard for the users to find the `ignore_unknown_options` option especially if they are not familiar with the codebase or `ldb` tool.

This PR changes the error message to help users to find out what's wrong and possible workaround for the issue

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11907

Test Plan:
Testing by reproducing the issue locally
```
❯./ldb --db=/data/users/jewoongh/db_crash_whitebox_T164195541/ get a
Failed: Invalid argument: Could not find option: : unknown_option_test
This tool was built with version 8.8.0. If your db is in a different version, please try again with option --ignore_unknown_options.
```

Reviewed By: jowlyzhang

Differential Revision: D49762291

Pulled By: jaykorean

fbshipit-source-id: 895570150fde886d5ec524908c4b2664c9230ac9
2023-09-29 09:58:40 -07:00
Levi Tamasi 01e2d33565 Add the wide-column aware merge API to the stress tests (#11906)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/11906

The patch adds stress test coverage for the wide-column aware `FullMergeV3` API by implementing a new `DBStressWideMergeOperator`. This operator is similar to `PutOperator` / `PutOperatorV2` in the sense that its result is based on the last merge operand; however, the merge result can be either a plain value or a wide-column entity, depending on the value base encoded into the operand and the value of the `use_put_entity_one_in` stress test parameter. Following the same rule for merge results that we do for writes ensures that the queries issued by the validation logic receive the expected results. The new operator is used instead of `PutOperatorV2` whenever `use_put_entity_one_in` is positive. Note that the patch also makes it possible to set `use_put_entity_one_in` and `use_merge` (but not `use_full_merge_v1`) at the same time, giving `use_put_entity_one_in` precedence, so the stress test will use `PutEntity` for writes passing the `use_put_entity_one_in` check described above and `Merge` for any other writes.

Reviewed By: jaykorean

Differential Revision: D49760024

fbshipit-source-id: 3893602c3e7935381b484f4f5026f1983e3a04a9
2023-09-29 08:54:50 -07:00
Fan Zhang(DevX) 8b566964b8 remove unnecessary autodeps suppression tag from rocksdb/src (#11904)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/11904

The tag is not needed, autodeps works fine with this file. it was added in D33962843 but the reason doing is not valid anymore. We are on the way of migrating most, if not all, users to autodeps, and deprecating the noautodeps tag.

Changed the tag in template and run `python3 buckifier/buckify_rocksdb.py` for regeneration

Reviewed By: jaykorean

Differential Revision: D49711337

fbshipit-source-id: c21892adfbc92e2ad868413746a0938062b6a543
2023-09-28 10:42:04 -07:00
Levi Tamasi 6b4315ee8b Extend the test coverage of FullMergeV3 (#11896)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/11896

The patch extends the test coverage of the wide column aware merge logic by adding two new tests that perform general transformations during merge by implementing the `FullMergeV3` interface. The first one uses a merge operator that produces a wide-column entity as result in all cases (i.e. even if the base value is a plain key-value, or if there is no base value). The second one uses a merge operator that results in a plain key-value in all cases.

Reviewed By: jaykorean

Differential Revision: D49665946

fbshipit-source-id: 419b9e557c064525b659685eb8c09ae446656439
2023-09-27 14:53:25 -07:00
anand76 35a0250293 Don't call InsertSaved on compressed only secondary cache (#11889)
Summary:
In https://github.com/facebook/rocksdb/issues/11812, the ```CacheWithSecondaryAdapter::Insert``` calls ```InsertSaved``` on the secondary cache to warm it up with the compressed blocks. This should only be done if its a stacked cache with compressed and nvm cache. If its in-memory compressed only, then don't call ```InsertSaved```.

Tests:
Add a new unit test

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11889

Reviewed By: akankshamahajan15

Differential Revision: D49615758

Pulled By: anand1976

fbshipit-source-id: 156ff968ad014ac319f8840da7a48193e4cebfa9
2023-09-27 12:08:08 -07:00
Hui Xiao fce04587b8 Only fallback to RocksDB internal prefetching on unsupported FS prefetching (#11897)
Summary:
**Context/Summary:**
https://github.com/facebook/rocksdb/pull/11631 introduced an undesired fallback behavior to RocksDB internal prefetching even when FS prefetching return non-OK status other than "Unsupported". We only want to fall back when FS prefetching is not supported.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11897

Test Plan: CI

Reviewed By: ajkr

Differential Revision: D49667055

Pulled By: hx235

fbshipit-source-id: fa36e4e5d6dc9507080217035f9d6ff8e4abda28
2023-09-26 18:44:41 -07:00
Hui Xiao 719f5511f6 No file system prefetching when Options::compaction_readahead_size is 0 (#11887)
Summary:
**Context/Summary:**

https://github.com/facebook/rocksdb/pull/11631 introduced `readahead()` system call for compaction read under non direct IO. When `Options::compaction_readahead_size` is 0, the `readahead()` will issued with a small size (i.e, the block size, by default 4KB)

Benchmarks shows that such readahead() call regresses the compaction read compared with "no readahead()" case (see Test Plan for more).

Therefore we decided to not issue such `readhead() ` when `Options::compaction_readahead_size` is 0.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11887

Test Plan:
Settings: `compaction_readahead_size = 0, use_direct_reads=false`
Setup:
```
TEST_TMPDIR=../ ./db_bench -benchmarks=filluniquerandom -disable_auto_compactions=true -write_buffer_size=1048576 -compression_type=none -value_size=10240 && tar -cf ../dbbench.tar -C ../dbbench/ .
```
Run:
```
for i in $(seq 3); do rm -rf ../dbbench/ && mkdir -p ../dbbench/ && tar -xf ../dbbench.tar -C ../dbbench/ . && sudo bash -c 'sync && echo 3 > /proc/sys/vm/drop_caches' && TEST_TMPDIR=../ /usr/bin/time ./db_bench_{pre_PR11631|PR11631|PR11631_with_improvementPR11887} -benchmarks=compact -use_existing_db=true -db=../dbbench/ -disable_auto_compactions=true -compression_type=none ; done |& grep elapsed
```

pre-PR11631("no readahead()" case):

PR11631:

PR11631+this improvement:

Reviewed By: ajkr

Differential Revision: D49607266

Pulled By: hx235

fbshipit-source-id: 2efa0dc91bac3c11cc2be057c53d894645f683ef
2023-09-26 10:08:43 -07:00
Yu Zhang 7ea6e724fa Mark recovery_in_prog_ to false whenever recovery thread joins (#11890)
Summary:
Make the `RecoverFromRetryableBGIOError` function always mark `recovery_in_prog_` to false when it returns.
Otherwise, in below code snippet, when db closes and the `error_handler_.CancelErrorRecovery()` call successfully joined the recovery thread, the immediately following while loop will incorrectly think the error recovery is still in progress and loops in `bg_cv_.Wait()`.

https://github.com/facebook/rocksdb/blob/1c871a4d8682ea260ba3b18ed43cd525a2141733/db/db_impl/db_impl.cc#L542-L545

This is the issue https://github.com/facebook/rocksdb/issues/11440

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11890

Reviewed By: anand1976

Differential Revision: D49624216

Pulled By: jowlyzhang

fbshipit-source-id: ee10cf6527d95b8dd4705a326eb6208d741fe002
2023-09-25 20:15:40 -07:00
Yu Zhang 6c564e2e17 Add some convenience util APIs to facilitate using U64Ts (#11888)
Summary:
Added some util function APIs to facilitate using the U64Ts.

The U64Ts format for encoding a timestamp is not entirely RocksDB internal. When users are using the user-defined timestamp feature from the transaction layer, its public APIs including `SetCommitTimestamp`, `GetCommitTimestamp`, `SetReadTimestampForValidation` are taking and returning timestamps as uint64_t.  But if users want to use the APIs from the DB layer, including populating `ReadOptions.timestamp`, interpreting `Iterator::timestamp()`, these APIs are using and returning U64Ts timestamps as an encoded Slice.  So these util functions are added to facilitate the usage.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11888

Reviewed By: ltamasi

Differential Revision: D49620709

Pulled By: jowlyzhang

fbshipit-source-id: ace8d782ee7c3372cf410abf761320d373e495e1
2023-09-25 19:00:39 -07:00
Changyu Bi 1c871a4d86 Only flush after recovery for retryable IOError (#11880)
Summary:
https://github.com/facebook/rocksdb/issues/11872 causes a unit test to start failing with the error message below. The cause is that the additional call to `FlushAllColumnFamilies()` in `DBImpl::ResumeImpl()` can run while DB is closing. More detailed explanation: there are two places where we call `ResumeImpl()`:

1. in `ErrorHandler::RecoverFromBGError`, for manual resume or recovery from errors like OutOfSpace through sst file manager, and
2. in `Errorhandler::RecoverFromRetryableBGIOError`, for error recovery from errors like flush failure due to retryable IOError. This is tracked by `ErrorHandler::recovery_thread_`.

Here is how DB close waits for error recovery: https://github.com/facebook/rocksdb/blob/49da91ec097b4efcd8a8e4dc1b287e9f81eb4093/db/db_impl/db_impl.cc#L540-L543

`CancelErrorRecovery()` waits until `recovery_thread_` finishes and `IsRecoveryInProgress()` checks the `recovery_in_prog_` flag. The additional call to `FlushAllColumnFamilies()` in `ResumeImpl()` happens after it clears bg error and the `recovery_in_prog_` flag: https://github.com/facebook/rocksdb/blob/49da91ec097b4efcd8a8e4dc1b287e9f81eb4093/db/db_impl/db_impl.cc#L436-L463. So if `ResumeImpl()` is called in `RecoverFromBGError()`, we can have a thread running `FlushAllColumnFamilies()` while DB is closing and thought that recovery is done.

The fix is to only do the additional call to `FlushAllColumnFamilies()` when doing error recovery through `Errorhandler::RecoverFromRetryableBGIOError` by setting flags in `DBRecoverContext`.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11880

Test Plan:
`gtest-parallel --repeat=100 --workers=4 ./error_handler_fs_test --gtest_filter="*AutoRecoverFlushError*"` reproduces the error pretty reliably.

```[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from DBErrorHandlingFSTest
[ RUN      ] DBErrorHandlingFSTest.AutoRecoverFlushError
error_handler_fs_test: db/column_family.cc:1618: rocksdb::ColumnFamilySet::~ColumnFamilySet(): Assertion `last_ref' failed.
Received signal 6 (Aborted)
...
https://github.com/facebook/rocksdb/issues/10 0x00007fac4409efd6 in __GI___assert_fail (assertion=0x7fac452c0afa "last_ref", file=0x7fac452c9fb5 "db/column_family.cc", line=1618, function=0x7fac452cb950 "rocksdb::ColumnFamilySet::~ColumnFamilySet()") at assert.c:101
101     in assert.c
https://github.com/facebook/rocksdb/issues/11 0x00007fac44b5324f in rocksdb::ColumnFamilySet::~ColumnFamilySet (this=0x7b5400000000) at db/column_family.cc:1618
1618        assert(last_ref);
https://github.com/facebook/rocksdb/issues/12 0x00007fac44e0f047 in std::default_delete<rocksdb::ColumnFamilySet>::operator() (this=0x7b5800000940, __ptr=0x7b5400000000) at /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/unique_ptr.h:85
85              delete __ptr;
https://github.com/facebook/rocksdb/issues/13 std::__uniq_ptr_impl<rocksdb::ColumnFamilySet, std::default_delete<rocksdb::ColumnFamilySet> >::reset (this=0x7b5800000940, __p=0x0) at /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/unique_ptr.h:182
182               _M_deleter()(__old_p);
https://github.com/facebook/rocksdb/issues/14 std::unique_ptr<rocksdb::ColumnFamilySet, std::default_delete<rocksdb::ColumnFamilySet> >::reset (this=0x7b5800000940, __p=0x0) at /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/unique_ptr.h:456
456             _M_t.reset(std::move(__p));
https://github.com/facebook/rocksdb/issues/15 rocksdb::VersionSet::~VersionSet (this=this@entry=0x7b5800000900) at db/version_set.cc:5081
5081      column_family_set_.reset();
https://github.com/facebook/rocksdb/issues/16 0x00007fac44e0f97a in rocksdb::VersionSet::~VersionSet (this=0x7b5800000900) at db/version_set.cc:5078
5078    VersionSet::~VersionSet() {
https://github.com/facebook/rocksdb/issues/17 0x00007fac44bf0b2f in std::default_delete<rocksdb::VersionSet>::operator() (this=0x7b8c00000068, __ptr=0x7b5800000900) at /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/unique_ptr.h:85
85              delete __ptr;
https://github.com/facebook/rocksdb/issues/18 std::__uniq_ptr_impl<rocksdb::VersionSet, std::default_delete<rocksdb::VersionSet> >::reset (this=0x7b8c00000068, __p=0x0) at /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/unique_ptr.h:182
182               _M_deleter()(__old_p);
https://github.com/facebook/rocksdb/issues/19 std::unique_ptr<rocksdb::VersionSet, std::default_delete<rocksdb::VersionSet> >::reset (this=0x7b8c00000068, __p=0x0) at /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/unique_ptr.h:456
456             _M_t.reset(std::move(__p));
https://github.com/facebook/rocksdb/issues/20 rocksdb::DBImpl::CloseHelper (this=this@entry=0x7b8c00000000) at db/db_impl/db_impl.cc:676
676       versions_.reset();
https://github.com/facebook/rocksdb/issues/21 0x00007fac44bf1346 in rocksdb::DBImpl::CloseImpl (this=0x7b8c00000000) at db/db_impl/db_impl.cc:720
720     Status DBImpl::CloseImpl() { return CloseHelper(); }
https://github.com/facebook/rocksdb/issues/22 rocksdb::DBImpl::~DBImpl (this=this@entry=0x7b8c00000000) at db/db_impl/db_impl.cc:738
738       closing_status_ = CloseImpl();
https://github.com/facebook/rocksdb/issues/23 0x00007fac44bf2bba in rocksdb::DBImpl::~DBImpl (this=0x7b8c00000000) at db/db_impl/db_impl.cc:722
722     DBImpl::~DBImpl() {
https://github.com/facebook/rocksdb/issues/24 0x00007fac455444d4 in rocksdb::DBTestBase::Close (this=this@entry=0x7b6c00000000) at db/db_test_util.cc:678
678       delete db_;
https://github.com/facebook/rocksdb/issues/25 0x00007fac455455fb in rocksdb::DBTestBase::TryReopen (this=this@entry=0x7b6c00000000, options=...) at db/db_test_util.cc:707
707       Close();
https://github.com/facebook/rocksdb/issues/26 0x00007fac45543459 in rocksdb::DBTestBase::Reopen (this=0x7ffed74b79a0, options=...) at db/db_test_util.cc:670
670       ASSERT_OK(TryReopen(options));
https://github.com/facebook/rocksdb/issues/27 0x00000000004f2522 in rocksdb::DBErrorHandlingFSTest_AutoRecoverFlushError_Test::TestBody (this=this@entry=0x7b6c00000000) at db/error_handler_fs_test.cc:1224
1224      Reopen(options);
```

Reviewed By: jowlyzhang

Differential Revision: D49579701

Pulled By: cbi42

fbshipit-source-id: 3fc8325e6dde7e7faa8bcad95060cb4e26eda638
2023-09-25 09:34:39 -07:00
akankshamahajan bd655b9af3 Disable AutoReadaheadSize in stress tests (#11883)
Summary:
Crash tests are failing with recent change of auto_readahead_size. Disable it in stress tests and enable it with fix to clear the crash tests failures.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/11883

Reviewed By: pdillinger

Differential Revision: D49597854

Pulled By: akankshamahajan15

fbshipit-source-id: 0af8ca7414ee9b92f244ee0fb811579c3c052b41
2023-09-25 09:06:22 -07:00
Changyu Bi 49da91ec09 Update files for version 8.8 (#11878)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/11878

Reviewed By: ajkr

Differential Revision: D49568389

Pulled By: cbi42

fbshipit-source-id: b2022735799be9b5e81e03dfb418f8b104632ecf
2023-09-23 11:02:19 -07:00
278 changed files with 9396 additions and 1681 deletions

No files matched your search

+71 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
+9 -6
View File
@@ -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 -2
View File
@@ -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)
+4
View File
@@ -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]
-2
View File
@@ -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")
"""
+1 -1
View File
@@ -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.
+75 -43
View File
@@ -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 {
+4
View File
@@ -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
View File
@@ -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));
}
+137 -43
View File
@@ -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);
+17 -4
View File
@@ -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
+10
View File
@@ -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_;
+2
View File
@@ -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;
+78 -12
View File
@@ -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) {
+8
View File
@@ -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
View File
@@ -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);
+16
View File
@@ -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
+3
View File
@@ -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
View File
@@ -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;
+17 -10
View File
@@ -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(),
+1 -1
View File
@@ -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
+8 -6
View File
@@ -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();
+1 -1
View File
@@ -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() {
+3
View File
@@ -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 {
+13 -39
View File
@@ -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) {
+1
View File
@@ -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 {
+1
View File
@@ -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);
};
+1
View File
@@ -389,6 +389,7 @@ TEST_F(DBBlockCacheTest, FillCacheAndIterateDB) {
while (iter->Valid()) {
iter->Next();
}
ASSERT_OK(iter->status());
delete iter;
iter = nullptr;
}
+1
View File
@@ -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);
+2
View File
@@ -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);
+1
View File
@@ -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;
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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.
+114 -36
View File
@@ -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,
+2 -1
View File
@@ -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
+9 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+1
View File
@@ -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
+29
View File
@@ -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
View File
@@ -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);
}
+94
View File
@@ -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) {
+258
View File
@@ -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_);
+8
View File
@@ -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;
}
+1
View File
@@ -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));
+5
View File
@@ -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);
+11
View File
@@ -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
View File
@@ -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
+3 -1
View File
@@ -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();
}
+6
View File
@@ -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));
+3
View File
@@ -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);
};
+5
View File
@@ -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();
+2
View File
@@ -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_;
+1
View File
@@ -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;
+1
View File
@@ -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;
};
+18
View File
@@ -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);
+2
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
+2 -1
View File
@@ -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;
}
+2
View File
@@ -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
View File
@@ -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();
}
+1 -1
View File
@@ -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.
+29
View File
@@ -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.
+17
View File
@@ -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,
+3
View File
@@ -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());
}
};
+4 -4
View File
@@ -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
+2
View File
@@ -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;
}
+1
View File
@@ -782,6 +782,7 @@ TEST_F(PrefixTest, PrefixSeekModePrev) {
}
}
}
ASSERT_OK(iter->status());
}
}
+4 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+520
View File
@@ -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;
+5
View File
@@ -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
View File
@@ -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);
+1
View File
@@ -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
+4 -4
View File
@@ -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);
}
+4 -7
View File
@@ -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);
}
+85
View File
@@ -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;
+10 -2
View File
@@ -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);
+15 -1
View File
@@ -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()) {
+22 -8
View File
@@ -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.");
+12
View File
@@ -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 {
+123 -82
View File
@@ -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) {
+3 -1
View File
@@ -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;
+12 -12
View File
@@ -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