Compare commits

...
80 Commits
Author SHA1 Message Date
Peter Dillinger fcd07e0f78 Fix a nuisance compiler warning from clang (#12144)
Summary:
Example:

```
cache/clock_cache.cc:56:7: error: fallthrough annotation in unreachable code [-Werror,-Wimplicit-fallthrough]
      FALLTHROUGH_INTENDED;
      ^
./port/lang.h:10:30: note: expanded from macro 'FALLTHROUGH_INTENDED'
                             ^
```

In clang < 14, this is annoyingly generated from -Wimplicit-fallthrough, but was changed to -Wunreachable-code-fallthrough (implied by -Wunreachable-code) in clang 14. See https://reviews.llvm.org/D107933 for how this nuisance pattern generated false positives similar to ours in the Linux kernel.

Just to underscore the ridiculousness of this warning, here an error is reported on the annotation, not the call to do_something(), depending on the constexpr value (https://godbolt.org/z/EvxqdPTdr):

```
#include <atomic>
void do_something();
void test(int v) {
    switch (v) {
        case 1:
            if constexpr (std::atomic<long>::is_always_lock_free) {
                return;
            } else {
                do_something();
                [[fallthrough]];
            }
        case 2:
            return;
    }
}
```

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

Test Plan: Added the warning to our Makefile for USE_CLANG, which reproduced the warning-as-error as shown above, but is now fixed.

Reviewed By: jaykorean

Differential Revision: D52139615

Pulled By: pdillinger

fbshipit-source-id: ba967ae700c0916d1a478bc465cf917633e337d9
2023-12-14 08:23:00 -08:00
anand76 0efb576ca3 Update version.h and HISTORY to 8.9.2 2023-12-12 11:47:43 -08:00
anand76 53649ce36b Sanitize the secondary_cache option in TieredCacheOptions (#12137)
Summary:
Sanitize the `secondary_cache` field in the `cache_opts` option of `TieredCacheOptions` to `nullptr` if set by the user. The nvm secondary cache should be directly set in `TieredCacheOptions`.

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

Reviewed By: akankshamahajan15

Differential Revision: D52063817

Pulled By: anand1976

fbshipit-source-id: 255116c665a9b908c8f44109a2d331d4b73e7591
2023-12-12 11:42:46 -08:00
anand76 6b389b43ff Fix false negative in TieredSecondaryCache nvm cache lookup (#12134)
Summary:
There is a bug in the `TieredSecondaryCache` that can result in a false negative. This can happen when a MultiGet does a cache lookup that gets a hit in the `TieredSecondaryCache` local nvm cache tier, and the result is available before MultiGet calls `WaitAll` (i.e the nvm cache `SecondaryCacheResultHandle` `IsReady` returns true).

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

Test Plan: Add a new unit test in tiered_secondary_cache_test

Reviewed By: akankshamahajan15

Differential Revision: D52023309

Pulled By: anand1976

fbshipit-source-id: e5ae681226a0f12753fecb2f6acc7e5f254ae72b
2023-12-12 11:42:05 -08:00
Levi Tamasi 49ce8a1064 Update version.h and HISTORY.md for 8.9.1 2023-12-08 12:44:09 -08:00
Levi Tamasi b8d41c55e7 Turn the default Timer in PeriodicTaskScheduler into a leaky Meyers singleton (#12128)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/12128

The patch turns the `Timer` Meyers singleton in `PeriodicTaskScheduler::Default()` into one of the leaky variety in order to prevent static destruction order issues.

Reviewed By: akankshamahajan15

Differential Revision: D51963950

fbshipit-source-id: 0fc34113ad03c51fdc83bdb8c2cfb6c9f6913948
2023-12-08 12:38:07 -08:00
akankshamahajan 3b1ce121c2 Update version.h to correct version 8.9
Summary:

Test Plan:

Reviewers:

Subscribers:

Tasks:

Tags:
2023-11-30 11:19:11 -08:00
Jay Huh 17e3628cb5 Merge memtable only UDT bugfix 2023-11-21 16:35:45 -08:00
Jay Huh 8bcbed2e93 Update HISTORY.md and version.h for 8.9.fb release 2023-11-21 16:33:46 -08:00
Yu Zhang 84a54e1e28 Fix some bugs in index builder and reader for the UDT in memtable only feature (#12062)
Summary:
These bugs surfaced while I was trying to add the stress test for the feature:

Bug 1) On the index building path: the optimization to use user key instead of internal key as separator needed a bit tweak for when user defined timestamps can be removed. Because even though the user key look different now and eligible to be used as separator, when their user-defined timestamps are removed, they could be equal and that invariant no longer stands.

Bug 2) On the index reading path: one path that builds the second level index iterator for `PartitionedIndexReader` are not passing the corresponding `user_defined_timestamps_persisted` flag. As a result, the default `true` value be used leading to no minimum timestamps padded when they should be.

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

Test Plan:
For bug 1): added separate unit test `BlockBasedTableReaderTest::Get` to exercise the `Get` API. It's a different code path from `MultiGet` so worth having its own test. Also in order to cover the bug, the test is modified to generate key values with the same user provided key, different timestamps and different sequence numbers. The test reads back different versions of the same user provided key.  `MultiGet` takes one `ReadOptions` with one read timestamp so we cannot test retrieving different versions of the same key easily.

For bug 2): simply added options `BlockBasedTableOptions.metadata_cache_options.partition_pinning = PinningTier::kAll` to exercise all the index iterator creating paths.

Reviewed By: ltamasi

Differential Revision: D51508280

Pulled By: jowlyzhang

fbshipit-source-id: 8b174d3d70373c0599266ac1f467f2bd4d7ea6e5
2023-11-21 14:05:02 -08:00
songqing d3e015fe06 Fix compact_files_example (#12084)
Summary:
The option "write_buffer_size" has changed from 4MB for 64MB by default, and the compact_files_example will not work as expected, as the test data written is only about 50MB and will not trigger compaction.

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

Reviewed By: cbi42

Differential Revision: D51499959

Pulled By: ajkr

fbshipit-source-id: 4f4b25ebc4b6bb568501adc8e97813edcddceea8
2023-11-21 09:34:59 -08:00
Andrew Kryczka 04cbc77b90 Add missing license to source files (#12083)
Summary:
Fixes https://github.com/facebook/rocksdb/issues/12079.

Fixed missing licenses in "\*.h" and "\*.cc" files

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

Reviewed By: cbi42

Differential Revision: D51489634

Pulled By: ajkr

fbshipit-source-id: 764bfee257b9d6603fd7606a55664b7537e1898f
2023-11-21 08:36:30 -08:00
anand76 336a74db60 Add some asserts in ~CacheWithSecondaryAdapter (#12082)
Summary:
Add some asserts in the `CacheWithSecondaryAdapter` destructor to help debug a crash test failure.

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

Reviewed By: cbi42

Differential Revision: D51486041

Pulled By: anand1976

fbshipit-source-id: 76537beed31ba27ab9ac8b4ce6deb775629e3be5
2023-11-20 17:48:17 -08:00
Changyu Bi fb5c8c7ea3 Do not compare op_type in WithinPenultimateLevelOutputRange() (#12081)
Summary:
`WithinPenultimateLevelOutputRange()` is updated in https://github.com/facebook/rocksdb/issues/12063 to check internal key range. However, op_type of a key can change during compaction, e.g. MERGE -> PUT, which makes a key larger and becomes out of penultimate output range. This has caused stress test failures with error message "Unsafe to store Seq later than snapshot in the last level if per_key_placement is enabled". So update `WithinPenultimateLevelOutputRange()` to only check user key and sequence number.

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

Test Plan:
* This repro can produce the corruption within a few runs. Ran it a few times after the fix and did not see Corruption failure.
```
python3 ./tools/db_crashtest.py whitebox --test_tiered_storage --random_kill_odd=888887 --use_merge=1 --writepercent=100 --readpercent=0 --prefixpercent=0 --delpercent=0 --delrangepercent=0 --iterpercent=0 --write_buffer_size=419430 --column_families=1 --read_fault_one_in=0 --write_fault_one_in=0
```

Reviewed By: ajkr

Differential Revision: D51481202

Pulled By: cbi42

fbshipit-source-id: cad6b65099733e03071b496e752bbdb09cf4db82
2023-11-20 17:07:28 -08:00
Timo Riski 39d33475da Fix build on FreeBSD (#11218) (#12078)
Summary:
Fixes https://github.com/facebook/rocksdb/issues/11218

Changes from https://github.com/facebook/rocksdb/issues/10881 broke FreeBSD builds with:

    env/io_posix.h:39:9: error: 'POSIX_MADV_NORMAL' macro redefined [-Werror,-Wmacro-redefined]

This commit fixes FreeBSD builds by ignoring MADV defines.

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

Reviewed By: cbi42

Differential Revision: D51452802

Pulled By: ajkr

fbshipit-source-id: 0a1f5a90954e7d257a95794277a843ac77f3a709
2023-11-20 10:11:16 -08:00
Changyu Bi b059c5680e Add missing copyright header (#12076)
Summary:
Required for open source repo.

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

Reviewed By: ajkr

Differential Revision: D51449839

Pulled By: cbi42

fbshipit-source-id: 4a25a3422880db3f28a2834d966341935db32530
2023-11-19 09:50:59 -08:00
Benoît Mériaux 7780e98268 add write_buffer_manager setter into options and tests in c bindings, (#12007)
Summary:
following https://github.com/facebook/rocksdb/pull/11710
 - add test on wbm c api
- add a setter for WBM in `DBOptions`

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

Reviewed By: cbi42

Differential Revision: D51430042

Pulled By: ajkr

fbshipit-source-id: 608bc4d3ed35a84200459d0230b35be64b3475f7
2023-11-17 11:34:05 -08:00
Changyu Bi 4e58cc6437 Check internal key range when compacting from last level to penultimate level (#12063)
Summary:
The test failure in https://github.com/facebook/rocksdb/issues/11909 shows that we may compact keys outside of internal key range of penultimate level input files from last level to penultimate level, which can potentially cause overlapping files in the penultimate level. This PR updates the  `Compaction::WithinPenultimateLevelOutputRange()` to check internal key range instead of user key.

Other fixes:
* skip range del sentinels when deciding output level for tiered compaction

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

Test Plan:
- existing unit tests
- apply the fix to https://github.com/facebook/rocksdb/issues/11905 and run `./tiered_compaction_test --gtest_filter="*RangeDelsCauseFileEndpointsToOverlap*"`

Reviewed By: ajkr

Differential Revision: D51288985

Pulled By: cbi42

fbshipit-source-id: 70085db5f5c3b15300bcbc39057d57b83fd9902a
2023-11-17 10:50:40 -08:00
Radek Hubner 2f9ea8193f Add HyperClockCache Java API. (#12065)
Summary:
Fix https://github.com/facebook/rocksdb/issues/11510

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

Reviewed By: ajkr

Differential Revision: D51406695

Pulled By: cbi42

fbshipit-source-id: b9e32da5f9bcafb5365e4349f7295be90d5aa7ba
2023-11-16 15:46:31 -08:00
nccx a9bd525b52 Add Qdrant to USERS.md (#12072)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/12072

Reviewed By: cbi42

Differential Revision: D51398080

Pulled By: ajkr

fbshipit-source-id: 1043f2b012bd744e9c53c638e1ba56a3e0392e11
2023-11-16 10:35:08 -08:00
Gus Wynn 6d10f8d690 add WriteBufferManager to c api (#11710)
Summary:
I want to use the `WriteBufferManager` in my rust project, which requires exposing it through the c api, just like `Cache` is.

Hopefully the changes are fairly straightfoward!

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

Reviewed By: cbi42

Differential Revision: D51166518

Pulled By: ajkr

fbshipit-source-id: cd266ff1e4a7ab145d05385cd125a8390f51f3fc
2023-11-16 10:34:00 -08:00
Andrew Kryczka 9202db1867 Consider archived WALs for deletion more frequently (#12069)
Summary:
Fixes https://github.com/facebook/rocksdb/issues/11000.

That issue pointed out that RocksDB was slow to delete archived WALs in case time-based and size-based expiration were enabled, and the time-based threshold (`WAL_ttl_seconds`) was small. This PR prevents the delay by taking into account `WAL_ttl_seconds` when deciding the frequency to process archived WALs for deletion.

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

Reviewed By: pdillinger

Differential Revision: D51262589

Pulled By: ajkr

fbshipit-source-id: e65431a06ee96f4c599ba84a27d1aedebecbb003
2023-11-15 15:42:28 -08:00
anand76 2222caec9e 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-14 16:25:52 -08:00
Radek Hubner a660e074cd Build RocksDBJava on Windows with Java8. (#12068)
Summary:
At the moment RocksDBJava uses the default CIrcleCI JVM on Windows builds. This can and has changed in the past and can cause some incompatibilities.

This PR addresses the problem of explicitly installing and using Liberica JDK 8 as Java 8 Is the primary target for RocksdbJava.

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

Reviewed By: cbi42

Differential Revision: D51307233

Pulled By: ajkr

fbshipit-source-id: 9cb4e173d8a9ac42e5f9fda1daf012302942fdbc
2023-11-14 14:39:31 -08:00
Yingchun Lai 37064d631b Add encfs plugin link (#12070)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/12070

Reviewed By: jaykorean

Differential Revision: D51307148

Pulled By: ajkr

fbshipit-source-id: d04335506becd5970802f87ab0573b6307479222
2023-11-14 07:33:21 -08:00
Dzmitry Ivaniuk 65d71ee371 Fix warnings when using API (#12066)
Summary:
Fixes https://github.com/facebook/rocksdb/issues/11457.

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

Reviewed By: cbi42

Differential Revision: D51259966

Pulled By: ajkr

fbshipit-source-id: a158b6f341b6b48233d917bfe4d00b639dbd8619
2023-11-13 20:03:44 -08:00
Changyu Bi e7896f03ad Enable unit test PrecludeLastLevelTest.RangeDelsCauseFileEndpointsToOverlap (#12064)
Summary:
Fixes https://github.com/facebook/rocksdb/issues/11909. The test passes after the change in https://github.com/facebook/rocksdb/issues/11917 to start mock clock from a non-zero time.

The reason for test failing is a bit complicated:
- The Put here https://github.com/pdillinger/rocksdb/blob/e4ad4a0ef1b852dc203311fb885c673c891f08e0/db/compaction/tiered_compaction_test.cc#L2045 happens before mock clock advances beyond 0.
- This causes oldest_key_time_ to be 0 for memtable.
- oldest_ancester_time of the first L0 file becomes 0
- L0 -> L5/6 compaction output files sets `oldest_ancestoer_time` to the current time due to these lines: https://github.com/facebook/rocksdb/blob/509947ce2c970d296fd0d868455d560c7f778a57/db/compaction/compaction_job.cc#L1898C34-L1904.
- This causes some small sequence number to be mapped to current time: https://github.com/facebook/rocksdb/blob/509947ce2c970d296fd0d868455d560c7f778a57/db/compaction/compaction_job.cc#L301
- Keys in L6 is being moved up to L5 due to the unexpected seqno_to_time mapping
- When compacting keys from last level to the penultimate level, we only check keys to be within user key range of penultimate level input files. If we compact the following file 3 with file 1 and output keys to L5, we can get the reported inconsistency bug.
```
L5: file 1 [K5@20, K10@kMaxSeqno], file 2 [K10@30, K14@34)
L6: file 3 [K6@5, K10@20]
```

https://github.com/facebook/rocksdb/issues/12063 will add fixes to check internal key range when compacting keys from last level up to the penultimate level.

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

Test Plan: the unit test passes

Reviewed By: ajkr

Differential Revision: D51281149

Pulled By: cbi42

fbshipit-source-id: 00b7f026c453454d9f3af5b2de441383a96f0c62
2023-11-13 15:26:52 -08:00
Jay Huh 8b8f6c63ef ColumnFamilyHandle Nullcheck in GetEntity and MultiGetEntity (#12057)
Summary:
- Add missing null check for ColumnFamilyHandle in `GetEntity()`
- `FailIfCfHasTs()` now returns `Status::InvalidArgument()` if `column_family` is null. `MultiGetEntity()` can rely on this for cfh null check.
- Added `DeleteRange` API using Default Column Family to be consistent with other major APIs (This was also causing Java Test failure after the `FailIfCfHasTs()` change)

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

Test Plan:
- Updated `DBWideBasicTest::GetEntityAsPinnableAttributeGroups` to include null CF case
- Updated `DBWideBasicTest::MultiCFMultiGetEntityAsPinnableAttributeGroups` to include null CF case

Reviewed By: jowlyzhang

Differential Revision: D51167445

Pulled By: jaykorean

fbshipit-source-id: 1c1e44fd7b7df4d2dc3bb2d7d251da85bad7d664
2023-11-13 14:30:04 -08:00
leipeng b3ffca0e29 DBImpl::DelayWrite: Remove bad WRITE_STALL histogram (#12067)
Summary:
When delay didn't happen, histogram WRITE_STALL is still recorded, and ticker STALL_MICROS is not recorded.

This is a bug, neither WRITE_STALL or STALL_MICROS should not be recorded when delay did not happen.

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

Reviewed By: cbi42

Differential Revision: D51263133

Pulled By: ajkr

fbshipit-source-id: bd82d8328fe088d613991966e83854afdabc6a25
2023-11-13 12:48:44 -08:00
brodyhuang 9fb6851918 fix(StackableDB): Resume API (#12060)
Summary:
When I call `DBWithTTLImpl::Resume()`, it returns `Status::NotSupported`.  Did `StackableDB` miss this API ?
Thanks !

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

Reviewed By: jaykorean

Differential Revision: D51202742

Pulled By: ajkr

fbshipit-source-id: 5e01a54a42efd81fd57b3c992b9af8bc45c59c9c
2023-11-13 12:09:58 -08:00
Yu Zhang 509947ce2c Quarantine files in a limbo state after a manifest error (#12030)
Summary:
Part of the procedures to handle manifest IO error is to disable file deletion in case some files in limbo state get deleted prematurely. This is not ideal because: 1) not all the VersionEdits whose commit encounter such an error contain updates for files, disabling file deletion sometimes are not necessary. 2) `EnableFileDeletion` has a force mode that could make other threads accidentally disrupt this procedure in recovery.  3) Disabling file deletion as a whole is also not as efficient as more precisely tracking impacted files from being prematurely deleted.  This PR replaces this mechanism with tracking such files and quarantine them from being deleted in `ErrorHandler`.

These are the types of files being actively tracked in quarantine in this PR:
1) new table files and blob files from a background job
2) old manifest file whose immediately following new manifest file's CURRENT file creation gets into unclear state. Current handling is not sufficient to make sure the old manifest file is kept in case it's needed.

Note that WAL logs are not part of the quarantine because `min_log_number_to_keep` is a safe mechanism and it's only updated after successful manifest commits so it can prevent this premature deletion issue from happening.

We track these files' file numbers because they share the same file number space.

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

Test Plan: Modified existing unit tests

Reviewed By: ajkr

Differential Revision: D51036774

Pulled By: jowlyzhang

fbshipit-source-id: 84ef26271fbbc888ef70da5c40fe843bd7038716
2023-11-11 08:11:11 -08:00
Andrew Kryczka 0ffc0c7db1 Allow TtlMergeOperator to wrap an unregistered MergeOperator (#12056)
Summary:
Followed mrambacher's first suggestion in https://github.com/facebook/rocksdb/pull/12044#issuecomment-1800706148.

This change allows serializing a `TtlMergeOperator` that wraps an unregistered `MergeOperator`. Such a `TtlMergeOperator` cannot be loaded (validation will fail in `TtlMergeOperator::ValidateOptions()`), but that is OK for us currently.

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

Reviewed By: hx235

Differential Revision: D51125097

Pulled By: ajkr

fbshipit-source-id: 8ed3705e8d36ab473673b9198eea6db64397ed15
2023-11-10 16:57:17 -08:00
Yu Zhang c6c683a0ca Remove the default force behavior for EnableFileDeletion API (#12001)
Summary:
Disabling file deletion can be critical for operations like making a backup, recovery from manifest IO error (for now). Ideally as long as there is one caller requesting file deletion disabled, it should be kept disabled until all callers agree to re-enable it. So this PR removes the default forcing behavior for the `EnableFileDeletion` API, and users need to explicitly pass the argument if they insisted on doing so knowing the consequence of what can be potentially disrupted.

This PR removes the API's default argument value so it will cause breakage for all users that are relying on the default value, regardless of whether the forcing behavior is critical for them.  When fixing this breakage, it's good to check if the forcing behavior is indeed needed and potential disruption is OK.

This PR also makes unit test that do not need force behavior to do a regular enable file deletion.

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

Reviewed By: ajkr

Differential Revision: D51214683

Pulled By: jowlyzhang

fbshipit-source-id: ca7b1ebf15c09eed00f954da2f75c00d2c6a97e4
2023-11-10 14:35:54 -08:00
Yueh-Hsuan Chiang 5ef92b8ea4 Add rocksdb_options_set_cf_paths (#11151)
Summary:
This PR adds a missing set function for rocksdb_options in the C-API:
rocksdb_options_set_cf_paths().  Without this function, users cannot
specify different paths for different column families as it will fall back
to db_paths.

As a bonus, this PR also includes rocksdb_sst_file_metadata_get_directory()
to the C api -- a missing public function that will also make the test easier to write.

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

Test Plan: Augment existing c_test to verify the specified cf_path.

Reviewed By: hx235

Differential Revision: D51201888

Pulled By: ajkr

fbshipit-source-id: 62a96451f26fab60ada2005ede3eea8e9b431f30
2023-11-10 11:36:11 -08:00
Yueh-Hsuan Chiang 73d223c4e2 Add auto_tuned option to RateLimiter C API (#12058)
Summary:
#### Problem
While the RocksDB C API does have the RateLimiter API, it does not
expose the auto_tuned option.

#### Summary of Change
This PR exposes auto_tuned RateLimiter option in RocksDB C API.

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

Test Plan: Augment the C API existing test to cover the new API.

Reviewed By: cbi42

Differential Revision: D51201933

Pulled By: ajkr

fbshipit-source-id: 5bc595a9cf9f88f50fee797b729ba96f09ed8266
2023-11-10 09:53:09 -08:00
Yu Zhang dfaf4dc111 Stubs for piping write time (#12043)
Summary:
As titled. This PR contains the API and stubbed implementation for piping write time.

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

Reviewed By: pdillinger

Differential Revision: D51076575

Pulled By: jowlyzhang

fbshipit-source-id: 3b341263498351b9ccaff27cf35d5aeb5bdf0cf1
2023-11-09 15:58:07 -08:00
Yingchun Lai c4c62c2304 Support to use environment variable to test customer encryption plugins (#12025)
Summary:
The CreateEnvTest.CreateEncryptedFileSystem unit test is to verify the creation functionality of EncryptedFileSystem, but now it just support the builtin CTREncryptionProvider class.
This patch make it flexible to use environment variable `TEST_FS_URI`, it is useful to test customer encryption plugins.

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

Reviewed By: anand1976

Differential Revision: D50799656

Pulled By: ajkr

fbshipit-source-id: dbcacfefbf07de9c7803f7707b34c5193bec17bf
2023-11-09 10:45:13 -08:00
brodyhuang e90e9825b4 Drop wal record when sequence is illegal (#11985)
Summary:
- Our database is corrupted, causing some sequences of wal record to be invalid (but the `record_checksum` looks fine).
- When we RecoverLogFiles in WALRecoveryMode::kPointInTimeRecovery, `assert(seq <= kMaxSequenceNumber)` will be failed.
- When it is found that sequence is illegal, can we drop the file  to recover as much data as possible ?  Thx !

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

Reviewed By: anand1976

Differential Revision: D50698039

Pulled By: ajkr

fbshipit-source-id: 1e42113b58823088d7c0c3a92af5b3efbb5f5296
2023-11-09 10:43:16 -08:00
Kasper Isager Dalsgarð f9b7877cf3 Ensure target_include_directories() is called with correct target name (#12055)
Summary:
`${PROJECT_NAME}` isn't guaranteed to match a target name when an artefact suffix is specified.

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

Reviewed By: anand1976

Differential Revision: D51125532

Pulled By: ajkr

fbshipit-source-id: cd1f4a5b11eb517c379e3ee3f78592f7e606a034
2023-11-09 10:41:38 -08:00
Hui Xiao f337533b6f Ensure and clarify how RocksDB calls TablePropertiesCollector's functions (#12053)
Summary:
**Context/Summary:**
It's intuitive for users to assume `TablePropertiesCollector::Finish()` is called only once by RocksDB internal by the word "finish".

However, this is currently not true as RocksDB also calls this function in `BlockBased/PlainTableBuilder::GetTableProperties()` to populate user collected properties on demand.

This PR avoids that by moving that populating to where we first call `Finish()` (i.e, `NotifyCollectTableCollectorsOnFinish`)

Bonus: clarified in the API that `GetReadableProperties()` will be called after `Finish()` and added UT to ensure that.

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

Test Plan:
- Modified test `DBPropertiesTest.GetUserDefinedTableProperties` to ensure `Finish()` only called once.
- Existing test particularly `db_properties_test, table_properties_collector_test` verify the functionality  `NotifyCollectTableCollectorsOnFinish` and `GetReadableProperties()` are not broken by this change.

Reviewed By: ajkr

Differential Revision: D51095434

Pulled By: hx235

fbshipit-source-id: 1c6275258f9b99dedad313ee8427119126817973
2023-11-08 14:00:36 -08:00
Peter Dillinger 65cde19f40 Safer wrapper for std::atomic, use in HCC (#12051)
Summary:
See new atomic.h file comments for motivation.

I have updated HyperClockCache to use the new atomic wrapper, fixing a few cases where an implicit conversion was accidentally used and therefore mixing std::memory_order_seq_cst where release/acquire ordering (or relaxed) was intended. There probably wasn't a real bug because I think all the cases happened to be in single-threaded contexts like constructors/destructors or statistical ops like `GetCapacity()` that don't need any particular ordering constraints.

Recommended follow-up:
* Replace other uses of std::atomic to help keep them safe from bugs.

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

Test Plan:
Did some local correctness stress testing with cache_bench. Also triggered 15 runs of fbcode_blackbox_crash_test and saw no related failures (just 3 failures in ~CacheWithSecondaryAdapter(), already known)

No performance difference seen before & after running simultaneously:
```
(while ./cache_bench -cache_type=fixed_hyper_clock_cache -populate_cache=0 -cache_size=3000000000 -ops_per_thread=500000 -threads=12 -histograms=0 2>&1 | grep parallel; do :; done) | awk '{ s += $3; c++; print "Avg time: " (s/c);}'
```

... for both fixed_hcc and auto_hcc.

Reviewed By: jowlyzhang

Differential Revision: D51090518

Pulled By: pdillinger

fbshipit-source-id: eeb324facb3185584603f9ea0c4de6f32919a2d7
2023-11-08 13:28:43 -08:00
Yingchun Lai e406c26c4e Update the API comments of NewRandomRWFile() (#11820)
Summary:
Env::NewRandomRWFile() will not create the file if it doesn't exist, as the test saying https://github.com/facebook/rocksdb/blob/main/env/env_test.cc#L2208.
This patch correct the comments of Env::NewRandomRWFile(), it may mislead the developers who use rocksdb Env() as an utility.

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

Reviewed By: ajkr

Differential Revision: D50176707

Pulled By: jowlyzhang

fbshipit-source-id: a6ee469f549360de8d551a4fe8517b4450df7b15
2023-11-08 12:28:00 -08:00
Peter Dillinger 9af25a392b Clean up AutoHyperClockTable::PurgeImpl (#12052)
Summary:
There was some unncessary logic (e.g. a dead assignment to home_shift) left over from earlier revision of the code.

Also, rename confusing ChainRewriteLock::new_head_ / GetNewHead() to saved_head_ / GetSavedHead().

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

Test Plan: existing tests

Reviewed By: jowlyzhang

Differential Revision: D51091499

Pulled By: pdillinger

fbshipit-source-id: 4b191b60a2b16085681e59d49c4d97e802869db8
2023-11-07 16:35:19 -08:00
Zaidoon Abd Al Hadi 58f2a29fb4 Expose Options::periodic_compaction_seconds through C API (#12019)
Summary:
fixes [11090](https://github.com/facebook/rocksdb/issues/11090)

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

Reviewed By: jaykorean

Differential Revision: D51076427

Pulled By: cbi42

fbshipit-source-id: de353ff66c7f73aba70ab3379e20d8c40f50d873
2023-11-07 12:46:50 -08:00
Alan Paxton c181667c4f FIX new blog post (JNI performance) Locate images correctly (#12050)
Summary:
We set up the images / references to the images wrongly in https://github.com/facebook/rocksdb/pull/11818
Images should be in the docs/static/images/… directory with an absolute reference to /static/images/…

Make it so.

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

Reviewed By: pdillinger

Differential Revision: D51079811

Pulled By: jaykorean

fbshipit-source-id: 4c1ab80d313b70d0e60eec94086451d7b2814922
2023-11-07 11:58:58 -08:00
Guozhang Wu c06309c832 Not to print unnecessary commands in Makefile (#11978)
Summary:
When I run `make check`, there is a command that should not be printed to screen, which is shown below.

```text
... ...
  Generating parallel test scripts for util_merge_operators_test
  Generating parallel test scripts for write_batch_with_index_test
make[2]: Leaving directory '/home/z/rocksdb'
make[1]: Leaving directory '/home/z/rocksdb'
  GEN      check
make[1]: Entering directory '/home/z/rocksdb'
$DEBUG_LEVEL is 1, $LIB_MODE is shared
Makefile:185: Warning: Compiling in debug mode. Don't use the resulting binary in production
printf '%s\n' ''                                                \
  'To monitor subtest <duration,pass/fail,name>,'               \
  '  run "make watch-log" in a separate window' '';             \
{ \
        printf './%s\n' db_bloom_filter_test deletefile_test env_test c_test; \
        find t -name 'run-*' -print; \
} \
  | perl -pe 's,(^.*MySQLStyleTransactionTest.*$|^.*SnapshotConcurrentAccessTest.*$|^.*SeqAdvanceConcurrentTest.*$|^t/run-table_test-HarnessTest.Randomized$|^t/run-db_test-.*(?:FileCreationRandomFailure|EncodeDecompressedBlockSizeTest)$|^.*RecoverFromCorruptedWALWithoutFlush$),100 $1,' | sort -k1,1gr | sed 's/^[.0-9]* //'                             \
  | grep -E '.'                                 \
  | grep -E -v '"^$"'                                   \
  | build_tools/gnu_parallel -j100% --plain --joblog=LOG --eta --gnu \
    --tmpdir=/dev/shm/rocksdb.6lop '{} >& t/log-{/} || bash -c "cat t/log-{/}; exit $?"' ; \
parallel_retcode=$? ; \
awk '{ if ($7 != 0 || $8 != 0) { if ($7 == "Exitval") { h = $0; } else { if (!f) print h; print; f = 1 } } } END { if(f) exit 1; }' < LOG ; \
awk_retcode=$?; \
if [ $parallel_retcode -ne 0 ] || [ $awk_retcode -ne 0 ] ; then exit 1 ; fi

To monitor subtest <duration,pass/fail,name>,
  run "make watch-log" in a separate window

Computers / CPU cores / Max jobs to run
1:local / 16 / 16
```

The `printf` command will make the output confusing. It would be better not to print it.

**Before Change**

![image](https://github.com/facebook/rocksdb/assets/30565051/92cf681a-40b7-462e-ae5b-23eeacbb8f82)

**After Change**

![image](https://github.com/facebook/rocksdb/assets/30565051/4a70b04b-e4ef-4bed-9ce0-d942ed9d132e)

**Test Plan**

Not applicable. This is a trivial change, only to add a `@` before a Makefile command, and it will not impact any workflows.

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

Reviewed By: jaykorean

Differential Revision: D51076606

Pulled By: cbi42

fbshipit-source-id: dc079ab8f60a5a5b9d04a83888884657b2e442ff
2023-11-07 11:44:20 -08:00
Peter Dillinger 16ae3548a2 AutoHCC: Improve/fix allocation/detection of grow homes (#12047)
Summary:
This change simplifies some code and logic by introducing a new atomic field that tracks the next slot to grow into. It should offer slightly better performance during the growth phase (not measurable; see Test Plan below) and fix a suspected (but unconfirmed) bug like this:
* Thread 1 is in non-trivial SplitForGrow() with grow_home=n.
* Thread 2 reaches Grow() with grow_home=2n, and waits at the start of SplitForGrow() for the rewrite lock on n. By this point, the head at 2n is marked with the new shift amount but no chain is locked.
* Thread 3 reaches Grow() with grow_home=4n, and waits before SplitForGrow() for the rewrite lock on n. By this point, the head at 4n is marked with the new shift amount but no chain is locked.
* Thread 4 reaches Grow() with grow_home=8n and meets no resistance to proceeding through a SplitForGrow() on an empty chain, permanently missing out on any entries from chain n that should have ended up here.

This is fixed by not updating the shift amount at the grow_home head until we have checked the preconditions that Grow()s feeding into this one have completed.

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

Test Plan:
Some manual cache_bench stress runs, and about 20 triggered runs of fbcode_blackbox_crash_test

No discernible performance difference on this benchmark, running before & after in parallel for a few minutes:
```
(while ./cache_bench -cache_type=auto_hyper_clock_cache -populate_cache=0 -cache_size=3000000000 -ops_per_thread=50000 -threads=12 -histograms=0 2>&1 | grep parallel; do :; done) | awk '{ s += $3; c++; print "Avg time: " (s/c);}'
```

Reviewed By: jowlyzhang

Differential Revision: D51017007

Pulled By: pdillinger

fbshipit-source-id: 5f6d6a6194fc966f94693f3205ed75c87cdad269
2023-11-07 10:40:39 -08:00
Jay Huh 2adef5367a AttributeGroups - PutEntity Implementation (#11977)
Summary:
Write Path for AttributeGroup Support. The new `PutEntity()` API uses `WriteBatch` and atomically writes WideColumns entities in multiple Column Families.

Combined the release note from PR https://github.com/facebook/rocksdb/issues/11925

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

Test Plan:
- `DBWideBasicTest::MultiCFMultiGetEntityAsPinnableAttributeGroups` updated
- `WriteBatchTest::AttributeGroupTest` added
- `WriteBatchTest::AttributeGroupSavePointTest` added

Reviewed By: ltamasi

Differential Revision: D50457122

Pulled By: jaykorean

fbshipit-source-id: 4997b265e415588ce077933082dcd1ac3eeae2cd
2023-11-06 16:52:51 -08:00
Peter Dillinger 92dc5f3e67 AutoHCC: fix a bug with "blind" Insert (#12046)
Summary:
I have finally tracked down and fixed a bug affecting AutoHCC that was causing CI crash test assertion failures in AutoHCC when using secondary cache, but I was only able to reproduce locally a couple of times, after very long runs/repetitions.

It turns out that the essential feature used by secondary cache to trigger the bug is Insert without keeping a handle, which is otherwise rarely used in RocksDB and not incorporated into cache_bench (also used for targeted correctness stress testing) until this change (new option `-blind_insert_percent`).

The problem was in copying some logic from FixedHCC that makes the entry "sharable" but unreferenced once populated, if no reference is to be saved. The problem in AutoHCC is that we can only add the entry to a chain after it is in the sharable state, and must be removed from the chain while in the "under (de)construction" state and before it is back in the "empty" state. Also, it is possible for Lookup to find entries that are not connected to any chain, by design for efficiency, and for Release to erase_if_last_ref. Therefore, we could have
* Thread 1 starts to Insert a cache entry without keeping ref, and pauses before adding to the chain.
* Thread 2 finds it with Lookup optimizations, and then does Release with `erase_if_last_ref=true` causing it to trigger erasure on the entry. It successfully locks the home chain for the entry and purges any entries pending erasure. It is OK that this entry is not found on the chain, as another thread is allowed to remove it from the chain before we are able to (but after is it marked for (de)construction). And after the purge of the chain, the entry is marked empty.
* Thread 1 resumes in adding the slot (presumed entry) to the home chain for what was being inserted, but that now violates invariants and sets up a race or double-chain-reference as another thread could insert a new entry in the slot and try to insert into a different chain.

This is easily fixed by holding on to a reference until inserted onto the chain.

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

Test Plan:
As I don't have a reliable local reproducer, I triggered 20 runs of internal CI on fbcode_blackbox_crash_test that were previously failing in AutoHCC with about 1/3 probability, and they all passed.

Also re-enabling AutoHCC in the crash test with this change. (Revert https://github.com/facebook/rocksdb/issues/12000)

Reviewed By: jowlyzhang

Differential Revision: D51016979

Pulled By: pdillinger

fbshipit-source-id: 3840fb829d65b97c779d8aed62a4a4a433aeff2b
2023-11-06 16:06:01 -08:00
Jay Huh 0ecfc4fbb4 AttributeGroups - GetEntity Implementation (#11943)
Summary:
Implementation of `GetEntity()` API that returns wide-column entities as AttributeGroups from multiple column families for a single key. Regarding the definition of Attribute groups, please see the detailed example description in PR https://github.com/facebook/rocksdb/issues/11925

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

Test Plan:
- `DBWideBasicTest::GetEntityAsPinnableAttributeGroups` added

will enable the new API in the `db_stress` after merging

Reviewed By: ltamasi

Differential Revision: D50195794

Pulled By: jaykorean

fbshipit-source-id: 218d54841ac7e337de62e13b1233b0a99bd91af3
2023-11-06 15:04:41 -08:00
Jay Huh 2dab137182 Mark more files for periodic compaction during offpeak (#12031)
Summary:
- The struct previously named `OffpeakTimeInfo` has been renamed to `OffpeakTimeOption` to indicate that it's a user-configurable option. Additionally, a new struct, `OffpeakTimeInfo`, has been introduced, which includes two fields: `is_now_offpeak` and `seconds_till_next_offpeak_start`. This change prevents the need to parse the `daily_offpeak_time_utc` string twice.
- It's worth noting that we may consider adding more fields to the `OffpeakTimeInfo` struct, such as `elapsed_seconds` and `total_seconds`, as needed for further optimization.
- Within `VersionStorageInfo::ComputeFilesMarkedForPeriodicCompaction()`, we've adjusted the `allowed_time_limit` to include files that are expected to expire by the next offpeak start.
- We might explore further optimizations, such as evenly distributing files to mark during offpeak hours, if the initial approach results in marking too many files simultaneously during the first scoring in offpeak hours. The primary objective of this PR is to prevent periodic compactions during non-offpeak hours when offpeak hours are configured. We'll start with this straightforward solution and assess whether it suffices for now.

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

Test Plan:
Unit Tests added
- `DBCompactionTest::LevelPeriodicCompactionOffpeak` for Leveled
- `DBTestUniversalCompaction2::PeriodicCompaction` for Universal

Reviewed By: cbi42

Differential Revision: D50900292

Pulled By: jaykorean

fbshipit-source-id: 267e7d3332d45a5d9881796786c8650fa0a3b43d
2023-11-06 11:43:59 -08:00
Peter Dillinger a399bbc037 More fixes and enhancements for cache_bench (#12041)
Summary:
Mostly things for using cache_bench for stress/correctness testing.
* Make secondary_cache_uri option work with HCC (forgot to update when secondary support was added for HCC)
* Add -pinned_ratio option to keep more than just one entry per thread pinned. This can be important for testing eviction stress.
* Add -vary_capacity_ratio for testing dynamically changing capacity.

Also added some overrides to CacheWrapper to help with diagnostic output.

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

Test Plan: manual, make check

Reviewed By: jowlyzhang

Differential Revision: D51013430

Pulled By: pdillinger

fbshipit-source-id: 7914adc1218f0afacace05ccd77d3bfb91a878d0
2023-11-06 09:59:09 -08:00
Alan Paxton 6979e9dc6a Create blog post from report on JNI performance work (#11818)
Summary:
We did some investigation into the performance of JNI for workloads emulating how data is carried between Java and C++
for RocksDB. The repo for our performance work lives at https://github.com/evolvedbinary/jni-benchmarks

This is a report text from that work, extracted as a blog post.
Along with some supporting files (png, pdf of graphs).

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

Reviewed By: jaykorean

Differential Revision: D50907467

Pulled By: pdillinger

fbshipit-source-id: ec6a43c83bd9ad94a3d11cfd87031e613acf7659
2023-11-06 09:15:00 -08:00
Changyu Bi 520c64fd2e Add missing status check in ExternalSstFileIngestionJob and ImportColumnFamilyJob (#12042)
Summary:
.. and update some unit tests that failed with this change. See comment in ExternalSSTFileBasicTest.IngestFileWithCorruptedDataBlock for more explanation.

The missing status check is not caught by `ASSERT_STATUS_CHECKED=1` due to this line: https://github.com/facebook/rocksdb/blob/8505b26db19871a8c8782a35a7b5be9d321d45e0/table/block_based/block.h#L394. Will explore if we can remove it.

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

Test Plan: existing unit tests.

Reviewed By: ajkr

Differential Revision: D50994769

Pulled By: cbi42

fbshipit-source-id: c91615bccd6094a91634c50b98401d456cbb927b
2023-11-06 07:41:36 -08:00
马越 19768a923a Add jni Support for API CreateColumnFamilyWithImport (#11646)
Summary:
- Add the following missing options to src/main/java/org/rocksdb/ImportColumnFamilyOptions.java and in java/rocksjni/import_column_family_options.cc in RocksJava.
- Add the struct to src/main/java/org/rocksdb/ExportImportFilesMetaData.java and in java/rocksjni/export_import_files_metadatajni.cc in RocksJava.
- Add New Java API `createColumnFamilyWithImport` to src/main/java/org/rocksdb/RocksDB.java
- Add New Java API `exportColumnFamily` to src/main/java/org/rocksdb/Checkpoint.java

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

Test Plan:
- added unit tests for exportColumnFamily in org.rocksdb.CheckpointTest
- added unit tests for createColumnFamilyWithImport to org.rocksdb.ImportColumnFamilyTest

Reviewed By: ajkr

Differential Revision: D50889700

Pulled By: cbi42

fbshipit-source-id: d623b35e445bba62a0d3c007d74352e937678f6c
2023-11-06 07:38:42 -08:00
Changyu Bi b48480cfd0 Enable TestIterateAgainstExpected() in more crash tests (#12040)
Summary:
db_stress flag `verify_iterator_with_expected_state_one_in` is only enabled for in crash test if --simple flag is set. This PR enables it for all supported crash tests by enabling it by default. This adds coverage for --txn and --enable_ts crash tests.

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

Test Plan:
ran crash tests that disabled this flag before for a few hours
```
python3 ./tools/db_crashtest.py blackbox --verify_iterator_with_expected_state_one_in=1 --txn --txn_write_policy=[0,1,2]

python3 ./tools/db_crashtest.py blackbox --verify_iterator_with_expected_state_one_in=1 --enable_ts
```

Reviewed By: ajkr, hx235

Differential Revision: D50980001

Pulled By: cbi42

fbshipit-source-id: 3daf6b4c32bdddc5df057240068162aa1a907587
2023-11-03 16:27:11 -07:00
Changyu Bi 8505b26db1 Fix stress test error message for black/whitebox test to catch failures (#12039)
Summary:
black/whitebox crash test relies on error/fail keyword in stderr to catch stress test failure. If a db_stress run prints an error message without these keyword, and then is killed before it graceful exits and prints out "Verification failed" here (https://github.com/facebook/rocksdb/blob/2648e0a747303e63796315049b9005c7320356c0/db_stress_tool/db_stress_driver.cc#L256), the error won't be caught. This is more likely to happen if db_stress is printing a stack trace. This PR fixes some error messages. Ideally in the future we should not rely on searching for keywords in stderr to determine failed stress tests.

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

Test Plan:
```
Added the following change on top of this PR to simulate exit without relevant keyword:

@@ -1586,6 +1587,8 @@ class NonBatchedOpsStressTest : public StressTest {
     assert(thread);
     assert(!rand_column_families.empty());
     assert(!rand_keys.empty());
+    fprintf(stderr, "Inconsistency");
+    thread->shared->SafeTerminate();

python3 ./tools/db_crashtest.py blackbox --simple --verify_iterator_with_expected_state_one_in=1 --interval=10

will print a stack trace but continue to run db_stress.
```

Reviewed By: jaykorean

Differential Revision: D50960076

Pulled By: cbi42

fbshipit-source-id: 5c60a1be04ce4a43adbd33f040d54434f2ae24c9
2023-11-03 09:53:22 -07:00
914022466 2648e0a747 Fix a bug when ingest plaintable sst file (#11969)
Summary:
Plaintable doesn't support SeekToLast. And GetIngestedFileInfo is using SeekToLast without checking the validity.

We are using IngestExternalFile or CreateColumnFamilyWithImport with some sst file in PlainTable format . But after running for a while, compaction error often happens. Such as
![image](https://github.com/facebook/rocksdb/assets/13954644/b4fa49fc-73fc-49ce-96c6-f198a30800b8)

I simply add some std::cerr log to find why.
![image](https://github.com/facebook/rocksdb/assets/13954644/2cf1d5ff-48cc-4125-b917-87090f764fcd)
It shows that the smallest key is always equal to largest key.
![image](https://github.com/facebook/rocksdb/assets/13954644/6d43e978-0be0-4306-aae3-f9e4ae366395)
Then I found the root cause is that PlainTable do not support SeekToLast, so the smallest key is always the same with the largest

I try to write an unit test. But it's not easy to reproduce this error.
(This PR is similar to https://github.com/facebook/rocksdb/pull/11266. Sorry for open another PR)

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

Reviewed By: ajkr

Differential Revision: D50933854

Pulled By: cbi42

fbshipit-source-id: 6c6af53c1388922cbabbe64ed3be1cdc58df5431
2023-11-02 13:45:37 -07:00
Yu Zhang a42910537d Save the correct user comparator name in OPTIONS file (#12037)
Summary:
I noticed the user comparator name in OPTIONS file can be incorrect when working on a recent stress test failure. The name of the comparator retrieved via the "Comparator::GetRootComparator" API is saved in OPTIONS file as the user comparator. The intention was to get the user comparator wrapped in the internal comparator. However `ImmutableCFOptions.user_comparator` has always been a user comparator of type `Comparator`. The corresponding `GetRootComparator` API is also defined only for user comparator type `Comparator`, not the internal key comparator type `InternalKeyComparator`.

For built in comparator `BytewiseComparator` and `ReverseBytewiseComparator`, there is no difference between `Comparator::Name` and `Comparator::GetRootComparator::Name` because these built in comparators' root comparator is themselves. However, for built in comparator `BytewiseComparatorWithU64Ts` and `ReverseBytewiseComparatorWithU64Ts`, there are differences. So this change update the logic to persist the user comparator's name, not its root comparator's name.

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

Test Plan:
The restore flow in stress test, which relies on converting Options object to string and back to Options object is updated to help validate comparator object can be correctly serialized and deserialized with the OPTIONS file mechanism

Updated unit test to use a comparator that has a root comparator that is not itself.

Reviewed By: cbi42

Differential Revision: D50909750

Pulled By: jowlyzhang

fbshipit-source-id: 9086d7135c7a6f4b5565fb47fce194ea0a024f52
2023-11-02 13:27:59 -07:00
马越 8e1adab5ce add RocksDB#clipColumnFamily to Java API (#11868)
Summary:
### main change:

- add java clipColumnFamily api in Rocksdb.java
The method signature of the new API is
 ```
 public void clipColumnFamily(final ColumnFamilyHandle columnFamilyHandle, final byte[] beginKey,
      final byte[] endKey)
```
### Test
add unit test RocksDBTest#clipColumnFamily()

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

Reviewed By: jaykorean

Differential Revision: D50889783

Pulled By: cbi42

fbshipit-source-id: 7f545171ad9adb9c20bdd92efae2e6bc55d5703f
2023-11-02 08:00:08 -07:00
Yu Zhang 4b013dcbed Remove VersionEdit's friends pattern (#12024)
Summary:
Almost each of VersionEdit private member has its own getter and setter. Current code access them with a combination of directly accessing private members and via getter and setters. There is no obvious benefits to have this pattern except potential performance gains. I tried this simple benchmark for removing the friends pattern completely, and there is no obvious regression. So I think it would good to remove VersionEdit's friends completely.

```TEST_TMPDIR=/dev/shm/rocksdb1 ./db_bench -benchmarks=fillseq -memtablerep=vector -allow_concurrent_memtable_write=false -num_column_families=10 -num=50000000```

With change:
fillseq      :       2.994 micros/op 333980 ops/sec 149.710 seconds 50000000 operations;   36.9 MB/s
fillseq      :       3.033 micros/op 329656 ops/sec 151.673 seconds 50000000 operations;   36.5 MB/s
fillseq      :       2.991 micros/op 334369 ops/sec 149.535 seconds 50000000 operations;   37.0 MB/s
Without change:
fillseq      :       3.015 micros/op 331715 ops/sec 150.732 seconds 50000000 operations;   36.7 MB/s
fillseq      :       3.044 micros/op 328553 ops/sec 152.182 seconds 50000000 operations;   36.3 MB/s
fillseq      :       3.091 micros/op 323520 ops/sec 154.550 seconds 50000000 operations;   35.8 MB/s

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

Reviewed By: pdillinger

Differential Revision: D50806066

Pulled By: jowlyzhang

fbshipit-source-id: 35d287ce638a38c30f243f85992e615b4c90eb27
2023-11-01 12:04:11 -07:00
Jay Huh 04225a2cfa Fix for RecoverFromRetryableBGIOError starting with recovery_in_prog_ false (#11991)
Summary:
cbi42 helped investigation and found a potential scenario where `RecoverFromRetryableBGIOError()` may start with `recovery_in_prog_ ` set as false. (and other booleans like `bg_error_` and `soft_error_no_bg_work_`)

**Thread 1**
- `StartRecoverFromRetryableBGIOError()`): (mutex held) sets `recovery_in_prog_ = true`

**Thread 1's `recovery_thread_`**
- (waits for mutex and acquires it)
- `RecoverFromRetryableBGIOError()` -> `ResumeImpl()` -> `ClearBGError()`: sets `recovery_in_prog_ = false`
- `ClearBGError()` -> `NotifyOnErrorRecoveryEnd()`: releases `mutex`

**Thread 2**
- `StartRecoverFromRetryableBGIOError()`): (mutex held) sets `recovery_in_prog_ = true`
- Waits for Thread 1 (`recovery_thread_`) to finish

**Thread 1's `recovery_thread_`**
- re-lock mutex in `NotifyOnErrorRecoveryEnd()`
- Still inside `RecoverFromRetryableBGIOError()`: sets `recovery_in_prog_ = false`
- Done

**Thread 2's `recovery_thread_`**
- recovery thread started with `recovery_in_prog_` set as `false`

# Fix
- Remove double-clearing `bg_error_`,  `recovery_in_prog_` and other fields after `ResumeImpl()` already returned `OK()`.
- Minor typo and linter fixes in `DBErrorHandlingFSTest`

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

Test Plan:
- `DBErrorHandlingFSTest::MultipleRecoveryThreads` added to reproduce the scenario.
- Adding `assert(recovery_in_prog_);` at the start of `ErrorHandler::RecoverFromRetryableBGIOError()` fails the test without the fix and succeeds with the fix as expected.

Reviewed By: cbi42

Differential Revision: D50506113

Pulled By: jaykorean

fbshipit-source-id: 6dabe01e9ecd3fc50bbe9019587f2f4858bed9c6
2023-10-31 16:13:36 -07:00
Yu Zhang 0b057a7acc Initialize comparator explicitly in PrepareOptionsForRestoredDB() (#12034)
Summary:
This is to fix below error seeing in stress test:
```
Failure in DB::Open in backup/restore with: Invalid argument: Cannot open a column family and disable user-defined timestamps feature if its existing persist_user_defined_timestamps flag is not false.
```

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

Reviewed By: cbi42

Differential Revision: D50860689

Pulled By: jowlyzhang

fbshipit-source-id: ebc6cf0a75caa43d3d3bd58e3d5c2ac754cc637c
2023-10-31 16:10:48 -07:00
Adam Retter e0c45c15a7 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 12:23:34 -07:00
Changyu Bi 2818a74b95 Initialize merge operator explicitly in PrepareOptionsForRestoredDB() (#12033)
Summary:
We are seeing the following stress test failure: `Failure in DB::Get in backup/restore with: Invalid argument: merge_operator is not properly initialized. Verification failed: Backup/restore failed: Invalid argument: merge_operator is not properly initialized.`. The reason is likely that `GetColumnFamilyOptionsFromString()` does not set merge operator if it's a customized merge operator. Fixing it by initializing merge operator explicitly.

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

Test Plan:
this repro gives the error consistently before this PR
```
./db_stress --acquire_snapshot_one_in=10000 --adaptive_readahead=0 --allow_concurrent_memtable_write=1 --allow_data_in_errors=True --async_io=0 --atomic_flush=1 --auto_readahead_size=1 --avoid_flush_during_recovery=0 --avoid_unnecessary_blocking_io=1 --backup_max_size=1048576000000 --backup_one_in=50 --batch_protection_bytes_per_key=8 --block_protection_bytes_per_key=2 --block_size=16384 --bloom_before_level=2147483646 --bloom_bits=31.014388066505518 --bottommost_compression_type=lz4hc --bottommost_file_compaction_delay=0 --bytes_per_sync=0 --cache_index_and_filter_blocks=0 --cache_size=33554432 --cache_type=fixed_hyper_clock_cache --charge_compression_dictionary_building_buffer=1 --charge_file_metadata=1 --charge_filter_construction=0 --charge_table_reader=1 --checkpoint_one_in=1000000 --checksum_type=kxxHash --clear_column_family_one_in=0 --column_families=1 --compact_files_one_in=1000000 --compact_range_one_in=1000000 --compaction_pri=3 --compaction_readahead_size=0 --compaction_ttl=10 --compressed_secondary_cache_ratio=0.0 --compressed_secondary_cache_size=0 --compression_checksum=1 --compression_max_dict_buffer_bytes=4095 --compression_max_dict_bytes=16384 --compression_parallel_threads=1 --compression_type=none --compression_use_zstd_dict_trainer=1 --compression_zstd_max_train_bytes=0 --continuous_verification_interval=0 --data_block_index_type=0 --db=/dev/shm/rocksdb_test/rocksdb_crashtest_blackbox --db_write_buffer_size=0 --delpercent=4 --delrangepercent=1 --destroy_db_initially=1 --detect_filter_construct_corruption=0 --disable_wal=1 --enable_compaction_filter=0 --enable_pipelined_write=0 --enable_thread_tracking=0 --expected_values_dir=/dev/shm/rocksdb_test/rocksdb_crashtest_expected --fail_if_options_file_error=1 --fifo_allow_compaction=0 --file_checksum_impl=xxh64 --flush_one_in=1000000 --format_version=2 --get_current_wal_file_one_in=0 --get_live_files_one_in=1000000 --get_property_one_in=1000000 --get_sorted_wal_files_one_in=0 --index_block_restart_interval=10 --index_type=2 --ingest_external_file_one_in=0 --initial_auto_readahead_size=16384 --iterpercent=10 --key_len_percent_dist=1,30,69 --level_compaction_dynamic_level_bytes=1 --lock_wal_one_in=1000000 --long_running_snapshots=1 --manual_wal_flush_one_in=0 --mark_for_compaction_one_file_in=10 --max_auto_readahead_size=524288 --max_background_compactions=1 --max_bytes_for_level_base=67108864 --max_key=100 --max_key_len=3 --max_manifest_file_size=1073741824 --max_write_batch_group_size_bytes=1048576 --max_write_buffer_number=3 --max_write_buffer_size_to_maintain=8388608 --memtable_max_range_deletions=1000 --memtable_prefix_bloom_size_ratio=0 --memtable_protection_bytes_per_key=2 --memtable_whole_key_filtering=0 --memtablerep=skip_list --min_write_buffer_number_to_merge=2 --mmap_read=1 --mock_direct_io=False --nooverwritepercent=1 --num_file_reads_for_auto_readahead=0 --open_files=-1 --open_metadata_write_fault_one_in=0 --open_read_fault_one_in=0 --open_write_fault_one_in=16 --ops_per_thread=100000000 --optimize_filters_for_memory=1 --paranoid_file_checks=0 --partition_filters=0 --partition_pinning=0 --pause_background_one_in=1000000 --periodic_compaction_seconds=0 --prefix_size=-1 --prefixpercent=0 --prepopulate_block_cache=1 --preserve_internal_time_seconds=0 --progress_reports=0 --read_fault_one_in=0 --readahead_size=0 --readpercent=50 --recycle_log_file_num=0 --reopen=0 --secondary_cache_fault_one_in=0 --set_options_one_in=0 --snapshot_hold_ops=100000 --sst_file_manager_bytes_per_sec=0 --sst_file_manager_bytes_per_truncate=0 --stats_dump_period_sec=0 --subcompactions=1 --sync=0 --sync_fault_injection=1 --target_file_size_base=16777216 --target_file_size_multiplier=1 --test_batches_snapshots=0 --top_level_index_pinning=0 --unpartitioned_pinning=1 --use_direct_io_for_flush_and_compaction=0 --use_direct_reads=0 --use_full_merge_v1=0 --use_get_entity=0 --use_merge=1 --use_multi_get_entity=0 --use_multiget=1 --use_put_entity_one_in=10 --use_write_buffer_manager=0 --user_timestamp_size=0 --value_size_mult=32 --verification_only=0 --verify_checksum=1 --verify_checksum_one_in=1000000 --verify_db_one_in=100000 --verify_file_checksums_one_in=1000000 --verify_iterator_with_expected_state_one_in=5 --verify_sst_unique_id_in_manifest=1 --wal_bytes_per_sync=524288 --wal_compression=zstd --write_buffer_size=33554432 --write_dbid_to_manifest=0 --write_fault_one_in=0 --writepercent=35
```

Reviewed By: hx235

Differential Revision: D50825558

Pulled By: cbi42

fbshipit-source-id: 8468dc0444c112415a515af8291ef3abec8a42de
2023-10-31 07:39:41 -07:00
Yingchun Lai 76402c034e Fix incorrect parameters order in env_basic_test.cc (#11997)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/11997

Reviewed By: jaykorean

Differential Revision: D50608182

Pulled By: ajkr

fbshipit-source-id: d33cfdb5adfea91175c8fa21e8b80e22f728f6c6
2023-10-30 10:47:04 -07:00
Radek Hubner b3fd3838d4 Remove build dependencies for java tests. (#12021)
Summary:
Final fix for https://github.com/facebook/rocksdb/issues/12013

- Reverting back changes on CirleCI explicit image declaration.
- Removed CMake dependencies between java classed and java test classes.

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

Reviewed By: akankshamahajan15

Differential Revision: D50745392

Pulled By: ajkr

fbshipit-source-id: 6a7a1da1e7e4da8da72130c9272915974e10fffc
2023-10-30 10:08:19 -07:00
Yu Zhang 60df39e530 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-28 09:50:52 -07:00
Hui Xiao 212b5bf826 Deep-copy Options in restored db for stress test to avoid race with SetOptions() (#12015)
Summary:
**Context**
DB open will persist the `Options` in memory to options file and verify the file right after the write. The verification is done by comparing the options from parsing the written options file against the `Options` object in memory. Upon inconsistency, corruption such as https://github.com/facebook/rocksdb/blob/main/options/options_parser.cc#L725 will be returned.

This verification assumes the `Options` object in memory is not changed from before the write till the verification. This assumption can break during [opening the restored db in stress test](https://github.com/facebook/rocksdb/blob/0f141352d8de2f743d222a6f2ff493a31dd2838c/db_stress_tool/db_stress_test_base.cc#L1784-L1799).

This [line](https://github.com/facebook/rocksdb/blob/0f141352d8de2f743d222a6f2ff493a31dd2838c/db_stress_tool/db_stress_test_base.cc#L1770) makes it shares some pointer options (e.g, `std::shared_ptr<const FilterPolicy> filter_policy`) with other threads (e.g, SetOptions()) in db stress.

And since https://github.com/facebook/rocksdb/pull/11838, filter_policy's field `bloom_before_level ` has now been mutable by SetOptions(). Therefore we started to see stress test failure like below:

```
Failure in DB::Open in backup/restore with: IO error: DB::Open() failed --- Unable to persist Options file: IO error: Unable to persist options.: Corruption: [RocksDBOptionsParser]:failed the verification on BlockBasedTable::: filter_policy.id

Verification failed: Backup/restore failed: IO error: DB::Open() failed --- Unable to persist Options file: IO error: Unable to persist options.: Corruption: [RocksDBOptionsParser]:failed the verification on BlockBasedTable::: filter_policy.id

db_stress: db_stress_tool/db_stress_test_base.cc:479: void rocksdb::StressTest::ProcessStatus(rocksdb::SharedState*, std::string, rocksdb::Status) const: Assertion `false' failed.
```

**Summary**
This PR uses "deep copy" of the `options_` by CreateXXXFromString() to avoid sharing pointer options.

**Test plan**
Run the below db stress command that failed before this PR and pass after
```
./db_stress --column_families=1 --threads=2 --preserve_unverified_changes=0 --acquire_snapshot_one_in=10000 --adaptive_readahead=0 --allow_data_in_errors=True --async_io=0 --auto_readahead_size=1 --avoid_flush_during_recovery=0 --avoid_unnecessary_blocking_io=0 --backup_max_size=104857600 --backup_one_in=10 --batch_protection_bytes_per_key=0 --block_protection_bytes_per_key=1 --block_size=16384 --bloom_before_level=2147483646 --bloom_bits=0 --bottommost_compression_type=disable --bottommost_file_compaction_delay=86400 --bytes_per_sync=0 --cache_index_and_filter_blocks=0 --cache_size=33554432 --cache_type=tiered_auto_hyper_clock_cache --charge_compression_dictionary_building_buffer=0 --charge_file_metadata=0 --charge_filter_construction=0 --charge_table_reader=1 --checkpoint_one_in=1000000 --checksum_type=kXXH3 --clear_column_family_one_in=0 --compact_files_one_in=1000000 --compact_range_one_in=1000000 --compaction_pri=2 --compaction_readahead_size=0 --compaction_ttl=0 --compressed_secondary_cache_ratio=0.3333333333333333 --compressed_secondary_cache_size=0 --compression_checksum=1 --compression_max_dict_buffer_bytes=0 --compression_max_dict_bytes=0 --compression_parallel_threads=1 --compression_type=lz4 --compression_use_zstd_dict_trainer=1 --compression_zstd_max_train_bytes=0 --continuous_verification_interval=0 --data_block_index_type=1 --db=/dev/shm/rocksdb_test/rocksdb_crashtest_blackbox --db_write_buffer_size=8388608 --delpercent=4 --delrangepercent=1 --destroy_db_initially=1 --detect_filter_construct_corruption=0 --disable_wal=0 --enable_compaction_filter=0 --enable_pipelined_write=0 --enable_thread_tracking=0 --expected_values_dir=/dev/shm/rocksdb_test/rocksdb_crashtest_expected --fail_if_options_file_error=1 --fifo_allow_compaction=1 --file_checksum_impl=big --flush_one_in=1000000 --format_version=2 --get_current_wal_file_one_in=0 --get_live_files_one_in=1000000 --get_property_one_in=1000000 --get_sorted_wal_files_one_in=0 --index_block_restart_interval=14 --index_type=0 --ingest_external_file_one_in=0 --initial_auto_readahead_size=524288 --iterpercent=10 --key_len_percent_dist=1,30,69 --level_compaction_dynamic_level_bytes=1 --lock_wal_one_in=1000000 --long_running_snapshots=1 --manual_wal_flush_one_in=1000 --mark_for_compaction_one_file_in=0 --max_auto_readahead_size=0 --max_background_compactions=20 --max_bytes_for_level_base=10485760 --max_key=2500 --max_key_len=3 --max_manifest_file_size=1073741824 --max_write_batch_group_size_bytes=16777216 --max_write_buffer_number=3 --max_write_buffer_size_to_maintain=4194304 --memtable_max_range_deletions=0 --memtable_prefix_bloom_size_ratio=0 --memtable_protection_bytes_per_key=0 --memtable_whole_key_filtering=0 --memtablerep=skip_list --min_write_buffer_number_to_merge=1 --mmap_read=1 --mock_direct_io=False --nooverwritepercent=1 --num_file_reads_for_auto_readahead=1 --open_files=500000 --open_metadata_write_fault_one_in=0 --open_read_fault_one_in=0 --open_write_fault_one_in=0 --ops_per_thread=100000000 --optimize_filters_for_memory=0 --paranoid_file_checks=1 --partition_filters=0 --partition_pinning=2 --pause_background_one_in=1000000 --periodic_compaction_seconds=0 --prefix_size=-1 --prefixpercent=0 --prepopulate_block_cache=0 --preserve_internal_time_seconds=3600 --progress_reports=0 --read_fault_one_in=1000 --readahead_size=0 --readpercent=50 --recycle_log_file_num=0 --reopen=0 --secondary_cache_fault_one_in=0 --secondary_cache_uri= --set_options_one_in=5 --snapshot_hold_ops=100000 --sst_file_manager_bytes_per_sec=104857600 --sst_file_manager_bytes_per_truncate=1048576 --stats_dump_period_sec=600 --subcompactions=3 --sync=0 --sync_fault_injection=0 --target_file_size_base=2097152 --target_file_size_multiplier=2 --test_batches_snapshots=0 --top_level_index_pinning=2 --unpartitioned_pinning=0 --use_direct_io_for_flush_and_compaction=0 --use_direct_reads=0 --use_full_merge_v1=0 --use_get_entity=0 --use_merge=1 --use_multi_get_entity=0 --use_multiget=1 --use_put_entity_one_in=0 --use_write_buffer_manager=0 --user_timestamp_size=0 --value_size_mult=32 --verification_only=0 --verify_checksum=1 --verify_checksum_one_in=1000000 --verify_db_one_in=100000 --verify_file_checksums_one_in=1000000 --verify_sst_unique_id_in_manifest=1 --wal_bytes_per_sync=524288 --wal_compression=zstd --write_buffer_size=4194304 --write_dbid_to_manifest=1 --write_fault_one_in=0 --writepercent=35
```

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

Reviewed By: pdillinger

Differential Revision: D50666136

Pulled By: hx235

fbshipit-source-id: 804acc23aecb4eedfe5c44f732e86291f2420b2b
2023-10-27 17:07:39 -07:00
Jay Huh e230e4d248 Make OffpeakTimeInfo available in VersionSet (#12018)
Summary:
As mentioned in  https://github.com/facebook/rocksdb/issues/11893, we are going to use the offpeak time information to pre-process TTL-based compactions. To do so, we need to access `daily_offpeak_time_utc` in `VersionStorageInfo::ComputeCompactionScore()` where we pick the files to compact. This PR is to make the offpeak time information available at the time of compaction-scoring. We are not changing any compaction scoring logic just yet. Will follow up in a separate PR.

There were two ways to achieve what we want.
1.  Make `MutableDBOptions` available in `ColumnFamilyData` and `ComputeCompactionScore()` take `MutableDBOptions` along with `ImmutableOptions` and `MutableCFOptions`.
2. Make `daily_offpeak_time_utc` and `IsNowOffpeak()` available in `VersionStorageInfo`.

We chose the latter as it involves smaller changes.

This change includes the following
- Introduction of `OffpeakTimeInfo` and `IsNowOffpeak()` has been moved from `MutableDBOptions`
- `OffpeakTimeInfo` added to `VersionSet` and it can be set during construction and by `ChangeOffpeakTimeInfo()`
- During `SetDBOptions()`, if offpeak time info needs to change, it calls `MaybeScheduleFlushOrCompaction()` to re-compute compaction scores and process compactions as needed

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

Test Plan:
- `DBOptionsTest::OffpeakTimes` changed to include checks for `MaybeScheduleFlushOrCompaction()` calls and `VersionSet`'s OffpeakTimeInfo value change during `SetDBOptions()`.
- `VersionSetTest::OffpeakTimeInfoTest` added to test `ChangeOffpeakTimeInfo()`. `IsNowOffpeak()` tests moved from `DBOptionsTest::OffpeakTimes`

Reviewed By: pdillinger

Differential Revision: D50723881

Pulled By: jaykorean

fbshipit-source-id: 3cff0291936f3729c0e9c7750834b9378fb435f6
2023-10-27 15:56:48 -07:00
Yu Zhang 526f36b483 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:48:21 -07:00
anand76 52be8f54f2 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-25 16:54:50 -07:00
Radek Hubner 8ee009f0d8 Downgrade windows 2019 build to older image. (#12014)
Summary:
This should fix failed java windows build  https://github.com/facebook/rocksdb/issues/12013

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

Reviewed By: ajkr

Differential Revision: D50664503

Pulled By: akankshamahajan15

fbshipit-source-id: 3466ce42d3cae3f8e0beba88a18744d647a32a2c
2023-10-25 15:43:05 -07:00
Hui Xiao 0f141352d8 Fix race between flush error recovery and db destruction (#12002)
Summary:
**Context:**
DB destruction will wait for ongoing error recovery through `EndAutoRecovery()` and join the recovery thread: https://github.com/facebook/rocksdb/blob/519f2a41fb76e5644c63e4e588addb3b88b36580/db/db_impl/db_impl.cc#L525 -> https://github.com/facebook/rocksdb/blob/519f2a41fb76e5644c63e4e588addb3b88b36580/db/error_handler.cc#L250 -> https://github.com/facebook/rocksdb/blob/519f2a41fb76e5644c63e4e588addb3b88b36580/db/error_handler.cc#L808-L823

However, due to a race between flush error recovery and db destruction, recovery can actually start after such wait during the db shutdown. The consequence is that the recovery thread created as part of this recovery will not be properly joined upon its destruction as part the db destruction. It then crashes the program as below.

```
std::terminate()
std::default_delete<std::thread>::operator()(std::thread*) const
std::unique_ptr<std::thread, std::default_delete<std::thread>>::~unique_ptr()
rocksdb::ErrorHandler::~ErrorHandler() (rocksdb/db/error_handler.h:31)
rocksdb::DBImpl::~DBImpl() (rocksdb/db/db_impl/db_impl.cc:725)
rocksdb::DBImpl::~DBImpl() (rocksdb/db/db_impl/db_impl.cc:725)
rocksdb::DBTestBase::Close() (rocksdb/db/db_test_util.cc:678)
```

**Summary:**
This PR fixed it by considering whether EndAutoRecovery() has been called before creating such thread. This fix is similar to how we currently [handle](https://github.com/facebook/rocksdb/blob/519f2a41fb76e5644c63e4e588addb3b88b36580/db/error_handler.cc#L688-L694) such case inside the created recovery thread.

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

Test Plan: A new UT repro-ed the crash before this fix and and pass after.

Reviewed By: ajkr

Differential Revision: D50586191

Pulled By: hx235

fbshipit-source-id: b372f6d7a94eadee4b9283b826cc5fb81779a093
2023-10-25 11:59:09 -07:00
qiuchengxuan f2c9075d16 Fix dead loop with kSkipAnyCorruptedRecords mode selected in some cases (#11955) (#11979)
Summary:
With fragmented record span across multiple blocks, if any following blocks corrupted with arbitary data, and intepreted log number less than the current log number, program will fall into infinite loop due to
not skipping buffer leading bytes

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

Test Plan: existing unit tests

Reviewed By: ajkr

Differential Revision: D50604408

Pulled By: jowlyzhang

fbshipit-source-id: e50a0c7e7c3d293fb9d5afec0a3eb4a1835b7a3b
2023-10-25 09:16:24 -07:00
Peter Dillinger dc87847e65 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-24 16:20:37 -07:00
Myth 0ff7665c95 Fix low priority write may cause crash when it is rate limited (#11932)
Summary:
Fixed https://github.com/facebook/rocksdb/issues/11902

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

Reviewed By: akankshamahajan15

Differential Revision: D50573356

Pulled By: hx235

fbshipit-source-id: adeb1abdc43b523b0357746055ce4a2eabde56a1
2023-10-24 14:41:46 -07:00
Hui Xiao ab15d33566 Update history, version and format testing for 8.8 (#12004)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/12004

Reviewed By: cbi42

Differential Revision: D50586984

Pulled By: hx235

fbshipit-source-id: 1480a8c2757340ebf83510557104aaa0e437b3ae
2023-10-24 12:03:07 -07:00
Akanksha Mahajan 917fd87513 Error out in case of std errors in blackbox test and export file in TARGETS
Summary:
-  Right now in blackbox test we don't exit if there are std::error as we do in whitebox crash tests. As result those errors are swallowed.
It only errors out if state is unexpected.

One example that was noticed in blackbox crash test -
```
stderr has error message:
***Error restoring historical expected values: Corruption: DB is older than any restorable expected state***
Running db_stress with pid=30454: /packages/rocksdb_db_stress_internal_repo/rocks_db_stress  ....
```

-  This diff also provided support to export files -  db_crashtest.py file to be used by different repo.

Reviewed By: ajkr

Differential Revision: D50564889

fbshipit-source-id: 7bafbbc6179dc79467ca2b680fe83afc7850616a
2023-10-24 11:46:18 -07:00
Hui Xiao 99b371b417 Skip subsequent trace writes after encountering trace write failure (#11996)
Summary:
**Context/Summary:**
We ignore trace writing status e.g, https://github.com/facebook/rocksdb/blob/543191f2eacadf14e3aa6ff9a08f85a8ad82da95/db/db_impl/db_impl_write.cc#L221-L222

If a write into the trace file fails, subsequent trace write will continue onto the same file.

This will trigger the assertion `assert(sync_without_flush_called_)` intended to catch write to a file that has previously seen error, added in https://github.com/facebook/rocksdb/pull/10489, https://github.com/facebook/rocksdb/pull/10555

Alternative (rejected) is to handle trace writing status at a higher level at e.g, https://github.com/facebook/rocksdb/blob/543191f2eacadf14e3aa6ff9a08f85a8ad82da95/db/db_impl/db_impl_write.cc#L221-L222. However, it makes sense to ignore such status considering tracing is not a critical but assistant component to db operation. And this alternative requires more code change. So it's better to handle the failure at a lower level as this PR

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

Test Plan: Add new UT failed before this PR and pass after

Reviewed By: akankshamahajan15

Differential Revision: D50532467

Pulled By: hx235

fbshipit-source-id: f2032abafd94917adbf89a20841d15b448782a33
2023-10-24 09:58:02 -07:00
182 changed files with 5017 additions and 1515 deletions

No files matched your search

+32 -4
View File
@@ -54,6 +54,7 @@ commands:
command: |
echo "Installing CMake..."
choco install cmake --installargs 'ADD_CMAKE_TO_PATH=System' -y
choco install liberica8jdk -y
mkdir $Env:THIRDPARTY_HOME
cd $Env:THIRDPARTY_HOME
echo "Building Snappy dependency..."
@@ -67,9 +68,10 @@ commands:
- run:
name: "Build RocksDB"
command: |
$env:Path = $env:JAVA_HOME + ";" + $env:Path
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
@@ -227,6 +229,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:
@@ -563,6 +568,24 @@ 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
JAVA_HOME: C:/Program Files/BellSoft/LibericaJDK-8
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
@@ -572,10 +595,12 @@ jobs:
CMAKE_HOME: C:/Program Files/CMake
CMAKE_BIN: C:/Program Files/CMake/bin/cmake.exe
CTEST_BIN: C:/Program Files/CMake/bin/ctest.exe
JAVA_HOME: C:/Program Files/BellSoft/LibericaJDK-8
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
@@ -588,10 +613,12 @@ jobs:
CMAKE_HOME: C:/Program Files/CMake
CMAKE_BIN: C:/Program Files/CMake/bin/cmake.exe
CTEST_BIN: C:/Program Files/CMake/bin/ctest.exe
JAVA_HOME: C:/Program Files/BellSoft/LibericaJDK-8
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
@@ -613,7 +640,7 @@ jobs:
- post-steps
build-linux-java-pmd:
machine:
machine:
image: ubuntu-2004:202111-02
resource_class: large
environment:
@@ -634,7 +661,7 @@ jobs:
- post-pmd-steps
build-linux-java-static:
executor: linux-docker
executor: linux-java-docker
resource_class: large
steps:
- pre-steps
@@ -647,7 +674,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:
@@ -887,6 +914,7 @@ workflows:
- build-linux-mini-crashtest
jobs-windows:
jobs:
- build-windows-vs2022-avx2
- build-windows-vs2022
- build-windows-vs2019
- build-cmake-mingw
+5 -3
View File
@@ -772,6 +772,7 @@ set(SOURCES
options/configurable.cc
options/customizable.cc
options/db_options.cc
options/offpeak_time_info.cc
options/options.cc
options/options_helper.cc
options/options_parser.cc
@@ -1105,11 +1106,15 @@ set(BUILD_VERSION_CC ${CMAKE_BINARY_DIR}/build_version.cc)
configure_file(util/build_version.cc.in ${BUILD_VERSION_CC} @ONLY)
add_library(${ROCKSDB_STATIC_LIB} STATIC ${SOURCES} ${BUILD_VERSION_CC})
target_include_directories(${ROCKSDB_STATIC_LIB} PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
target_link_libraries(${ROCKSDB_STATIC_LIB} PRIVATE
${THIRDPARTY_LIBS} ${SYSTEM_LIBS})
if(ROCKSDB_BUILD_SHARED)
add_library(${ROCKSDB_SHARED_LIB} SHARED ${SOURCES} ${BUILD_VERSION_CC})
target_include_directories(${ROCKSDB_SHARED_LIB} PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
target_link_libraries(${ROCKSDB_SHARED_LIB} PRIVATE
${THIRDPARTY_LIBS} ${SYSTEM_LIBS})
@@ -1596,6 +1601,3 @@ option(WITH_BENCHMARK "build benchmark tests" OFF)
if(WITH_BENCHMARK)
add_subdirectory(${PROJECT_SOURCE_DIR}/microbench/)
endif()
target_include_directories(${PROJECT_NAME} PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
+57
View File
@@ -1,6 +1,63 @@
# Rocksdb Change Log
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
## 8.9.2 (12/12/2023)
### Bug Fixes
* A lookup by MultiGet in a TieredCache that goes to the local flash cache and finishes with very low latency, i.e before the subsequent call to WaitAll, is ignored, resulting in a false negative and a memory leak.
* Sanitize the cache_opts->secondary_cache field in TieredCacheOptions. It can be mistakenly set by the user, which would conflict with nvm_sec_cache in TieredCacheOptions if not sanitized to nullptr.
## 8.9.1 (12/8/2023)
### Bug Fixes
* Avoid destroying the periodic task scheduler's default timer in order to prevent static destruction order issues.
## 8.9.0 (11/17/2023)
### New Features
* Add GetEntity() and PutEntity() API implementation for Attribute Group support. Through the use of Column Families, AttributeGroup enables users to logically group wide-column entities.
### Public API Changes
* Added rocksdb_ratelimiter_create_auto_tuned API to create an auto-tuned GenericRateLimiter.
* Added clipColumnFamily() to the Java API to clip the entries in the CF according to the range [begin_key, end_key).
* Make the `EnableFileDeletion` API not default to force enabling. For users that rely on this default behavior and still
want to continue to use force enabling, they need to explicitly pass a `true` to `EnableFileDeletion`.
* Add new Cache APIs GetSecondaryCacheCapacity() and GetSecondaryCachePinnedUsage() to return the configured capacity, and cache reservation charged to the secondary cache.
### Behavior Changes
* During off-peak hours defined by `daily_offpeak_time_utc`, the compaction picker will select a larger number of files for periodic compaction. This selection will include files that are projected to expire by the next off-peak start time, ensuring that these files are not chosen for periodic compaction outside of off-peak hours.
* If an error occurs when writing to a trace file after `DB::StartTrace()`, the subsequent trace writes are skipped to avoid writing to a file that has previously seen error. In this case, `DB::EndTrace()` will also return a non-ok status with info about the error occured previously in its status message.
* Deleting stale files upon recovery are delegated to SstFileManger if available so they can be rate limited.
* Make RocksDB only call `TablePropertiesCollector::Finish()` once.
* When `WAL_ttl_seconds > 0`, we now process archived WALs for deletion at least every `WAL_ttl_seconds / 2` seconds. Previously it could be less frequent in case of small `WAL_ttl_seconds` values when size-based expiration (`WAL_size_limit_MB > 0 `) was simultaneously enabled.
### Bug Fixes
* Fixed a crash or assertion failure bug in experimental new HyperClockCache variant, especially when running with a SecondaryCache.
* Fix a race between flush error recovery and db destruction that can lead to db crashing.
* Fixed some bugs in the index builder/reader path for user-defined timestamps in Memtable only feature.
## 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.
### 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
### 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.
+4 -4
View File
@@ -539,7 +539,7 @@ endif
ifdef USE_CLANG
# Used by some teams in Facebook
WARNING_FLAGS += -Wshift-sign-overflow -Wambiguous-reversed-operator
WARNING_FLAGS += -Wshift-sign-overflow -Wambiguous-reversed-operator -Wimplicit-fallthrough
endif
ifeq ($(PLATFORM), OS_OPENBSD)
@@ -994,7 +994,7 @@ endif
.PHONY: check_0
check_0:
printf '%s\n' '' \
@printf '%s\n' '' \
'To monitor subtest <duration,pass/fail,name>,' \
' run "make watch-log" in a separate window' ''; \
{ \
@@ -1016,7 +1016,7 @@ valgrind-exclude-regexp = InlineSkipTest.ConcurrentInsert|TransactionStressTest.
.PHONY: valgrind_check_0
valgrind_check_0: test_log_prefix := valgrind_
valgrind_check_0:
printf '%s\n' '' \
@printf '%s\n' '' \
'To monitor subtest <duration,pass/fail,name>,' \
' run "make watch-log" in a separate window' ''; \
{ \
@@ -2107,7 +2107,7 @@ LZ4_VER ?= 1.9.4
LZ4_SHA256 ?= 0b0e3aa07c8c063ddf40b082bdf7e37a1562bda40a0ff5272957f3e987e0e54b
LZ4_DOWNLOAD_BASE ?= https://github.com/lz4/lz4/archive
ZSTD_VER ?= 1.5.5
ZSTD_SHA256 ?= 9c4396cc829cfae319a6e2615202e82aad41372073482fce286fac78646d3ee4
ZSTD_SHA256 ?= 98e9c3d949d1b924e28e01eccb7deed865eefebf25c2f21c702e5cd5b63b85e1
ZSTD_DOWNLOAD_BASE ?= https://github.com/facebook/zstd/archive
CURL_SSL_OPTS ?= --tlsv1
+2 -1
View File
@@ -5,4 +5,5 @@ This is the list of all known third-party plugins for RocksDB. If something is m
* [ZenFS](https://github.com/westerndigitalcorporation/zenfs): a file system for zoned block devices
* [RADOS](https://github.com/riversand963/rocksdb-rados-env): an Env used for interacting with RADOS. Migrated from RocksDB main repo.
* [PMEM](https://github.com/pmem/pmem-rocksdb-plugin): a collection of plugins to enable Persistent Memory on RocksDB.
* [IPPCP](https://github.com/intel/ippcp-plugin-rocksdb): a plugin to enable encryption on RocksDB based on Intel optimized open source IPP-Crypto library.
* [IPPCP](https://github.com/intel/ippcp-plugin-rocksdb): a plugin to enable encryption on RocksDB based on Intel optimized open source IPP-Crypto library.
* [encfs](https://github.com/pegasus-kv/encfs): a plugin to enable encryption on RocksDB based on OpenSSL library.
+3
View File
@@ -163,6 +163,7 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
"options/configurable.cc",
"options/customizable.cc",
"options/db_options.cc",
"options/offpeak_time_info.cc",
"options/options.cc",
"options/options_helper.cc",
"options/options_parser.cc",
@@ -5620,3 +5621,5 @@ cpp_unittest_wrapper(name="write_unprepared_transaction_test",
deps=[":rocksdb_test_lib"],
extra_compiler_flags=[])
export_file(name = "tools/db_crashtest.py")
+3
View File
@@ -152,6 +152,9 @@ LzLabs is using RocksDB as a storage engine in their multi-database distributed
## ArangoDB
[ArangoDB](https://www.arangodb.com/) is a native multi-model database with flexible data models for documents, graphs, and key-values, for building high performance applications using a convenient SQL-like query language or JavaScript extensions. It uses RocksDB as its storage engine.
## Qdrant
[Qdrant](https://qdrant.tech/) is an open source vector database, it [uses](https://qdrant.tech/documentation/concepts/storage/) RocksDB as its persistent storage.
## Milvus
[Milvus](https://milvus.io/) is an open source vector database for unstructured data. It uses RocksDB not only as one of the supported kv storage engines, but also as a message queue.
+1
View File
@@ -303,6 +303,7 @@ def generate_targets(repo_path, deps_map):
deps=json.dumps(deps["extra_deps"] + [":rocksdb_test_lib"]),
extra_compiler_flags=json.dumps(deps["extra_compiler_flags"]),
)
TARGETS.export_file("tools/db_crashtest.py")
print(ColorString.info("Generated TARGETS Summary:"))
print(ColorString.info("- %d libs" % TARGETS.total_lib))
+6
View File
@@ -148,3 +148,9 @@ add_c_test_wrapper()
).encode("utf-8")
)
self.total_test = self.total_test + 1
def export_file(self, name):
with open(self.path, "a") as targets_file:
targets_file.write(
targets_cfg.export_file_template.format(name=name)
)
+4
View File
@@ -37,3 +37,7 @@ fancy_bench_template = """
fancy_bench_wrapper(suite_name="{name}", binary_to_bench_to_metric_list_map={bench_config}, slow={slow}, expected_runtime={expected_runtime}, sl_iterations={sl_iterations}, regression_threshold={regression_threshold})
"""
export_file_template = """
export_file(name = "{name}")
"""
+88 -40
View File
@@ -63,11 +63,22 @@ DEFINE_int32(
DEFINE_uint32(skew, 5, "Degree of skew in key selection. 0 = no skew");
DEFINE_bool(populate_cache, true, "Populate cache before operations");
DEFINE_uint32(lookup_insert_percent, 87,
DEFINE_double(pinned_ratio, 0.25,
"Keep roughly this portion of entries pinned in cache.");
DEFINE_double(
vary_capacity_ratio, 0.0,
"If greater than 0.0, will periodically vary the capacity between this "
"ratio less than full size and full size. If vary_capacity_ratio + "
"pinned_ratio is close to or exceeds 1.0, the cache might thrash.");
DEFINE_uint32(lookup_insert_percent, 82,
"Ratio of lookup (+ insert on not found) to total workload "
"(expressed as a percentage)");
DEFINE_uint32(insert_percent, 2,
"Ratio of insert to total workload (expressed as a percentage)");
DEFINE_uint32(blind_insert_percent, 5,
"Ratio of insert without keeping handle to total workload "
"(expressed as a percentage)");
DEFINE_uint32(lookup_percent, 10,
"Ratio of lookup to total workload (expressed as a percentage)");
DEFINE_uint32(erase_percent, 1,
@@ -101,7 +112,6 @@ DEFINE_uint32(seed, 0, "Hashing/random seed to use. 0 = choose at random");
DEFINE_string(secondary_cache_uri, "",
"Full URI for creating a custom secondary cache object");
static class std::shared_ptr<ROCKSDB_NAMESPACE::SecondaryCache> secondary_cache;
DEFINE_string(cache_type, "lru_cache", "Type of block cache.");
@@ -200,16 +210,19 @@ class SharedState {
bool Started() const { return start_; }
void AddLookupStats(uint64_t hits, uint64_t misses) {
void AddLookupStats(uint64_t hits, uint64_t misses, size_t pinned_count) {
MutexLock l(&mu_);
lookup_count_ += hits + misses;
lookup_hits_ += hits;
pinned_count_ += pinned_count;
}
double GetLookupHitRatio() const {
return 1.0 * lookup_hits_ / lookup_count_;
}
size_t GetPinnedCount() const { return pinned_count_; }
private:
port::Mutex mu_;
port::CondVar cv_;
@@ -221,6 +234,7 @@ class SharedState {
uint64_t num_done_ = 0;
uint64_t lookup_count_ = 0;
uint64_t lookup_hits_ = 0;
size_t pinned_count_ = 0;
};
// Per-thread state for concurrent executions of the same benchmark.
@@ -313,6 +327,28 @@ Cache::CacheItemHelper helper2(CacheEntryRole::kIndexBlock, DeleteFn, SizeFn,
Cache::CacheItemHelper helper3_wos(CacheEntryRole::kFilterBlock, DeleteFn);
Cache::CacheItemHelper helper3(CacheEntryRole::kFilterBlock, DeleteFn, SizeFn,
SaveToFn, CreateFn, &helper3_wos);
void ConfigureSecondaryCache(ShardedCacheOptions& opts) {
if (!FLAGS_secondary_cache_uri.empty()) {
std::shared_ptr<SecondaryCache> secondary_cache;
Status s = SecondaryCache::CreateFromString(
ConfigOptions(), FLAGS_secondary_cache_uri, &secondary_cache);
if (secondary_cache == nullptr) {
fprintf(stderr,
"No secondary cache registered matching string: %s status=%s\n",
FLAGS_secondary_cache_uri.c_str(), s.ToString().c_str());
exit(1);
}
opts.secondary_cache = secondary_cache;
}
}
ShardedCacheBase* AsShardedCache(Cache* c) {
if (!FLAGS_secondary_cache_uri.empty()) {
c = static_cast_with_check<CacheWrapper>(c)->GetTarget().get();
}
return static_cast_with_check<ShardedCacheBase>(c);
}
} // namespace
class CacheBench {
@@ -327,7 +363,9 @@ class CacheBench {
FLAGS_lookup_insert_percent),
insert_threshold_(lookup_insert_threshold_ +
kHundredthUint64 * FLAGS_insert_percent),
lookup_threshold_(insert_threshold_ +
blind_insert_threshold_(insert_threshold_ +
kHundredthUint64 * FLAGS_blind_insert_percent),
lookup_threshold_(blind_insert_threshold_ +
kHundredthUint64 * FLAGS_lookup_percent),
erase_threshold_(lookup_threshold_ +
kHundredthUint64 * FLAGS_erase_percent) {
@@ -366,6 +404,7 @@ class CacheBench {
fprintf(stderr, "Cache type not supported.\n");
exit(1);
}
ConfigureSecondaryCache(opts);
cache_ = opts.MakeSharedCache();
} else if (FLAGS_cache_type == "lru_cache") {
LRUCacheOptions opts(FLAGS_cache_size, FLAGS_num_shard_bits,
@@ -373,19 +412,7 @@ class CacheBench {
0.5 /* high_pri_pool_ratio */);
opts.hash_seed = BitwiseAnd(FLAGS_seed, INT32_MAX);
opts.memory_allocator = allocator;
if (!FLAGS_secondary_cache_uri.empty()) {
Status s = SecondaryCache::CreateFromString(
ConfigOptions(), FLAGS_secondary_cache_uri, &secondary_cache);
if (secondary_cache == nullptr) {
fprintf(
stderr,
"No secondary cache registered matching string: %s status=%s\n",
FLAGS_secondary_cache_uri.c_str(), s.ToString().c_str());
exit(1);
}
opts.secondary_cache = secondary_cache;
}
ConfigureSecondaryCache(opts);
cache_ = NewLRUCache(opts);
} else {
fprintf(stderr, "Cache type not supported.\n");
@@ -505,6 +532,8 @@ class CacheBench {
size_t slot = cache_->GetTableAddressCount();
printf("Final load factor: %g (%zu / %zu)\n", 1.0 * occ / slot, occ, slot);
printf("Final pinned count: %zu\n", shared.GetPinnedCount());
if (FLAGS_histograms) {
printf("\nOperation latency (ns):\n");
HistogramImpl combined;
@@ -536,6 +565,7 @@ class CacheBench {
// Cumulative thresholds in the space of a random uint64_t
const uint64_t lookup_insert_threshold_;
const uint64_t insert_threshold_;
const uint64_t blind_insert_threshold_;
const uint64_t lookup_threshold_;
const uint64_t erase_threshold_;
@@ -651,29 +681,44 @@ class CacheBench {
uint64_t lookup_misses = 0;
uint64_t lookup_hits = 0;
// To hold handles for a non-trivial amount of time
Cache::Handle* handle = nullptr;
std::deque<Cache::Handle*> pinned;
size_t total_pin_count = static_cast<size_t>(
(FLAGS_cache_size * FLAGS_pinned_ratio) / FLAGS_value_bytes + 0.999999);
// For this thread. Some round up, some round down, as appropriate
size_t pin_count = (total_pin_count + thread->tid) / FLAGS_threads;
KeyGen gen;
const auto clock = SystemClock::Default().get();
uint64_t start_time = clock->NowMicros();
StopWatchNano timer(clock);
auto system_clock = SystemClock::Default();
size_t steps_to_next_capacity_change = 0;
for (uint64_t i = 0; i < FLAGS_ops_per_thread; i++) {
Slice key = gen.GetRand(thread->rnd, max_key_, FLAGS_skew);
uint64_t random_op = thread->rnd.Next();
if (FLAGS_vary_capacity_ratio > 0.0 && thread->tid == 0) {
if (steps_to_next_capacity_change == 0) {
double cut_ratio = static_cast<double>(thread->rnd.Next()) /
static_cast<double>(UINT64_MAX) *
FLAGS_vary_capacity_ratio;
cache_->SetCapacity(FLAGS_cache_size * (1.0 - cut_ratio));
steps_to_next_capacity_change =
static_cast<size_t>(FLAGS_ops_per_thread / 100);
} else {
--steps_to_next_capacity_change;
}
}
if (FLAGS_histograms) {
timer.Start();
}
if (random_op < lookup_insert_threshold_) {
if (handle) {
cache_->Release(handle);
handle = nullptr;
}
// do lookup
handle = cache_->Lookup(key, &helper2, /*context*/ nullptr,
Cache::Priority::LOW);
auto handle = cache_->Lookup(key, &helper2, /*context*/ nullptr,
Cache::Priority::LOW);
if (handle) {
++lookup_hits;
if (!FLAGS_lean) {
@@ -681,32 +726,31 @@ class CacheBench {
result += NPHash64(static_cast<char*>(cache_->Value(handle)),
FLAGS_value_bytes);
}
pinned.push_back(handle);
} else {
++lookup_misses;
// do insert
Status s = cache_->Insert(
key, createValue(thread->rnd, cache_->memory_allocator()),
&helper2, FLAGS_value_bytes, &handle);
&helper2, FLAGS_value_bytes, &pinned.emplace_back());
assert(s.ok());
}
} else if (random_op < insert_threshold_) {
if (handle) {
cache_->Release(handle);
handle = nullptr;
}
// do insert
Status s = cache_->Insert(
key, createValue(thread->rnd, cache_->memory_allocator()), &helper3,
FLAGS_value_bytes, &handle);
FLAGS_value_bytes, &pinned.emplace_back());
assert(s.ok());
} else if (random_op < blind_insert_threshold_) {
// insert without keeping a handle
Status s = cache_->Insert(
key, createValue(thread->rnd, cache_->memory_allocator()), &helper3,
FLAGS_value_bytes);
assert(s.ok());
} else if (random_op < lookup_threshold_) {
if (handle) {
cache_->Release(handle);
handle = nullptr;
}
// do lookup
handle = cache_->Lookup(key, &helper2, /*context*/ nullptr,
Cache::Priority::LOW);
auto handle = cache_->Lookup(key, &helper2, /*context*/ nullptr,
Cache::Priority::LOW);
if (handle) {
++lookup_hits;
if (!FLAGS_lean) {
@@ -714,6 +758,7 @@ class CacheBench {
result += NPHash64(static_cast<char*>(cache_->Value(handle)),
FLAGS_value_bytes);
}
pinned.push_back(handle);
} else {
++lookup_misses;
}
@@ -727,7 +772,6 @@ class CacheBench {
if (FLAGS_histograms) {
thread->latency_ns_hist.Add(timer.ElapsedNanos());
}
thread->shared->AddLookupStats(lookup_hits, lookup_misses);
if (FLAGS_usleep > 0) {
unsigned us =
static_cast<unsigned>(thread->rnd.Uniform(FLAGS_usleep + 1));
@@ -735,12 +779,17 @@ class CacheBench {
system_clock->SleepForMicroseconds(us);
}
}
while (pinned.size() > pin_count) {
cache_->Release(pinned.front());
pinned.pop_front();
}
}
if (FLAGS_early_exit) {
MutexLock l(thread->shared->GetMutex());
exit(0);
}
if (handle) {
thread->shared->AddLookupStats(lookup_hits, lookup_misses, pinned.size());
for (auto handle : pinned) {
cache_->Release(handle);
handle = nullptr;
}
@@ -769,8 +818,7 @@ class CacheBench {
printf("Cache size : %s\n",
BytesToHumanString(FLAGS_cache_size).c_str());
printf("Num shard bits : %d\n",
static_cast_with_check<ShardedCacheBase>(cache_.get())
->GetNumShardBits());
AsShardedCache(cache_.get())->GetNumShardBits());
printf("Max key : %" PRIu64 "\n", max_key_);
printf("Resident ratio : %g\n", FLAGS_resident_ratio);
printf("Skew degree : %u\n", FLAGS_skew);
+288 -353
View File
File diff suppressed because it is too large. Load diff
+39 -32
View File
@@ -24,6 +24,7 @@
#include "port/port.h"
#include "rocksdb/cache.h"
#include "rocksdb/secondary_cache.h"
#include "util/atomic.h"
#include "util/autovector.h"
#include "util/math.h"
@@ -368,7 +369,7 @@ struct ClockHandle : public ClockHandleBasicData {
// TODO: make these coundown values tuning parameters for eviction?
// See above. Mutable for read reference counting.
mutable std::atomic<uint64_t> meta{};
mutable AcqRelAtomic<uint64_t> meta{};
}; // struct ClockHandle
class BaseClockTable {
@@ -395,19 +396,15 @@ class BaseClockTable {
void Ref(ClockHandle& handle);
size_t GetOccupancy() const {
return occupancy_.load(std::memory_order_relaxed);
}
size_t GetOccupancy() const { return occupancy_.LoadRelaxed(); }
size_t GetUsage() const { return usage_.load(std::memory_order_relaxed); }
size_t GetUsage() const { return usage_.LoadRelaxed(); }
size_t GetStandaloneUsage() const {
return standalone_usage_.load(std::memory_order_relaxed);
}
size_t GetStandaloneUsage() const { return standalone_usage_.LoadRelaxed(); }
uint32_t GetHashSeed() const { return hash_seed_; }
uint64_t GetYieldCount() const { return yield_count_.load(); }
uint64_t GetYieldCount() const { return yield_count_.LoadRelaxed(); }
struct EvictionData {
size_t freed_charge = 0;
@@ -460,21 +457,23 @@ class BaseClockTable {
// operations in ClockCacheShard.
// Clock algorithm sweep pointer.
std::atomic<uint64_t> clock_pointer_{};
// (Relaxed: only needs to be consistent with itself.)
RelaxedAtomic<uint64_t> clock_pointer_{};
// Counter for number of times we yield to wait on another thread.
std::atomic<uint64_t> yield_count_{};
// (Relaxed: a simple stat counter.)
RelaxedAtomic<uint64_t> yield_count_{};
// TODO: is this separation needed if we don't do background evictions?
ALIGN_AS(CACHE_LINE_SIZE)
// Number of elements in the table.
std::atomic<size_t> occupancy_{};
AcqRelAtomic<size_t> occupancy_{};
// Memory usage by entries tracked by the cache (including standalone)
std::atomic<size_t> usage_{};
AcqRelAtomic<size_t> usage_{};
// Part of usage by standalone entries (not in table)
std::atomic<size_t> standalone_usage_{};
AcqRelAtomic<size_t> standalone_usage_{};
ALIGN_AS(CACHE_LINE_SIZE)
const CacheMetadataChargePolicy metadata_charge_policy_;
@@ -500,7 +499,11 @@ class FixedHyperClockTable : public BaseClockTable {
struct ALIGN_AS(64U) HandleImpl : public ClockHandle {
// The number of elements that hash to this slot or a lower one, but wind
// up in this slot or a higher one.
std::atomic<uint32_t> displacements{};
// (Relaxed: within a Cache op, does not need consistency with entries
// inserted/removed during that op. For example, a Lookup() that
// happens-after an Insert() will see an appropriate displacements value
// for the entry to be in a published state.)
RelaxedAtomic<uint32_t> displacements{};
// Whether this is a "deteched" handle that is independently allocated
// with `new` (so must be deleted with `delete`).
@@ -787,17 +790,16 @@ class AutoHyperClockTable : public BaseClockTable {
// See above. The head pointer is logically independent of the rest of
// the entry, including the chain next pointer.
std::atomic<uint64_t> head_next_with_shift{kUnusedMarker};
std::atomic<uint64_t> chain_next_with_shift{kUnusedMarker};
AcqRelAtomic<uint64_t> head_next_with_shift{kUnusedMarker};
AcqRelAtomic<uint64_t> chain_next_with_shift{kUnusedMarker};
// For supporting CreateStandalone and some fallback cases.
inline bool IsStandalone() const {
return head_next_with_shift.load(std::memory_order_acquire) ==
kStandaloneMarker;
return head_next_with_shift.Load() == kStandaloneMarker;
}
inline void SetStandalone() {
head_next_with_shift.store(kStandaloneMarker, std::memory_order_release);
head_next_with_shift.Store(kStandaloneMarker);
}
}; // struct HandleImpl
@@ -939,22 +941,25 @@ class AutoHyperClockTable : public BaseClockTable {
// log time to find the correct chain, but normally this value enables
// readers to find the correct chain on the first try.
//
// NOTES: length_info_ is only updated at the end of a Grow operation,
// so that waiting in Grow operations isn't done while entries are pinned
// for internal operation purposes. Thus, Lookup and Insert have to
// detect and support cases where length_info hasn't caught up to updated
// chains. Winning grow thread is the one that transitions
// head_next_with_shift from zeros. Grow threads can spin/yield wait for
// preconditions and postconditions to be met.
std::atomic<uint64_t> length_info_;
// To maximize parallelization of Grow() operations, this field is only
// updated opportunistically after Grow() operations and in DoInsert() where
// it is found to be out-of-date. See CatchUpLengthInfoNoWait().
AcqRelAtomic<uint64_t> length_info_;
// An already-computed version of the usable length times the max load
// factor. Could be slightly out of date but GrowIfNeeded()/Grow() handle
// that internally.
std::atomic<size_t> occupancy_limit_;
// (Relaxed: allowed to lag behind length_info_ by a little)
RelaxedAtomic<size_t> occupancy_limit_;
// The next index to use from array_ upon the next Grow(). Might be ahead of
// length_info_.
// (Relaxed: self-contained source of truth for next grow home)
RelaxedAtomic<size_t> grow_frontier_;
// See explanation in AutoHyperClockTable::Evict
std::atomic<size_t> clock_pointer_mask_;
// (Relaxed: allowed to lag behind clock_pointer_ and length_info_ state)
RelaxedAtomic<size_t> clock_pointer_mask_;
}; // class AutoHyperClockTable
// A single shard of sharded cache.
@@ -1070,10 +1075,12 @@ class ALIGN_AS(CACHE_LINE_SIZE) ClockCacheShard final : public CacheShardBase {
Table table_;
// Maximum total charge of all elements stored in the table.
std::atomic<size_t> capacity_;
// (Relaxed: eventual consistency/update is OK)
RelaxedAtomic<size_t> capacity_;
// Whether to reject insertion if cache reaches its full capacity.
std::atomic<bool> strict_capacity_limit_;
// (Relaxed: eventual consistency/update is OK)
RelaxedAtomic<bool> strict_capacity_limit_;
}; // class ClockCacheShard
template <class Table>
+49 -22
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,28 +1314,50 @@ 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,
DynamicUpdateWithReservationUnderflow) {
TEST_P(CompressedSecCacheTestWithTiered, ReservationOverCapacity) {
CompressedSecondaryCache* sec_cache =
reinterpret_cast<CompressedSecondaryCache*>(GetSecondaryCache());
std::shared_ptr<Cache> tiered_cache = GetTieredCache();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
{{"CacheWithSecondaryAdapter::Release:ChargeSecCache1",
"CacheWithSecondaryAdapter::UpdateCacheReservationRatio:Begin"},
{"CacheWithSecondaryAdapter::UpdateCacheReservationRatio:End",
"CacheWithSecondaryAdapter::Release:ChargeSecCache2"}});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
port::Thread reserve_release_thread([&]() {
EXPECT_EQ(cache_res_mgr()->UpdateCacheReservation(50), Status::OK());
EXPECT_EQ(cache_res_mgr()->UpdateCacheReservation(0), Status::OK());
});
ASSERT_OK(UpdateTieredCache(tiered_cache, 100 << 20, 0.01));
reserve_release_thread.join();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
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, 100 << 20, 0.3));
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));
}
INSTANTIATE_TEST_CASE_P(
+105 -65
View File
@@ -83,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);
@@ -103,8 +106,7 @@ CacheWithSecondaryAdapter::CacheWithSecondaryAdapter(
// secondary cache is freed from the reservation.
s = pri_cache_res_->UpdateCacheReservation(sec_capacity);
assert(s.ok());
sec_cache_res_ratio_.store((double)sec_capacity / target_->GetCapacity(),
std::memory_order_relaxed);
sec_cache_res_ratio_ = (double)sec_capacity / target_->GetCapacity();
}
}
@@ -113,10 +115,12 @@ 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());
assert(placeholder_usage_ == 0);
assert(reserved_usage_ == 0);
assert(pri_cache_res_->GetTotalMemoryUsed() == sec_capacity);
}
#endif // NDEBUG
@@ -236,13 +240,31 @@ 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_.load(std::memory_order_relaxed)));
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.
@@ -287,14 +309,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_.load(std::memory_order_relaxed)));
TEST_SYNC_POINT("CacheWithSecondaryAdapter::Release:ChargeSecCache1");
TEST_SYNC_POINT("CacheWithSecondaryAdapter::Release:ChargeSecCache2");
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);
@@ -441,13 +476,11 @@ const char* CacheWithSecondaryAdapter::Name() const {
// where the new capacity < total cache reservations.
void CacheWithSecondaryAdapter::SetCapacity(size_t capacity) {
size_t sec_capacity = static_cast<size_t>(
capacity * (distribute_cache_res_
? sec_cache_res_ratio_.load(std::memory_order_relaxed)
: 0.0));
capacity * (distribute_cache_res_ ? sec_cache_res_ratio_ : 0.0));
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()) {
@@ -462,9 +495,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);
}
@@ -489,6 +530,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
@@ -503,12 +567,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_.load(std::memory_order_relaxed) == 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);
@@ -518,38 +581,17 @@ Status CacheWithSecondaryAdapter::UpdateCacheReservationRatio(
return s;
}
TEST_SYNC_POINT(
"CacheWithSecondaryAdapter::UpdateCacheReservationRatio:Begin");
// There's a possible race condition here. Since the read of pri_cache_res_
// memory used (secondary cache usage charged to primary cache), and the
// change to sec_cache_res_ratio_ are not guarded by a mutex, its possible
// that an Insert/Release in another thread might decrease/increase the
// pri_cache_res_ reservation by the wrong amount. This should not be a
// problem because updating the sec/pri ratio is a rare operation, and
// the worst that can happen is we may over/under charge the secondary
// cache usage by a little bit. But we do need to protect against
// underflow of old_sec_reserved.
// TODO: Make the accounting more accurate by tracking the total memory
// reservation on the primary cache. This will also allow us to remove
// the restriction of not being able to change the sec/pri ratio from
// 0.0 to higher.
size_t sec_charge_to_pri = pri_cache_res_->GetTotalMemoryUsed();
size_t old_sec_reserved = (old_sec_capacity > sec_charge_to_pri)
? (old_sec_capacity - sec_charge_to_pri)
: 0;
// 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_.load(std::memory_order_relaxed)));
sec_cache_res_ratio_.store(compressed_secondary_ratio,
std::memory_order_relaxed);
// 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
@@ -557,12 +599,13 @@ Status CacheWithSecondaryAdapter::UpdateCacheReservationRatio(
// cache utilization (increase in capacity - increase in share of cache
// reservation)
// 3. Increase secondary cache capacity
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 {
@@ -576,21 +619,16 @@ Status CacheWithSecondaryAdapter::UpdateCacheReservationRatio(
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;
}
}
TEST_SYNC_POINT("CacheWithSecondaryAdapter::UpdateCacheReservationRatio:End");
#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;
}
@@ -645,12 +683,14 @@ std::shared_ptr<Cache> NewTieredCache(const TieredCacheOptions& _opts) {
*(static_cast_with_check<LRUCacheOptions, ShardedCacheOptions>(
opts.cache_opts));
cache_opts.capacity = opts.total_capacity;
cache_opts.secondary_cache = nullptr;
cache = cache_opts.MakeSharedCache();
} else if (opts.cache_type == PrimaryCacheType::kCacheTypeHCC) {
HyperClockCacheOptions cache_opts =
*(static_cast_with_check<HyperClockCacheOptions, ShardedCacheOptions>(
opts.cache_opts));
cache_opts.capacity = opts.total_capacity;
cache_opts.secondary_cache = nullptr;
cache = cache_opts.MakeSharedCache();
} else {
return nullptr;
+18 -5
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);
@@ -80,11 +86,18 @@ class CacheWithSecondaryAdapter : public CacheWrapper {
std::shared_ptr<ConcurrentCacheReservationManager> pri_cache_res_;
// Fraction of a cache memory reservation to be assigned to the secondary
// cache
std::atomic<double> sec_cache_res_ratio_;
port::Mutex mutex_;
#ifndef NDEBUG
bool ratio_changed_ = false;
#endif
double sec_cache_res_ratio_;
// 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;
+2 -4
View File
@@ -109,10 +109,8 @@ void TieredSecondaryCache::WaitAll(
}
nvm_sec_cache_->WaitAll(nvm_handles);
for (auto handle : my_handles) {
assert(handle->IsReady());
auto nvm_handle = handle->inner_handle();
handle->SetSize(nvm_handle->Size());
handle->SetValue(nvm_handle->Value());
assert(handle->inner_handle()->IsReady());
handle->Complete();
}
}
+6 -2
View File
@@ -79,7 +79,10 @@ class TieredSecondaryCache : public SecondaryCacheWrapper {
~ResultHandle() override {}
bool IsReady() override {
return !inner_handle_ || inner_handle_->IsReady();
if (inner_handle_ && inner_handle_->IsReady()) {
Complete();
}
return ready_;
}
void Wait() override {
@@ -92,10 +95,10 @@ class TieredSecondaryCache : public SecondaryCacheWrapper {
Cache::ObjectPtr Value() override { return value_; }
void Complete() {
assert(IsReady());
size_ = inner_handle_->Size();
value_ = inner_handle_->Value();
inner_handle_.reset();
ready_ = true;
}
void SetInnerHandle(std::unique_ptr<SecondaryCacheResultHandle>&& handle) {
@@ -115,6 +118,7 @@ class TieredSecondaryCache : public SecondaryCacheWrapper {
CreateContext ctx_;
size_t size_;
Cache::ObjectPtr value_;
bool ready_ = false;
};
static void NoopDelete(Cache::ObjectPtr /*obj*/,
+129 -9
View File
@@ -15,10 +15,11 @@ namespace ROCKSDB_NAMESPACE {
class TestSecondaryCache : public SecondaryCache {
public:
explicit TestSecondaryCache(size_t capacity)
explicit TestSecondaryCache(size_t capacity, bool ready_before_wait)
: cache_(NewLRUCache(capacity, 0, false, 0.5 /* high_pri_pool_ratio */,
nullptr, kDefaultToAdaptiveMutex,
kDontChargeCacheMetadata)),
ready_before_wait_(ready_before_wait),
num_insert_saved_(0),
num_hits_(0),
num_misses_(0) {}
@@ -88,7 +89,8 @@ class TestSecondaryCache : public SecondaryCache {
/*alloc*/ nullptr, &value, &charge);
if (s.ok()) {
secondary_handle.reset(new TestSecondaryCacheResultHandle(
cache_.get(), handle, value, charge, /*ready=*/wait));
cache_.get(), handle, value, charge,
/*ready=*/wait || ready_before_wait_));
kept_in_sec_cache = true;
} else {
cache_.Release(handle);
@@ -168,6 +170,7 @@ class TestSecondaryCache : public SecondaryCache {
BasicTypedSharedCacheInterface<char[], CacheEntryRole::kMisc>;
using TypedHandle = SharedCache::TypedHandle;
SharedCache cache_;
bool ready_before_wait_;
uint32_t num_insert_saved_;
uint32_t num_hits_;
uint32_t num_misses_;
@@ -179,11 +182,10 @@ class DBTieredSecondaryCacheTest : public DBTestBase {
DBTieredSecondaryCacheTest()
: DBTestBase("db_tiered_secondary_cache_test", /*env_do_fsync=*/true) {}
std::shared_ptr<Cache> NewCache(size_t pri_capacity,
size_t compressed_capacity,
size_t nvm_capacity,
TieredAdmissionPolicy adm_policy =
TieredAdmissionPolicy::kAdmPolicyAuto) {
std::shared_ptr<Cache> NewCache(
size_t pri_capacity, size_t compressed_capacity, size_t nvm_capacity,
TieredAdmissionPolicy adm_policy = TieredAdmissionPolicy::kAdmPolicyAuto,
bool ready_before_wait = false) {
LRUCacheOptions lru_opts;
TieredCacheOptions opts;
lru_opts.capacity = 0;
@@ -194,10 +196,11 @@ class DBTieredSecondaryCacheTest : public DBTestBase {
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 =
opts.compressed_secondary_ratio = compressed_secondary_ratio_ =
(double)compressed_capacity / opts.total_capacity;
if (nvm_capacity > 0) {
nvm_sec_cache_.reset(new TestSecondaryCache(nvm_capacity));
nvm_sec_cache_.reset(
new TestSecondaryCache(nvm_capacity, ready_before_wait));
opts.nvm_sec_cache = nvm_sec_cache_;
}
opts.adm_policy = adm_policy;
@@ -207,6 +210,12 @@ class DBTieredSecondaryCacheTest : public DBTestBase {
return cache_;
}
void ClearPrimaryCache() {
ASSERT_EQ(UpdateTieredCache(cache_, -1, 1.0), Status::OK());
ASSERT_EQ(UpdateTieredCache(cache_, -1, compressed_secondary_ratio_),
Status::OK());
}
TestSecondaryCache* nvm_sec_cache() { return nvm_sec_cache_.get(); }
CompressedSecondaryCache* compressed_secondary_cache() {
@@ -218,6 +227,7 @@ class DBTieredSecondaryCacheTest : public DBTestBase {
private:
std::shared_ptr<Cache> cache_;
std::shared_ptr<TestSecondaryCache> nvm_sec_cache_;
double compressed_secondary_ratio_;
};
// In this test, the block size is set to 4096. Each value is 1007 bytes, so
@@ -582,6 +592,116 @@ TEST_F(DBTieredSecondaryCacheTest, WaitAllTest) {
Destroy(options);
}
TEST_F(DBTieredSecondaryCacheTest, ReadyBeforeWaitAllTest) {
if (!LZ4_Supported()) {
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
return;
}
BlockBasedTableOptions table_options;
table_options.block_cache = NewCache(250 * 1024, 20 * 1024, 256 * 1024,
TieredAdmissionPolicy::kAdmPolicyAuto,
/*ready_before_wait=*/true);
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));
options.statistics = CreateDBStatistics();
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());
std::vector<std::string> keys;
std::vector<std::string> values;
keys.push_back(Key(0));
keys.push_back(Key(4));
keys.push_back(Key(8));
values = MultiGet(keys, /*snapshot=*/nullptr, /*async=*/true);
ASSERT_EQ(values.size(), keys.size());
for (auto value : values) {
ASSERT_EQ(1007, value.size());
}
ASSERT_EQ(nvm_sec_cache()->num_insert_saved(), 3u);
ASSERT_EQ(nvm_sec_cache()->num_misses(), 3u);
ASSERT_EQ(nvm_sec_cache()->num_hits(), 0u);
ASSERT_EQ(options.statistics->getTickerCount(BLOCK_CACHE_MISS), 3u);
keys.clear();
values.clear();
keys.push_back(Key(12));
keys.push_back(Key(16));
keys.push_back(Key(20));
values = MultiGet(keys, /*snapshot=*/nullptr, /*async=*/true);
ASSERT_EQ(values.size(), keys.size());
for (auto value : values) {
ASSERT_EQ(1007, value.size());
}
ASSERT_EQ(nvm_sec_cache()->num_insert_saved(), 6u);
ASSERT_EQ(nvm_sec_cache()->num_misses(), 6u);
ASSERT_EQ(nvm_sec_cache()->num_hits(), 0u);
ASSERT_EQ(options.statistics->getTickerCount(BLOCK_CACHE_MISS), 6u);
keys.clear();
values.clear();
keys.push_back(Key(0));
keys.push_back(Key(4));
keys.push_back(Key(8));
values = MultiGet(keys, /*snapshot=*/nullptr, /*async=*/true);
ASSERT_EQ(values.size(), keys.size());
for (auto value : values) {
ASSERT_EQ(1007, value.size());
}
ASSERT_EQ(nvm_sec_cache()->num_insert_saved(), 6u);
ASSERT_EQ(nvm_sec_cache()->num_misses(), 6u);
ASSERT_EQ(nvm_sec_cache()->num_hits(), 3u);
ASSERT_EQ(options.statistics->getTickerCount(BLOCK_CACHE_MISS), 6u);
ClearPrimaryCache();
keys.clear();
values.clear();
keys.push_back(Key(0));
keys.push_back(Key(32));
keys.push_back(Key(36));
values = MultiGet(keys, /*snapshot=*/nullptr, /*async=*/true);
ASSERT_EQ(values.size(), keys.size());
for (auto value : values) {
ASSERT_EQ(1007, value.size());
}
ASSERT_EQ(nvm_sec_cache()->num_insert_saved(), 8u);
ASSERT_EQ(nvm_sec_cache()->num_misses(), 8u);
ASSERT_EQ(nvm_sec_cache()->num_hits(), 4u);
ASSERT_EQ(options.statistics->getTickerCount(BLOCK_CACHE_MISS), 8u);
keys.clear();
values.clear();
keys.push_back(Key(0));
keys.push_back(Key(32));
keys.push_back(Key(36));
values = MultiGet(keys, /*snapshot=*/nullptr, /*async=*/true);
ASSERT_EQ(values.size(), keys.size());
for (auto value : values) {
ASSERT_EQ(1007, value.size());
}
ASSERT_EQ(nvm_sec_cache()->num_insert_saved(), 8u);
ASSERT_EQ(nvm_sec_cache()->num_misses(), 8u);
ASSERT_EQ(nvm_sec_cache()->num_hits(), 4u);
ASSERT_EQ(options.statistics->getTickerCount(BLOCK_CACHE_MISS), 8u);
Destroy(options);
}
// This test is for iteration. It iterates through a set of keys in two
// passes. First pass loads the compressed blocks into the nvm tier, and
// the second pass should hit all of those blocks.
+99
View File
@@ -45,6 +45,7 @@
#include "rocksdb/utilities/transaction_db.h"
#include "rocksdb/utilities/write_batch_with_index.h"
#include "rocksdb/write_batch.h"
#include "rocksdb/write_buffer_manager.h"
#include "utilities/merge_operators.h"
using ROCKSDB_NAMESPACE::BackupEngine;
@@ -125,6 +126,7 @@ using ROCKSDB_NAMESPACE::WALRecoveryMode;
using ROCKSDB_NAMESPACE::WritableFile;
using ROCKSDB_NAMESPACE::WriteBatch;
using ROCKSDB_NAMESPACE::WriteBatchWithIndex;
using ROCKSDB_NAMESPACE::WriteBufferManager;
using ROCKSDB_NAMESPACE::WriteOptions;
using std::unordered_set;
@@ -219,6 +221,9 @@ struct rocksdb_memory_allocator_t {
struct rocksdb_cache_t {
std::shared_ptr<Cache> rep;
};
struct rocksdb_write_buffer_manager_t {
std::shared_ptr<WriteBufferManager> rep;
};
struct rocksdb_livefiles_t {
std::vector<LiveFileMetaData> rep;
};
@@ -2904,6 +2909,16 @@ void rocksdb_options_set_db_paths(rocksdb_options_t* opt,
opt->rep.db_paths = db_paths;
}
void rocksdb_options_set_cf_paths(rocksdb_options_t* opt,
const rocksdb_dbpath_t** dbpath_values,
size_t num_paths) {
std::vector<DbPath> cf_paths(num_paths);
for (size_t i = 0; i < num_paths; ++i) {
cf_paths[i] = dbpath_values[i]->rep;
}
opt->rep.cf_paths = cf_paths;
}
void rocksdb_options_set_env(rocksdb_options_t* opt, rocksdb_env_t* env) {
opt->rep.env = (env ? env->rep : nullptr);
}
@@ -2935,6 +2950,11 @@ void rocksdb_options_set_write_buffer_size(rocksdb_options_t* opt, size_t s) {
opt->rep.write_buffer_size = s;
}
void rocksdb_options_set_write_buffer_manager(
rocksdb_options_t* opt, rocksdb_write_buffer_manager_t* wbm) {
opt->rep.write_buffer_manager = wbm->rep;
}
size_t rocksdb_options_get_write_buffer_size(rocksdb_options_t* opt) {
return opt->rep.write_buffer_size;
}
@@ -3029,6 +3049,16 @@ void rocksdb_options_set_max_bytes_for_level_multiplier_additional(
}
}
void rocksdb_options_set_periodic_compaction_seconds(rocksdb_options_t* opt,
uint64_t seconds) {
opt->rep.periodic_compaction_seconds = seconds;
}
uint64_t rocksdb_options_get_periodic_compaction_seconds(
rocksdb_options_t* opt) {
return opt->rep.periodic_compaction_seconds;
}
void rocksdb_options_enable_statistics(rocksdb_options_t* opt) {
opt->rep.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
}
@@ -3957,6 +3987,16 @@ rocksdb_ratelimiter_t* rocksdb_ratelimiter_create(int64_t rate_bytes_per_sec,
return rate_limiter;
}
rocksdb_ratelimiter_t* rocksdb_ratelimiter_create_auto_tuned(
int64_t rate_bytes_per_sec, int64_t refill_period_us, int32_t fairness) {
rocksdb_ratelimiter_t* rate_limiter = new rocksdb_ratelimiter_t;
rate_limiter->rep.reset(NewGenericRateLimiter(rate_bytes_per_sec,
refill_period_us, fairness,
RateLimiter::Mode::kWritesOnly,
true)); // auto_tuned
return rate_limiter;
}
void rocksdb_ratelimiter_destroy(rocksdb_ratelimiter_t* limiter) {
delete limiter;
}
@@ -4866,6 +4906,60 @@ size_t rocksdb_cache_get_occupancy_count(const rocksdb_cache_t* cache) {
return cache->rep->GetOccupancyCount();
}
rocksdb_write_buffer_manager_t* rocksdb_write_buffer_manager_create(
size_t buffer_size, bool allow_stall) {
rocksdb_write_buffer_manager_t* wbm = new rocksdb_write_buffer_manager_t;
wbm->rep.reset(new WriteBufferManager(buffer_size, {}, allow_stall));
return wbm;
}
rocksdb_write_buffer_manager_t* rocksdb_write_buffer_manager_create_with_cache(
size_t buffer_size, const rocksdb_cache_t* cache, bool allow_stall) {
rocksdb_write_buffer_manager_t* wbm = new rocksdb_write_buffer_manager_t;
wbm->rep.reset(new WriteBufferManager(buffer_size, cache->rep, allow_stall));
return wbm;
}
void rocksdb_write_buffer_manager_destroy(rocksdb_write_buffer_manager_t* wbm) {
delete wbm;
}
bool rocksdb_write_buffer_manager_enabled(rocksdb_write_buffer_manager_t* wbm) {
return wbm->rep->enabled();
}
bool rocksdb_write_buffer_manager_cost_to_cache(
rocksdb_write_buffer_manager_t* wbm) {
return wbm->rep->cost_to_cache();
}
size_t rocksdb_write_buffer_manager_memory_usage(
rocksdb_write_buffer_manager_t* wbm) {
return wbm->rep->memory_usage();
}
size_t rocksdb_write_buffer_manager_mutable_memtable_memory_usage(
rocksdb_write_buffer_manager_t* wbm) {
return wbm->rep->mutable_memtable_memory_usage();
}
size_t rocksdb_write_buffer_manager_dummy_entries_in_cache_usage(
rocksdb_write_buffer_manager_t* wbm) {
return wbm->rep->dummy_entries_in_cache_usage();
}
size_t rocksdb_write_buffer_manager_buffer_size(
rocksdb_write_buffer_manager_t* wbm) {
return wbm->rep->buffer_size();
}
void rocksdb_write_buffer_manager_set_buffer_size(
rocksdb_write_buffer_manager_t* wbm, size_t new_size) {
wbm->rep->SetBufferSize(new_size);
}
ROCKSDB_LIBRARY_API void rocksdb_write_buffer_manager_set_allow_stall(
rocksdb_write_buffer_manager_t* wbm, bool new_allow_stall) {
wbm->rep->SetAllowStall(new_allow_stall);
}
rocksdb_dbpath_t* rocksdb_dbpath_create(const char* path,
uint64_t target_size) {
rocksdb_dbpath_t* result = new rocksdb_dbpath_t;
@@ -5464,6 +5558,11 @@ char* rocksdb_sst_file_metadata_get_relative_filename(
return strdup(file_meta->rep->relative_filename.c_str());
}
char* rocksdb_sst_file_metadata_get_directory(
rocksdb_sst_file_metadata_t* file_meta) {
return strdup(file_meta->rep->directory.c_str());
}
uint64_t rocksdb_sst_file_metadata_get_size(
rocksdb_sst_file_metadata_t* file_meta) {
return file_meta->rep->size;
+85 -3
View File
@@ -713,6 +713,11 @@ int main(int argc, char** argv) {
rocksdb_options_set_ratelimiter(options, rate_limiter);
rocksdb_ratelimiter_destroy(rate_limiter);
rate_limiter =
rocksdb_ratelimiter_create_auto_tuned(1000 * 1024 * 1024, 100 * 1000, 10);
rocksdb_options_set_ratelimiter(options, rate_limiter);
rocksdb_ratelimiter_destroy(rate_limiter);
roptions = rocksdb_readoptions_create();
rocksdb_readoptions_set_verify_checksums(roptions, 1);
rocksdb_readoptions_set_fill_cache(roptions, 1);
@@ -1474,10 +1479,20 @@ int main(int argc, char** argv) {
CheckCondition(cflen == 2);
rocksdb_list_column_families_destroy(column_fams, cflen);
rocksdb_options_t* cf_options = rocksdb_options_create();
rocksdb_options_t* cf_options_1 = rocksdb_options_create();
rocksdb_options_t* cf_options_2 = rocksdb_options_create();
// use dbpathname2 as the cf_path for "cf1"
rocksdb_dbpath_t* dbpath2;
char dbpathname2[200];
snprintf(dbpathname2, sizeof(dbpathname2), "%s/rocksdb_c_test-%d-dbpath2",
GetTempDir(), ((int)geteuid()));
dbpath2 = rocksdb_dbpath_create(dbpathname2, 1024 * 1024);
const rocksdb_dbpath_t* cf_paths[1] = {dbpath2};
rocksdb_options_set_cf_paths(cf_options_2, cf_paths, 1);
const char* cf_names[2] = {"default", "cf1"};
const rocksdb_options_t* cf_opts[2] = {cf_options, cf_options};
const rocksdb_options_t* cf_opts[2] = {cf_options_1, cf_options_2};
rocksdb_column_family_handle_t* handles[2];
LoadAndCheckLatestOptions(dbname, env, false, cache, NULL, 2, cf_names,
@@ -1505,6 +1520,37 @@ int main(int argc, char** argv) {
rocksdb_flushoptions_t* flush_options = rocksdb_flushoptions_create();
rocksdb_flushoptions_set_wait(flush_options, 1);
rocksdb_flush_cf(db, flush_options, handles[1], &err);
// make sure all files in "cf1" are under the specified cf path
{
rocksdb_column_family_metadata_t* cf_meta =
rocksdb_get_column_family_metadata_cf(db, handles[1]);
size_t cf_file_count = rocksdb_column_family_metadata_get_size(cf_meta);
assert(cf_file_count > 0);
size_t level_count =
rocksdb_column_family_metadata_get_level_count(cf_meta);
assert(level_count > 0);
for (size_t l = 0; l < level_count; ++l) {
rocksdb_level_metadata_t* level_meta =
rocksdb_column_family_metadata_get_level_metadata(cf_meta, l);
assert(level_meta);
size_t file_count = rocksdb_level_metadata_get_file_count(level_meta);
for (size_t f = 0; f < file_count; ++f) {
rocksdb_sst_file_metadata_t* file_meta =
rocksdb_level_metadata_get_sst_file_metadata(level_meta, f);
assert(file_meta);
char* file_path = rocksdb_sst_file_metadata_get_directory(file_meta);
assert(strcmp(file_path, dbpathname2) == 0);
Free(&file_path);
rocksdb_sst_file_metadata_destroy(file_meta);
}
rocksdb_level_metadata_destroy(level_meta);
}
rocksdb_column_family_metadata_destroy(cf_meta);
}
CheckNoError(err) rocksdb_flushoptions_destroy(flush_options);
CheckGetCF(db, roptions, handles[1], "foo", "hello");
@@ -1668,7 +1714,9 @@ int main(int argc, char** argv) {
}
rocksdb_destroy_db(options, dbname, &err);
rocksdb_options_destroy(db_options);
rocksdb_options_destroy(cf_options);
rocksdb_options_destroy(cf_options_1);
rocksdb_options_destroy(cf_options_2);
rocksdb_dbpath_destroy(dbpath2);
}
StartPhase("prefix");
@@ -1873,6 +1921,10 @@ int main(int argc, char** argv) {
CheckCondition(2.0 ==
rocksdb_options_get_max_bytes_for_level_multiplier(o));
rocksdb_options_set_periodic_compaction_seconds(o, 100000);
CheckCondition(100000 ==
rocksdb_options_get_periodic_compaction_seconds(o));
rocksdb_options_set_skip_stats_update_on_db_open(o, 1);
CheckCondition(1 == rocksdb_options_get_skip_stats_update_on_db_open(o));
@@ -2303,6 +2355,12 @@ int main(int argc, char** argv) {
CheckCondition(2.0 ==
rocksdb_options_get_max_bytes_for_level_multiplier(o));
rocksdb_options_set_periodic_compaction_seconds(copy, 8000);
CheckCondition(8000 ==
rocksdb_options_get_periodic_compaction_seconds(copy));
CheckCondition(100000 ==
rocksdb_options_get_periodic_compaction_seconds(o));
rocksdb_options_set_skip_stats_update_on_db_open(copy, 0);
CheckCondition(0 == rocksdb_options_get_skip_stats_update_on_db_open(copy));
CheckCondition(1 == rocksdb_options_get_skip_stats_update_on_db_open(o));
@@ -3723,6 +3781,30 @@ int main(int argc, char** argv) {
rocksdb_wait_for_compact_options_destroy(wco);
}
StartPhase("write_buffer_manager");
{
rocksdb_cache_t* lru;
lru = rocksdb_cache_create_lru(100);
rocksdb_write_buffer_manager_t* write_buffer_manager;
write_buffer_manager =
rocksdb_write_buffer_manager_create_with_cache(200, lru, false);
CheckCondition(true ==
rocksdb_write_buffer_manager_enabled(write_buffer_manager));
CheckCondition(true == rocksdb_write_buffer_manager_cost_to_cache(
write_buffer_manager));
CheckCondition(
200 == rocksdb_write_buffer_manager_buffer_size(write_buffer_manager));
rocksdb_write_buffer_manager_set_buffer_size(write_buffer_manager, 300);
CheckCondition(
300 == rocksdb_write_buffer_manager_buffer_size(write_buffer_manager));
rocksdb_write_buffer_manager_destroy(write_buffer_manager);
rocksdb_cache_destroy(lru);
}
StartPhase("cancel_all_background_work");
rocksdb_cancel_all_background_work(db, 1);
+62 -14
View File
@@ -115,6 +115,42 @@ void Compaction::GetBoundaryKeys(
}
}
void Compaction::GetBoundaryInternalKeys(
VersionStorageInfo* vstorage,
const std::vector<CompactionInputFiles>& inputs, InternalKey* smallest_key,
InternalKey* largest_key, int exclude_level) {
bool initialized = false;
const InternalKeyComparator* icmp = vstorage->InternalComparator();
for (size_t i = 0; i < inputs.size(); ++i) {
if (inputs[i].files.empty() || inputs[i].level == exclude_level) {
continue;
}
if (inputs[i].level == 0) {
// we need to consider all files on level 0
for (const auto* f : inputs[i].files) {
if (!initialized || icmp->Compare(f->smallest, *smallest_key) < 0) {
*smallest_key = f->smallest;
}
if (!initialized || icmp->Compare(f->largest, *largest_key) > 0) {
*largest_key = f->largest;
}
initialized = true;
}
} else {
// we only need to consider the first and last file
if (!initialized ||
icmp->Compare(inputs[i].files[0]->smallest, *smallest_key) < 0) {
*smallest_key = inputs[i].files[0]->smallest;
}
if (!initialized ||
icmp->Compare(inputs[i].files.back()->largest, *largest_key) > 0) {
*largest_key = inputs[i].files.back()->largest;
}
initialized = true;
}
}
}
std::vector<CompactionInputFiles> Compaction::PopulateWithAtomicBoundaries(
VersionStorageInfo* vstorage, std::vector<CompactionInputFiles> inputs) {
const Comparator* ucmp = vstorage->InternalComparator()->user_comparator();
@@ -399,9 +435,14 @@ void Compaction::PopulatePenultimateLevelOutputRange() {
}
}
GetBoundaryKeys(input_vstorage_, inputs_,
&penultimate_level_smallest_user_key_,
&penultimate_level_largest_user_key_, exclude_level);
// FIXME: should make use of `penultimate_output_range_type_`.
// FIXME: when last level's input range does not overlap with
// penultimate level, and penultimate level input is empty,
// this call will not set penultimate_level_smallest_ or
// penultimate_level_largest_. No keys will be compacted up.
GetBoundaryInternalKeys(input_vstorage_, inputs_,
&penultimate_level_smallest_,
&penultimate_level_largest_, exclude_level);
}
Compaction::~Compaction() {
@@ -426,33 +467,40 @@ bool Compaction::OverlapPenultimateLevelOutputRange(
if (!SupportsPerKeyPlacement()) {
return false;
}
// See FIXME in Compaction::PopulatePenultimateLevelOutputRange().
// We do not compact any key up in this case.
if (penultimate_level_smallest_.size() == 0 ||
penultimate_level_largest_.size() == 0) {
return false;
}
const Comparator* ucmp =
input_vstorage_->InternalComparator()->user_comparator();
return ucmp->CompareWithoutTimestamp(
smallest_key, penultimate_level_largest_user_key_) <= 0 &&
smallest_key, penultimate_level_largest_.user_key()) <= 0 &&
ucmp->CompareWithoutTimestamp(
largest_key, penultimate_level_smallest_user_key_) >= 0;
largest_key, penultimate_level_smallest_.user_key()) >= 0;
}
// key includes timestamp if user-defined timestamp is enabled.
bool Compaction::WithinPenultimateLevelOutputRange(const Slice& key) const {
bool Compaction::WithinPenultimateLevelOutputRange(
const ParsedInternalKey& ikey) const {
if (!SupportsPerKeyPlacement()) {
return false;
}
if (penultimate_level_smallest_user_key_.empty() ||
penultimate_level_largest_user_key_.empty()) {
if (penultimate_level_smallest_.size() == 0 ||
penultimate_level_largest_.size() == 0) {
return false;
}
const Comparator* ucmp =
input_vstorage_->InternalComparator()->user_comparator();
const InternalKeyComparator* icmp = input_vstorage_->InternalComparator();
return ucmp->CompareWithoutTimestamp(
key, penultimate_level_smallest_user_key_) >= 0 &&
ucmp->CompareWithoutTimestamp(
key, penultimate_level_largest_user_key_) <= 0;
// op_type of a key can change during compaction, e.g. Merge -> Put.
return icmp->CompareKeySeq(ikey, penultimate_level_smallest_.Encode()) >= 0 &&
icmp->CompareKeySeq(ikey, penultimate_level_largest_.Encode()) <= 0;
}
bool Compaction::InputCompressionMatchesOutput() const {
+10 -13
View File
@@ -353,14 +353,6 @@ class Compaction {
Slice GetLargestUserKey() const { return largest_user_key_; }
Slice GetPenultimateLevelSmallestUserKey() const {
return penultimate_level_smallest_user_key_;
}
Slice GetPenultimateLevelLargestUserKey() const {
return penultimate_level_largest_user_key_;
}
PenultimateOutputRangeType GetPenultimateOutputRangeType() const {
return penultimate_output_range_type_;
}
@@ -383,10 +375,8 @@ class Compaction {
// per_key_placement feature, which is safe to place the key to the
// penultimate level. different compaction strategy has different rules.
// If per_key_placement is not supported, always return false.
// TODO: currently it doesn't support moving data from the last level to the
// penultimate level
// key includes timestamp if user-defined timestamp is enabled.
bool WithinPenultimateLevelOutputRange(const Slice& key) const;
bool WithinPenultimateLevelOutputRange(const ParsedInternalKey& ikey) const;
CompactionReason compaction_reason() const { return compaction_reason_; }
@@ -456,6 +446,13 @@ class Compaction {
Slice* smallest_key, Slice* largest_key,
int exclude_level = -1);
// get the smallest and largest internal key present in files to be compacted
static void GetBoundaryInternalKeys(
VersionStorageInfo* vstorage,
const std::vector<CompactionInputFiles>& inputs,
InternalKey* smallest_key, InternalKey* largest_key,
int exclude_level = -1);
// populate penultimate level output range, which will be used to determine if
// a key is safe to output to the penultimate level (details see
// `Compaction::WithinPenultimateLevelOutputRange()`.
@@ -568,8 +565,8 @@ class Compaction {
// Key range for penultimate level output
// includes timestamp if user-defined timestamp is enabled.
// penultimate_output_range_type_ shows the range type
Slice penultimate_level_smallest_user_key_;
Slice penultimate_level_largest_user_key_;
InternalKey penultimate_level_smallest_;
InternalKey penultimate_level_largest_;
PenultimateOutputRangeType penultimate_output_range_type_ =
PenultimateOutputRangeType::kNotSupported;
};
+7 -4
View File
@@ -1227,7 +1227,7 @@ void CompactionIterator::DecideOutputLevel() {
// not from this compaction.
// TODO: add statistic for declined output_to_penultimate_level
bool safe_to_penultimate_level =
compaction_->WithinPenultimateLevelOutputRange(ikey_.user_key);
compaction_->WithinPenultimateLevelOutputRange(ikey_);
if (!safe_to_penultimate_level) {
output_to_penultimate_level_ = false;
// It could happen when disable/enable `last_level_temperature` while
@@ -1256,10 +1256,13 @@ void CompactionIterator::PrepareOutput() {
} else if (ikey_.type == kTypeBlobIndex) {
GarbageCollectBlobIfNeeded();
}
}
if (compaction_ != nullptr && compaction_->SupportsPerKeyPlacement()) {
DecideOutputLevel();
// For range del sentinel, we don't use it to cut files for bottommost
// compaction. So it should not make a difference which output level we
// decide.
if (compaction_ != nullptr && compaction_->SupportsPerKeyPlacement()) {
DecideOutputLevel();
}
}
// Zeroing out the sequence number leads to better compression.
+5 -3
View File
@@ -119,7 +119,8 @@ class CompactionIterator {
virtual bool SupportsPerKeyPlacement() const = 0;
// `key` includes timestamp if user-defined timestamp is enabled.
virtual bool WithinPenultimateLevelOutputRange(const Slice& key) const = 0;
virtual bool WithinPenultimateLevelOutputRange(
const ParsedInternalKey&) const = 0;
};
class RealCompaction : public CompactionProxy {
@@ -186,8 +187,9 @@ class CompactionIterator {
// Check if key is within penultimate level output range, to see if it's
// safe to output to the penultimate level for per_key_placement feature.
// `key` includes timestamp if user-defined timestamp is enabled.
bool WithinPenultimateLevelOutputRange(const Slice& key) const override {
return compaction_->WithinPenultimateLevelOutputRange(key);
bool WithinPenultimateLevelOutputRange(
const ParsedInternalKey& ikey) const override {
return compaction_->WithinPenultimateLevelOutputRange(ikey);
}
private:
+3 -2
View File
@@ -184,8 +184,9 @@ class FakeCompaction : public CompactionIterator::CompactionProxy {
return supports_per_key_placement;
}
bool WithinPenultimateLevelOutputRange(const Slice& key) const override {
return (!key.starts_with("unsafe_pb"));
bool WithinPenultimateLevelOutputRange(
const ParsedInternalKey& key) const override {
return (!key.user_key.starts_with("unsafe_pb"));
}
bool key_not_exists_beyond_output_level = false;
+2
View File
@@ -1900,6 +1900,8 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
sub_compact->start.has_value() ? &tmp_start : nullptr,
sub_compact->end.has_value() ? &tmp_end : nullptr);
if (oldest_ancester_time == std::numeric_limits<uint64_t>::max()) {
// TODO: fix DBSSTTest.GetTotalSstFilesSize and use
// kUnknownOldestAncesterTime
oldest_ancester_time = current_time;
}
+16 -11
View File
@@ -215,7 +215,9 @@ class CompactionJobTestBase : public testing::Test {
dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr,
/*io_tracer=*/nullptr, /*db_id*/ "", /*db_session_id*/ "")),
/*io_tracer=*/nullptr, /*db_id=*/"", /*db_session_id=*/"",
/*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr)),
shutting_down_(false),
mock_table_factory_(new mock::MockTableFactory()),
error_handler_(nullptr, db_options_, &mutex_),
@@ -540,11 +542,12 @@ class CompactionJobTestBase : public testing::Test {
ASSERT_OK(s);
db_options_.info_log = info_log;
versions_.reset(
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id*/ "", /*db_session_id*/ ""));
versions_.reset(new VersionSet(
dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr));
compaction_job_stats_.Reset();
ASSERT_OK(SetIdentityFile(env_, dbname_));
@@ -1524,13 +1527,15 @@ TEST_F(CompactionJobTest, VerifyPenultimateLevelOutput) {
{files0, files1, files2, files3}, input_levels,
/*verify_func=*/[&](Compaction& comp) {
for (char c = 'a'; c <= 'z'; c++) {
std::string c_str;
c_str = c;
const Slice key(c_str);
if (c == 'a') {
ASSERT_FALSE(comp.WithinPenultimateLevelOutputRange(key));
ParsedInternalKey pik("a", 0U, kTypeValue);
ASSERT_FALSE(comp.WithinPenultimateLevelOutputRange(pik));
} else {
ASSERT_TRUE(comp.WithinPenultimateLevelOutputRange(key));
std::string c_str{c};
// WithinPenultimateLevelOutputRange checks internal key range.
// 'z' is the last key, so set seqno properly.
ParsedInternalKey pik(c_str, c == 'z' ? 12U : 0U, kTypeValue);
ASSERT_TRUE(comp.WithinPenultimateLevelOutputRange(pik));
}
}
});
+4 -2
View File
@@ -85,7 +85,8 @@ class CompactionPickerTestBase : public testing::Test {
vstorage_.reset(new VersionStorageInfo(
&icmp_, ucmp_, options_.num_levels, style, nullptr, false,
EpochNumberRequirement::kMustPresent, ioptions_.clock,
options_.bottommost_file_compaction_delay));
options_.bottommost_file_compaction_delay,
OffpeakTimeOption(mutable_db_options_.daily_offpeak_time_utc)));
vstorage_->PrepareForVersionAppend(ioptions_, mutable_cf_options_);
}
@@ -95,7 +96,8 @@ class CompactionPickerTestBase : public testing::Test {
temp_vstorage_.reset(new VersionStorageInfo(
&icmp_, ucmp_, options_.num_levels, ioptions_.compaction_style,
vstorage_.get(), false, EpochNumberRequirement::kMustPresent,
ioptions_.clock, options_.bottommost_file_compaction_delay));
ioptions_.clock, options_.bottommost_file_compaction_delay,
OffpeakTimeOption(mutable_db_options_.daily_offpeak_time_utc)));
}
void DeleteVersionStorage() {
+107 -8
View File
@@ -1202,15 +1202,120 @@ TEST_P(TieredCompactionTest, RangeBasedTieredStorageLevel) {
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
ASSERT_EQ(GetSstSizeHelper(Temperature::kUnknown), 0);
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
ASSERT_EQ(
options.statistics->getTickerCount(COMPACTION_RANGE_DEL_DROP_OBSOLETE),
1);
// Tests that we only compact keys up to penultimate level
// that are within penultimate level input's internal key range.
{
MutexLock l(&mutex);
hot_start = Key(0);
hot_end = Key(100);
}
const Snapshot* temp_snap = db_->GetSnapshot();
// Key(0) and Key(1) here are inserted with higher sequence number
// than Key(0) and Key(1) inserted above.
// Only Key(0) in last level will be compacted up, not Key(1).
ASSERT_OK(Put(Key(0), "value" + std::to_string(0)));
ASSERT_OK(Put(Key(1), "value" + std::to_string(100)));
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
ASSERT_EQ("0,0,0,0,0,1,1", FilesPerLevel());
{
std::vector<LiveFileMetaData> metas;
db_->GetLiveFilesMetaData(&metas);
for (const auto& f : metas) {
if (f.temperature == Temperature::kUnknown) {
// Expect Key(0), Key(0), Key(1)
ASSERT_EQ(f.num_entries, 3);
ASSERT_EQ(f.smallestkey, Key(0));
ASSERT_EQ(f.largestkey, Key(1));
} else {
ASSERT_EQ(f.temperature, Temperature::kCold);
// Key(2)-Key(49) and Key(100).
ASSERT_EQ(f.num_entries, 50);
}
}
}
db_->ReleaseSnapshot(temp_snap);
}
INSTANTIATE_TEST_CASE_P(TieredCompactionTest, TieredCompactionTest,
testing::Bool());
TEST_P(TieredCompactionTest, CheckInternalKeyRange) {
// When compacting keys from the last level to penultimate level,
// output to penultimate level should be within internal key range
// of input files from penultimate level.
// Set up:
// L5:
// File 1: DeleteRange[1, 3)@4, File 2: [3@5, 100@6]
// L6:
// File 3: [2@1, 3@2], File 4: [50@3]
//
// When File 1 and File 3 are being compacted,
// Key(3) cannot be compacted up, otherwise it causes
// inconsistency where File 3's Key(3) has a lower sequence number
// than File 2's Key(3).
const int kNumLevels = 7;
auto options = CurrentOptions();
SetColdTemperature(options);
options.level_compaction_dynamic_level_bytes = true;
options.num_levels = kNumLevels;
options.statistics = CreateDBStatistics();
options.max_subcompactions = 10;
options.preclude_last_level_data_seconds = 10000;
DestroyAndReopen(options);
auto cmp = options.comparator;
std::string hot_start = Key(0);
std::string hot_end = Key(0);
SyncPoint::GetInstance()->SetCallBack(
"CompactionIterator::PrepareOutput.context", [&](void* arg) {
auto context = static_cast<PerKeyPlacementContext*>(arg);
context->output_to_penultimate_level =
cmp->Compare(context->key, hot_start) >= 0 &&
cmp->Compare(context->key, hot_end) < 0;
});
SyncPoint::GetInstance()->EnableProcessing();
// File 1
ASSERT_OK(Put(Key(2), "val2"));
ASSERT_OK(Put(Key(3), "val3"));
ASSERT_OK(Flush());
MoveFilesToLevel(6);
// File 2
ASSERT_OK(Put(Key(50), "val50"));
ASSERT_OK(Flush());
MoveFilesToLevel(6);
const Snapshot* snapshot = db_->GetSnapshot();
hot_end = Key(100);
std::string start = Key(1);
std::string end = Key(3);
ASSERT_OK(
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), start, end));
ASSERT_OK(Flush());
MoveFilesToLevel(5);
// File 3
ASSERT_OK(Put(Key(3), "vall"));
ASSERT_OK(Put(Key(100), "val100"));
ASSERT_OK(Flush());
MoveFilesToLevel(5);
// Try to compact keys up
CompactRangeOptions cro;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
start = Key(1);
end = Key(2);
Slice begin_slice(start);
Slice end_slice(end);
ASSERT_OK(db_->CompactRange(cro, &begin_slice, &end_slice));
// Without internal key range checking, we get the following error:
// Corruption: force_consistency_checks(DEBUG): VersionBuilder: L5 has
// overlapping ranges: file #18 largest key: '6B6579303030303033' seq:102,
// type:1 vs. file #15 smallest key: '6B6579303030303033' seq:104, type:1
db_->ReleaseSnapshot(snapshot);
}
class PrecludeLastLevelTest : public DBTestBase {
public:
PrecludeLastLevelTest()
@@ -1971,13 +2076,7 @@ TEST_F(PrecludeLastLevelTest, PartialPenultimateLevelCompaction) {
Close();
}
// 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) {
TEST_F(PrecludeLastLevelTest, RangeDelsCauseFileEndpointsToOverlap) {
const int kNumLevels = 7;
const int kSecondsPerKey = 10;
const int kNumFiles = 3;
+7
View File
@@ -4415,6 +4415,8 @@ TEST_F(DBBasicTest, ManifestWriteFailure) {
options.create_if_missing = true;
options.disable_auto_compactions = true;
options.env = env_;
options.enable_blob_files = true;
options.blob_file_size = 0;
DestroyAndReopen(options);
ASSERT_OK(Put("foo", "bar"));
ASSERT_OK(Flush());
@@ -4435,6 +4437,11 @@ TEST_F(DBBasicTest, ManifestWriteFailure) {
SyncPoint::GetInstance()->ClearAllCallBacks();
SyncPoint::GetInstance()->EnableProcessing();
Reopen(options);
// The IO error was a mocked one from the `AfterSyncManifest` callback. The
// Flush's VersionEdit actually made it into the Manifest. So these keys can
// be read back. Read them to check all live sst files and blob files.
ASSERT_EQ("bar", Get("foo"));
ASSERT_EQ("value", Get("key"));
}
TEST_F(DBBasicTest, DestroyDefaultCfHandle) {
+107 -7
View File
@@ -19,7 +19,7 @@
#include "rocksdb/concurrent_task_limiter.h"
#include "rocksdb/experimental.h"
#include "rocksdb/sst_file_writer.h"
#include "rocksdb/utilities/convenience.h"
#include "test_util/mock_time_env.h"
#include "test_util/sync_point.h"
#include "test_util/testutil.h"
#include "util/concurrent_task_limiter_impl.h"
@@ -4794,9 +4794,9 @@ TEST_F(DBCompactionTest, LevelTtlCascadingCompactions) {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"VersionEdit::EncodeTo:VarintOldestAncesterTime", [&](void* arg) {
if (if_restart && if_open_all_files) {
std::string* encoded_fieled = static_cast<std::string*>(arg);
*encoded_fieled = "";
PutVarint64(encoded_fieled, 0);
std::string* encoded_field = static_cast<std::string*>(arg);
*encoded_field = "";
PutVarint64(encoded_field, 0);
}
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
@@ -4942,9 +4942,9 @@ TEST_F(DBCompactionTest, LevelPeriodicCompaction) {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"VersionEdit::EncodeTo:VarintFileCreationTime", [&](void* arg) {
if (if_restart && if_open_all_files) {
std::string* encoded_fieled = static_cast<std::string*>(arg);
*encoded_fieled = "";
PutVarint64(encoded_fieled, 0);
std::string* encoded_field = static_cast<std::string*>(arg);
*encoded_field = "";
PutVarint64(encoded_field, 0);
}
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
@@ -5020,6 +5020,106 @@ TEST_F(DBCompactionTest, LevelPeriodicCompaction) {
}
}
TEST_F(DBCompactionTest, LevelPeriodicCompactionOffpeak) {
// This test simply checks if offpeak adjustment works in Leveled
// Compactions. For testing offpeak periodic compactions in various
// scenarios, please refer to
// DBTestUniversalCompaction2::PeriodicCompactionOffpeak
constexpr int kNumKeysPerFile = 32;
constexpr int kNumLevelFiles = 2;
constexpr int kValueSize = 100;
constexpr int kSecondsPerDay = 86400;
constexpr int kSecondsPerHour = 3600;
constexpr int kSecondsPerMinute = 60;
for (bool if_restart : {false, true}) {
SCOPED_TRACE("if_restart=" + std::to_string(if_restart));
Options options = CurrentOptions();
options.ttl = 0;
options.periodic_compaction_seconds = 5 * kSecondsPerDay; // 5 days
// In the case where all files are opened and doing DB restart
// forcing the file creation time in manifest file to be 0 to
// simulate the case of reading from an old version.
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"VersionEdit::EncodeTo:VarintFileCreationTime", [&](void* arg) {
if (if_restart) {
std::string* encoded_field = static_cast<std::string*>(arg);
*encoded_field = "";
PutVarint64(encoded_field, 0);
}
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
// Just to add some extra random days to current time
Random rnd(test::RandomSeed());
int days = rnd.Uniform(100);
int periodic_compactions = 0;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"LevelCompactionPicker::PickCompaction:Return", [&](void* arg) {
Compaction* compaction = static_cast<Compaction*>(arg);
auto compaction_reason = compaction->compaction_reason();
if (compaction_reason == CompactionReason::kPeriodicCompaction) {
periodic_compactions++;
}
});
// Starting at 12:15AM
int now_hour = 0;
int now_minute = 15;
auto mock_clock = std::make_shared<MockSystemClock>(env_->GetSystemClock());
auto mock_env = std::make_unique<CompositeEnvWrapper>(env_, mock_clock);
options.env = mock_env.get();
mock_clock->SetCurrentTime(days * kSecondsPerDay +
now_hour * kSecondsPerHour +
now_minute * kSecondsPerMinute);
// Offpeak is set from 12:30AM to 4:30AM
options.daily_offpeak_time_utc = "00:30-04:30";
Reopen(options);
for (int i = 0; i < kNumLevelFiles; ++i) {
for (int j = 0; j < kNumKeysPerFile; ++j) {
ASSERT_OK(
Put(Key(i * kNumKeysPerFile + j), rnd.RandomString(kValueSize)));
}
ASSERT_OK(Flush());
}
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ASSERT_EQ("2", FilesPerLevel());
ASSERT_EQ(0, periodic_compactions);
// Move clock forward by 1 hour. Now at 1:15AM Day 0. No compaction.
mock_clock->MockSleepForSeconds(1 * kSecondsPerHour);
ASSERT_OK(Put("a", "1"));
ASSERT_OK(Flush());
ASSERT_OK(dbfull()->TEST_WaitForCompact());
// Assert that the files stay in the same level
ASSERT_EQ("3", FilesPerLevel());
ASSERT_EQ(0, periodic_compactions);
MoveFilesToLevel(1);
ASSERT_EQ("0,3", FilesPerLevel());
// Move clock forward by 4 days and check if it triggers periodic
// comapaction at 1:15AM Day 4. Files created on Day 0 at 12:15AM is
// expected to expire before the offpeak starts next day at 12:30AM
mock_clock->MockSleepForSeconds(4 * kSecondsPerDay);
ASSERT_OK(Put("b", "2"));
if (if_restart) {
Reopen(options);
} else {
ASSERT_OK(Flush());
}
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ASSERT_EQ("1,3", FilesPerLevel());
// The two old files go through the periodic compaction process
ASSERT_EQ(2, periodic_compactions);
Destroy(options);
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
}
}
TEST_F(DBCompactionTest, LevelPeriodicCompactionWithOldDB) {
// This test makes sure that periodic compactions are working with a DB
// where file_creation_time of some files is 0.
+1 -1
View File
@@ -121,7 +121,7 @@ Status DBImpl::GetSortedWalFiles(VectorLogPtr& files) {
// DisableFileDeletions / EnableFileDeletions not supported in read-only DB
if (deletions_disabled.ok()) {
Status s2 = EnableFileDeletions(/*force*/ false);
Status s2 = EnableFileDeletions(/*force=*/false);
assert(s2.ok());
s2.PermitUncheckedError();
} else {
+82 -10
View File
@@ -276,10 +276,11 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
this->RecordSeqnoToTimeMapping(/*populate_historical_seconds=*/0);
});
versions_.reset(new VersionSet(dbname_, &immutable_db_options_, file_options_,
table_cache_.get(), write_buffer_manager_,
&write_controller_, &block_cache_tracer_,
io_tracer_, db_id_, db_session_id_));
versions_.reset(new VersionSet(
dbname_, &immutable_db_options_, file_options_, table_cache_.get(),
write_buffer_manager_, &write_controller_, &block_cache_tracer_,
io_tracer_, db_id_, db_session_id_, options.daily_offpeak_time_utc,
&error_handler_));
column_family_memtables_.reset(
new ColumnFamilyMemTablesImpl(versions_->GetColumnFamilySet()));
@@ -359,10 +360,8 @@ Status DBImpl::ResumeImpl(DBRecoverContext context) {
if (io_s.IsIOError()) {
// If resuming from IOError resulted from MANIFEST write, then assert
// that we must have already set the MANIFEST writer to nullptr during
// clean-up phase MANIFEST writing. We must have also disabled file
// deletions.
// clean-up phase MANIFEST writing.
assert(!versions_->descriptor_log_);
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
@@ -1328,17 +1327,24 @@ Status DBImpl::SetDBOptions(
const bool max_compactions_increased =
new_bg_job_limits.max_compactions >
current_bg_job_limits.max_compactions;
const bool offpeak_time_changed =
versions_->offpeak_time_option().daily_offpeak_time_utc !=
new_db_options.daily_offpeak_time_utc;
if (max_flushes_increased || max_compactions_increased) {
if (max_flushes_increased || max_compactions_increased ||
offpeak_time_changed) {
if (max_flushes_increased) {
env_->IncBackgroundThreadsIfNeeded(new_bg_job_limits.max_flushes,
Env::Priority::HIGH);
}
if (max_compactions_increased) {
env_->IncBackgroundThreadsIfNeeded(new_bg_job_limits.max_compactions,
Env::Priority::LOW);
}
if (offpeak_time_changed) {
versions_->ChangeOffpeakTimeOption(
new_db_options.daily_offpeak_time_utc);
}
MaybeScheduleFlushOrCompaction();
}
@@ -2050,6 +2056,73 @@ Status DBImpl::GetEntity(const ReadOptions& _read_options,
return GetImpl(read_options, key, get_impl_options);
}
Status DBImpl::GetEntity(const ReadOptions& _read_options, const Slice& key,
PinnableAttributeGroups* result) {
if (!result) {
return Status::InvalidArgument(
"Cannot call GetEntity without PinnableAttributeGroups object");
}
Status s;
const size_t num_column_families = result->size();
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
_read_options.io_activity != Env::IOActivity::kGetEntity) {
s = Status::InvalidArgument(
"Cannot call GetEntity with `ReadOptions::io_activity` != "
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kGetEntity`");
for (size_t i = 0; i < num_column_families; ++i) {
(*result)[i].SetStatus(s);
}
return s;
}
// return early if no CF was passed in
if (num_column_families == 0) {
return s;
}
ReadOptions read_options(_read_options);
if (read_options.io_activity == Env::IOActivity::kUnknown) {
read_options.io_activity = Env::IOActivity::kGetEntity;
}
std::vector<Slice> keys;
std::vector<ColumnFamilyHandle*> column_families;
for (size_t i = 0; i < num_column_families; ++i) {
// If any of the CFH is null, break early since the entire query will fail
if (!(*result)[i].column_family()) {
s = Status::InvalidArgument(
"DB failed to query because one or more group(s) have null column "
"family handle");
(*result)[i].SetStatus(
Status::InvalidArgument("Column family handle cannot be null"));
break;
}
// Adding the same key slice for different CFs
keys.emplace_back(key);
column_families.emplace_back((*result)[i].column_family());
}
if (!s.ok()) {
for (size_t i = 0; i < num_column_families; ++i) {
if ((*result)[i].status().ok()) {
(*result)[i].SetStatus(
Status::Incomplete("DB not queried due to invalid argument(s) in "
"one or more of the attribute groups"));
}
}
return s;
}
std::vector<PinnableWideColumns> columns(num_column_families);
std::vector<Status> statuses(num_column_families);
MultiGetCommon(
read_options, num_column_families, column_families.data(), keys.data(),
/* values */ nullptr, columns.data(),
/* timestamps */ nullptr, statuses.data(), /* sorted_input */ false);
// Set results
for (size_t i = 0; i < num_column_families; ++i) {
(*result)[i].Reset();
(*result)[i].SetStatus(statuses[i]);
(*result)[i].SetColumns(std::move(columns[i]));
}
return s;
}
bool DBImpl::ShouldReferenceSuperVersion(const MergeContext& merge_context) {
// If both thresholds are reached, a function returning merge operands as
// `PinnableSlice`s should reference the `SuperVersion` to avoid large and/or
@@ -2829,7 +2902,6 @@ void DBImpl::MultiGetCommon(const ReadOptions& read_options,
bool should_fail = false;
for (size_t i = 0; i < num_keys; ++i) {
ColumnFamilyHandle* cfh = column_families[i];
assert(cfh);
if (read_options.timestamp) {
statuses[i] = FailIfTsMismatchCf(cfh, *(read_options.timestamp));
if (!statuses[i].ok()) {
+11 -7
View File
@@ -197,6 +197,8 @@ class DBImpl : public DB {
Status PutEntity(const WriteOptions& options,
ColumnFamilyHandle* column_family, const Slice& key,
const WideColumns& columns) override;
Status PutEntity(const WriteOptions& options, const Slice& key,
const AttributeGroups& attribute_groups) override;
using DB::Merge;
Status Merge(const WriteOptions& options, ColumnFamilyHandle* column_family,
@@ -242,6 +244,8 @@ class DBImpl : public DB {
Status GetEntity(const ReadOptions& options,
ColumnFamilyHandle* column_family, const Slice& key,
PinnableWideColumns* columns) override;
Status GetEntity(const ReadOptions& options, const Slice& key,
PinnableAttributeGroups* result) override;
using DB::GetMergeOperands;
Status GetMergeOperands(const ReadOptions& options,
@@ -1186,6 +1190,7 @@ class DBImpl : public DB {
size_t TEST_GetWalPreallocateBlockSize(uint64_t write_buffer_size) const;
void TEST_WaitForPeriodicTaskRun(std::function<void()> callback) const;
SeqnoToTimeMapping TEST_GetSeqnoToTimeMapping() const;
const autovector<uint64_t>& TEST_GetFilesToQuarantine() const;
size_t TEST_EstimateInMemoryStatsHistorySize() const;
uint64_t TEST_GetCurrentLogNumber() const {
@@ -1393,8 +1398,9 @@ 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;
};
@@ -2375,10 +2381,6 @@ 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);
@@ -2868,7 +2870,9 @@ static void ClipToRange(T* ptr, V minvalue, V maxvalue) {
inline Status DBImpl::FailIfCfHasTs(
const ColumnFamilyHandle* column_family) const {
column_family = column_family ? column_family : DefaultColumnFamily();
if (!column_family) {
return Status::InvalidArgument("column family handle cannot be null");
}
assert(column_family);
const Comparator* const ucmp = column_family->GetComparator();
assert(ucmp);
+1
View File
@@ -2783,6 +2783,7 @@ void DBImpl::EnableManualCompaction() {
void DBImpl::MaybeScheduleFlushOrCompaction() {
mutex_.AssertHeld();
TEST_SYNC_POINT("DBImpl::MaybeScheduleFlushOrCompaction:Start");
if (!opened_successfully_) {
// Compaction may introduce data race to DB open
return;
+4
View File
@@ -309,6 +309,10 @@ SeqnoToTimeMapping DBImpl::TEST_GetSeqnoToTimeMapping() const {
return seqno_to_time_mapping_;
}
const autovector<uint64_t>& DBImpl::TEST_GetFilesToQuarantine() const {
InstrumentedMutexLock l(&mutex_);
return error_handler_.GetFilesToQuarantine();
}
size_t DBImpl::TEST_EstimateInMemoryStatsHistorySize() const {
InstrumentedMutexLock l(&const_cast<DBImpl*>(this)->stats_history_mutex_);
+8 -9
View File
@@ -100,14 +100,6 @@ 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_;
}
@@ -154,6 +146,7 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
// mutex_ cannot be released. Otherwise, we might see no min_pending_output
// here but later find newer generated unfinalized files while scanning.
job_context->min_pending_output = MinObsoleteSstNumberToKeep();
job_context->files_to_quarantine = error_handler_.GetFilesToQuarantine();
// Get obsolete files. This function will also update the list of
// pending files in VersionSet().
@@ -427,6 +420,8 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
state.blob_live.end());
std::unordered_set<uint64_t> log_recycle_files_set(
state.log_recycle_files.begin(), state.log_recycle_files.end());
std::unordered_set<uint64_t> quarantine_files_set(
state.files_to_quarantine.begin(), state.files_to_quarantine.end());
auto candidate_files = state.full_scan_candidate_files;
candidate_files.reserve(
@@ -530,6 +525,10 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
continue;
}
if (quarantine_files_set.find(number) != quarantine_files_set.end()) {
continue;
}
bool keep = true;
switch (type) {
case kWalFile:
@@ -1003,7 +1002,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);
}
}
}
+12 -2
View File
@@ -942,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;
}
@@ -1248,6 +1251,13 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& wal_numbers,
}
SequenceNumber sequence = WriteBatchInternal::Sequence(batch_to_use);
if (sequence > kMaxSequenceNumber) {
reporter.Corruption(
record.size(),
Status::Corruption("sequence " + std::to_string(sequence) +
" is too large"));
continue;
}
if (immutable_db_options_.wal_recovery_mode ==
WALRecoveryMode::kPointInTimeRecovery) {
+4
View File
@@ -53,6 +53,10 @@ class DBImplReadOnly : public DBImpl {
const WideColumns& /* columns */) override {
return Status::NotSupported("Not supported operation in read only mode.");
}
Status PutEntity(const WriteOptions& /* options */, const Slice& /* key */,
const AttributeGroups& /* attribute_groups */) override {
return Status::NotSupported("Not supported operation in read only mode.");
}
using DBImpl::Merge;
virtual Status Merge(const WriteOptions& /*options*/,
+4
View File
@@ -136,6 +136,10 @@ class DBImplSecondary : public DBImpl {
const WideColumns& /* columns */) override {
return Status::NotSupported("Not supported operation in secondary mode.");
}
Status PutEntity(const WriteOptions& /* options */, const Slice& /* key */,
const AttributeGroups& /* attribute_groups */) override {
return Status::NotSupported("Not supported operation in secondary mode.");
}
using DBImpl::Merge;
Status Merge(const WriteOptions& /*options*/,
+39 -7
View File
@@ -48,6 +48,17 @@ Status DBImpl::PutEntity(const WriteOptions& options,
return DB::PutEntity(options, column_family, key, columns);
}
Status DBImpl::PutEntity(const WriteOptions& options, const Slice& key,
const AttributeGroups& attribute_groups) {
for (const AttributeGroup& ag : attribute_groups) {
const Status s = FailIfCfHasTs(ag.column_family());
if (!s.ok()) {
return s;
}
}
return DB::PutEntity(options, key, attribute_groups);
}
Status DBImpl::Merge(const WriteOptions& o, ColumnFamilyHandle* column_family,
const Slice& key, const Slice& val) {
const Status s = FailIfCfHasTs(column_family);
@@ -1814,11 +1825,9 @@ uint64_t DBImpl::GetMaxTotalWalSize() const {
Status DBImpl::DelayWrite(uint64_t num_bytes, WriteThread& write_thread,
const WriteOptions& write_options) {
mutex_.AssertHeld();
uint64_t time_delayed = 0;
uint64_t start_time = 0;
bool delayed = false;
{
StopWatch sw(immutable_db_options_.clock, stats_, WRITE_STALL,
Histograms::HISTOGRAM_ENUM_MAX, &time_delayed);
// To avoid parallel timed delays (bad throttling), only support them
// on the primary write queue.
uint64_t delay;
@@ -1834,6 +1843,7 @@ Status DBImpl::DelayWrite(uint64_t num_bytes, WriteThread& write_thread,
if (write_options.no_slowdown) {
return Status::Incomplete("Write stall");
}
start_time = immutable_db_options_.clock->NowMicros();
TEST_SYNC_POINT("DBImpl::DelayWrite:Sleep");
// Notify write_thread about the stall so it can setup a barrier and
@@ -1846,7 +1856,7 @@ Status DBImpl::DelayWrite(uint64_t num_bytes, WriteThread& write_thread,
// (slightly longer because WriteController minimum delay is 1ms, in
// case of sleep imprecision, rounding, etc.)
const uint64_t kDelayInterval = 1001;
uint64_t stall_end = sw.start_time() + delay;
uint64_t stall_end = start_time + delay;
while (write_controller_.NeedsDelay()) {
if (immutable_db_options_.clock->NowMicros() >= stall_end) {
// We already delayed this write `delay` microseconds
@@ -1887,9 +1897,11 @@ Status DBImpl::DelayWrite(uint64_t num_bytes, WriteThread& write_thread,
}
assert(!delayed || !write_options.no_slowdown);
if (delayed) {
auto time_delayed = immutable_db_options_.clock->NowMicros() - start_time;
default_cf_internal_stats_->AddDBStats(
InternalStats::kIntStatsWriteStallMicros, time_delayed);
RecordTick(stats_, STALL_MICROS, time_delayed);
RecordInHistogram(stats_, WRITE_STALL, time_delayed);
}
// If DB is not in read-only mode and write_controller is not stopping
@@ -1956,9 +1968,13 @@ Status DBImpl::ThrottleLowPriWritesIfNeeded(const WriteOptions& write_options,
// a chance to run. Now we guarantee we are still slowly making
// progress.
PERF_TIMER_GUARD(write_delay_time);
write_controller_.low_pri_rate_limiter()->Request(
my_batch->GetDataSize(), Env::IO_HIGH, nullptr /* stats */,
RateLimiter::OpType::kWrite);
auto data_size = my_batch->GetDataSize();
while (data_size > 0) {
size_t allowed = write_controller_.low_pri_rate_limiter()->RequestToken(
data_size, 0 /* alignment */, Env::IO_HIGH, nullptr /* stats */,
RateLimiter::OpType::kWrite);
data_size -= allowed;
}
}
}
return Status::OK();
@@ -2381,6 +2397,22 @@ Status DB::PutEntity(const WriteOptions& options,
return Write(options, &batch);
}
Status DB::PutEntity(const WriteOptions& options, const Slice& key,
const AttributeGroups& attribute_groups) {
ColumnFamilyHandle* default_cf = DefaultColumnFamily();
assert(default_cf);
const Comparator* const default_cf_ucmp = default_cf->GetComparator();
assert(default_cf_ucmp);
WriteBatch batch(0 /* reserved_bytes */, 0 /* max_bytes */,
options.protection_bytes_per_key,
default_cf_ucmp->timestamp_size());
const Status s = batch.PutEntity(key, attribute_groups);
if (!s.ok()) {
return s;
}
return Write(options, &batch);
}
Status DB::Delete(const WriteOptions& opt, ColumnFamilyHandle* column_family,
const Slice& key) {
WriteBatch batch(0 /* reserved_bytes */, 0 /* max_bytes */,
+3
View File
@@ -114,6 +114,9 @@ Status DBIter::GetProperty(std::string prop_name, std::string* prop) {
} else if (prop_name == "rocksdb.iterator.internal-key") {
*prop = saved_key_.GetUserKey().ToString();
return Status::OK();
} else if (prop_name == "rocksdb.iterator.write-time") {
// TODO(yuzhangyu): implement return the actual write time.
return Status::NotSupported("write time property is under construction");
}
return Status::InvalidArgument("Unidentified property.");
}
+1 -1
View File
@@ -236,7 +236,7 @@ TEST_F(DBTestXactLogIterator, TransactionLogIteratorCorruptedLog) {
ASSERT_OK(test::TruncateFile(env_, logfile_path,
wal_files.front()->SizeFileBytes() / 2));
ASSERT_OK(db_->EnableFileDeletions());
ASSERT_OK(db_->EnableFileDeletions(/*force=*/false));
// Insert a new entry to a new log file
ASSERT_OK(Put("key1025", DummyString(10)));
+93 -107
View File
@@ -1099,7 +1099,7 @@ TEST_F(DBOptionsTest, SetFIFOCompactionOptions) {
ASSERT_EQ(fifo_temp_opt[1].age, 30000);
}
TEST_F(DBOptionsTest, OffPeakTimes) {
TEST_F(DBOptionsTest, OffpeakTimes) {
Options options;
options.create_if_missing = true;
Random rnd(test::RandomSeed());
@@ -1155,139 +1155,125 @@ TEST_F(DBOptionsTest, OffPeakTimes) {
verify_valid();
}
auto verify_is_now_offpeak = [&](bool expected, int now_utc_hour,
int now_utc_minute, int now_utc_second = 0) {
auto verify_offpeak_info = [&](bool expected_is_now_off_peak,
int expected_seconds_till_next_offpeak_start,
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);
mock_clock->SetCurrentTime(
days * OffpeakTimeOption::kSecondsPerDay +
now_utc_hour * OffpeakTimeOption::kSecondsPerHour +
now_utc_minute * OffpeakTimeOption::kSecondsPerMinute + 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()));
auto offpeak_option = OffpeakTimeOption(options.daily_offpeak_time_utc);
int64_t now;
ASSERT_OK(mock_clock.get()->GetCurrentTime(&now));
auto offpeak_info = offpeak_option.GetOffpeakTimeInfo(now);
ASSERT_EQ(expected_is_now_off_peak, offpeak_info.is_now_offpeak);
ASSERT_EQ(expected_seconds_till_next_offpeak_start,
offpeak_info.seconds_till_next_offpeak_start);
};
options.daily_offpeak_time_utc = "";
verify_is_now_offpeak(false, 12, 30);
verify_offpeak_info(false, 0, 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);
verify_offpeak_info(false, 1 * OffpeakTimeOption::kSecondsPerHour, 5, 30);
verify_offpeak_info(true, 24 * OffpeakTimeOption::kSecondsPerHour, 6, 30);
verify_offpeak_info(true, 20 * OffpeakTimeOption::kSecondsPerHour, 10, 30);
verify_offpeak_info(true, 19 * OffpeakTimeOption::kSecondsPerHour, 11, 30);
verify_offpeak_info(false, 17 * OffpeakTimeOption::kSecondsPerHour, 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);
verify_offpeak_info(false, 17 * OffpeakTimeOption::kSecondsPerHour, 6, 30);
verify_offpeak_info(true, 24 * OffpeakTimeOption::kSecondsPerHour, 23, 30);
verify_offpeak_info(true,
23 * OffpeakTimeOption::kSecondsPerHour +
30 * OffpeakTimeOption::kSecondsPerMinute,
0, 0);
verify_offpeak_info(true,
22 * OffpeakTimeOption::kSecondsPerHour +
30 * OffpeakTimeOption::kSecondsPerMinute,
1, 0);
verify_offpeak_info(true, 19 * OffpeakTimeOption::kSecondsPerHour, 4, 30);
verify_offpeak_info(false,
18 * OffpeakTimeOption::kSecondsPerHour +
59 * OffpeakTimeOption::kSecondsPerMinute,
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);
verify_offpeak_info(true, 24 * OffpeakTimeOption::kSecondsPerHour, 0, 0);
verify_offpeak_info(true, 12 * OffpeakTimeOption::kSecondsPerHour, 12, 00);
verify_offpeak_info(true, 1 * OffpeakTimeOption::kSecondsPerMinute, 23, 59);
verify_offpeak_info(true, 59, 23, 59, 1);
verify_offpeak_info(true, 1, 23, 59, 59);
// Open the db and test by Get/SetDBOptions
options.daily_offpeak_time_utc = "";
// Start with a valid option
options.daily_offpeak_time_utc = "01:30-04:15";
DestroyAndReopen(options);
ASSERT_EQ("", dbfull()->GetDBOptions().daily_offpeak_time_utc);
ASSERT_EQ("01:30-04:15", dbfull()->GetDBOptions().daily_offpeak_time_utc);
int may_schedule_compaction_called = 0;
SyncPoint::GetInstance()->SetCallBack(
"DBImpl::MaybeScheduleFlushOrCompaction:Start",
[&](void*) { may_schedule_compaction_called++; });
SyncPoint::GetInstance()->EnableProcessing();
// Make sure calling SetDBOptions with invalid option does not change the
// value nor call MaybeScheduleFlushOrCompaction()
for (std::string invalid_case : invalid_cases) {
ASSERT_NOK(
dbfull()->SetDBOptions({{"daily_offpeak_time_utc", invalid_case}}));
ASSERT_EQ("01:30-04:15", dbfull()
->GetVersionSet()
->offpeak_time_option()
.daily_offpeak_time_utc);
ASSERT_EQ(1 * kSecondInHour + 30 * kSecondInMinute,
dbfull()
->GetVersionSet()
->offpeak_time_option()
.daily_offpeak_start_time_utc);
ASSERT_EQ(4 * kSecondInHour + 15 * kSecondInMinute,
dbfull()
->GetVersionSet()
->offpeak_time_option()
.daily_offpeak_end_time_utc);
}
ASSERT_EQ(0, may_schedule_compaction_called);
// Changing to new valid values should call MaybeScheduleFlushOrCompaction()
// and sets the offpeak_time_option in VersionSet
int expected_count = 0;
for (std::string valid_case : valid_cases) {
if (dbfull()
->GetVersionSet()
->offpeak_time_option()
.daily_offpeak_time_utc != valid_case) {
expected_count++;
}
ASSERT_OK(dbfull()->SetDBOptions({{"daily_offpeak_time_utc", valid_case}}));
ASSERT_EQ(valid_case, dbfull()->GetDBOptions().daily_offpeak_time_utc);
ASSERT_EQ(valid_case, dbfull()
->GetVersionSet()
->offpeak_time_option()
.daily_offpeak_time_utc);
}
Close();
ASSERT_EQ(expected_count, may_schedule_compaction_called);
// 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()));
// Changing to the same value should not call MaybeScheduleFlushOrCompaction()
ASSERT_OK(
dbfull()->SetDBOptions({{"daily_offpeak_time_utc", "06:30-11:30"}}));
may_schedule_compaction_called = 0;
ASSERT_OK(
dbfull()->SetDBOptions({{"daily_offpeak_time_utc", "06:30-11:30"}}));
ASSERT_EQ(0, may_schedule_compaction_called);
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
Close();
}
+7 -3
View File
@@ -107,12 +107,12 @@ TEST_F(DBPropertiesTest, Empty) {
dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num));
ASSERT_EQ("0", num);
ASSERT_OK(db_->EnableFileDeletions(false));
ASSERT_OK(db_->EnableFileDeletions(/*force=*/false));
ASSERT_TRUE(
dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num));
ASSERT_EQ("0", num);
ASSERT_OK(db_->EnableFileDeletions());
ASSERT_OK(db_->EnableFileDeletions(/*force=*/true));
ASSERT_TRUE(
dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num));
ASSERT_EQ("1", num);
@@ -1084,12 +1084,14 @@ class CountingUserTblPropCollector : public TablePropertiesCollector {
const char* Name() const override { return "CountingUserTblPropCollector"; }
Status Finish(UserCollectedProperties* properties) override {
assert(!finish_called_);
std::string encoded;
PutVarint32(&encoded, count_);
*properties = UserCollectedProperties{
{"CountingUserTblPropCollector", message_},
{"Count", encoded},
};
finish_called_ = true;
return Status::OK();
}
@@ -1101,12 +1103,14 @@ class CountingUserTblPropCollector : public TablePropertiesCollector {
}
UserCollectedProperties GetReadableProperties() const override {
assert(finish_called_);
return UserCollectedProperties{};
}
private:
std::string message_ = "Rocksdb";
uint32_t count_ = 0;
bool finish_called_ = false;
};
class CountingUserTblPropCollectorFactory
@@ -1740,7 +1744,7 @@ TEST_F(DBPropertiesTest, SstFilesSize) {
ASSERT_EQ(obsolete_sst_size, sst_size);
// Let the obsolete files be deleted.
ASSERT_OK(db_->EnableFileDeletions());
ASSERT_OK(db_->EnableFileDeletions(/*force=*/false));
ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kObsoleteSstFilesSize,
&obsolete_sst_size));
ASSERT_EQ(obsolete_sst_size, 0);
+26 -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
@@ -1520,6 +1538,11 @@ TEST_F(DBSSTTest, OpenDBWithInfiniteMaxOpenFilesSubjectToMemoryLimit) {
}
TEST_F(DBSSTTest, GetTotalSstFilesSize) {
// FIXME: L0 file and L1+ file also differ in size of `oldest_key_time`.
// L0 file has non-zero `oldest_key_time` while L1+ files have 0.
// The test passes since L1+ file uses current time instead of 0
// as oldest_ancestor_time.
//
// We don't propagate oldest-key-time table property on compaction and
// just write 0 as default value. This affect the exact table size, since
// we encode table properties as varint64. Force time to be 0 to work around
+13 -3
View File
@@ -3882,16 +3882,26 @@ TEST_F(DBTest2, LowPriWrite) {
ASSERT_OK(Put("", "", wo));
ASSERT_EQ(1, rate_limit_count.load());
wo.low_pri = true;
std::string big_value = std::string(1 * 1024 * 1024, 'x');
ASSERT_OK(Put("", big_value, wo));
ASSERT_LT(1, rate_limit_count.load());
// Reset
rate_limit_count = 0;
wo.low_pri = false;
ASSERT_OK(Put("", big_value, wo));
ASSERT_EQ(0, rate_limit_count.load());
TEST_SYNC_POINT("DBTest.LowPriWrite:0");
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
ASSERT_OK(dbfull()->TEST_WaitForCompact());
wo.low_pri = true;
ASSERT_OK(Put("", "", wo));
ASSERT_EQ(1, rate_limit_count.load());
ASSERT_EQ(0, rate_limit_count.load());
wo.low_pri = false;
ASSERT_OK(Put("", "", wo));
ASSERT_EQ(1, rate_limit_count.load());
ASSERT_EQ(0, rate_limit_count.load());
}
TEST_F(DBTest2, RateLimitedCompactionReads) {
@@ -4125,7 +4135,7 @@ TEST_F(DBTest2, LiveFilesOmitObsoleteFiles) {
ASSERT_OK(env_->FileExists(LogFileName(dbname_, log_file->LogNumber())));
}
ASSERT_OK(db_->EnableFileDeletions());
ASSERT_OK(db_->EnableFileDeletions(/*force=*/false));
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
}
+138
View File
@@ -7,9 +7,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. See the AUTHORS file for names of contributors.
#include <string>
#include "db/db_test_util.h"
#include "port/stack_trace.h"
#include "rocksdb/utilities/table_properties_collectors.h"
#include "test_util/mock_time_env.h"
#include "test_util/sync_point.h"
#include "test_util/testutil.h"
#include "util/random.h"
@@ -2230,6 +2233,141 @@ TEST_F(DBTestUniversalCompaction2, PeriodicCompaction) {
ASSERT_EQ(4, output_level);
}
TEST_F(DBTestUniversalCompaction2, PeriodicCompactionOffpeak) {
constexpr int kSecondsPerDay = 86400;
constexpr int kSecondsPerHour = 3600;
constexpr int kSecondsPerMinute = 60;
Options opts = CurrentOptions();
opts.compaction_style = kCompactionStyleUniversal;
opts.level0_file_num_compaction_trigger = 10;
opts.max_open_files = -1;
opts.compaction_options_universal.size_ratio = 10;
opts.compaction_options_universal.min_merge_width = 2;
opts.compaction_options_universal.max_size_amplification_percent = 200;
opts.periodic_compaction_seconds = 5 * kSecondsPerDay; // 5 days
opts.num_levels = 5;
// Just to add some extra random days to current time
Random rnd(test::RandomSeed());
int days = rnd.Uniform(100);
int periodic_compactions = 0;
int start_level = -1;
int output_level = -1;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"UniversalCompactionPicker::PickPeriodicCompaction:Return",
[&](void* arg) {
Compaction* compaction = reinterpret_cast<Compaction*>(arg);
ASSERT_TRUE(arg != nullptr);
ASSERT_TRUE(compaction->compaction_reason() ==
CompactionReason::kPeriodicCompaction);
start_level = compaction->start_level();
output_level = compaction->output_level();
periodic_compactions++;
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
for (std::string preset_offpeak_time : {"", "00:30-04:30", "10:30-02:30"}) {
SCOPED_TRACE("preset_offpeak_time=" + preset_offpeak_time);
for (std::string new_offpeak_time : {"", "23:30-02:30"}) {
SCOPED_TRACE("new_offpeak_time=" + new_offpeak_time);
std::vector<std::pair<int, int>> times_to_test = {
{0, 0}, {2, 30}, {3, 15}, {5, 10}, {13, 30}, {23, 30}};
for (std::pair<int, int> now : times_to_test) {
int now_hour = now.first;
int now_minute = now.second;
SCOPED_TRACE("now=" + std::to_string(now_hour) + ":" +
std::to_string(now_minute));
auto mock_clock =
std::make_shared<MockSystemClock>(env_->GetSystemClock());
auto mock_env = std::make_unique<CompositeEnvWrapper>(env_, mock_clock);
opts.env = mock_env.get();
mock_clock->SetCurrentTime(days * kSecondsPerDay +
now_hour * kSecondsPerHour +
now_minute * kSecondsPerMinute);
opts.daily_offpeak_time_utc = preset_offpeak_time;
Reopen(opts);
ASSERT_OK(Put("foo", "bar1"));
ASSERT_OK(Flush());
ASSERT_EQ(0, periodic_compactions);
// Move clock forward by 8 hours. There should be no periodic
// compaction, yet.
mock_clock->MockSleepForSeconds(8 * kSecondsPerHour);
ASSERT_OK(Put("foo", "bar2"));
ASSERT_OK(Flush());
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ASSERT_EQ(0, periodic_compactions);
// Move clock forward by 4 days
mock_clock->MockSleepForSeconds(4 * kSecondsPerDay);
ASSERT_OK(Put("foo", "bar3"));
ASSERT_OK(Flush());
ASSERT_OK(dbfull()->TEST_WaitForCompact());
int64_t mock_now;
ASSERT_OK(mock_clock->GetCurrentTime(&mock_now));
auto offpeak_time_info =
dbfull()->GetVersionSet()->offpeak_time_option().GetOffpeakTimeInfo(
mock_now);
// At this point, the first file is 4 days and 8 hours old.
// If it's offpeak now and the file is expected to expire before the
// next offpeak starts
if (offpeak_time_info.is_now_offpeak &&
offpeak_time_info.seconds_till_next_offpeak_start /
kSecondsPerHour >
16) {
ASSERT_EQ(1, periodic_compactions);
} else {
ASSERT_EQ(0, periodic_compactions);
// Change offpeak option by SetDBOption()
if (preset_offpeak_time != new_offpeak_time) {
ASSERT_OK(dbfull()->SetDBOptions(
{{"daily_offpeak_time_utc", new_offpeak_time}}));
ASSERT_OK(Put("foo", "bar4"));
ASSERT_OK(Flush());
ASSERT_OK(dbfull()->TEST_WaitForCompact());
offpeak_time_info = dbfull()
->GetVersionSet()
->offpeak_time_option()
.GetOffpeakTimeInfo(mock_now);
// if the first file is now eligible to be picked up
if (offpeak_time_info.is_now_offpeak &&
offpeak_time_info.seconds_till_next_offpeak_start /
kSecondsPerHour >
16) {
ASSERT_OK(Put("foo", "bar5"));
ASSERT_OK(Flush());
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ASSERT_EQ(1, periodic_compactions);
}
}
// If the file has not been picked up yet (no offpeak set, or offpeak
// set but then unset before the file becomes eligible)
if (periodic_compactions == 0) {
// move clock forward by one more day
mock_clock->MockSleepForSeconds(1 * kSecondsPerDay);
ASSERT_OK(Put("foo", "bar6"));
ASSERT_OK(Flush());
ASSERT_OK(dbfull()->TEST_WaitForCompact());
}
}
ASSERT_EQ(1, periodic_compactions);
ASSERT_EQ(0, start_level);
ASSERT_EQ(4, output_level);
Destroy(opts);
periodic_compactions = 0;
}
}
}
}
} // namespace ROCKSDB_NAMESPACE
+4 -2
View File
@@ -1195,7 +1195,7 @@ TEST_F(DBWALTest, DISABLED_FullPurgePreservesLogPendingReuse) {
ROCKSDB_NAMESPACE::port::Thread thread([&]() {
TEST_SYNC_POINT(
"DBWALTest::FullPurgePreservesLogPendingReuse:PreFullPurge");
ASSERT_OK(db_->EnableFileDeletions(true));
ASSERT_OK(db_->EnableFileDeletions(/*force=*/true));
TEST_SYNC_POINT(
"DBWALTest::FullPurgePreservesLogPendingReuse:PostFullPurge");
});
@@ -1541,7 +1541,9 @@ class RecoveryTestHelper {
test->dbname_, &db_options, file_options, table_cache.get(),
&write_buffer_manager, &write_controller,
/*block_cache_tracer=*/nullptr,
/*io_tracer=*/nullptr, /*db_id*/ "", /*db_session_id*/ ""));
/*io_tracer=*/nullptr, /*db_id=*/"", /*db_session_id=*/"",
options.daily_offpeak_time_utc,
/*error_handler=*/nullptr));
wal_manager.reset(
new WalManager(db_options, file_options, /*io_tracer=*/nullptr));
+21
View File
@@ -355,6 +355,7 @@ class InternalKeyComparator
// Same as Compare except that it excludes the value type from comparison
int CompareKeySeq(const Slice& a, const Slice& b) const;
int CompareKeySeq(const ParsedInternalKey& a, const Slice& b) const;
const Comparator* user_comparator() const {
return user_comparator_.user_comparator();
@@ -976,6 +977,26 @@ inline int InternalKeyComparator::CompareKeySeq(const Slice& akey,
return r;
}
inline int InternalKeyComparator::CompareKeySeq(const ParsedInternalKey& a,
const Slice& b) const {
// Order by:
// increasing user key (according to user-supplied comparator)
// decreasing sequence number
int r = user_comparator_.Compare(a.user_key, ExtractUserKey(b));
if (r == 0) {
// Shift the number to exclude the last byte which contains the value type
const uint64_t anum = a.sequence;
const uint64_t bnum =
DecodeFixed64(b.data() + b.size() - kNumInternalBytes) >> 8;
if (anum > bnum) {
r = -1;
} else if (anum < bnum) {
r = +1;
}
}
return r;
}
inline int InternalKeyComparator::Compare(const Slice& a,
SequenceNumber a_global_seqno,
const Slice& b,
+40 -32
View File
@@ -396,15 +396,6 @@ 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_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;
DBRecoverContext context;
if (bg_io_err.GetScope() != IOStatus::IOErrorScope::kIOErrorScopeFile &&
@@ -505,6 +496,31 @@ const Status& ErrorHandler::SetBGError(const Status& bg_status,
}
}
void ErrorHandler::AddFilesToQuarantine(
autovector<const autovector<uint64_t>*> files_to_quarantine) {
db_mutex_->AssertHeld();
std::ostringstream quarantine_files_oss;
bool is_first_one = true;
for (const auto* files : files_to_quarantine) {
assert(files);
for (uint64_t file_number : *files) {
files_to_quarantine_.push_back(file_number);
quarantine_files_oss << (is_first_one ? "" : ", ") << file_number;
is_first_one = false;
}
}
ROCKS_LOG_INFO(db_options_.info_log,
"ErrorHandler: added file numbers %s to quarantine.\n",
quarantine_files_oss.str().c_str());
}
void ErrorHandler::ClearFilesToQuarantine() {
db_mutex_->AssertHeld();
files_to_quarantine_.clear();
ROCKS_LOG_INFO(db_options_.info_log,
"ErrorHandler: cleared files in quarantine.\n");
}
Status ErrorHandler::OverrideNoSpaceError(const Status& bg_error,
bool* auto_recovery) {
if (bg_error.severity() >= Status::Severity::kFatalError) {
@@ -552,28 +568,18 @@ Status ErrorHandler::ClearBGError() {
// Signal that recovery succeeded
if (recovery_error_.ok()) {
assert(files_to_quarantine_.empty());
Status old_bg_error = bg_error_;
// old_bg_error is only for notifying listeners, so may not be checked
old_bg_error.PermitUncheckedError();
// Clear and check the recovery IO and BG error
is_db_stopped_.store(false, std::memory_order_release);
bg_error_ = Status::OK();
recovery_error_ = IOStatus::OK();
bg_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_);
}
@@ -645,6 +651,13 @@ const Status& ErrorHandler::StartRecoverFromRetryableBGIOError(
} else if (db_options_.max_bgerror_resume_count <= 0 || recovery_in_prog_) {
// Auto resume BG error is not enabled, directly return bg_error_.
return bg_error_;
} else if (end_recovery_) {
// Can temporarily release db mutex
EventHelpers::NotifyOnErrorRecoveryEnd(db_options_.listeners, bg_error_,
Status::ShutdownInProgress(),
db_mutex_);
db_mutex_->AssertHeld();
return bg_error_;
}
if (bg_error_stats_ != nullptr) {
RecordTick(bg_error_stats_.get(), ERROR_HANDLER_AUTORESUME_COUNT);
@@ -664,11 +677,14 @@ const Status& ErrorHandler::StartRecoverFromRetryableBGIOError(
// wait the previous recover thread to finish and create a new thread
// to recover from the bg error.
db_mutex_->Unlock();
TEST_SYNC_POINT(
"StartRecoverFromRetryableBGIOError:BeforeWaitingForOtherThread");
old_recovery_thread->join();
TEST_SYNC_POINT(
"StartRecoverFromRetryableBGIOError:AfterWaitingForOtherThread");
db_mutex_->Lock();
}
TEST_SYNC_POINT("StartRecoverFromRetryableBGIOError::in_progress");
recovery_thread_.reset(
new port::Thread(&ErrorHandler::RecoverFromRetryableBGIOError, this));
@@ -682,6 +698,7 @@ const Status& ErrorHandler::StartRecoverFromRetryableBGIOError(
// Automatic recover from Retryable BG IO error. Must be called after db
// mutex is released.
void ErrorHandler::RecoverFromRetryableBGIOError() {
assert(recovery_in_prog_);
TEST_SYNC_POINT("RecoverFromRetryableBGIOError:BeforeStart");
TEST_SYNC_POINT("RecoverFromRetryableBGIOError:BeforeStart2");
InstrumentedMutexLock l(db_mutex_);
@@ -747,22 +764,12 @@ void ErrorHandler::RecoverFromRetryableBGIOError() {
// recover from the retryable IO error and no other BG errors. Clean
// the bg_error and notify user.
TEST_SYNC_POINT("RecoverFromRetryableBGIOError:RecoverSuccess");
Status old_bg_error = bg_error_;
is_db_stopped_.store(false, std::memory_order_release);
bg_error_ = Status::OK();
bg_error_.PermitUncheckedError();
EventHelpers::NotifyOnErrorRecoveryEnd(
db_options_.listeners, old_bg_error, bg_error_, db_mutex_);
if (bg_error_stats_ != nullptr) {
RecordTick(bg_error_stats_.get(),
ERROR_HANDLER_AUTORESUME_SUCCESS_COUNT);
RecordInHistogram(bg_error_stats_.get(),
ERROR_HANDLER_AUTORESUME_RETRY_COUNT, retry_count);
}
recovery_in_prog_ = false;
if (soft_error_no_bg_work_) {
soft_error_no_bg_work_ = false;
}
return;
} else {
// In this case: 1) recovery_error_ is more serious or not retryable
@@ -819,6 +826,7 @@ void ErrorHandler::EndAutoRecovery() {
old_recovery_thread->join();
db_mutex_->Lock();
}
TEST_SYNC_POINT("PostEndAutoRecovery");
return;
}
+20 -5
View File
@@ -4,11 +4,14 @@
// (found in the LICENSE.Apache file in the root directory).
#pragma once
#include <sstream>
#include "monitoring/instrumented_mutex.h"
#include "options/db_options.h"
#include "rocksdb/io_status.h"
#include "rocksdb/listener.h"
#include "rocksdb/status.h"
#include "util/autovector.h"
namespace ROCKSDB_NAMESPACE {
@@ -42,8 +45,7 @@ class ErrorHandler {
recovery_in_prog_(false),
soft_error_no_bg_work_(false),
is_db_stopped_(false),
bg_error_stats_(db_options.statistics),
recovery_disabled_file_deletion_(false) {
bg_error_stats_(db_options.statistics) {
// Clear the checked flag for uninitialized errors
bg_error_.PermitUncheckedError();
recovery_error_.PermitUncheckedError();
@@ -81,6 +83,16 @@ class ErrorHandler {
void EndAutoRecovery();
void AddFilesToQuarantine(
autovector<const autovector<uint64_t>*> files_to_quarantine);
const autovector<uint64_t>& GetFilesToQuarantine() const {
db_mutex_->AssertHeld();
return files_to_quarantine_;
}
void ClearFilesToQuarantine();
private:
DBImpl* db_;
const ImmutableDBOptions& db_options_;
@@ -109,9 +121,12 @@ 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_;
// During recovery from manifest IO errors, files whose VersionEdits entries
// could be in an ambiguous state are quarantined and file deletion refrain
// from deleting them. Successful recovery will clear this vector. Files are
// added to this vector while DB mutex was locked, this data structure is
// unsorted.
autovector<uint64_t> files_to_quarantine_;
const Status& HandleKnownErrors(const Status& bg_err,
BackgroundErrorReason reason);
+254 -99
View File
@@ -7,12 +7,15 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. See the AUTHORS file for names of contributors.
#include <memory>
#include "db/db_test_util.h"
#include "file/sst_file_manager_impl.h"
#include "port/stack_trace.h"
#include "rocksdb/io_status.h"
#include "rocksdb/sst_file_manager.h"
#include "test_util/sync_point.h"
#include "test_util/testharness.h"
#include "util/random.h"
#include "utilities/fault_injection_env.h"
#include "utilities/fault_injection_fs.h"
@@ -152,9 +155,9 @@ class ErrorHandlerFSListener : public EventListener {
FaultInjectionTestFS* fault_fs_;
};
TEST_F(DBErrorHandlingFSTest, FLushWriteError) {
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
TEST_F(DBErrorHandlingFSTest, FlushWriteError) {
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -197,9 +200,9 @@ TEST_F(DBErrorHandlingFSTest, FLushWriteError) {
// All the NoSpace IOError will be handled as the regular BG Error no matter the
// retryable flag is set of not. So the auto resume for retryable IO Error will
// not be triggered. Also, it is mapped as hard error.
TEST_F(DBErrorHandlingFSTest, FLushWriteNoSpaceError) {
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
TEST_F(DBErrorHandlingFSTest, FlushWriteNoSpaceError) {
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -241,9 +244,9 @@ TEST_F(DBErrorHandlingFSTest, FLushWriteNoSpaceError) {
Destroy(options);
}
TEST_F(DBErrorHandlingFSTest, FLushWriteRetryableError) {
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
TEST_F(DBErrorHandlingFSTest, FlushWriteRetryableError) {
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -315,9 +318,9 @@ TEST_F(DBErrorHandlingFSTest, FLushWriteRetryableError) {
Destroy(options);
}
TEST_F(DBErrorHandlingFSTest, FLushWriteFileScopeError) {
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
TEST_F(DBErrorHandlingFSTest, FlushWriteFileScopeError) {
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -399,9 +402,9 @@ TEST_F(DBErrorHandlingFSTest, FLushWriteFileScopeError) {
Destroy(options);
}
TEST_F(DBErrorHandlingFSTest, FLushWALWriteRetryableError) {
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
TEST_F(DBErrorHandlingFSTest, FlushWALWriteRetryableError) {
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -443,9 +446,9 @@ TEST_F(DBErrorHandlingFSTest, FLushWALWriteRetryableError) {
Destroy(options);
}
TEST_F(DBErrorHandlingFSTest, FLushWALAtomicWriteRetryableError) {
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
TEST_F(DBErrorHandlingFSTest, FlushWALAtomicWriteRetryableError) {
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -489,9 +492,9 @@ TEST_F(DBErrorHandlingFSTest, FLushWALAtomicWriteRetryableError) {
}
// The flush error is injected before we finish the table build
TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableError1) {
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
TEST_F(DBErrorHandlingFSTest, FlushWritNoWALRetryableError1) {
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -545,9 +548,9 @@ TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableError1) {
}
// The retryable IO error is injected before we sync table
TEST_F(DBErrorHandlingFSTest, FLushWriteNoWALRetryableError2) {
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
TEST_F(DBErrorHandlingFSTest, FlushWriteNoWALRetryableError2) {
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -589,9 +592,9 @@ TEST_F(DBErrorHandlingFSTest, FLushWriteNoWALRetryableError2) {
}
// The retryable IO error is injected before we close the table file
TEST_F(DBErrorHandlingFSTest, FLushWriteNoWALRetryableError3) {
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
TEST_F(DBErrorHandlingFSTest, FlushWriteNoWALRetryableError3) {
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -633,8 +636,8 @@ TEST_F(DBErrorHandlingFSTest, FLushWriteNoWALRetryableError3) {
}
TEST_F(DBErrorHandlingFSTest, ManifestWriteError) {
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -658,6 +661,7 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteError) {
SyncPoint::GetInstance()->EnableProcessing();
s = Flush();
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
ASSERT_FALSE(dbfull()->TEST_GetFilesToQuarantine().empty());
SyncPoint::GetInstance()->ClearAllCallBacks();
SyncPoint::GetInstance()->DisableProcessing();
fault_fs_->SetFilesystemActive(true);
@@ -666,6 +670,7 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteError) {
new_manifest = GetManifestNameFromLiveFiles();
ASSERT_NE(new_manifest, old_manifest);
ASSERT_TRUE(dbfull()->TEST_GetFilesToQuarantine().empty());
Reopen(options);
ASSERT_EQ("val", Get(Key(0)));
@@ -674,8 +679,8 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteError) {
}
TEST_F(DBErrorHandlingFSTest, ManifestWriteRetryableError) {
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -701,6 +706,7 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteRetryableError) {
SyncPoint::GetInstance()->EnableProcessing();
s = Flush();
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError);
ASSERT_FALSE(dbfull()->TEST_GetFilesToQuarantine().empty());
SyncPoint::GetInstance()->ClearAllCallBacks();
SyncPoint::GetInstance()->DisableProcessing();
fault_fs_->SetFilesystemActive(true);
@@ -709,6 +715,7 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteRetryableError) {
new_manifest = GetManifestNameFromLiveFiles();
ASSERT_NE(new_manifest, old_manifest);
ASSERT_TRUE(dbfull()->TEST_GetFilesToQuarantine().empty());
Reopen(options);
ASSERT_EQ("val", Get(Key(0)));
@@ -717,8 +724,8 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteRetryableError) {
}
TEST_F(DBErrorHandlingFSTest, ManifestWriteFileScopeError) {
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -746,6 +753,7 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteFileScopeError) {
[&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); });
SyncPoint::GetInstance()->EnableProcessing();
s = Flush();
ASSERT_FALSE(dbfull()->TEST_GetFilesToQuarantine().empty());
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError);
SyncPoint::GetInstance()->ClearAllCallBacks();
SyncPoint::GetInstance()->DisableProcessing();
@@ -755,6 +763,7 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteFileScopeError) {
new_manifest = GetManifestNameFromLiveFiles();
ASSERT_NE(new_manifest, old_manifest);
ASSERT_TRUE(dbfull()->TEST_GetFilesToQuarantine().empty());
Reopen(options);
ASSERT_EQ("val", Get(Key(0)));
@@ -763,8 +772,8 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteFileScopeError) {
}
TEST_F(DBErrorHandlingFSTest, ManifestWriteNoWALRetryableError) {
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -792,6 +801,7 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteNoWALRetryableError) {
SyncPoint::GetInstance()->EnableProcessing();
s = Flush();
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError);
ASSERT_FALSE(dbfull()->TEST_GetFilesToQuarantine().empty());
SyncPoint::GetInstance()->ClearAllCallBacks();
SyncPoint::GetInstance()->DisableProcessing();
fault_fs_->SetFilesystemActive(true);
@@ -800,6 +810,7 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteNoWALRetryableError) {
new_manifest = GetManifestNameFromLiveFiles();
ASSERT_NE(new_manifest, old_manifest);
ASSERT_TRUE(dbfull()->TEST_GetFilesToQuarantine().empty());
Reopen(options);
ASSERT_EQ("val", Get(Key(0)));
@@ -808,8 +819,8 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteNoWALRetryableError) {
}
TEST_F(DBErrorHandlingFSTest, DoubleManifestWriteError) {
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -833,11 +844,13 @@ TEST_F(DBErrorHandlingFSTest, DoubleManifestWriteError) {
SyncPoint::GetInstance()->EnableProcessing();
s = Flush();
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
ASSERT_FALSE(dbfull()->TEST_GetFilesToQuarantine().empty());
fault_fs_->SetFilesystemActive(true);
// This Resume() will attempt to create a new manifest file and fail again
s = dbfull()->Resume();
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
ASSERT_FALSE(dbfull()->TEST_GetFilesToQuarantine().empty());
fault_fs_->SetFilesystemActive(true);
SyncPoint::GetInstance()->ClearAllCallBacks();
SyncPoint::GetInstance()->DisableProcessing();
@@ -848,6 +861,7 @@ TEST_F(DBErrorHandlingFSTest, DoubleManifestWriteError) {
new_manifest = GetManifestNameFromLiveFiles();
ASSERT_NE(new_manifest, old_manifest);
ASSERT_TRUE(dbfull()->TEST_GetFilesToQuarantine().empty());
Reopen(options);
ASSERT_EQ("val", Get(Key(0)));
@@ -860,8 +874,8 @@ TEST_F(DBErrorHandlingFSTest, CompactionManifestWriteError) {
ROCKSDB_GTEST_SKIP("Test requires non-mock environment");
return;
}
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -914,6 +928,7 @@ TEST_F(DBErrorHandlingFSTest, CompactionManifestWriteError) {
fault_fs_->SetFilesystemActive(true);
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
TEST_SYNC_POINT("CompactionManifestWriteError:1");
ASSERT_FALSE(dbfull()->TEST_GetFilesToQuarantine().empty());
TEST_SYNC_POINT("CompactionManifestWriteError:2");
s = dbfull()->TEST_WaitForCompact();
@@ -922,6 +937,7 @@ TEST_F(DBErrorHandlingFSTest, CompactionManifestWriteError) {
new_manifest = GetManifestNameFromLiveFiles();
ASSERT_NE(new_manifest, old_manifest);
ASSERT_TRUE(dbfull()->TEST_GetFilesToQuarantine().empty());
Reopen(options);
ASSERT_EQ("val", Get(Key(0)));
ASSERT_EQ("val", Get(Key(1)));
@@ -930,8 +946,8 @@ TEST_F(DBErrorHandlingFSTest, CompactionManifestWriteError) {
}
TEST_F(DBErrorHandlingFSTest, CompactionManifestWriteRetryableError) {
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -980,6 +996,7 @@ TEST_F(DBErrorHandlingFSTest, CompactionManifestWriteRetryableError) {
ASSERT_OK(s);
TEST_SYNC_POINT("CompactionManifestWriteError:0");
ASSERT_FALSE(dbfull()->TEST_GetFilesToQuarantine().empty());
TEST_SYNC_POINT("CompactionManifestWriteError:1");
s = dbfull()->TEST_WaitForCompact();
@@ -993,6 +1010,7 @@ TEST_F(DBErrorHandlingFSTest, CompactionManifestWriteRetryableError) {
new_manifest = GetManifestNameFromLiveFiles();
ASSERT_NE(new_manifest, old_manifest);
ASSERT_TRUE(dbfull()->TEST_GetFilesToQuarantine().empty());
Reopen(options);
ASSERT_EQ("val", Get(Key(0)));
@@ -1002,8 +1020,8 @@ TEST_F(DBErrorHandlingFSTest, CompactionManifestWriteRetryableError) {
}
TEST_F(DBErrorHandlingFSTest, CompactionWriteError) {
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -1044,8 +1062,8 @@ TEST_F(DBErrorHandlingFSTest, CompactionWriteError) {
}
TEST_F(DBErrorHandlingFSTest, DISABLED_CompactionWriteRetryableError) {
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -1091,8 +1109,8 @@ TEST_F(DBErrorHandlingFSTest, DISABLED_CompactionWriteRetryableError) {
}
TEST_F(DBErrorHandlingFSTest, DISABLED_CompactionWriteFileScopeError) {
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -1183,8 +1201,8 @@ TEST_F(DBErrorHandlingFSTest, AutoRecoverFlushError) {
ROCKSDB_GTEST_SKIP("Test requires non-mock environment");
return;
}
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -1228,8 +1246,8 @@ TEST_F(DBErrorHandlingFSTest, AutoRecoverFlushError) {
}
TEST_F(DBErrorHandlingFSTest, FailRecoverFlushError) {
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -1257,8 +1275,8 @@ TEST_F(DBErrorHandlingFSTest, WALWriteError) {
ROCKSDB_GTEST_SKIP("Test requires non-mock environment");
return;
}
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -1330,8 +1348,8 @@ TEST_F(DBErrorHandlingFSTest, WALWriteError) {
}
TEST_F(DBErrorHandlingFSTest, WALWriteRetryableError) {
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -1425,8 +1443,8 @@ TEST_F(DBErrorHandlingFSTest, MultiCFWALWriteError) {
ROCKSDB_GTEST_SKIP("Test requires non-mock environment");
return;
}
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -1757,10 +1775,10 @@ TEST_F(DBErrorHandlingFSTest, MultiDBVariousErrors) {
// to soft error and trigger auto resume. During auto resume, SwitchMemtable
// is disabled to avoid small SST tables. Write can still be applied before
// the bg error is cleaned unless the memtable is full.
TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableErrorAutoRecover1) {
TEST_F(DBErrorHandlingFSTest, FlushWritNoWALRetryableErrorAutoRecover1) {
// Activate the FS before the first resume
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -1781,7 +1799,7 @@ TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableErrorAutoRecover1) {
ASSERT_OK(Put(Key(1), "val1", wo));
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
{{"RecoverFromRetryableBGIOError:LoopOut",
"FLushWritNoWALRetryableeErrorAutoRecover1:1"}});
"FlushWritNoWALRetryableeErrorAutoRecover1:1"}});
SyncPoint::GetInstance()->SetCallBack(
"BuildTable:BeforeFinishBuildTable",
[&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); });
@@ -1790,7 +1808,7 @@ TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableErrorAutoRecover1) {
s = Flush();
ASSERT_EQ("val1", Get(Key(1)));
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError);
TEST_SYNC_POINT("FLushWritNoWALRetryableeErrorAutoRecover1:1");
TEST_SYNC_POINT("FlushWritNoWALRetryableeErrorAutoRecover1:1");
ASSERT_EQ("val1", Get(Key(1)));
ASSERT_EQ("val1", Get(Key(1)));
SyncPoint::GetInstance()->DisableProcessing();
@@ -1827,10 +1845,94 @@ TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableErrorAutoRecover1) {
Destroy(options);
}
TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableErrorAutoRecover2) {
TEST_F(DBErrorHandlingFSTest, MultipleRecoveryThreads) {
// This test creates a scenario where second write's recovery can get started
// while mutex is released for a short period during
// NotifyOnErrorRecoveryEnd() from the first write's recovery. This is to make
// sure RecoverFromRetryableBGIOError() from the second write's recovery
// thread does not start with recovery_in_prog_ = false;
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
options.listeners.emplace_back(listener);
options.max_bgerror_resume_count = 100;
options.bgerror_resume_retry_interval = 1000000; // 1 second
options.statistics = CreateDBStatistics();
listener->EnableAutoRecovery(false);
DestroyAndReopen(options);
IOStatus error_msg = IOStatus::IOError("Retryable IO Error");
error_msg.SetRetryable(true);
WriteOptions wo = WriteOptions();
wo.disableWAL = true;
fault_fs_->SetFilesystemActive(false, error_msg);
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
{{"NotifyOnErrorRecoveryEnd:MutexUnlocked:1",
"MultipleRecoveryThreads:1"},
{"MultipleRecoveryThreads:2",
"NotifyOnErrorRecoveryEnd:MutexUnlocked:2"},
{"StartRecoverFromRetryableBGIOError:BeforeWaitingForOtherThread",
"MultipleRecoveryThreads:3"},
{"RecoverFromRetryableBGIOError:RecoverSuccess",
"MultipleRecoveryThreads:4"},
{"MultipleRecoveryThreads:4",
"StartRecoverFromRetryableBGIOError:AfterWaitingForOtherThread"}});
SyncPoint::GetInstance()->EnableProcessing();
// First write with read fault injected and recovery will start
{
ASSERT_OK(Put(Key(1), "val1", wo));
Status s = Flush();
ASSERT_NOK(s);
}
// Remove read fault injection so that first recovery can go through
fault_fs_->SetFilesystemActive(true);
// At this point, first recovery is now at NotifyOnErrorRecoveryEnd. Mutex is
// released.
TEST_SYNC_POINT("MultipleRecoveryThreads:1");
ROCKSDB_NAMESPACE::port::Thread second_write([&] {
// Second write with read fault injected
fault_fs_->SetFilesystemActive(false, error_msg);
ASSERT_OK(Put(Key(2), "val2", wo));
Status s = Flush();
ASSERT_NOK(s);
});
// Second bg thread before waiting for the first thread's recovery thread
TEST_SYNC_POINT("MultipleRecoveryThreads:3");
// First thread's recovery thread continues
TEST_SYNC_POINT("MultipleRecoveryThreads:2");
// Wait for the first thread's recovery to finish
// (this sets recovery_in_prog_ = false)
// And second thread continues and starts recovery thread
TEST_SYNC_POINT("MultipleRecoveryThreads:4");
second_write.join();
// Remove error injection so that second thread recovery can go through
fault_fs_->SetFilesystemActive(true);
// Set up sync point so that we can wait for the recovery thread to finish
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
{{"RecoverFromRetryableBGIOError:RecoverSuccess",
"MultipleRecoveryThreads:6"}});
// Wait for the second thread's recovery to be done
TEST_SYNC_POINT("MultipleRecoveryThreads:6");
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
Destroy(options);
}
TEST_F(DBErrorHandlingFSTest, FlushWritNoWALRetryableErrorAutoRecover2) {
// Activate the FS before the first resume
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -1888,10 +1990,10 @@ TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableErrorAutoRecover2) {
// Auto resume fromt the flush retryable IO error. Activate the FS before the
// first resume. Resume is successful
TEST_F(DBErrorHandlingFSTest, FLushWritRetryableErrorAutoRecover1) {
TEST_F(DBErrorHandlingFSTest, FlushWritRetryableErrorAutoRecover1) {
// Activate the FS before the first resume
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -1930,10 +2032,10 @@ TEST_F(DBErrorHandlingFSTest, FLushWritRetryableErrorAutoRecover1) {
// Auto resume fromt the flush retryable IO error and set the retry limit count.
// Never activate the FS and auto resume should fail at the end
TEST_F(DBErrorHandlingFSTest, FLushWritRetryableErrorAutoRecover2) {
TEST_F(DBErrorHandlingFSTest, FlushWritRetryableErrorAutoRecover2) {
// Fail all the resume and let user to resume
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -1950,18 +2052,18 @@ TEST_F(DBErrorHandlingFSTest, FLushWritRetryableErrorAutoRecover2) {
ASSERT_OK(Put(Key(1), "val1"));
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
{{"FLushWritRetryableeErrorAutoRecover2:0",
{{"FlushWritRetryableeErrorAutoRecover2:0",
"RecoverFromRetryableBGIOError:BeforeStart"},
{"RecoverFromRetryableBGIOError:LoopOut",
"FLushWritRetryableeErrorAutoRecover2:1"}});
"FlushWritRetryableeErrorAutoRecover2:1"}});
SyncPoint::GetInstance()->SetCallBack(
"BuildTable:BeforeFinishBuildTable",
[&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); });
SyncPoint::GetInstance()->EnableProcessing();
s = Flush();
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError);
TEST_SYNC_POINT("FLushWritRetryableeErrorAutoRecover2:0");
TEST_SYNC_POINT("FLushWritRetryableeErrorAutoRecover2:1");
TEST_SYNC_POINT("FlushWritRetryableeErrorAutoRecover2:0");
TEST_SYNC_POINT("FlushWritRetryableeErrorAutoRecover2:1");
fault_fs_->SetFilesystemActive(true);
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
SyncPoint::GetInstance()->DisableProcessing();
@@ -1983,8 +2085,8 @@ TEST_F(DBErrorHandlingFSTest, FLushWritRetryableErrorAutoRecover2) {
// Fail the first resume and let the second resume be successful.
TEST_F(DBErrorHandlingFSTest, ManifestWriteRetryableErrorAutoRecover) {
// Fail the first resume and let the second resume be successful
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -2036,8 +2138,8 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteRetryableErrorAutoRecover) {
TEST_F(DBErrorHandlingFSTest, ManifestWriteNoWALRetryableErrorAutoRecover) {
// Fail the first resume and let the second resume be successful
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -2091,8 +2193,8 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteNoWALRetryableErrorAutoRecover) {
TEST_F(DBErrorHandlingFSTest,
CompactionManifestWriteRetryableErrorAutoRecover) {
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -2181,8 +2283,8 @@ TEST_F(DBErrorHandlingFSTest, CompactionWriteRetryableErrorAutoRecover) {
// compaction, the FS is set to active and compaction is successful, so
// the test will hit the CompactionJob::FinishCompactionOutputFile1 sync
// point.
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -2235,8 +2337,8 @@ TEST_F(DBErrorHandlingFSTest, CompactionWriteRetryableErrorAutoRecover) {
}
TEST_F(DBErrorHandlingFSTest, WALWriteRetryableErrorAutoRecover1) {
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -2338,8 +2440,8 @@ TEST_F(DBErrorHandlingFSTest, WALWriteRetryableErrorAutoRecover1) {
TEST_F(DBErrorHandlingFSTest, WALWriteRetryableErrorAutoRecover2) {
// Fail the first recover and try second time.
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -2439,10 +2541,10 @@ TEST_F(DBErrorHandlingFSTest, WALWriteRetryableErrorAutoRecover2) {
// Fail auto resume from a flush retryable error and verify that
// OnErrorRecoveryEnd listener callback is called
TEST_F(DBErrorHandlingFSTest, FLushWritRetryableErrorAbortRecovery) {
TEST_F(DBErrorHandlingFSTest, FlushWritRetryableErrorAbortRecovery) {
// Activate the FS before the first resume
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -2473,6 +2575,59 @@ TEST_F(DBErrorHandlingFSTest, FLushWritRetryableErrorAbortRecovery) {
Destroy(options);
}
TEST_F(DBErrorHandlingFSTest, FlushErrorRecoveryRaceWithDBDestruction) {
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
options.listeners.emplace_back(listener);
DestroyAndReopen(options);
ASSERT_OK(Put("k1", "val"));
// Inject retryable flush error
bool error_set = false;
SyncPoint::GetInstance()->SetCallBack(
"BuildTable:BeforeOutputValidation", [&](void*) {
if (error_set) {
return;
}
IOStatus st = IOStatus::IOError("Injected");
st.SetRetryable(true);
fault_fs_->SetFilesystemActive(false, st);
error_set = true;
});
port::Thread db_close_thread;
SyncPoint::GetInstance()->SetCallBack(
"BuildTable:BeforeDeleteFile", [&](void*) {
// Clear retryable flush error injection
fault_fs_->SetFilesystemActive(true);
// Coerce race between ending auto recovery in db destruction and flush
// error recovery
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
{{"PostEndAutoRecovery", "FlushJob::WriteLevel0Table"}});
db_close_thread = port::Thread([&] { Close(); });
});
SyncPoint::GetInstance()->EnableProcessing();
Status s = Flush();
ASSERT_NOK(s);
int placeholder = 1;
listener->WaitForRecovery(placeholder);
ASSERT_TRUE(listener->new_bg_error().IsShutdownInProgress());
// Prior to the fix, the db close will crash due to the recovery thread for
// flush error is not joined by the time of destruction.
db_close_thread.join();
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
Destroy(options);
}
TEST_F(DBErrorHandlingFSTest, FlushReadError) {
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
@@ -2680,9 +2835,9 @@ TEST_F(DBErrorHandlingFSTest, CompactionReadRetryableErrorAutoRecover) {
class DBErrorHandlingFencingTest : public DBErrorHandlingFSTest,
public testing::WithParamInterface<bool> {};
TEST_P(DBErrorHandlingFencingTest, FLushWriteFenced) {
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
TEST_P(DBErrorHandlingFencingTest, FlushWriteFenced) {
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -2709,8 +2864,8 @@ TEST_P(DBErrorHandlingFencingTest, FLushWriteFenced) {
}
TEST_P(DBErrorHandlingFencingTest, ManifestWriteFenced) {
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -2744,8 +2899,8 @@ TEST_P(DBErrorHandlingFencingTest, ManifestWriteFenced) {
}
TEST_P(DBErrorHandlingFencingTest, CompactionWriteFenced) {
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
@@ -2785,8 +2940,8 @@ TEST_P(DBErrorHandlingFencingTest, CompactionWriteFenced) {
}
TEST_P(DBErrorHandlingFencingTest, WALWriteFenced) {
std::shared_ptr<ErrorHandlerFSListener> listener(
new ErrorHandlerFSListener());
std::shared_ptr<ErrorHandlerFSListener> listener =
std::make_shared<ErrorHandlerFSListener>();
Options options = GetDefaultOptions();
options.env = fault_env_.get();
options.create_if_missing = true;
+2
View File
@@ -230,6 +230,8 @@ void EventHelpers::NotifyOnErrorRecoveryEnd(
db_mutex->AssertHeld();
// release lock while notifying events
db_mutex->Unlock();
TEST_SYNC_POINT("NotifyOnErrorRecoveryEnd:MutexUnlocked:1");
TEST_SYNC_POINT("NotifyOnErrorRecoveryEnd:MutexUnlocked:2");
for (auto& listener : listeners) {
BackgroundErrorRecoveryInfo info;
info.old_bg_error = old_bg_error;
+21 -14
View File
@@ -1548,6 +1548,11 @@ TEST_F(ExternalSSTFileBasicTest, RangeDeletionEndComesBeforeStart) {
}
TEST_P(ExternalSSTFileBasicTest, IngestFileWithBadBlockChecksum) {
bool verify_checksums_before_ingest = std::get<1>(GetParam());
if (!verify_checksums_before_ingest) {
ROCKSDB_GTEST_BYPASS("Bypassing test when !verify_checksums_before_ingest");
return;
}
bool change_checksum_called = false;
const auto& change_checksum = [&](void* arg) {
if (!change_checksum_called) {
@@ -1565,24 +1570,20 @@ TEST_P(ExternalSSTFileBasicTest, IngestFileWithBadBlockChecksum) {
SyncPoint::GetInstance()->EnableProcessing();
int file_id = 0;
bool write_global_seqno = std::get<0>(GetParam());
bool verify_checksums_before_ingest = std::get<1>(GetParam());
do {
Options options = CurrentOptions();
DestroyAndReopen(options);
std::map<std::string, std::string> true_data;
Status s = GenerateAndAddExternalFile(
options, {1, 2, 3, 4, 5, 6}, ValueType::kTypeValue, file_id++,
write_global_seqno, verify_checksums_before_ingest, &true_data);
if (verify_checksums_before_ingest) {
ASSERT_NOK(s);
} else {
ASSERT_OK(s);
}
write_global_seqno, /*verify_checksums_before_ingest=*/true,
&true_data);
ASSERT_NOK(s);
change_checksum_called = false;
} while (ChangeOptionsForFileIngestionTest());
}
TEST_P(ExternalSSTFileBasicTest, IngestFileWithFirstByteTampered) {
TEST_P(ExternalSSTFileBasicTest, IngestFileWithCorruptedDataBlock) {
if (!random_rwfile_supported_) {
ROCKSDB_GTEST_SKIP("Test requires NewRandomRWFile support");
return;
@@ -1590,15 +1591,21 @@ TEST_P(ExternalSSTFileBasicTest, IngestFileWithFirstByteTampered) {
SyncPoint::GetInstance()->DisableProcessing();
int file_id = 0;
EnvOptions env_options;
Random rnd(301);
do {
Options options = CurrentOptions();
options.compression = kNoCompression;
BlockBasedTableOptions table_options;
table_options.block_size = 4 * 1024;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
std::string file_path = sst_files_dir_ + std::to_string(file_id++);
SstFileWriter sst_file_writer(env_options, options);
Status s = sst_file_writer.Open(file_path);
ASSERT_OK(s);
// This should write more than 2 data blocks.
for (int i = 0; i != 100; ++i) {
std::string key = Key(i);
std::string value = Key(i) + std::to_string(0);
std::string value = rnd.RandomString(200);
ASSERT_OK(sst_file_writer.Put(key, value));
}
ASSERT_OK(sst_file_writer.Finish());
@@ -1609,11 +1616,11 @@ TEST_P(ExternalSSTFileBasicTest, IngestFileWithFirstByteTampered) {
ASSERT_GT(file_size, 8);
std::unique_ptr<RandomRWFile> rwfile;
ASSERT_OK(env_->NewRandomRWFile(file_path, &rwfile, EnvOptions()));
// Manually corrupt the file
// We deterministically corrupt the first byte because we currently
// cannot choose a random offset. The reason for this limitation is that
// we do not checksum property block at present.
const uint64_t offset = 0;
// Corrupt the second data block.
// We need to corrupt a non-first and non-last data block
// since we access them to get smallest and largest internal
// key in the file in GetIngestedFileInfo().
const uint64_t offset = 5000;
char scratch[8] = {0};
Slice buf;
ASSERT_OK(rwfile->Read(offset, sizeof(scratch), &buf, scratch));
+31 -4
View File
@@ -769,8 +769,6 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
std::unique_ptr<InternalIterator> iter(table_reader->NewIterator(
ro, sv->mutable_cf_options.prefix_extractor.get(), /*arena=*/nullptr,
/*skip_filters=*/false, TableReaderCaller::kExternalSSTIngestion));
std::unique_ptr<InternalIterator> range_del_iter(
table_reader->NewRangeTombstoneIterator(ro));
// Get first (smallest) and last (largest) key from file.
file_to_ingest->smallest_internal_key =
@@ -792,8 +790,33 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
}
file_to_ingest->smallest_internal_key.SetFrom(key);
iter->SeekToLast();
pik_status = ParseInternalKey(iter->key(), &key, allow_data_in_errors);
Slice largest;
if (strcmp(cfd_->ioptions()->table_factory->Name(), "PlainTable") == 0) {
// PlainTable iterator does not support SeekToLast().
largest = iter->key();
for (; iter->Valid(); iter->Next()) {
if (cfd_->internal_comparator().Compare(iter->key(), largest) > 0) {
largest = iter->key();
}
}
if (!iter->status().ok()) {
return iter->status();
}
} else {
iter->SeekToLast();
if (!iter->Valid()) {
if (iter->status().ok()) {
// The file contains at least 1 key since iter is valid after
// SeekToFirst().
return Status::Corruption("Can not find largest key in sst file");
} else {
return iter->status();
}
}
largest = iter->key();
}
pik_status = ParseInternalKey(largest, &key, allow_data_in_errors);
if (!pik_status.ok()) {
return Status::Corruption("Corrupted key in external file. ",
pik_status.getState());
@@ -804,8 +827,12 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
file_to_ingest->largest_internal_key.SetFrom(key);
bounds_set = true;
} else if (!iter->status().ok()) {
return iter->status();
}
std::unique_ptr<InternalIterator> range_del_iter(
table_reader->NewRangeTombstoneIterator(ro));
// We may need to adjust these key bounds, depending on whether any range
// deletion tombstones extend past them.
const Comparator* ucmp = cfd_->internal_comparator().user_comparator();
+6 -5
View File
@@ -127,11 +127,12 @@ class FlushJobTestBase : public testing::Test {
column_families.emplace_back(cf_name, cf_options_);
}
versions_.reset(
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id*/ "", /*db_session_id*/ ""));
versions_.reset(new VersionSet(
dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr));
EXPECT_OK(versions_->Recover(column_families, false));
}
+31 -4
View File
@@ -186,7 +186,8 @@ Status ImportColumnFamilyJob::Run() {
cfd_->NumberLevels(), cfd_->ioptions()->compaction_style,
nullptr /* src_vstorage */, cfd_->ioptions()->force_consistency_checks,
EpochNumberRequirement::kMightMissing, cfd_->ioptions()->clock,
cfd_->GetLatestMutableCFOptions()->bottommost_file_compaction_delay);
cfd_->GetLatestMutableCFOptions()->bottommost_file_compaction_delay,
cfd_->current()->version_set()->offpeak_time_option());
Status s;
for (size_t i = 0; s.ok() && i < files_to_import_.size(); ++i) {
@@ -365,9 +366,35 @@ Status ImportColumnFamilyJob::GetIngestedFileInfo(
bool bound_set = false;
if (iter->Valid()) {
file_to_import->smallest_internal_key.DecodeFrom(iter->key());
iter->SeekToLast();
file_to_import->largest_internal_key.DecodeFrom(iter->key());
Slice largest;
if (strcmp(cfd_->ioptions()->table_factory->Name(), "PlainTable") == 0) {
// PlainTable iterator does not support SeekToLast().
largest = iter->key();
for (; iter->Valid(); iter->Next()) {
if (cfd_->internal_comparator().Compare(iter->key(), largest) > 0) {
largest = iter->key();
}
}
if (!iter->status().ok()) {
return iter->status();
}
} else {
iter->SeekToLast();
if (!iter->Valid()) {
if (iter->status().ok()) {
// The file contains at least 1 key since iter is valid after
// SeekToFirst().
return Status::Corruption("Can not find largest key in sst file");
} else {
return iter->status();
}
}
largest = iter->key();
}
file_to_import->largest_internal_key.DecodeFrom(largest);
bound_set = true;
} else if (!iter->status().ok()) {
return iter->status();
}
std::unique_ptr<InternalIterator> range_del_iter{
@@ -429,4 +456,4 @@ Status ImportColumnFamilyJob::GetIngestedFileInfo(
return status;
}
} // namespace ROCKSDB_NAMESPACE
} // namespace ROCKSDB_NAMESPACE
+11
View File
@@ -15,6 +15,7 @@
#include "db/column_family.h"
#include "db/log_writer.h"
#include "db/version_set.h"
#include "util/autovector.h"
namespace ROCKSDB_NAMESPACE {
@@ -170,6 +171,16 @@ struct JobContext {
// will be reused later
std::vector<uint64_t> log_recycle_files;
// Files quarantined from deletion. This list contains file numbers for files
// that are in an ambiguous states. This includes newly generated SST files
// and blob files from flush and compaction job whose VersionEdits' persist
// state in Manifest are unclear. An old manifest file whose immediately
// following new manifest file's CURRENT file creation is in an unclear state.
// WAL logs don't have this premature deletion risk since
// min_log_number_to_keep is only updated after successful manifest commits.
// So this data structure doesn't track log files.
autovector<uint64_t> files_to_quarantine;
// a list of manifest files that we need to delete
std::vector<std::string> manifest_delete_files;
+14 -7
View File
@@ -469,12 +469,14 @@ unsigned int Reader::ReadPhysicalRecord(Slice* result, size_t* drop_size,
const unsigned int type = header[6];
const uint32_t length = a | (b << 8);
int header_size = kHeaderSize;
if ((type >= kRecyclableFullType && type <= kRecyclableLastType) ||
type == kRecyclableUserDefinedTimestampSizeType) {
const bool is_recyclable_type =
((type >= kRecyclableFullType && type <= kRecyclableLastType) ||
type == kRecyclableUserDefinedTimestampSizeType);
if (is_recyclable_type) {
header_size = kRecyclableHeaderSize;
if (end_of_buffer_offset_ - buffer_.size() == 0) {
recycled_ = true;
}
header_size = kRecyclableHeaderSize;
// We need enough for the larger header
if (buffer_.size() < static_cast<size_t>(kRecyclableHeaderSize)) {
int r = kEof;
@@ -483,11 +485,8 @@ unsigned int Reader::ReadPhysicalRecord(Slice* result, size_t* drop_size,
}
continue;
}
const uint32_t log_num = DecodeFixed32(header + 7);
if (log_num != log_number_) {
return kOldRecord;
}
}
if (header_size + length > buffer_.size()) {
assert(buffer_.size() >= static_cast<size_t>(header_size));
*drop_size = buffer_.size();
@@ -499,6 +498,14 @@ unsigned int Reader::ReadPhysicalRecord(Slice* result, size_t* drop_size,
return kBadRecordLen;
}
if (is_recyclable_type) {
const uint32_t log_num = DecodeFixed32(header + 7);
if (log_num != log_number_) {
buffer_.remove_prefix(header_size + length);
return kOldRecord;
}
}
if (type == kZeroType && length == 0) {
// Skip zero length record without reporting any drops since
// such records are produced by the mmap based writing code in
+6 -4
View File
@@ -106,8 +106,9 @@ class MemTableListTest : public testing::Test {
VersionSet versions(dbname, &immutable_db_options, env_options,
table_cache.get(), &write_buffer_manager,
&write_controller, /*block_cache_tracer=*/nullptr,
/*io_tracer=*/nullptr, /*db_id*/ "",
/*db_session_id*/ "");
/*io_tracer=*/nullptr, /*db_id=*/"",
/*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr);
std::vector<ColumnFamilyDescriptor> cf_descs;
cf_descs.emplace_back(kDefaultColumnFamilyName, ColumnFamilyOptions());
cf_descs.emplace_back("one", ColumnFamilyOptions());
@@ -157,8 +158,9 @@ class MemTableListTest : public testing::Test {
VersionSet versions(dbname, &immutable_db_options, env_options,
table_cache.get(), &write_buffer_manager,
&write_controller, /*block_cache_tracer=*/nullptr,
/*io_tracer=*/nullptr, /*db_id*/ "",
/*db_session_id*/ "");
/*io_tracer=*/nullptr, /*db_id=*/"",
/*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr);
std::vector<ColumnFamilyDescriptor> cf_descs;
cf_descs.emplace_back(kDefaultColumnFamilyName, ColumnFamilyOptions());
cf_descs.emplace_back("one", ColumnFamilyOptions());
+1 -1
View File
@@ -165,7 +165,7 @@ TEST_F(ObsoleteFilesTest, DeleteObsoleteOptionsFile) {
{{"paranoid_file_checks", "true"}}));
}
}
ASSERT_OK(dbfull()->EnableFileDeletions(true /* force */));
ASSERT_OK(dbfull()->EnableFileDeletions(/*force=*/false));
Close();
+1 -2
View File
@@ -94,7 +94,7 @@ Status PeriodicTaskScheduler::Unregister(PeriodicTaskType task_type) {
}
Timer* PeriodicTaskScheduler::Default() {
static Timer timer(SystemClock::Default().get());
STATIC_AVOID_DESTRUCTION(Timer, timer)(SystemClock::Default().get());
return &timer;
}
@@ -108,4 +108,3 @@ void PeriodicTaskScheduler::TEST_OverrideTimer(SystemClock* clock) {
#endif // NDEBUG
} // namespace ROCKSDB_NAMESPACE
+4 -2
View File
@@ -122,7 +122,8 @@ class Repairer {
vset_(dbname_, &immutable_db_options_, file_options_,
raw_table_cache_.get(), &wb_, &wc_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", db_session_id_),
/*db_id=*/"", db_session_id_, db_options.daily_offpeak_time_utc,
/*error_handler=*/nullptr),
next_file_number_(1),
db_lock_(nullptr),
closed_(false) {
@@ -694,7 +695,8 @@ class Repairer {
cfd->NumberLevels(), cfd->ioptions()->compaction_style,
nullptr /* src_vstorage */, cfd->ioptions()->force_consistency_checks,
EpochNumberRequirement::kMightMissing, cfd->ioptions()->clock,
/*bottommost_file_compaction_delay=*/0);
/*bottommost_file_compaction_delay=*/0,
cfd->current()->version_set()->offpeak_time_option());
Status s;
VersionEdit dummy_edit;
for (const auto* table : cf_id_and_tables.second) {
+33 -23
View File
@@ -38,7 +38,8 @@ class VersionBuilderTest : public testing::Test {
mutable_cf_options_(options_),
vstorage_(&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel,
nullptr, false, EpochNumberRequirement::kMustPresent,
ioptions_.clock, options_.bottommost_file_compaction_delay),
ioptions_.clock, options_.bottommost_file_compaction_delay,
OffpeakTimeOption(options_.daily_offpeak_time_utc)),
file_num_(1) {
mutable_cf_options_.RefreshDerivedOptions(ioptions_);
size_being_compacted_.resize(options_.num_levels);
@@ -202,7 +203,8 @@ TEST_F(VersionBuilderTest, ApplyAndSaveTo) {
VersionStorageInfo new_vstorage(
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, nullptr, false,
EpochNumberRequirement::kMightMissing, nullptr, 0);
EpochNumberRequirement::kMightMissing, nullptr, 0,
OffpeakTimeOption(options_.daily_offpeak_time_utc));
ASSERT_OK(version_builder.Apply(&version_edit));
ASSERT_OK(version_builder.SaveTo(&new_vstorage));
@@ -253,7 +255,8 @@ TEST_F(VersionBuilderTest, ApplyAndSaveToDynamic) {
VersionStorageInfo new_vstorage(
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, nullptr, false,
EpochNumberRequirement::kMightMissing, nullptr, 0);
EpochNumberRequirement::kMightMissing, nullptr, 0,
OffpeakTimeOption(options_.daily_offpeak_time_utc));
ASSERT_OK(version_builder.Apply(&version_edit));
ASSERT_OK(version_builder.SaveTo(&new_vstorage));
@@ -308,7 +311,8 @@ TEST_F(VersionBuilderTest, ApplyAndSaveToDynamic2) {
VersionStorageInfo new_vstorage(
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, nullptr, false,
EpochNumberRequirement::kMightMissing, nullptr, 0);
EpochNumberRequirement::kMightMissing, nullptr, 0,
OffpeakTimeOption(options_.daily_offpeak_time_utc));
ASSERT_OK(version_builder.Apply(&version_edit));
ASSERT_OK(version_builder.SaveTo(&new_vstorage));
@@ -365,7 +369,8 @@ TEST_F(VersionBuilderTest, ApplyMultipleAndSaveTo) {
VersionStorageInfo new_vstorage(
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, nullptr, false,
EpochNumberRequirement::kMightMissing, nullptr, 0);
EpochNumberRequirement::kMightMissing, nullptr, 0,
OffpeakTimeOption(options_.daily_offpeak_time_utc));
ASSERT_OK(version_builder.Apply(&version_edit));
ASSERT_OK(version_builder.SaveTo(&new_vstorage));
@@ -388,7 +393,8 @@ TEST_F(VersionBuilderTest, ApplyDeleteAndSaveTo) {
VersionStorageInfo new_vstorage(
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, nullptr, false,
EpochNumberRequirement::kMightMissing, nullptr, 0);
EpochNumberRequirement::kMightMissing, nullptr, 0,
OffpeakTimeOption(options_.daily_offpeak_time_utc));
VersionEdit version_edit;
version_edit.AddFile(
@@ -557,7 +563,7 @@ TEST_F(VersionBuilderTest, ApplyFileDeletionAndAddition) {
VersionStorageInfo new_vstorage(
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, &vstorage_,
force_consistency_checks, EpochNumberRequirement::kMightMissing, nullptr,
0);
0, OffpeakTimeOption(options_.daily_offpeak_time_utc));
ASSERT_OK(builder.SaveTo(&new_vstorage));
@@ -702,7 +708,7 @@ TEST_F(VersionBuilderTest, ApplyFileAdditionAndDeletion) {
VersionStorageInfo new_vstorage(
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, &vstorage_,
force_consistency_checks, EpochNumberRequirement::kMightMissing, nullptr,
0);
0, OffpeakTimeOption(options_.daily_offpeak_time_utc));
ASSERT_OK(builder.SaveTo(&new_vstorage));
@@ -747,7 +753,7 @@ TEST_F(VersionBuilderTest, ApplyBlobFileAddition) {
VersionStorageInfo new_vstorage(
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, &vstorage_,
force_consistency_checks, EpochNumberRequirement::kMightMissing, nullptr,
0);
0, OffpeakTimeOption(options_.daily_offpeak_time_utc));
ASSERT_OK(builder.SaveTo(&new_vstorage));
@@ -887,7 +893,7 @@ TEST_F(VersionBuilderTest, ApplyBlobFileGarbageFileInBase) {
VersionStorageInfo new_vstorage(
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, &vstorage_,
force_consistency_checks, EpochNumberRequirement::kMightMissing, nullptr,
0);
0, OffpeakTimeOption(options_.daily_offpeak_time_utc));
ASSERT_OK(builder.SaveTo(&new_vstorage));
@@ -961,7 +967,7 @@ TEST_F(VersionBuilderTest, ApplyBlobFileGarbageFileAdditionApplied) {
VersionStorageInfo new_vstorage(
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, &vstorage_,
force_consistency_checks, EpochNumberRequirement::kMightMissing, nullptr,
0);
0, OffpeakTimeOption(options_.daily_offpeak_time_utc));
ASSERT_OK(builder.SaveTo(&new_vstorage));
@@ -1142,7 +1148,7 @@ TEST_F(VersionBuilderTest, SaveBlobFilesTo) {
VersionStorageInfo new_vstorage(
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, &vstorage_,
force_consistency_checks, EpochNumberRequirement::kMightMissing, nullptr,
0);
0, OffpeakTimeOption(options_.daily_offpeak_time_utc));
ASSERT_OK(builder.SaveTo(&new_vstorage));
@@ -1191,7 +1197,7 @@ TEST_F(VersionBuilderTest, SaveBlobFilesTo) {
VersionStorageInfo newer_vstorage(
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, &new_vstorage,
force_consistency_checks, EpochNumberRequirement::kMightMissing, nullptr,
0);
0, OffpeakTimeOption(options_.daily_offpeak_time_utc));
ASSERT_OK(second_builder.SaveTo(&newer_vstorage));
@@ -1278,7 +1284,7 @@ TEST_F(VersionBuilderTest, SaveBlobFilesToConcurrentJobs) {
VersionStorageInfo new_vstorage(
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, &vstorage_,
force_consistency_checks, EpochNumberRequirement::kMightMissing, nullptr,
0);
0, OffpeakTimeOption(options_.daily_offpeak_time_utc));
ASSERT_OK(builder.SaveTo(&new_vstorage));
@@ -1382,7 +1388,7 @@ TEST_F(VersionBuilderTest, CheckConsistencyForBlobFiles) {
VersionStorageInfo new_vstorage(
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, &vstorage_,
force_consistency_checks, EpochNumberRequirement::kMightMissing, nullptr,
0);
0, OffpeakTimeOption(options_.daily_offpeak_time_utc));
ASSERT_OK(builder.SaveTo(&new_vstorage));
@@ -1422,7 +1428,7 @@ TEST_F(VersionBuilderTest, CheckConsistencyForBlobFilesInconsistentLinks) {
VersionStorageInfo new_vstorage(
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, &vstorage_,
force_consistency_checks, EpochNumberRequirement::kMightMissing, nullptr,
0);
0, OffpeakTimeOption(options_.daily_offpeak_time_utc));
const Status s = builder.SaveTo(&new_vstorage);
ASSERT_TRUE(s.IsCorruption());
@@ -1464,7 +1470,7 @@ TEST_F(VersionBuilderTest, CheckConsistencyForBlobFilesAllGarbage) {
VersionStorageInfo new_vstorage(
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, &vstorage_,
force_consistency_checks, EpochNumberRequirement::kMightMissing, nullptr,
0);
0, OffpeakTimeOption(options_.daily_offpeak_time_utc));
const Status s = builder.SaveTo(&new_vstorage);
ASSERT_TRUE(s.IsCorruption());
@@ -1514,7 +1520,7 @@ TEST_F(VersionBuilderTest, CheckConsistencyForBlobFilesAllGarbageLinkedSsts) {
VersionStorageInfo new_vstorage(
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, &vstorage_,
force_consistency_checks, EpochNumberRequirement::kMightMissing, nullptr,
0);
0, OffpeakTimeOption(options_.daily_offpeak_time_utc));
const Status s = builder.SaveTo(&new_vstorage);
ASSERT_TRUE(s.IsCorruption());
@@ -1678,7 +1684,7 @@ TEST_F(VersionBuilderTest, MaintainLinkedSstsForBlobFiles) {
VersionStorageInfo new_vstorage(
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, &vstorage_,
force_consistency_checks, EpochNumberRequirement::kMightMissing, nullptr,
0);
0, OffpeakTimeOption(options_.daily_offpeak_time_utc));
ASSERT_OK(builder.SaveTo(&new_vstorage));
@@ -1730,7 +1736,8 @@ TEST_F(VersionBuilderTest, CheckConsistencyForFileDeletedTwice) {
VersionStorageInfo new_vstorage(
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, nullptr,
true /* force_consistency_checks */,
EpochNumberRequirement::kMightMissing, nullptr, 0);
EpochNumberRequirement::kMightMissing, nullptr, 0,
OffpeakTimeOption(options_.daily_offpeak_time_utc));
ASSERT_OK(version_builder.Apply(&version_edit));
ASSERT_OK(version_builder.SaveTo(&new_vstorage));
@@ -1741,7 +1748,8 @@ TEST_F(VersionBuilderTest, CheckConsistencyForFileDeletedTwice) {
VersionStorageInfo new_vstorage2(
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, nullptr,
true /* force_consistency_checks */,
EpochNumberRequirement::kMightMissing, nullptr, 0);
EpochNumberRequirement::kMightMissing, nullptr, 0,
OffpeakTimeOption(options_.daily_offpeak_time_utc));
ASSERT_NOK(version_builder2.Apply(&version_edit));
UnrefFilesInVersion(&new_vstorage);
@@ -1780,7 +1788,8 @@ TEST_F(VersionBuilderTest, CheckConsistencyForL0FilesSortedByEpochNumber) {
VersionStorageInfo new_vstorage_1(
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel,
nullptr /* src_vstorage */, true /* force_consistency_checks */,
EpochNumberRequirement::kMightMissing, nullptr, 0);
EpochNumberRequirement::kMightMissing, nullptr, 0,
OffpeakTimeOption(options_.daily_offpeak_time_utc));
ASSERT_OK(version_builder_1.Apply(&version_edit_1));
s = version_builder_1.SaveTo(&new_vstorage_1);
@@ -1818,7 +1827,8 @@ TEST_F(VersionBuilderTest, CheckConsistencyForL0FilesSortedByEpochNumber) {
VersionStorageInfo new_vstorage_2(
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel,
nullptr /* src_vstorage */, true /* force_consistency_checks */,
EpochNumberRequirement::kMightMissing, nullptr, 0);
EpochNumberRequirement::kMightMissing, nullptr, 0,
OffpeakTimeOption(options_.daily_offpeak_time_utc));
ASSERT_OK(version_builder_2.Apply(&version_edit_2));
s = version_builder_2.SaveTo(&new_vstorage_2);
+29 -10
View File
@@ -492,6 +492,7 @@ class VersionEdit {
file_checksum_func_name, unique_id,
compensated_range_deletion_size, tail_size,
user_defined_timestamps_persisted));
files_to_quarantine_.push_back(file);
if (!HasLastSequence() || largest_seqno > GetLastSequence()) {
SetLastSequence(largest_seqno);
}
@@ -500,6 +501,7 @@ class VersionEdit {
void AddFile(int level, const FileMetaData& f) {
assert(f.fd.smallest_seqno <= f.fd.largest_seqno);
new_files_.emplace_back(level, f);
files_to_quarantine_.push_back(f.fd.GetNumber());
if (!HasLastSequence() || f.fd.largest_seqno > GetLastSequence()) {
SetLastSequence(f.fd.largest_seqno);
}
@@ -536,10 +538,13 @@ class VersionEdit {
blob_file_additions_.emplace_back(
blob_file_number, total_blob_count, total_blob_bytes,
std::move(checksum_method), std::move(checksum_value));
files_to_quarantine_.push_back(blob_file_number);
}
void AddBlobFile(BlobFileAddition blob_file_addition) {
blob_file_additions_.emplace_back(std::move(blob_file_addition));
files_to_quarantine_.push_back(
blob_file_additions_.back().GetBlobFileNumber());
}
// Retrieve all the blob files added.
@@ -551,6 +556,11 @@ class VersionEdit {
void SetBlobFileAdditions(BlobFileAdditions blob_file_additions) {
assert(blob_file_additions_.empty());
blob_file_additions_ = std::move(blob_file_additions);
std::for_each(
blob_file_additions_.begin(), blob_file_additions_.end(),
[&](const BlobFileAddition& blob_file) {
files_to_quarantine_.push_back(blob_file.GetBlobFileNumber());
});
}
// Add garbage for an existing blob file. Note: intentionally broken English
@@ -618,6 +628,8 @@ class VersionEdit {
}
uint32_t GetColumnFamily() const { return column_family_; }
const std::string& GetColumnFamilyName() const { return column_family_name_; }
// set column family ID by calling SetColumnFamily()
void AddColumnFamily(const std::string& name) {
assert(!is_column_family_drop_);
@@ -648,6 +660,9 @@ class VersionEdit {
remaining_entries_ = remaining_entries;
}
bool IsInAtomicGroup() const { return is_in_atomic_group_; }
void SetRemainingEntries(uint32_t remaining_entries) {
remaining_entries_ = remaining_entries;
}
uint32_t GetRemainingEntries() const { return remaining_entries_; }
bool HasFullHistoryTsLow() const { return !full_history_ts_low_.empty(); }
@@ -674,20 +689,14 @@ class VersionEdit {
std::optional<size_t> ts_sz = std::nullopt) const;
Status DecodeFrom(const Slice& src);
const autovector<uint64_t>* GetFilesToQuarantineIfCommitFail() const {
return &files_to_quarantine_;
}
std::string DebugString(bool hex_key = false) const;
std::string DebugJSON(int edit_num, bool hex_key = false) const;
private:
friend class ReactiveVersionSet;
friend class VersionEditHandlerBase;
friend class ListColumnFamiliesHandler;
friend class VersionEditHandler;
friend class VersionEditHandlerPointInTime;
friend class DumpManifestHandler;
friend class VersionSet;
friend class Version;
friend class AtomicGroupReadBuffer;
bool GetLevel(Slice* input, int* level, const char** msg);
const char* DecodeNewFile4From(Slice* input);
@@ -745,6 +754,16 @@ class VersionEdit {
std::string full_history_ts_low_;
bool persist_user_defined_timestamps_ = true;
// Newly created table files and blob files are eligible for deletion if they
// are not registered as live files after the background jobs creating them
// have finished. In case committing the VersionEdit containing such changes
// to manifest encountered an error, we want to quarantine these files from
// deletion to avoid prematurely deleting files that ended up getting recorded
// in Manifest as live files.
// Since table files and blob files share the same file number space, we just
// record the file number here.
autovector<uint64_t> files_to_quarantine_;
};
} // namespace ROCKSDB_NAMESPACE
+75 -76
View File
@@ -43,7 +43,7 @@ void VersionEditHandlerBase::Iterate(log::Reader& reader,
break;
}
ColumnFamilyData* cfd = nullptr;
if (edit.is_in_atomic_group_) {
if (edit.IsInAtomicGroup()) {
if (read_buffer_.IsFull()) {
for (auto& e : read_buffer_.replay_buffer()) {
s = ApplyVersionEdit(e, &cfd);
@@ -101,20 +101,18 @@ void VersionEditHandlerBase::Iterate(log::Reader& reader,
Status ListColumnFamiliesHandler::ApplyVersionEdit(
VersionEdit& edit, ColumnFamilyData** /*unused*/) {
Status s;
if (edit.is_column_family_add_) {
if (column_family_names_.find(edit.column_family_) !=
column_family_names_.end()) {
uint32_t cf_id = edit.GetColumnFamily();
if (edit.IsColumnFamilyAdd()) {
if (column_family_names_.find(cf_id) != column_family_names_.end()) {
s = Status::Corruption("Manifest adding the same column family twice");
} else {
column_family_names_.insert(
{edit.column_family_, edit.column_family_name_});
column_family_names_.insert({cf_id, edit.GetColumnFamilyName()});
}
} else if (edit.is_column_family_drop_) {
if (column_family_names_.find(edit.column_family_) ==
column_family_names_.end()) {
} else if (edit.IsColumnFamilyDrop()) {
if (column_family_names_.find(cf_id) == column_family_names_.end()) {
s = Status::Corruption("Manifest - dropping non-existing column family");
} else {
column_family_names_.erase(edit.column_family_);
column_family_names_.erase(cf_id);
}
}
return s;
@@ -201,9 +199,9 @@ Status VersionEditHandler::Initialize() {
Status VersionEditHandler::ApplyVersionEdit(VersionEdit& edit,
ColumnFamilyData** cfd) {
Status s;
if (edit.is_column_family_add_) {
if (edit.IsColumnFamilyAdd()) {
s = OnColumnFamilyAdd(edit, cfd);
} else if (edit.is_column_family_drop_) {
} else if (edit.IsColumnFamilyDrop()) {
s = OnColumnFamilyDrop(edit, cfd);
} else if (edit.IsWalAddition()) {
s = OnWalAddition(edit);
@@ -227,22 +225,22 @@ Status VersionEditHandler::OnColumnFamilyAdd(VersionEdit& edit,
assert(cfd != nullptr);
*cfd = nullptr;
const std::string& cf_name = edit.GetColumnFamilyName();
Status s;
if (cf_in_builders || cf_in_not_found) {
s = Status::Corruption("MANIFEST adding the same column family twice: " +
edit.column_family_name_);
cf_name);
}
if (s.ok()) {
auto cf_options = name_to_options_.find(edit.column_family_name_);
auto cf_options = name_to_options_.find(cf_name);
// implicitly add persistent_stats column family without requiring user
// to specify
ColumnFamilyData* tmp_cfd = nullptr;
bool is_persistent_stats_column_family =
edit.column_family_name_.compare(kPersistentStatsColumnFamilyName) == 0;
cf_name.compare(kPersistentStatsColumnFamilyName) == 0;
if (cf_options == name_to_options_.end() &&
!is_persistent_stats_column_family) {
column_families_not_found_.emplace(edit.column_family_,
edit.column_family_name_);
column_families_not_found_.emplace(edit.GetColumnFamily(), cf_name);
} else {
if (is_persistent_stats_column_family) {
ColumnFamilyOptions cfo;
@@ -270,7 +268,7 @@ Status VersionEditHandler::OnColumnFamilyDrop(VersionEdit& edit,
if (cf_in_builders) {
tmp_cfd = DestroyCfAndCleanup(edit);
} else if (cf_in_not_found) {
column_families_not_found_.erase(edit.column_family_);
column_families_not_found_.erase(edit.GetColumnFamily());
} else {
s = Status::Corruption("MANIFEST - dropping non-existing column family");
}
@@ -305,10 +303,10 @@ Status VersionEditHandler::OnNonCfOperation(VersionEdit& edit,
}
ColumnFamilyData* tmp_cfd = nullptr;
if (s.ok()) {
auto builder_iter = builders_.find(edit.column_family_);
auto builder_iter = builders_.find(edit.GetColumnFamily());
assert(builder_iter != builders_.end());
tmp_cfd = version_set_->GetColumnFamilySet()->GetColumnFamily(
edit.column_family_);
edit.GetColumnFamily());
assert(tmp_cfd != nullptr);
// It's important to handle file boundaries before `MaybeCreateVersion`
// because `VersionEditHandlerPointInTime::MaybeCreateVersion` does
@@ -362,11 +360,12 @@ void VersionEditHandler::CheckColumnFamilyId(const VersionEdit& edit,
// record. Once we encounter column family drop record,
// we will delete the column family from
// column_families_not_found.
bool in_not_found = column_families_not_found_.find(edit.column_family_) !=
uint32_t cf_id = edit.GetColumnFamily();
bool in_not_found = column_families_not_found_.find(cf_id) !=
column_families_not_found_.end();
// in builders means that user supplied that column family
// option AND that we encountered column family add record
bool in_builders = builders_.find(edit.column_family_) != builders_.end();
bool in_builders = builders_.find(cf_id) != builders_.end();
// They cannot both be true
assert(!(in_not_found && in_builders));
*cf_in_not_found = in_not_found;
@@ -378,17 +377,17 @@ void VersionEditHandler::CheckIterationResult(const log::Reader& reader,
assert(s != nullptr);
if (!s->ok()) {
// Do nothing here.
} else if (!version_edit_params_.has_log_number_ ||
!version_edit_params_.has_next_file_number_ ||
!version_edit_params_.has_last_sequence_) {
} else if (!version_edit_params_.HasLogNumber() ||
!version_edit_params_.HasNextFile() ||
!version_edit_params_.HasLastSequence()) {
std::string msg("no ");
if (!version_edit_params_.has_log_number_) {
if (!version_edit_params_.HasLogNumber()) {
msg.append("log_file_number, ");
}
if (!version_edit_params_.has_next_file_number_) {
if (!version_edit_params_.HasNextFile()) {
msg.append("next_file_number, ");
}
if (!version_edit_params_.has_last_sequence_) {
if (!version_edit_params_.HasLastSequence()) {
msg.append("last_sequence, ");
}
msg = msg.substr(0, msg.size() - 2);
@@ -409,11 +408,11 @@ void VersionEditHandler::CheckIterationResult(const log::Reader& reader,
}
if (s->ok()) {
version_set_->GetColumnFamilySet()->UpdateMaxColumnFamily(
version_edit_params_.max_column_family_);
version_edit_params_.GetMaxColumnFamily());
version_set_->MarkMinLogNumberToKeep(
version_edit_params_.min_log_number_to_keep_);
version_set_->MarkFileNumberUsed(version_edit_params_.prev_log_number_);
version_set_->MarkFileNumberUsed(version_edit_params_.log_number_);
version_edit_params_.GetMinLogNumberToKeep());
version_set_->MarkFileNumberUsed(version_edit_params_.GetPrevLogNumber());
version_set_->MarkFileNumberUsed(version_edit_params_.GetLogNumber());
for (auto* cfd : *(version_set_->GetColumnFamilySet())) {
if (cfd->IsDropped()) {
continue;
@@ -464,9 +463,9 @@ void VersionEditHandler::CheckIterationResult(const log::Reader& reader,
if (s->ok()) {
version_set_->manifest_file_size_ = reader.GetReadOffset();
assert(version_set_->manifest_file_size_ > 0);
version_set_->next_file_number_.store(
version_edit_params_.next_file_number_ + 1);
SequenceNumber last_seq = version_edit_params_.last_sequence_;
version_set_->next_file_number_.store(version_edit_params_.GetNextFile() +
1);
SequenceNumber last_seq = version_edit_params_.GetLastSequence();
assert(last_seq != kMaxSequenceNumber);
if (last_seq != kMaxSequenceNumber &&
last_seq > version_set_->last_allocated_sequence_.load()) {
@@ -488,46 +487,46 @@ void VersionEditHandler::CheckIterationResult(const log::Reader& reader,
// sequence number zeroed through compaction.
version_set_->descriptor_last_sequence_ = last_seq;
}
version_set_->prev_log_number_ = version_edit_params_.prev_log_number_;
version_set_->prev_log_number_ = version_edit_params_.GetPrevLogNumber();
}
}
ColumnFamilyData* VersionEditHandler::CreateCfAndInit(
const ColumnFamilyOptions& cf_options, const VersionEdit& edit) {
uint32_t cf_id = edit.GetColumnFamily();
ColumnFamilyData* cfd =
version_set_->CreateColumnFamily(cf_options, read_options_, &edit);
assert(cfd != nullptr);
cfd->set_initialized();
assert(builders_.find(edit.column_family_) == builders_.end());
builders_.emplace(edit.column_family_,
assert(builders_.find(cf_id) == builders_.end());
builders_.emplace(cf_id,
VersionBuilderUPtr(new BaseReferencedVersionBuilder(cfd)));
if (track_missing_files_) {
cf_to_missing_files_.emplace(edit.column_family_,
std::unordered_set<uint64_t>());
cf_to_missing_blob_files_high_.emplace(edit.column_family_,
kInvalidBlobFileNumber);
cf_to_missing_files_.emplace(cf_id, std::unordered_set<uint64_t>());
cf_to_missing_blob_files_high_.emplace(cf_id, kInvalidBlobFileNumber);
}
return cfd;
}
ColumnFamilyData* VersionEditHandler::DestroyCfAndCleanup(
const VersionEdit& edit) {
auto builder_iter = builders_.find(edit.column_family_);
uint32_t cf_id = edit.GetColumnFamily();
auto builder_iter = builders_.find(cf_id);
assert(builder_iter != builders_.end());
builders_.erase(builder_iter);
if (track_missing_files_) {
auto missing_files_iter = cf_to_missing_files_.find(edit.column_family_);
auto missing_files_iter = cf_to_missing_files_.find(cf_id);
assert(missing_files_iter != cf_to_missing_files_.end());
cf_to_missing_files_.erase(missing_files_iter);
auto missing_blob_files_high_iter =
cf_to_missing_blob_files_high_.find(edit.column_family_);
cf_to_missing_blob_files_high_.find(cf_id);
assert(missing_blob_files_high_iter !=
cf_to_missing_blob_files_high_.end());
cf_to_missing_blob_files_high_.erase(missing_blob_files_high_iter);
}
ColumnFamilyData* ret =
version_set_->GetColumnFamilySet()->GetColumnFamily(edit.column_family_);
version_set_->GetColumnFamilySet()->GetColumnFamily(cf_id);
assert(ret != nullptr);
ret->SetDropped();
ret->UnrefAndTryDelete();
@@ -598,33 +597,33 @@ Status VersionEditHandler::LoadTables(ColumnFamilyData* cfd,
Status VersionEditHandler::ExtractInfoFromVersionEdit(ColumnFamilyData* cfd,
const VersionEdit& edit) {
Status s;
if (edit.has_db_id_) {
if (edit.HasDbId()) {
version_set_->db_id_ = edit.GetDbId();
version_edit_params_.SetDBId(edit.db_id_);
version_edit_params_.SetDBId(edit.GetDbId());
}
if (cfd != nullptr) {
if (edit.has_log_number_) {
if (cfd->GetLogNumber() > edit.log_number_) {
if (edit.HasLogNumber()) {
if (cfd->GetLogNumber() > edit.GetLogNumber()) {
ROCKS_LOG_WARN(
version_set_->db_options()->info_log,
"MANIFEST corruption detected, but ignored - Log numbers in "
"records NOT monotonically increasing");
} else {
cfd->SetLogNumber(edit.log_number_);
version_edit_params_.SetLogNumber(edit.log_number_);
cfd->SetLogNumber(edit.GetLogNumber());
version_edit_params_.SetLogNumber(edit.GetLogNumber());
}
}
if (edit.has_comparator_) {
if (edit.HasComparatorName()) {
bool mark_sst_files_has_no_udt = false;
// If `persist_user_defined_timestamps` flag is recorded in manifest, it
// is guaranteed to be in the same VersionEdit as comparator. Otherwise,
// it's not recorded and it should have default value true.
s = ValidateUserDefinedTimestampsOptions(
cfd->user_comparator(), edit.comparator_,
cfd->user_comparator(), edit.GetComparatorName(),
cfd->ioptions()->persist_user_defined_timestamps,
edit.persist_user_defined_timestamps_, &mark_sst_files_has_no_udt);
edit.GetPersistUserDefinedTimestamps(), &mark_sst_files_has_no_udt);
if (!s.ok() && cf_to_cmp_names_) {
cf_to_cmp_names_->emplace(cfd->GetID(), edit.comparator_);
cf_to_cmp_names_->emplace(cfd->GetID(), edit.GetComparatorName());
}
if (mark_sst_files_has_no_udt) {
cfds_to_mark_no_udt_.insert(cfd->GetID());
@@ -637,29 +636,29 @@ Status VersionEditHandler::ExtractInfoFromVersionEdit(ColumnFamilyData* cfd,
}
if (s.ok()) {
if (edit.has_prev_log_number_) {
version_edit_params_.SetPrevLogNumber(edit.prev_log_number_);
if (edit.HasPrevLogNumber()) {
version_edit_params_.SetPrevLogNumber(edit.GetPrevLogNumber());
}
if (edit.has_next_file_number_) {
version_edit_params_.SetNextFile(edit.next_file_number_);
if (edit.HasNextFile()) {
version_edit_params_.SetNextFile(edit.GetNextFile());
}
if (edit.has_max_column_family_) {
version_edit_params_.SetMaxColumnFamily(edit.max_column_family_);
if (edit.HasMaxColumnFamily()) {
version_edit_params_.SetMaxColumnFamily(edit.GetMaxColumnFamily());
}
if (edit.has_min_log_number_to_keep_) {
version_edit_params_.min_log_number_to_keep_ =
std::max(version_edit_params_.min_log_number_to_keep_,
edit.min_log_number_to_keep_);
if (edit.HasMinLogNumberToKeep()) {
version_edit_params_.SetMinLogNumberToKeep(
std::max(version_edit_params_.GetMinLogNumberToKeep(),
edit.GetMinLogNumberToKeep()));
}
if (edit.has_last_sequence_) {
if (edit.HasLastSequence()) {
// `VersionEdit::last_sequence_`s are assumed to be non-decreasing. This
// is legacy behavior that cannot change without breaking downgrade
// compatibility.
assert(!version_edit_params_.has_last_sequence_ ||
version_edit_params_.last_sequence_ <= edit.last_sequence_);
version_edit_params_.SetLastSequence(edit.last_sequence_);
assert(!version_edit_params_.HasLastSequence() ||
version_edit_params_.GetLastSequence() <= edit.GetLastSequence());
version_edit_params_.SetLastSequence(edit.GetLastSequence());
}
if (!version_edit_params_.has_prev_log_number_) {
if (!version_edit_params_.HasPrevLogNumber()) {
version_edit_params_.SetPrevLogNumber(0);
}
}
@@ -765,7 +764,7 @@ void VersionEditHandlerPointInTime::CheckIterationResult(
ColumnFamilyData* VersionEditHandlerPointInTime::DestroyCfAndCleanup(
const VersionEdit& edit) {
ColumnFamilyData* cfd = VersionEditHandler::DestroyCfAndCleanup(edit);
auto v_iter = versions_.find(edit.column_family_);
auto v_iter = versions_.find(edit.GetColumnFamily());
if (v_iter != versions_.end()) {
delete v_iter->second;
versions_.erase(v_iter);
@@ -777,7 +776,7 @@ Status VersionEditHandlerPointInTime::MaybeCreateVersion(
const VersionEdit& edit, ColumnFamilyData* cfd, bool force_create_version) {
assert(cfd != nullptr);
if (!force_create_version) {
assert(edit.column_family_ == cfd->GetID());
assert(edit.GetColumnFamily() == cfd->GetID());
}
auto missing_files_iter = cf_to_missing_files_.find(cfd->GetID());
assert(missing_files_iter != cf_to_missing_files_.end());
@@ -860,9 +859,9 @@ Status VersionEditHandlerPointInTime::MaybeCreateVersion(
const bool has_missing_files =
!missing_files.empty() || has_missing_blob_files;
bool missing_info = !version_edit_params_.has_log_number_ ||
!version_edit_params_.has_next_file_number_ ||
!version_edit_params_.has_last_sequence_;
bool missing_info = !version_edit_params_.HasLogNumber() ||
!version_edit_params_.HasNextFile() ||
!version_edit_params_.HasLastSequence();
// Create version before apply edit. The version will represent the state
// before applying the version edit.
+2 -2
View File
@@ -133,8 +133,8 @@ class VersionEditHandler : public VersionEditHandlerBase {
bool HasMissingFiles() const;
void GetDbId(std::string* db_id) const {
if (db_id && version_edit_params_.has_db_id_) {
*db_id = version_edit_params_.db_id_;
if (db_id && version_edit_params_.HasDbId()) {
*db_id = version_edit_params_.GetDbId();
}
}
+90 -57
View File
@@ -2124,7 +2124,8 @@ VersionStorageInfo::VersionStorageInfo(
CompactionStyle compaction_style, VersionStorageInfo* ref_vstorage,
bool _force_consistency_checks,
EpochNumberRequirement epoch_number_requirement, SystemClock* clock,
uint32_t bottommost_file_compaction_delay)
uint32_t bottommost_file_compaction_delay,
OffpeakTimeOption offpeak_time_option)
: internal_comparator_(internal_comparator),
user_comparator_(user_comparator),
// cfd is nullptr if Version is dummy
@@ -2156,7 +2157,8 @@ VersionStorageInfo::VersionStorageInfo(
bottommost_file_compaction_delay_(bottommost_file_compaction_delay),
finalized_(false),
force_consistency_checks_(_force_consistency_checks),
epoch_number_requirement_(epoch_number_requirement) {
epoch_number_requirement_(epoch_number_requirement),
offpeak_time_option_(std::move(offpeak_time_option)) {
if (ref_vstorage != nullptr) {
accumulated_file_size_ = ref_vstorage->accumulated_file_size_;
accumulated_raw_key_size_ = ref_vstorage->accumulated_raw_key_size_;
@@ -2200,9 +2202,9 @@ Version::Version(ColumnFamilyData* column_family_data, VersionSet* vset,
cfd_ == nullptr ? false : cfd_->ioptions()->force_consistency_checks,
epoch_number_requirement,
cfd_ == nullptr ? nullptr : cfd_->ioptions()->clock,
cfd_ == nullptr
? 0
: mutable_cf_options.bottommost_file_compaction_delay),
cfd_ == nullptr ? 0
: mutable_cf_options.bottommost_file_compaction_delay,
vset->offpeak_time_option()),
vset_(vset),
next_(this),
prev_(this),
@@ -3670,6 +3672,16 @@ void VersionStorageInfo::ComputeFilesMarkedForPeriodicCompaction(
const uint64_t allowed_time_limit =
current_time - periodic_compaction_seconds;
// Find the adjust_allowed_time_limit such that it includes files that are
// going to expire by the time next daily offpeak starts.
const OffpeakTimeInfo offpeak_time_info =
offpeak_time_option_.GetOffpeakTimeInfo(current_time);
const uint64_t adjusted_allowed_time_limit =
allowed_time_limit +
(offpeak_time_info.is_now_offpeak
? offpeak_time_info.seconds_till_next_offpeak_start
: 0);
for (int level = 0; level <= last_level; level++) {
for (auto f : files_[level]) {
if (!f->being_compacted) {
@@ -3696,7 +3708,7 @@ void VersionStorageInfo::ComputeFilesMarkedForPeriodicCompaction(
}
}
if (file_modification_time > 0 &&
file_modification_time < allowed_time_limit) {
file_modification_time < adjusted_allowed_time_limit) {
files_marked_for_periodic_compaction_.emplace_back(level, f);
}
}
@@ -5000,15 +5012,15 @@ struct VersionSet::ManifestWriter {
Status AtomicGroupReadBuffer::AddEdit(VersionEdit* edit) {
assert(edit);
if (edit->is_in_atomic_group_) {
if (edit->IsInAtomicGroup()) {
TEST_SYNC_POINT("AtomicGroupReadBuffer::AddEdit:AtomicGroup");
if (replay_buffer_.empty()) {
replay_buffer_.resize(edit->remaining_entries_ + 1);
replay_buffer_.resize(edit->GetRemainingEntries() + 1);
TEST_SYNC_POINT_CALLBACK(
"AtomicGroupReadBuffer::AddEdit:FirstInAtomicGroup", edit);
}
read_edits_in_atomic_group_++;
if (read_edits_in_atomic_group_ + edit->remaining_entries_ !=
if (read_edits_in_atomic_group_ + edit->GetRemainingEntries() !=
static_cast<uint32_t>(replay_buffer_.size())) {
TEST_SYNC_POINT_CALLBACK(
"AtomicGroupReadBuffer::AddEdit:IncorrectAtomicGroupSize", edit);
@@ -5043,15 +5055,14 @@ void AtomicGroupReadBuffer::Clear() {
replay_buffer_.clear();
}
VersionSet::VersionSet(const std::string& dbname,
const ImmutableDBOptions* _db_options,
const FileOptions& storage_options, Cache* table_cache,
WriteBufferManager* write_buffer_manager,
WriteController* write_controller,
BlockCacheTracer* const block_cache_tracer,
const std::shared_ptr<IOTracer>& io_tracer,
const std::string& db_id,
const std::string& db_session_id)
VersionSet::VersionSet(
const std::string& dbname, const ImmutableDBOptions* _db_options,
const FileOptions& storage_options, Cache* table_cache,
WriteBufferManager* write_buffer_manager, WriteController* write_controller,
BlockCacheTracer* const block_cache_tracer,
const std::shared_ptr<IOTracer>& io_tracer, const std::string& db_id,
const std::string& db_session_id, const std::string& daily_offpeak_time_utc,
ErrorHandler* const error_handler)
: column_family_set_(new ColumnFamilySet(
dbname, _db_options, storage_options, table_cache,
write_buffer_manager, write_controller, block_cache_tracer, io_tracer,
@@ -5076,7 +5087,9 @@ VersionSet::VersionSet(const std::string& dbname,
file_options_(storage_options),
block_cache_tracer_(block_cache_tracer),
io_tracer_(io_tracer),
db_session_id_(db_session_id) {}
db_session_id_(db_session_id),
offpeak_time_option_(OffpeakTimeOption(daily_offpeak_time_utc)),
error_handler_(error_handler) {}
VersionSet::~VersionSet() {
// we need to delete column_family_set_ because its destructor depends on
@@ -5175,6 +5188,8 @@ Status VersionSet::ProcessManifestWrites(
autovector<Version*> versions;
autovector<const MutableCFOptions*> mutable_cf_options_ptrs;
std::vector<std::unique_ptr<BaseReferencedVersionBuilder>> builder_guards;
autovector<const autovector<uint64_t>*> files_to_quarantine_if_commit_fail;
autovector<uint64_t> limbo_descriptor_log_file_number;
// Tracking `max_last_sequence` is needed to ensure we write
// `VersionEdit::last_sequence_`s in non-decreasing order according to the
@@ -5208,15 +5223,15 @@ Status VersionSet::ProcessManifestWrites(
// don't update, then Recover can report corrupted atomic group because
// the `remaining_entries_` do not match.
if (!batch_edits.empty()) {
if (batch_edits.back()->is_in_atomic_group_ &&
batch_edits.back()->remaining_entries_ > 0) {
if (batch_edits.back()->IsInAtomicGroup() &&
batch_edits.back()->GetRemainingEntries() > 0) {
assert(group_start < batch_edits.size());
const auto& edit_list = last_writer->edit_list;
size_t k = 0;
while (k < edit_list.size()) {
if (!edit_list[k]->is_in_atomic_group_) {
if (!edit_list[k]->IsInAtomicGroup()) {
break;
} else if (edit_list[k]->remaining_entries_ == 0) {
} else if (edit_list[k]->GetRemainingEntries() == 0) {
++k;
break;
}
@@ -5224,8 +5239,10 @@ Status VersionSet::ProcessManifestWrites(
}
for (auto i = group_start; i < batch_edits.size(); ++i) {
assert(static_cast<uint32_t>(k) <=
batch_edits.back()->remaining_entries_);
batch_edits[i]->remaining_entries_ -= static_cast<uint32_t>(k);
batch_edits.back()->GetRemainingEntries());
batch_edits[i]->SetRemainingEntries(
batch_edits[i]->GetRemainingEntries() -
static_cast<uint32_t>(k));
}
}
}
@@ -5268,10 +5285,10 @@ Status VersionSet::ProcessManifestWrites(
assert(ucmp);
std::optional<size_t> edit_ts_sz = ucmp->timestamp_size();
for (const auto& e : last_writer->edit_list) {
if (e->is_in_atomic_group_) {
if (batch_edits.empty() || !batch_edits.back()->is_in_atomic_group_ ||
(batch_edits.back()->is_in_atomic_group_ &&
batch_edits.back()->remaining_entries_ == 0)) {
if (e->IsInAtomicGroup()) {
if (batch_edits.empty() || !batch_edits.back()->IsInAtomicGroup() ||
(batch_edits.back()->IsInAtomicGroup() &&
batch_edits.back()->GetRemainingEntries() == 0)) {
group_start = batch_edits.size();
}
} else if (group_start != std::numeric_limits<size_t>::max()) {
@@ -5310,7 +5327,7 @@ Status VersionSet::ProcessManifestWrites(
// remaining_entries_.
size_t k = 0;
while (k < batch_edits.size()) {
while (k < batch_edits.size() && !batch_edits[k]->is_in_atomic_group_) {
while (k < batch_edits.size() && !batch_edits[k]->IsInAtomicGroup()) {
++k;
}
if (k == batch_edits.size()) {
@@ -5318,19 +5335,19 @@ Status VersionSet::ProcessManifestWrites(
}
size_t i = k;
while (i < batch_edits.size()) {
if (!batch_edits[i]->is_in_atomic_group_) {
if (!batch_edits[i]->IsInAtomicGroup()) {
break;
}
assert(i - k + batch_edits[i]->remaining_entries_ ==
batch_edits[k]->remaining_entries_);
if (batch_edits[i]->remaining_entries_ == 0) {
assert(i - k + batch_edits[i]->GetRemainingEntries() ==
batch_edits[k]->GetRemainingEntries());
if (batch_edits[i]->GetRemainingEntries() == 0) {
++i;
break;
}
++i;
}
assert(batch_edits[i - 1]->is_in_atomic_group_);
assert(0 == batch_edits[i - 1]->remaining_entries_);
assert(batch_edits[i - 1]->IsInAtomicGroup());
assert(0 == batch_edits[i - 1]->GetRemainingEntries());
std::vector<VersionEdit*> tmp;
for (size_t j = k; j != i; ++j) {
tmp.emplace_back(batch_edits[j]);
@@ -5456,6 +5473,8 @@ Status VersionSet::ProcessManifestWrites(
assert(batch_edits.size() == batch_edits_ts_sz.size());
for (size_t bidx = 0; bidx < batch_edits.size(); bidx++) {
auto& e = batch_edits[bidx];
files_to_quarantine_if_commit_fail.push_back(
e->GetFilesToQuarantineIfCommitFail());
std::string record;
if (!e->EncodeTo(&record, batch_edits_ts_sz[bidx])) {
s = Status::Corruption("Unable to encode VersionEdit:" +
@@ -5505,6 +5524,11 @@ Status VersionSet::ProcessManifestWrites(
dir_contains_current_file);
if (!io_s.ok()) {
s = io_s;
// Quarantine old manifest file in case new manifest file's CURRENT file
// wasn't created successfully and the old manifest is needed.
limbo_descriptor_log_file_number.push_back(manifest_file_number_);
files_to_quarantine_if_commit_fail.push_back(
&limbo_descriptor_log_file_number);
}
}
@@ -5513,7 +5537,7 @@ Status VersionSet::ProcessManifestWrites(
new_manifest_file_size = descriptor_log_->file()->GetFileSize();
}
if (first_writer.edit_list.front()->is_column_family_drop_) {
if (first_writer.edit_list.front()->IsColumnFamilyDrop()) {
TEST_SYNC_POINT("VersionSet::LogAndApply::ColumnFamilyDrop:0");
TEST_SYNC_POINT("VersionSet::LogAndApply::ColumnFamilyDrop:1");
TEST_SYNC_POINT("VersionSet::LogAndApply::ColumnFamilyDrop:2");
@@ -5541,9 +5565,16 @@ Status VersionSet::ProcessManifestWrites(
if (!io_s.ok()) {
if (io_status_.ok()) {
io_status_ = io_s;
if (error_handler_) {
error_handler_->AddFilesToQuarantine(
files_to_quarantine_if_commit_fail);
}
}
} else if (!io_status_.ok()) {
io_status_ = io_s;
if (error_handler_) {
error_handler_->ClearFilesToQuarantine();
}
}
// Append the old manifest file to the obsolete_manifest_ list to be deleted
@@ -5555,13 +5586,13 @@ Status VersionSet::ProcessManifestWrites(
// Install the new versions
if (s.ok()) {
if (first_writer.edit_list.front()->is_column_family_add_) {
if (first_writer.edit_list.front()->IsColumnFamilyAdd()) {
assert(batch_edits.size() == 1);
assert(new_cf_options != nullptr);
assert(max_last_sequence == descriptor_last_sequence_);
CreateColumnFamily(*new_cf_options, read_options,
first_writer.edit_list.front());
} else if (first_writer.edit_list.front()->is_column_family_drop_) {
} else if (first_writer.edit_list.front()->IsColumnFamilyDrop()) {
assert(batch_edits.size() == 1);
assert(max_last_sequence == descriptor_last_sequence_);
first_writer.cfd->SetDropped();
@@ -5574,22 +5605,22 @@ Status VersionSet::ProcessManifestWrites(
for (const auto& e : batch_edits) {
ColumnFamilyData* cfd = nullptr;
if (!e->IsColumnFamilyManipulation()) {
cfd = column_family_set_->GetColumnFamily(e->column_family_);
cfd = column_family_set_->GetColumnFamily(e->GetColumnFamily());
// e would not have been added to batch_edits if its corresponding
// column family is dropped.
assert(cfd);
}
if (cfd) {
if (e->has_log_number_ && e->log_number_ > cfd->GetLogNumber()) {
cfd->SetLogNumber(e->log_number_);
if (e->HasLogNumber() && e->GetLogNumber() > cfd->GetLogNumber()) {
cfd->SetLogNumber(e->GetLogNumber());
}
if (e->HasFullHistoryTsLow()) {
cfd->SetFullHistoryTsLow(e->GetFullHistoryTsLow());
}
}
if (e->has_min_log_number_to_keep_) {
if (e->HasMinLogNumberToKeep()) {
last_min_log_number_to_keep =
std::max(last_min_log_number_to_keep, e->min_log_number_to_keep_);
std::max(last_min_log_number_to_keep, e->GetMinLogNumberToKeep());
}
}
@@ -5606,7 +5637,7 @@ Status VersionSet::ProcessManifestWrites(
descriptor_last_sequence_ = max_last_sequence;
manifest_file_number_ = pending_manifest_file_number_;
manifest_file_size_ = new_manifest_file_size;
prev_log_number_ = first_writer.edit_list.front()->prev_log_number_;
prev_log_number_ = first_writer.edit_list.front()->GetPrevLogNumber();
} else {
std::string version_edits;
for (auto& e : batch_edits) {
@@ -5754,7 +5785,7 @@ Status VersionSet::LogAndApply(
int num_cfds = static_cast<int>(column_family_datas.size());
if (num_cfds == 1 && column_family_datas[0] == nullptr) {
assert(edit_lists.size() == 1 && edit_lists[0].size() == 1);
assert(edit_lists[0][0]->is_column_family_add_);
assert(edit_lists[0][0]->IsColumnFamilyAdd());
assert(new_cf_options != nullptr);
}
std::deque<ManifestWriter> writers;
@@ -5818,7 +5849,7 @@ void VersionSet::LogAndApplyCFHelper(VersionEdit* edit,
edit->SetNextFile(next_file_number_.load());
assert(!edit->HasLastSequence());
edit->SetLastSequence(*max_last_sequence);
if (edit->is_column_family_drop_) {
if (edit->IsColumnFamilyDrop()) {
// if we drop column family, we have to make sure to save max column family,
// so that we don't reuse existing ID
edit->SetMaxColumnFamily(column_family_set_->GetMaxColumnFamily());
@@ -5836,12 +5867,12 @@ Status VersionSet::LogAndApplyHelper(ColumnFamilyData* cfd,
assert(!edit->IsColumnFamilyManipulation());
assert(max_last_sequence != nullptr);
if (edit->has_log_number_) {
assert(edit->log_number_ >= cfd->GetLogNumber());
assert(edit->log_number_ < next_file_number_.load());
if (edit->HasLogNumber()) {
assert(edit->GetLogNumber() >= cfd->GetLogNumber());
assert(edit->GetLogNumber() < next_file_number_.load());
}
if (!edit->has_prev_log_number_) {
if (!edit->HasPrevLogNumber()) {
edit->SetPrevLogNumber(prev_log_number_);
}
edit->SetNextFile(next_file_number_.load());
@@ -5933,7 +5964,7 @@ Status VersionSet::Recover(
handler.Iterate(reader, &log_read_status);
s = handler.status();
if (s.ok()) {
log_number = handler.GetVersionEditParams().log_number_;
log_number = handler.GetVersionEditParams().GetLogNumber();
current_manifest_file_size = reader.GetReadOffset();
assert(current_manifest_file_size != 0);
handler.GetDbId(db_id);
@@ -6201,7 +6232,8 @@ Status VersionSet::ReduceNumberOfLevels(const std::string& dbname,
VersionSet versions(dbname, &db_options, file_options, tc.get(), &wb, &wc,
nullptr /*BlockCacheTracer*/, nullptr /*IOTracer*/,
/*db_id*/ "",
/*db_session_id*/ "");
/*db_session_id*/ "", options->daily_offpeak_time_utc,
/*error_handler_*/ nullptr);
Status status;
std::vector<ColumnFamilyDescriptor> dummy;
@@ -7104,7 +7136,7 @@ uint64_t VersionSet::GetObsoleteSstFilesSize() const {
ColumnFamilyData* VersionSet::CreateColumnFamily(
const ColumnFamilyOptions& cf_options, const ReadOptions& read_options,
const VersionEdit* edit) {
assert(edit->is_column_family_add_);
assert(edit->IsColumnFamilyAdd());
MutableCFOptions dummy_cf_options;
Version* dummy_versions =
@@ -7113,7 +7145,7 @@ ColumnFamilyData* VersionSet::CreateColumnFamily(
// by avoiding calling "delete" explicitly (~Version is private)
dummy_versions->Ref();
auto new_cfd = column_family_set_->CreateColumnFamily(
edit->column_family_name_, edit->column_family_, dummy_versions,
edit->GetColumnFamilyName(), edit->GetColumnFamily(), dummy_versions,
cf_options);
Version* v = new Version(new_cfd, this, file_options_,
@@ -7130,7 +7162,7 @@ ColumnFamilyData* VersionSet::CreateColumnFamily(
// cfd is not available to client
new_cfd->CreateNewMemtable(*new_cfd->GetLatestMutableCFOptions(),
LastSequence());
new_cfd->SetLogNumber(edit->log_number_);
new_cfd->SetLogNumber(edit->GetLogNumber());
return new_cfd;
}
@@ -7242,7 +7274,8 @@ ReactiveVersionSet::ReactiveVersionSet(
: VersionSet(dbname, _db_options, _file_options, table_cache,
write_buffer_manager, write_controller,
/*block_cache_tracer=*/nullptr, io_tracer, /*db_id*/ "",
/*db_session_id*/ "") {}
/*db_session_id*/ "", /*daily_offpeak_time_utc*/ "",
/*error_handler=*/nullptr) {}
ReactiveVersionSet::~ReactiveVersionSet() {}
+25 -3
View File
@@ -38,6 +38,7 @@
#include "db/compaction/compaction.h"
#include "db/compaction/compaction_picker.h"
#include "db/dbformat.h"
#include "db/error_handler.h"
#include "db/file_indexer.h"
#include "db/log_reader.h"
#include "db/range_del_aggregator.h"
@@ -53,6 +54,7 @@
#endif
#include "monitoring/instrumented_mutex.h"
#include "options/db_options.h"
#include "options/offpeak_time_info.h"
#include "port/port.h"
#include "rocksdb/env.h"
#include "rocksdb/file_checksum.h"
@@ -134,7 +136,8 @@ class VersionStorageInfo {
bool _force_consistency_checks,
EpochNumberRequirement epoch_number_requirement,
SystemClock* clock,
uint32_t bottommost_file_compaction_delay);
uint32_t bottommost_file_compaction_delay,
OffpeakTimeOption offpeak_time_option);
// No copying allowed
VersionStorageInfo(const VersionStorageInfo&) = delete;
void operator=(const VersionStorageInfo&) = delete;
@@ -751,7 +754,8 @@ class VersionStorageInfo {
// target sizes.
uint64_t estimated_compaction_needed_bytes_;
// Used for computing bottommost files marked for compaction.
// Used for computing bottommost files marked for compaction and checking for
// offpeak time.
SystemClock* clock_;
uint32_t bottommost_file_compaction_delay_;
@@ -763,6 +767,8 @@ class VersionStorageInfo {
EpochNumberRequirement epoch_number_requirement_;
OffpeakTimeOption offpeak_time_option_;
friend class Version;
friend class VersionSet;
};
@@ -1146,7 +1152,9 @@ class VersionSet {
WriteController* write_controller,
BlockCacheTracer* const block_cache_tracer,
const std::shared_ptr<IOTracer>& io_tracer,
const std::string& db_id, const std::string& db_session_id);
const std::string& db_id, const std::string& db_session_id,
const std::string& daily_offpeak_time_utc,
ErrorHandler* const error_handler);
// No copying allowed
VersionSet(const VersionSet&) = delete;
void operator=(const VersionSet&) = delete;
@@ -1501,6 +1509,14 @@ class VersionSet {
new_options.writable_file_max_buffer_size;
}
// TODO - Consider updating together when file options change in SetDBOptions
const OffpeakTimeOption& offpeak_time_option() {
return offpeak_time_option_;
}
void ChangeOffpeakTimeOption(const std::string& daily_offpeak_time_utc) {
offpeak_time_option_.SetFromOffpeakTimeString(daily_offpeak_time_utc);
}
const ImmutableDBOptions* db_options() const { return db_options_; }
static uint64_t GetNumLiveVersions(Version* dummy_versions);
@@ -1651,6 +1667,12 @@ class VersionSet {
std::string db_session_id_;
// Off-peak time option used for compaction scoring
OffpeakTimeOption offpeak_time_option_;
// Pointer to the DB's ErrorHandler.
ErrorHandler* const error_handler_;
private:
// REQUIRES db mutex at beginning. may release and re-acquire db mutex
Status ProcessManifestWrites(std::deque<ManifestWriter>& writers,
+156 -51
View File
@@ -21,6 +21,7 @@
#include "table/block_based/block_based_table_factory.h"
#include "table/mock_table.h"
#include "table/unique_id_impl.h"
#include "test_util/mock_time_env.h"
#include "test_util/testharness.h"
#include "test_util/testutil.h"
#include "util/string_util.h"
@@ -132,7 +133,8 @@ class VersionStorageInfoTestBase : public testing::Test {
/*src_vstorage=*/nullptr,
/*_force_consistency_checks=*/false,
EpochNumberRequirement::kMustPresent, ioptions_.clock,
mutable_cf_options_.bottommost_file_compaction_delay) {}
mutable_cf_options_.bottommost_file_compaction_delay,
OffpeakTimeOption()) {}
~VersionStorageInfoTestBase() override {
for (int i = 0; i < vstorage_.num_levels(); ++i) {
@@ -1199,11 +1201,12 @@ class VersionSetTestBase {
immutable_options_.fs = fs_;
immutable_options_.clock = env_->GetSystemClock().get();
versions_.reset(
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id*/ "", /*db_session_id*/ ""));
versions_.reset(new VersionSet(
dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr));
reactive_versions_ = std::make_shared<ReactiveVersionSet>(
dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_, nullptr);
@@ -1303,11 +1306,12 @@ class VersionSetTestBase {
}
void ReopenDB() {
versions_.reset(
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id*/ "", /*db_session_id*/ ""));
versions_.reset(new VersionSet(
dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr));
EXPECT_OK(versions_->Recover(column_families_, false));
}
@@ -1815,11 +1819,12 @@ TEST_F(VersionSetTest, WalAddition) {
// Recover a new VersionSet.
{
std::unique_ptr<VersionSet> new_versions(
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id*/ "", /*db_session_id*/ ""));
std::unique_ptr<VersionSet> new_versions(new VersionSet(
dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr));
ASSERT_OK(new_versions->Recover(column_families_, /*read_only=*/false));
const auto& wals = new_versions->GetWalSet().GetWals();
ASSERT_EQ(wals.size(), 1);
@@ -1882,11 +1887,12 @@ TEST_F(VersionSetTest, WalCloseWithoutSync) {
// Recover a new VersionSet.
{
std::unique_ptr<VersionSet> new_versions(
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id*/ "", /*db_session_id*/ ""));
std::unique_ptr<VersionSet> new_versions(new VersionSet(
dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr));
ASSERT_OK(new_versions->Recover(column_families_, false));
const auto& wals = new_versions->GetWalSet().GetWals();
ASSERT_EQ(wals.size(), 2);
@@ -1935,11 +1941,12 @@ TEST_F(VersionSetTest, WalDeletion) {
// Recover a new VersionSet, only the non-closed WAL should show up.
{
std::unique_ptr<VersionSet> new_versions(
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id*/ "", /*db_session_id*/ ""));
std::unique_ptr<VersionSet> new_versions(new VersionSet(
dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr));
ASSERT_OK(new_versions->Recover(column_families_, false));
const auto& wals = new_versions->GetWalSet().GetWals();
ASSERT_EQ(wals.size(), 1);
@@ -1973,11 +1980,12 @@ TEST_F(VersionSetTest, WalDeletion) {
// Recover from the new MANIFEST, only the non-closed WAL should show up.
{
std::unique_ptr<VersionSet> new_versions(
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id*/ "", /*db_session_id*/ ""));
std::unique_ptr<VersionSet> new_versions(new VersionSet(
dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr));
ASSERT_OK(new_versions->Recover(column_families_, false));
const auto& wals = new_versions->GetWalSet().GetWals();
ASSERT_EQ(wals.size(), 1);
@@ -2093,11 +2101,12 @@ TEST_F(VersionSetTest, DeleteWalsBeforeNonExistingWalNumber) {
// Recover a new VersionSet, WAL0 is deleted, WAL1 is not.
{
std::unique_ptr<VersionSet> new_versions(
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id*/ "", /*db_session_id*/ ""));
std::unique_ptr<VersionSet> new_versions(new VersionSet(
dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr));
ASSERT_OK(new_versions->Recover(column_families_, false));
const auto& wals = new_versions->GetWalSet().GetWals();
ASSERT_EQ(wals.size(), 1);
@@ -2129,11 +2138,12 @@ TEST_F(VersionSetTest, DeleteAllWals) {
// Recover a new VersionSet, all WALs are deleted.
{
std::unique_ptr<VersionSet> new_versions(
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id*/ "", /*db_session_id*/ ""));
std::unique_ptr<VersionSet> new_versions(new VersionSet(
dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr));
ASSERT_OK(new_versions->Recover(column_families_, false));
const auto& wals = new_versions->GetWalSet().GetWals();
ASSERT_EQ(wals.size(), 0);
@@ -2171,11 +2181,12 @@ TEST_F(VersionSetTest, AtomicGroupWithWalEdits) {
// Recover a new VersionSet, the min log number and the last WAL should be
// kept.
{
std::unique_ptr<VersionSet> new_versions(
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id*/ "", /*db_session_id*/ ""));
std::unique_ptr<VersionSet> new_versions(new VersionSet(
dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr));
std::string db_id;
ASSERT_OK(
new_versions->Recover(column_families_, /*read_only=*/false, &db_id));
@@ -2190,6 +2201,99 @@ TEST_F(VersionSetTest, AtomicGroupWithWalEdits) {
}
}
TEST_F(VersionSetTest, OffpeakTimeInfoTest) {
Random rnd(test::RandomSeed());
// 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;
versions_->ChangeOffpeakTimeOption("23:30-04:30");
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_hour * 3600 + now_minute * 60);
int64_t now;
ASSERT_OK(mock_clock.get()->GetCurrentTime(&now));
// Starting at 1:30PM. It's not off-peak
ASSERT_FALSE(
versions_->offpeak_time_option().GetOffpeakTimeInfo(now).is_now_offpeak);
// Now it's at 4:30PM. Still not off-peak
mock_clock->MockSleepForSeconds(3 * 3600);
ASSERT_OK(mock_clock.get()->GetCurrentTime(&now));
ASSERT_FALSE(
versions_->offpeak_time_option().GetOffpeakTimeInfo(now).is_now_offpeak);
// Now it's at 11:30PM. It's off-peak
mock_clock->MockSleepForSeconds(7 * 3600);
ASSERT_OK(mock_clock.get()->GetCurrentTime(&now));
ASSERT_TRUE(
versions_->offpeak_time_option().GetOffpeakTimeInfo(now).is_now_offpeak);
// Now it's at 2:30AM next day. It's still off-peak
mock_clock->MockSleepForSeconds(3 * 3600);
ASSERT_OK(mock_clock.get()->GetCurrentTime(&now));
ASSERT_TRUE(
versions_->offpeak_time_option().GetOffpeakTimeInfo(now).is_now_offpeak);
// Now it's at 4:30AM. It's still off-peak
mock_clock->MockSleepForSeconds(2 * 3600);
ASSERT_OK(mock_clock.get()->GetCurrentTime(&now));
ASSERT_TRUE(
versions_->offpeak_time_option().GetOffpeakTimeInfo(now).is_now_offpeak);
// Sleep for one more minute. It's at 4:31AM It's no longer off-peak
mock_clock->MockSleepForSeconds(60);
ASSERT_OK(mock_clock.get()->GetCurrentTime(&now));
ASSERT_FALSE(
versions_->offpeak_time_option().GetOffpeakTimeInfo(now).is_now_offpeak);
// Entire day offpeak
versions_->ChangeOffpeakTimeOption("00:00-23:59");
// It doesn't matter what time it is. It should be just offpeak.
ASSERT_TRUE(
versions_->offpeak_time_option().GetOffpeakTimeInfo(now).is_now_offpeak);
// Mock Sleep for 3 hours. It's still off-peak
mock_clock->MockSleepForSeconds(3 * 3600);
ASSERT_OK(mock_clock.get()->GetCurrentTime(&now));
ASSERT_TRUE(
versions_->offpeak_time_option().GetOffpeakTimeInfo(now).is_now_offpeak);
// Mock Sleep for 20 hours. It's still off-peak
mock_clock->MockSleepForSeconds(20 * 3600);
ASSERT_OK(mock_clock.get()->GetCurrentTime(&now));
ASSERT_TRUE(
versions_->offpeak_time_option().GetOffpeakTimeInfo(now).is_now_offpeak);
// Mock Sleep for 59 minutes. It's still off-peak
mock_clock->MockSleepForSeconds(59 * 60);
ASSERT_OK(mock_clock.get()->GetCurrentTime(&now));
ASSERT_TRUE(
versions_->offpeak_time_option().GetOffpeakTimeInfo(now).is_now_offpeak);
// Mock Sleep for 59 seconds. It's still off-peak
mock_clock->MockSleepForSeconds(59);
ASSERT_OK(mock_clock.get()->GetCurrentTime(&now));
ASSERT_TRUE(
versions_->offpeak_time_option().GetOffpeakTimeInfo(now).is_now_offpeak);
// Mock Sleep for 1 second (exactly 24h passed). It's still off-peak
mock_clock->MockSleepForSeconds(1);
ASSERT_OK(mock_clock.get()->GetCurrentTime(&now));
ASSERT_TRUE(
versions_->offpeak_time_option().GetOffpeakTimeInfo(now).is_now_offpeak);
// Another second for sanity check
mock_clock->MockSleepForSeconds(1);
ASSERT_OK(mock_clock.get()->GetCurrentTime(&now));
ASSERT_TRUE(
versions_->offpeak_time_option().GetOffpeakTimeInfo(now).is_now_offpeak);
}
TEST_F(VersionStorageInfoTest, AddRangeDeletionCompensatedFileSize) {
// Tests that compensated range deletion size is added to compensated file
// size.
@@ -2236,11 +2340,12 @@ class VersionSetWithTimestampTest : public VersionSetTest {
}
void VerifyFullHistoryTsLow(uint64_t expected_ts_low) {
std::unique_ptr<VersionSet> vset(
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id*/ "", /*db_session_id*/ ""));
std::unique_ptr<VersionSet> vset(new VersionSet(
dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr));
ASSERT_OK(vset->Recover(column_families_, /*read_only=*/false,
/*db_id=*/nullptr));
for (auto* cfd : *(vset->GetColumnFamilySet())) {
+3 -1
View File
@@ -25,7 +25,9 @@ class OfflineManifestWriter {
options.table_cache_numshardbits)),
versions_(db_path, &immutable_db_options_, sopt_, tc_.get(), &wb_, &wc_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id*/ "", /*db_session_id*/ "") {}
/*db_id=*/"", /*db_session_id=*/"",
options.daily_offpeak_time_utc,
/*error_handler=*/nullptr) {}
Status Recover(const std::vector<ColumnFamilyDescriptor>& column_families) {
return versions_.Recover(column_families, /*read_only*/ false,
+5 -4
View File
@@ -153,10 +153,11 @@ void WalManager::PurgeObsoleteWALFiles() {
return;
}
uint64_t const now_seconds = static_cast<uint64_t>(current_time);
uint64_t const time_to_check = (ttl_enabled && !size_limit_enabled)
? db_options_.WAL_ttl_seconds / 2
: kDefaultIntervalToDeleteObsoleteWAL;
uint64_t const time_to_check =
ttl_enabled
? std::min(kDefaultIntervalToDeleteObsoleteWAL,
std::max(uint64_t{1}, db_options_.WAL_ttl_seconds / 2))
: kDefaultIntervalToDeleteObsoleteWAL;
if (purge_wal_files_last_run_ + time_to_check > now_seconds) {
return;
}
+6 -6
View File
@@ -50,11 +50,12 @@ class WalManagerTest : public testing::Test {
db_options_.fs = env_->GetFileSystem();
db_options_.clock = env_->GetSystemClock().get();
versions_.reset(
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id*/ "", /*db_session_id*/ ""));
versions_.reset(new VersionSet(
dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr));
wal_manager_.reset(
new WalManager(db_options_, env_options_, nullptr /*IOTracer*/));
@@ -333,4 +334,3 @@ int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
+183 -17
View File
@@ -236,6 +236,144 @@ TEST_F(DBWideBasicTest, PutEntityColumnFamily) {
ASSERT_OK(db_->Write(WriteOptions(), &batch));
}
TEST_F(DBWideBasicTest, GetEntityAsPinnableAttributeGroups) {
Options options = GetDefaultOptions();
CreateAndReopenWithCF({"hot_cf", "cold_cf"}, options);
constexpr int kDefaultCfHandleIndex = 0;
constexpr int kHotCfHandleIndex = 1;
constexpr int kColdCfHandleIndex = 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"}};
AttributeGroups first_key_attribute_groups{
AttributeGroup(handles_[kDefaultCfHandleIndex], first_default_columns),
AttributeGroup(handles_[kHotCfHandleIndex], first_hot_columns),
AttributeGroup(handles_[kColdCfHandleIndex], first_cold_columns)};
AttributeGroups second_key_attribute_groups{
AttributeGroup(handles_[kHotCfHandleIndex], second_hot_columns),
AttributeGroup(handles_[kColdCfHandleIndex], second_cold_columns)};
ASSERT_OK(
db_->PutEntity(WriteOptions(), first_key, first_key_attribute_groups));
ASSERT_OK(
db_->PutEntity(WriteOptions(), second_key, second_key_attribute_groups));
std::vector<ColumnFamilyHandle*> all_cfs = handles_;
std::vector<ColumnFamilyHandle*> default_and_hot_cfs{
{handles_[kDefaultCfHandleIndex], handles_[kHotCfHandleIndex]}};
std::vector<ColumnFamilyHandle*> hot_and_cold_cfs{
{handles_[kHotCfHandleIndex], handles_[kColdCfHandleIndex]}};
std::vector<ColumnFamilyHandle*> default_null_and_hot_cfs{
handles_[kDefaultCfHandleIndex], nullptr, handles_[kHotCfHandleIndex],
nullptr};
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;
};
{
// Case 1. Invalid Argument (passing in null CF)
AttributeGroups ag{
AttributeGroup(nullptr, first_default_columns),
AttributeGroup(handles_[kHotCfHandleIndex], first_hot_columns)};
ASSERT_NOK(db_->PutEntity(WriteOptions(), first_key, ag));
PinnableAttributeGroups result = create_result(default_null_and_hot_cfs);
Status s = db_->GetEntity(ReadOptions(), first_key, &result);
ASSERT_NOK(s);
ASSERT_TRUE(s.IsInvalidArgument());
// Valid CF, but failed with Incomplete status due to other attribute groups
ASSERT_TRUE(result[0].status().IsIncomplete());
// Null CF
ASSERT_TRUE(result[1].status().IsInvalidArgument());
// Valid CF, but failed with Incomplete status due to other attribute groups
ASSERT_TRUE(result[2].status().IsIncomplete());
// Null CF, but failed with Incomplete status because the nullcheck break
// out early in the loop
ASSERT_TRUE(result[3].status().IsIncomplete());
}
{
// Case 2. Get first key from default cf and hot_cf and second key from
// hot_cf and cold_cf
constexpr size_t num_column_families = 2;
PinnableAttributeGroups first_key_result =
create_result(default_and_hot_cfs);
PinnableAttributeGroups second_key_result = create_result(hot_and_cold_cfs);
// GetEntity for first_key
ASSERT_OK(db_->GetEntity(ReadOptions(), first_key, &first_key_result));
ASSERT_EQ(num_column_families, first_key_result.size());
// We expect to get values for all keys and CFs
for (size_t i = 0; i < num_column_families; ++i) {
ASSERT_OK(first_key_result[i].status());
}
// verify values for first key (default cf and hot cf)
ASSERT_EQ(first_default_columns, first_key_result[0].columns());
ASSERT_EQ(first_hot_columns, first_key_result[1].columns());
// GetEntity for second_key
ASSERT_OK(db_->GetEntity(ReadOptions(), second_key, &second_key_result));
ASSERT_EQ(num_column_families, second_key_result.size());
// We expect to get values for all keys and CFs
for (size_t i = 0; i < num_column_families; ++i) {
ASSERT_OK(second_key_result[i].status());
}
// verify values for second key (hot cf and cold cf)
ASSERT_EQ(second_hot_columns, second_key_result[0].columns());
ASSERT_EQ(second_cold_columns, second_key_result[1].columns());
}
{
// Case 3. Get first key and second key from all cfs. For the second key, we
// don't expect to get columns from default cf.
constexpr size_t num_column_families = 3;
PinnableAttributeGroups first_key_result = create_result(all_cfs);
PinnableAttributeGroups second_key_result = create_result(all_cfs);
// GetEntity for first_key
ASSERT_OK(db_->GetEntity(ReadOptions(), first_key, &first_key_result));
ASSERT_EQ(num_column_families, first_key_result.size());
// We expect to get values for all keys and CFs
for (size_t i = 0; i < num_column_families; ++i) {
ASSERT_OK(first_key_result[i].status());
}
// verify values for first key
ASSERT_EQ(first_default_columns, first_key_result[0].columns());
ASSERT_EQ(first_hot_columns, first_key_result[1].columns());
ASSERT_EQ(first_cold_columns, first_key_result[2].columns());
// GetEntity for second_key
ASSERT_OK(db_->GetEntity(ReadOptions(), second_key, &second_key_result));
ASSERT_EQ(num_column_families, second_key_result.size());
// key does not exist in default cf
ASSERT_NOK(second_key_result[0].status());
ASSERT_TRUE(second_key_result[0].status().IsNotFound());
// verify values for second key (hot cf and cold cf)
ASSERT_OK(second_key_result[1].status());
ASSERT_OK(second_key_result[2].status());
ASSERT_EQ(second_hot_columns, second_key_result[1].columns());
ASSERT_EQ(second_cold_columns, second_key_result[2].columns());
}
}
TEST_F(DBWideBasicTest, MultiCFMultiGetEntity) {
Options options = GetDefaultOptions();
CreateAndReopenWithCF({"corinthian"}, options);
@@ -274,9 +412,9 @@ 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 int kDefaultCfHandleIndex = 0;
constexpr int kHotCfHandleIndex = 1;
constexpr int kColdCfHandleIndex = 2;
constexpr char first_key[] = "first";
WideColumns first_default_columns{
@@ -293,25 +431,28 @@ TEST_F(DBWideBasicTest, MultiCFMultiGetEntityAsPinnableAttributeGroups) {
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));
AttributeGroups first_key_attribute_groups{
AttributeGroup(handles_[kDefaultCfHandleIndex], first_default_columns),
AttributeGroup(handles_[kHotCfHandleIndex], first_hot_columns),
AttributeGroup(handles_[kColdCfHandleIndex], first_cold_columns)};
AttributeGroups second_key_attribute_groups{
AttributeGroup(handles_[kHotCfHandleIndex], second_hot_columns),
AttributeGroup(handles_[kColdCfHandleIndex], second_cold_columns)};
ASSERT_OK(
db_->PutEntity(WriteOptions(), first_key, first_key_attribute_groups));
ASSERT_OK(
db_->PutEntity(WriteOptions(), second_key, second_key_attribute_groups));
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]}};
{handles_[kDefaultCfHandleIndex], handles_[kHotCfHandleIndex]}};
std::vector<ColumnFamilyHandle*> hot_and_cold_cfs{
{handles_[HOT_CF_HANDLE_INDEX], handles_[COLD_CF_HANDLE_INDEX]}};
{handles_[kHotCfHandleIndex], handles_[kColdCfHandleIndex]}};
std::vector<ColumnFamilyHandle*> null_and_hot_cfs{
nullptr, handles_[kHotCfHandleIndex], nullptr};
auto create_result =
[](const std::vector<ColumnFamilyHandle*>& column_families)
-> PinnableAttributeGroups {
@@ -322,7 +463,7 @@ TEST_F(DBWideBasicTest, MultiCFMultiGetEntityAsPinnableAttributeGroups) {
return result;
};
{
// Check for invalid argument
// Check for invalid read option argument
ReadOptions read_options;
read_options.io_activity = Env::IOActivity::kGetEntity;
std::vector<PinnableAttributeGroups> results;
@@ -336,6 +477,31 @@ TEST_F(DBWideBasicTest, MultiCFMultiGetEntityAsPinnableAttributeGroups) {
ASSERT_TRUE(results[i][j].status().IsInvalidArgument());
}
}
// Check for invalid column family in Attribute Group result
results.clear();
results.emplace_back(create_result(null_and_hot_cfs));
results.emplace_back(create_result(all_cfs));
db_->MultiGetEntity(ReadOptions(), num_keys, keys.data(), results.data());
// First one failed due to null CFs in the AttributeGroup
// Null CF
ASSERT_NOK(results[0][0].status());
ASSERT_TRUE(results[0][0].status().IsInvalidArgument());
// Valid CF, but failed with incomplete status because of other attribute
// groups
ASSERT_NOK(results[0][1].status());
ASSERT_TRUE(results[0][1].status().IsIncomplete());
// Null CF
ASSERT_NOK(results[0][2].status());
ASSERT_TRUE(results[0][2].status().IsInvalidArgument());
// Second one failed with Incomplete because first one failed
ASSERT_NOK(results[1][0].status());
ASSERT_TRUE(results[1][0].status().IsIncomplete());
ASSERT_NOK(results[1][1].status());
ASSERT_TRUE(results[1][1].status().IsIncomplete());
ASSERT_NOK(results[1][2].status());
ASSERT_TRUE(results[1][2].status().IsIncomplete());
}
{
// Case 1. Get first key from default cf and hot_cf and second key from
+17
View File
@@ -39,6 +39,7 @@
#include "rocksdb/write_batch.h"
#include <algorithm>
#include <cstdint>
#include <limits>
#include <map>
#include <stack>
@@ -1016,6 +1017,22 @@ Status WriteBatch::PutEntity(ColumnFamilyHandle* column_family,
return WriteBatchInternal::PutEntity(this, cf_id, key, columns);
}
Status WriteBatch::PutEntity(const Slice& key,
const AttributeGroups& attribute_groups) {
if (attribute_groups.empty()) {
return Status::InvalidArgument(
"Cannot call this method with empty attribute groups");
}
Status s;
for (const AttributeGroup& ag : attribute_groups) {
s = PutEntity(ag.column_family(), key, ag.columns());
if (!s.ok()) {
return s;
}
}
return s;
}
Status WriteBatchInternal::InsertNoop(WriteBatch* b) {
b->rep_.push_back(static_cast<char>(kTypeNoop));
return Status::OK();
+98
View File
@@ -12,7 +12,9 @@
#include "db/column_family.h"
#include "db/db_test_util.h"
#include "db/memtable.h"
#include "db/wide/wide_columns_helper.h"
#include "db/write_batch_internal.h"
#include "dbformat.h"
#include "rocksdb/comparator.h"
#include "rocksdb/db.h"
#include "rocksdb/env.h"
@@ -276,6 +278,21 @@ struct TestHandler : public WriteBatch::Handler {
}
return Status::OK();
}
Status PutEntityCF(uint32_t column_family_id, const Slice& key,
const Slice& entity) override {
std::ostringstream oss;
Status s = WideColumnsHelper::DumpSliceAsWideColumns(entity, oss, false);
if (!s.ok()) {
return s;
}
if (column_family_id == 0) {
seen += "PutEntity(" + key.ToString() + ", " + oss.str() + ")";
} else {
seen += "PutEntityCF(" + std::to_string(column_family_id) + ", " +
key.ToString() + ", " + oss.str() + ")";
}
return Status::OK();
}
Status DeleteCF(uint32_t column_family_id, const Slice& key) override {
if (column_family_id == 0) {
seen += "Delete(" + key.ToString() + ")";
@@ -665,6 +682,82 @@ class ColumnFamilyHandleImplDummy : public ColumnFamilyHandleImpl {
};
} // anonymous namespace
TEST_F(WriteBatchTest, AttributeGroupTest) {
WriteBatch batch;
ColumnFamilyHandleImplDummy zero(0), two(2);
AttributeGroups foo_ags;
WideColumn zero_col_1{"0_c_1_n", "0_c_1_v"};
WideColumn zero_col_2{"0_c_2_n", "0_c_2_v"};
WideColumns zero_col_1_col_2{zero_col_1, zero_col_2};
WideColumn two_col_1{"2_c_1_n", "2_c_1_v"};
WideColumn two_col_2{"2_c_2_n", "2_c_2_v"};
WideColumns two_col_1_col_2{two_col_1, two_col_2};
foo_ags.emplace_back(&zero, zero_col_1_col_2);
foo_ags.emplace_back(&two, two_col_1_col_2);
ASSERT_OK(batch.PutEntity("foo", foo_ags));
TestHandler handler;
ASSERT_OK(batch.Iterate(&handler));
ASSERT_EQ(
"PutEntity(foo, 0_c_1_n:0_c_1_v "
"0_c_2_n:0_c_2_v)"
"PutEntityCF(2, foo, 2_c_1_n:2_c_1_v "
"2_c_2_n:2_c_2_v)",
handler.seen);
}
TEST_F(WriteBatchTest, AttributeGroupSavePointTest) {
WriteBatch batch;
batch.SetSavePoint();
ColumnFamilyHandleImplDummy zero(0), two(2), three(3);
AttributeGroups foo_ags;
WideColumn zero_col_1{"0_c_1_n", "0_c_1_v"};
WideColumn zero_col_2{"0_c_2_n", "0_c_2_v"};
WideColumns zero_col_1_col_2{zero_col_1, zero_col_2};
WideColumn two_col_1{"2_c_1_n", "2_c_1_v"};
WideColumn two_col_2{"2_c_2_n", "2_c_2_v"};
WideColumns two_col_1_col_2{two_col_1, two_col_2};
foo_ags.emplace_back(&zero, zero_col_1_col_2);
foo_ags.emplace_back(&two, two_col_1_col_2);
AttributeGroups bar_ags;
WideColumn three_col_1{"3_c_1_n", "3_c_1_v"};
WideColumn three_col_2{"3_c_2_n", "3_c_2_v"};
WideColumns three_col_1_col_2{three_col_1, three_col_2};
bar_ags.emplace_back(&zero, zero_col_1_col_2);
bar_ags.emplace_back(&three, three_col_1_col_2);
ASSERT_OK(batch.PutEntity("foo", foo_ags));
batch.SetSavePoint();
ASSERT_OK(batch.PutEntity("bar", bar_ags));
TestHandler handler;
ASSERT_OK(batch.Iterate(&handler));
ASSERT_EQ(
"PutEntity(foo, 0_c_1_n:0_c_1_v 0_c_2_n:0_c_2_v)"
"PutEntityCF(2, foo, 2_c_1_n:2_c_1_v 2_c_2_n:2_c_2_v)"
"PutEntity(bar, 0_c_1_n:0_c_1_v 0_c_2_n:0_c_2_v)"
"PutEntityCF(3, bar, 3_c_1_n:3_c_1_v 3_c_2_n:3_c_2_v)",
handler.seen);
ASSERT_OK(batch.RollbackToSavePoint());
handler.seen.clear();
ASSERT_OK(batch.Iterate(&handler));
ASSERT_EQ(
"PutEntity(foo, 0_c_1_n:0_c_1_v 0_c_2_n:0_c_2_v)"
"PutEntityCF(2, foo, 2_c_1_n:2_c_1_v 2_c_2_n:2_c_2_v)",
handler.seen);
}
TEST_F(WriteBatchTest, ColumnFamiliesBatchTest) {
WriteBatch batch;
ColumnFamilyHandleImplDummy zero(0), two(2), three(3), eight(8);
@@ -677,6 +770,9 @@ TEST_F(WriteBatchTest, ColumnFamiliesBatchTest) {
ASSERT_OK(batch.Merge(&three, Slice("threethree"), Slice("3three")));
ASSERT_OK(batch.Put(&zero, Slice("foo"), Slice("bar")));
ASSERT_OK(batch.Merge(Slice("omom"), Slice("nom")));
// TODO(yuzhangyu): implement this.
ASSERT_TRUE(
batch.TimedPut(&zero, Slice("foo"), Slice("bar"), 0u).IsNotSupported());
TestHandler handler;
ASSERT_OK(batch.Iterate(&handler));
@@ -704,6 +800,8 @@ TEST_F(WriteBatchTest, ColumnFamiliesBatchWithIndexTest) {
ASSERT_OK(batch.Merge(&three, Slice("threethree"), Slice("3three")));
ASSERT_OK(batch.Put(&zero, Slice("foo"), Slice("bar")));
ASSERT_OK(batch.Merge(Slice("omom"), Slice("nom")));
ASSERT_TRUE(
batch.TimedPut(&zero, Slice("foo"), Slice("bar"), 0u).IsNotSupported());
std::unique_ptr<WBWIIterator> iter;
+1 -1
View File
@@ -189,7 +189,7 @@ void CompressedCacheSetCapacityThread(void* v) {
s.ToString().c_str());
}
} else if (FLAGS_compressed_secondary_cache_ratio > 0.0) {
if (thread->rand.OneIn(2)) {
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) {
+83 -22
View File
@@ -11,6 +11,7 @@
#include <ios>
#include <thread>
#include "rocksdb/options.h"
#include "util/compression.h"
#ifdef GFLAGS
#include "db_stress_tool/db_stress_common.h"
@@ -18,6 +19,7 @@
#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 "options/options_parser.h"
#include "rocksdb/convenience.h"
#include "rocksdb/filter_policy.h"
#include "rocksdb/secondary_cache.h"
@@ -1483,11 +1485,11 @@ void StressTest::VerifyIterator(ThreadState* thread,
}
if (op == kLastOpSeekToFirst && ro.iterate_lower_bound != nullptr) {
// SeekToFirst() with lower bound is not well defined.
// SeekToFirst() with lower bound is not well-defined.
*diverged = true;
return;
} else if (op == kLastOpSeekToLast && ro.iterate_upper_bound != nullptr) {
// SeekToLast() with higher bound is not well defined.
// SeekToLast() with higher bound is not well-defined.
*diverged = true;
return;
} else if (op == kLastOpSeek && ro.iterate_lower_bound != nullptr &&
@@ -1498,7 +1500,7 @@ void StressTest::VerifyIterator(ThreadState* thread,
options_.comparator->CompareWithoutTimestamp(
*ro.iterate_lower_bound, /*a_has_ts=*/false,
*ro.iterate_upper_bound, /*b_has_ts*/ false) >= 0))) {
// Lower bound behavior is not well defined if it is larger than
// Lower bound behavior is not well-defined if it is larger than
// seek key or upper bound. Disable the check for now.
*diverged = true;
return;
@@ -1510,7 +1512,7 @@ void StressTest::VerifyIterator(ThreadState* thread,
options_.comparator->CompareWithoutTimestamp(
*ro.iterate_lower_bound, /*a_has_ts=*/false,
*ro.iterate_upper_bound, /*b_has_ts=*/false) >= 0))) {
// Uppder bound behavior is not well defined if it is smaller than
// Upper bound behavior is not well-defined if it is smaller than
// seek key or lower bound. Disable the check for now.
*diverged = true;
return;
@@ -1538,13 +1540,13 @@ void StressTest::VerifyIterator(ThreadState* thread,
}
}
fprintf(stderr,
"Control interator is invalid but iterator has key %s "
"Control iterator is invalid but iterator has key %s "
"%s\n",
iter->key().ToString(true).c_str(), op_logs.c_str());
*diverged = true;
} else if (cmp_iter->Valid()) {
// Iterator is not valid. It can be legimate if it has already been
// Iterator is not valid. It can be legitimate if it has already been
// out of upper or lower bound, or filtered out by prefix iterator.
const Slice& total_order_key = cmp_iter->key();
@@ -1567,7 +1569,7 @@ void StressTest::VerifyIterator(ThreadState* thread,
return;
}
fprintf(stderr,
"Iterator stays in prefix but contol doesn't"
"Iterator stays in prefix but control doesn't"
" iterator key %s control iterator key %s %s\n",
iter->key().ToString(true).c_str(),
cmp_iter->key().ToString(true).c_str(), op_logs.c_str());
@@ -1614,7 +1616,8 @@ void StressTest::VerifyIterator(ThreadState* thread,
}
if (*diverged) {
fprintf(stderr, "Control CF %s\n", cmp_cfh->GetName().c_str());
fprintf(stderr, "VerifyIterator failed. Control CF %s\n",
cmp_cfh->GetName().c_str());
thread->stats.AddErrors(1);
// Fail fast to preserve the DB state.
thread->shared->SetVerificationFailure();
@@ -1765,13 +1768,16 @@ Status StressTest::TestBackupRestore(
}
DB* restored_db = nullptr;
std::vector<ColumnFamilyHandle*> restored_cf_handles;
// Not yet implemented: opening restored BlobDB or TransactionDB
Options restore_options;
if (s.ok() && !FLAGS_use_txn && !FLAGS_use_blob_db) {
s = PrepareOptionsForRestoredDB(&restore_options);
if (!s.ok()) {
from = "PrepareRestoredDBOptions in backup/restore";
}
}
if (s.ok() && !FLAGS_use_txn && !FLAGS_use_blob_db) {
Options restore_options(options_);
restore_options.best_efforts_recovery = false;
restore_options.listeners.clear();
// Avoid dangling/shared file descriptors, for reliable destroy
restore_options.sst_file_manager = nullptr;
std::vector<ColumnFamilyDescriptor> cf_descriptors;
// TODO(ajkr): `column_family_names_` is not safe to access here when
// `clear_column_family_one_in != 0`. But we can't easily switch to
@@ -1889,6 +1895,68 @@ Status StressTest::TestBackupRestore(
return s;
}
void InitializeMergeOperator(Options& options) {
if (FLAGS_use_full_merge_v1) {
options.merge_operator = MergeOperators::CreateDeprecatedPutOperator();
} else {
if (FLAGS_use_put_entity_one_in > 0) {
options.merge_operator = std::make_shared<DBStressWideMergeOperator>();
} else {
options.merge_operator = MergeOperators::CreatePutOperator();
}
}
}
Status StressTest::PrepareOptionsForRestoredDB(Options* options) {
assert(options);
// To avoid race with other threads' operations (e.g, SetOptions())
// on the same pointer sub-option (e.g, `std::shared_ptr<const FilterPolicy>
// filter_policy`) while having the same settings as `options_`, we create a
// new Options object from `options_`'s string to deep copy these pointer
// sub-options
Status s;
ConfigOptions config_opts;
std::string db_options_str;
s = GetStringFromDBOptions(config_opts, options_, &db_options_str);
if (!s.ok()) {
return s;
}
DBOptions db_options;
s = GetDBOptionsFromString(config_opts, Options(), db_options_str,
&db_options);
if (!s.ok()) {
return s;
}
std::string cf_options_str;
s = GetStringFromColumnFamilyOptions(config_opts, options_, &cf_options_str);
if (!s.ok()) {
return s;
}
ColumnFamilyOptions cf_options;
s = GetColumnFamilyOptionsFromString(config_opts, Options(), cf_options_str,
&cf_options);
if (!s.ok()) {
return s;
}
*options = Options(db_options, cf_options);
options->best_efforts_recovery = false;
options->listeners.clear();
// Avoid dangling/shared file descriptors, for reliable destroy
options->sst_file_manager = nullptr;
// GetColumnFamilyOptionsFromString does not create customized merge operator.
InitializeMergeOperator(*options);
if (FLAGS_user_timestamp_size > 0) {
// Check OPTIONS string loading can bootstrap the correct user comparator
// from object registry.
assert(options->comparator);
assert(options->comparator == test::BytewiseComparatorWithU64TsWrapper());
}
return Status::OK();
}
Status StressTest::TestApproximateSize(
ThreadState* thread, uint64_t iteration,
const std::vector<int>& rand_column_families,
@@ -3371,15 +3439,8 @@ void InitializeOptionsFromFlags(
options.memtable_factory.reset(new VectorRepFactory());
break;
}
if (FLAGS_use_full_merge_v1) {
options.merge_operator = MergeOperators::CreateDeprecatedPutOperator();
} else {
if (FLAGS_use_put_entity_one_in > 0) {
options.merge_operator = std::make_shared<DBStressWideMergeOperator>();
} else {
options.merge_operator = MergeOperators::CreatePutOperator();
}
}
InitializeMergeOperator(options);
if (FLAGS_enable_compaction_filter) {
options.compaction_filter_factory =
+2
View File
@@ -194,6 +194,8 @@ class StressTest {
const std::vector<int>& rand_column_families,
const std::vector<int64_t>& rand_keys);
virtual Status PrepareOptionsForRestoredDB(Options* options);
virtual Status TestCheckpoint(ThreadState* thread,
const std::vector<int>& rand_column_families,
const std::vector<int64_t>& rand_keys);
+31 -17
View File
@@ -612,7 +612,7 @@ class NonBatchedOpsStressTest : public StressTest {
}
Status s = NewTxn(wo, &txn);
if (!s.ok()) {
fprintf(stderr, "NewTxn: %s\n", s.ToString().c_str());
fprintf(stderr, "NewTxn error: %s\n", s.ToString().c_str());
thread->shared->SafeTerminate();
}
}
@@ -662,7 +662,8 @@ class NonBatchedOpsStressTest : public StressTest {
assert(false);
}
if (!s.ok()) {
fprintf(stderr, "Transaction put: %s\n", s.ToString().c_str());
fprintf(stderr, "Transaction put error: %s\n",
s.ToString().c_str());
thread->shared->SafeTerminate();
}
} else {
@@ -1696,11 +1697,14 @@ class NonBatchedOpsStressTest : public StressTest {
thread->shared->SetVerificationFailure();
if (iter->Valid()) {
fprintf(stderr,
"Expected state has key %s, iterator is at key %s\n",
"Verification failed. Expected state has key %s, iterator "
"is at key %s\n",
Slice(Key(j)).ToString(true).c_str(),
iter->key().ToString(true).c_str());
} else {
fprintf(stderr, "Expected state has key %s, iterator is invalid\n",
fprintf(stderr,
"Verification failed. Expected state has key %s, iterator "
"is invalid\n",
Slice(Key(j)).ToString(true).c_str());
}
fprintf(stderr, "Column family: %s, op_logs: %s\n",
@@ -1750,7 +1754,8 @@ class NonBatchedOpsStressTest : public StressTest {
if (static_cast<int64_t>(curr) <= last_key) {
thread->shared->SetVerificationFailure();
fprintf(stderr,
"TestIterateAgainstExpected found unexpectedly small key\n");
"TestIterateAgainstExpected failed: found unexpectedly small "
"key\n");
fprintf(stderr, "Column family: %s, op_logs: %s\n",
cfh->GetName().c_str(), op_logs.c_str());
fprintf(stderr, "Last op found key: %s, expected at least: %s\n",
@@ -1807,7 +1812,8 @@ class NonBatchedOpsStressTest : public StressTest {
if (last_key <= static_cast<int64_t>(curr)) {
thread->shared->SetVerificationFailure();
fprintf(stderr,
"TestIterateAgainstExpected found unexpectedly large key\n");
"TestIterateAgainstExpected failed: found unexpectedly large "
"key\n");
fprintf(stderr, "Column family: %s, op_logs: %s\n",
cfh->GetName().c_str(), op_logs.c_str());
fprintf(stderr, "Last op found key: %s, expected at most: %s\n",
@@ -1830,7 +1836,9 @@ class NonBatchedOpsStressTest : public StressTest {
op_logs += "P";
}
if (thread->rand.OneIn(2)) {
// Write-prepared and Write-unprepared do not support Refresh() yet.
if (!(FLAGS_use_txn && FLAGS_txn_write_policy != 0) &&
thread->rand.OneIn(2)) {
pre_read_expected_values.clear();
post_read_expected_values.clear();
// Refresh after forward/backward scan to allow higher chance of SV
@@ -1839,7 +1847,9 @@ class NonBatchedOpsStressTest : public StressTest {
pre_read_expected_values.push_back(
shared->Get(rand_column_family, i + lb));
}
iter->Refresh();
Status rs = iter->Refresh();
assert(rs.ok());
op_logs += "Refresh ";
for (int64_t i = 0; i < static_cast<int64_t>(expected_values_size); ++i) {
post_read_expected_values.push_back(
shared->Get(rand_column_family, i + lb));
@@ -1869,7 +1879,8 @@ class NonBatchedOpsStressTest : public StressTest {
if (static_cast<int64_t>(curr) < mid) {
thread->shared->SetVerificationFailure();
fprintf(stderr,
"TestIterateAgainstExpected found unexpectedly small key\n");
"TestIterateAgainstExpected failed: found unexpectedly small "
"key\n");
fprintf(stderr, "Column family: %s, op_logs: %s\n",
cfh->GetName().c_str(), op_logs.c_str());
fprintf(stderr, "Last op found key: %s, expected at least: %s\n",
@@ -1892,7 +1903,8 @@ class NonBatchedOpsStressTest : public StressTest {
if (mid < static_cast<int64_t>(curr)) {
thread->shared->SetVerificationFailure();
fprintf(stderr,
"TestIterateAgainstExpected found unexpectedly large key\n");
"TestIterateAgainstExpected failed: found unexpectedly large "
"key\n");
fprintf(stderr, "Column family: %s, op_logs: %s\n",
cfh->GetName().c_str(), op_logs.c_str());
fprintf(stderr, "Last op found key: %s, expected at most: %s\n",
@@ -1932,7 +1944,9 @@ class NonBatchedOpsStressTest : public StressTest {
post_read_expected_value)) {
// Fail fast to preserve the DB state.
thread->shared->SetVerificationFailure();
fprintf(stderr, "Iterator has key %s, but expected state does not.\n",
fprintf(stderr,
"Verification failed: iterator has key %s, but expected "
"state does not.\n",
iter->key().ToString(true).c_str());
fprintf(stderr, "Column family: %s, op_logs: %s\n",
cfh->GetName().c_str(), op_logs.c_str());
@@ -1950,9 +1964,9 @@ class NonBatchedOpsStressTest : public StressTest {
GetIntVal(iter->key().ToString(), &next);
if (next <= curr) {
thread->shared->SetVerificationFailure();
fprintf(
stderr,
"TestIterateAgainstExpected found unexpectedly small key\n");
fprintf(stderr,
"TestIterateAgainstExpected failed: found unexpectedly "
"small key\n");
fprintf(stderr, "Column family: %s, op_logs: %s\n",
cfh->GetName().c_str(), op_logs.c_str());
fprintf(stderr, "Last op found key: %s, expected at least: %s\n",
@@ -1975,9 +1989,9 @@ class NonBatchedOpsStressTest : public StressTest {
GetIntVal(iter->key().ToString(), &prev);
if (curr <= prev) {
thread->shared->SetVerificationFailure();
fprintf(
stderr,
"TestIterateAgainstExpected found unexpectedly large key\n");
fprintf(stderr,
"TestIterateAgainstExpected failed: found unexpectedly "
"large key\n");
fprintf(stderr, "Column family: %s, op_logs: %s\n",
cfh->GetName().c_str(), op_logs.c_str());
fprintf(stderr, "Last op found key: %s, expected at most: %s\n",
@@ -0,0 +1,287 @@
---
title: Java API Performance Improvements
layout: post
author: alanpaxton
category: blog
---
# RocksDB Java API Performance Improvements
Evolved Binary has been working on several aspects of how the Java API to RocksDB can be improved. Two aspects of this which are of particular importance are performance and the developer experience.
* We have built some synthetic benchmark code to determine which are the most efficient methods of transferring data between Java and C++.
* We have used the results of the synthetic benchmarking to guide plans for rationalising the API interfaces.
* We have made some opportunistic performance optimizations/fixes within the Java API which have already yielded noticable improvements.
## Synthetic JNI API Performance Benchmarks
The synthetic benchmark repository contains tests designed to isolate the Java to/from C++ interaction of a canonical data intensive Key/Value Store implemented in C++ with a Java (JNI) API layered on top.
JNI provides several mechanisms for allowing transfer of data between Java buffers and C++ buffers. These mechanisms are not trivial, because they require the JNI system to ensure that Java memory under the control of the JVM is not moved or garbage collected whilst it is being accessed outside the direct control of the JVM.
We set out to determine which of multiple options for transfer of data from `C++` to `Java` and vice-versa were the most efficient. We used the [Java Microbenchmark Harness](https://github.com/openjdk/jmh) to set up repeatable benchmarks to measure all the options.
We explore these and some other potential mechanisms in the detailed results (in our [Synthetic JNI performance repository](https://github.com/evolvedbinary/jni-benchmarks/blob/main/DataBenchmarks.md))
We summarise this work here:
### The Model
* In `C++` we represent the on-disk data as an in-memory map of `(key, value)`
pairs.
* For a fetch query, we expect the result to be a Java object with access to the
contents of the _value_. This may be a standard Java object which does the job
of data access (a `byte[]` or a `ByteBuffer`) or an object of our own devising
which holds references to the value in some form (a `FastBuffer` pointing to
`com.sun.unsafe.Unsafe` unsafe memory, for instance).
### Data Types
There are several potential data types for holding data for transfer, and they
are unsurprisingly quite connected underneath.
#### Byte Array
The simplest data container is a _raw_ array of bytes (`byte[]`).
There are 3 different mechanisms for transferring data between a `byte[]` and
C++
* At the C++ side, the method
[`JNIEnv.GetArrayCritical()`](https://docs.oracle.com/en/java/javase/13/docs/specs/jni/functions.html#getprimitivearraycritical)
allows access to a C++ pointer to the underlying array.
* The `JNIEnv` methods `GetByteArrayElements()` and `ReleaseByteArrayElements()`
fetch references/copies to and from the contents of a byte array, with less
concern for critical sections than the _critical_ methods, though they are
consequently more likely/certain to result in (extra) copies.
* The `JNIEnv` methods `GetByteArrayRegion()` and `SetByteArrayRegion()`
transfer raw C++ buffer data to and from the contents of a byte array. These
must ultimately do some data pinning for the duration of copies; the
mechanisms may be similar or different to the _critical_ operations, and
therefore performance may differ.
#### Byte Buffer
A `ByteBuffer` abstracts the contents of a collection of bytes, and was in fact
introduced to support a range of higher-performance I/O operations in some
circumstances.
There are 2 types of byte buffers in Java, _indirect_ and _direct_. Indirect
byte buffers are the standard, and the memory they use is on-heap as with all
usual Java objects. In contrast, direct byte buffers are used to wrap off-heap
memory which is accessible to direct network I/O. Either type of `ByteBuffer`
can be allocated at the Java side, using the `allocate()` and `allocateDirect()`
methods respectively.
Direct byte buffers can be created in C++ using the JNI method
[`JNIEnv.NewDirectByteBuffer()`](https://docs.oracle.com/en/java/javase/13/docs/specs/jni/functions.html#newdirectbytebuffer)
to wrap some native (C++) memory.
Direct byte buffers can be accessed in C++ using the
[`JNIEnv.GetDirectBufferAddress()`](https://docs.oracle.com/en/java/javase/13/docs/specs/jni/functions.html#GetDirectBufferAddress)
and measured using
[`JNIEnv.GetDirectBufferCapacity()`](https://docs.oracle.com/en/java/javase/13/docs/specs/jni/functions.html#GetDirectBufferCapacity)
#### Unsafe Memory
The call `com.sun.unsafe.Unsafe.allocateMemory()` returns a handle which is (of course) just a pointer to raw memory, and
can be used as such on the C++ side. We could turn it into a byte buffer on the
C++ side by calling `JNIEnv.NewDirectByteBuffer()`, or simply use it as a native
C++ buffer at the expected address, assuming we record or remember how much
space was allocated.
A custom `FastBuffer` class provides access to unsafe memory from the Java side.
#### Allocation
For these benchmarks, allocation has been excluded from the benchmark costs by
pre-allocating a quantity of buffers of the appropriate kind as part of the test
setup. Each run of the benchmark acquires an existing buffer from a pre-allocated
FIFO list, and returns it afterwards. A small test has
confirmed that the request and return cycle is of insignificant cost compared to
the benchmark API call.
### GetJNIBenchmark Performance
Benchmarks ran for a duration of order 6 hours on an otherwise unloaded VM,
the error bars are small and we can have strong confidence in the values
derived and plotted.
![Raw JNI Get small](/static/images/jni-get-benchmarks/fig_1024_1_none_nopoolbig.png)
Comparing all the benchmarks as the data size tends large, the conclusions we
can draw are:
- Indirect byte buffers add cost; they are effectively an overhead on plain
`byte[]` and the JNI-side only allows them to be accessed via their
encapsulated `byte[]`.
- `SetRegion` and `GetCritical` mechanisms for copying data into a `byte[]` are
of very comparable performance; presumably the behaviour behind the scenes of
`SetRegion` is very similar to that of declaring a critical region, doing a
`memcpy()` and releasing the critical region.
- `GetElements` methods for transferring data from C++ to Java are consistently
less efficient than `SetRegion` and `GetCritical`.
- Getting into a raw memory buffer, passed as an address (the `handle` of an
`Unsafe` or of a netty `ByteBuf`) is of similar cost to the more efficient
`byte[]` operations.
- Getting into a direct `nio.ByteBuffer` is of similar cost again; while the
ByteBuffer is passed over JNI as an ordinary Java object, JNI has a specific
method for getting hold of the address of the direct buffer, and using this, the
`get()` cost with a ByteBuffer is just that of the underlying C++ `memcpy()`.
At small(er) data sizes, we can see whether other factors are important.
![Raw JNI Get large](/static/images/jni-get-benchmarks/fig_1024_1_none_nopoolsmall.png)
- Indirect byte buffers are the most significant overhead here. Again, we can
conclude that this is due to pure overhead compared to `byte[]` operations.
- At the lowest data sizes, netty `ByteBuf`s and unsafe memory are marginally
more efficient than `byte[]`s or (slightly less efficient) direct
`nio.Bytebuffer`s. This may be explained by even the small cost of
calling the JNI model on the C++ side simply to acquire a
direct buffer address. The margins (nanoseconds) here are extremely small.
#### Post processing the results
Our benchmark model for post-processing is to transfer the results into a
`byte[]`. Where the result is already a `byte[]` this may seem like an unfair
extra cost, but the aim is to model the least cost processing step for any kind
of result.
- Copying into a `byte[]` using the bulk methods supported by `byte[]`,
`nio.ByteBuffer` have comparable performance.
- Accessing the contents of an `Unsafe` buffer using the supplied unsafe methods
is inefficient. The access is word by
word, in Java.
- Accessing the contents of a netty `ByteBuf` is similarly inefficient; again
the access is presumably word by word, using normal
Java mechanisms.
![Copy out JNI Get](/static/images/jni-get-benchmarks/fig_1024_1_copyout_nopoolbig.png)
### PutJNIBenchmark
We benchmarked `Put` methods in a similar synthetic fashion in less depth, but enough to confirm that the performance profile is similar/symmetrical. As with `get()` using `GetElements` is the least performant way of implementing transfers to/from Java objects in C++/JNI, and other JNI mechanisms do not differ greatly one from another.
## Lessons from Synthetic API
Performance analysis shows that for `get()`, fetching into allocated `byte[]` is
equally as efficient as any other mechanism, as long as JNI region methods are used
for the internal data transfer. Copying out or otherwise using the
result on the Java side is straightforward and efficient. Using `byte[]` avoids the manual memory
management required with direct `nio.ByteBuffer`s, which extra work does not
appear to provide any gain. A C++ implementation using the `GetRegion` JNI
method is probably to be preferred to using `GetCritical` because while their
performance is equal, `GetRegion` is a higher-level/simpler abstraction.
Vitally, whatever JNI transfer mechanism is chosen, the buffer allocation
mechanism and pattern is crucial to achieving good performance. We experimented
with making use of netty's pooled allocator part of the benchmark, and the
difference of `getIntoPooledNettyByteBuf`, using the allocator, compared to
`getIntoNettyByteBuf` using the same pre-allocate on setup as every other
benchmark, is significant.
Equally importantly, transfer of data to or from buffers should where possible
be done in bulk, using array copy or buffer copy mechanisms. Thought should
perhaps be given to supporting common transformations in the underlying C++
layer.
## API Recommendations
Of course there is some noise within the results. but we can agree:
* Don't make copies you don't need to make
* Don't allocate/deallocate when you can avoid it
Translating this into designing an efficient API, we want to:
* Support API methods that return results in buffers supplied by the client.
* Support `byte[]`-based APIs as the simplest way of getting data into a usable configuration for a broad range of Java use.
* Support direct `ByteBuffer`s as these can reduce copies when used as part of a chain of `ByteBuffer`-based operations. This sort of sophisticated streaming model is most likely to be used by clients where performance is important, and so we decide to support it.
* Support indirect `ByteBuffer`s for a combination of reasons:
* API consistency between direct and indirect buffers
* Simplicity of implementation, as we can wrap `byte[]`-oriented methods
* Continue to support methods which allocate return buffers per-call, as these are the easiest to use on initial encounter with the RocksDB API.
High performance Java interaction with RocksDB ultimately requires architectural decisions by the client
* Use more complex (client supplied buffer) API methods where performance matters
* Don't allocate/deallocate where you don't need to
* recycle your own buffers where this makes sense
* or make sure that you are supplying the ultimate destination buffer (your cache, or a target network buffer) as input to RocksDB `get()` and `put()` calls
We are currently implementing a number of extra methods consistently across the Java fetch and store APIs to RocksDB in the PR [Java API consistency between RocksDB.put() , .merge() and Transaction.put() , .merge()](https://github.com/facebook/rocksdb/pull/11019) according to these principles.
## Optimizations
### Reduce Copies within API Implementation
Having analysed JNI performance as described, we reviewed the core of RocksJNI for opportunities to improve the performance. We noticed one thing in particular; some of the `get()` methods of the Java API had not been updated to take advantage of the new [`PinnableSlice`](http://rocksdb.org/blog/2017/08/24/pinnableslice.html) methods.
Fixing this turned out to be a straightforward change, which has now been incorporated in the codebase [Improve Java API `get()` performance by reducing copies](https://github.com/facebook/rocksdb/pull/10970)
#### Performance Results
Using the JMH performances tests we updated as part of the above PR, we can see a small but consistent improvement in performance for all of the different get method variants which we have enhanced in the PR.
```sh
java -jar target/rocksdbjni-jmh-1.0-SNAPSHOT-benchmarks.jar -p keyCount=1000,50000 -p keySize=128 -p valueSize=1024,16384 -p columnFamilyTestType="1_column_family" GetBenchmarks.get GetBenchmarks.preallocatedByteBufferGet GetBenchmarks.preallocatedGet
```
The y-axis shows `ops/sec` in throughput, so higher is better.
![](/static/images/jni-get-benchmarks/optimization-graph.png)
### Analysis
Before the invention of the Pinnable Slice the simplest RocksDB (native) API `Get()` looked like this:
```cpp
Status Get(const ReadOptions& options,
ColumnFamilyHandle* column_family, const Slice& key,
std::string* value)
```
After PinnableSlice the correct way for new code to implement a `get()` is like this
```cpp
Status Get(const ReadOptions& options,
ColumnFamilyHandle* column_family, const Slice& key,
PinnableSlice* value)
```
But of course RocksDB has to support legacy code, so there is an `inline` method in `db.h` which re-implements the former using the latter.
And RocksJava API implementation seamlessly continues to use the `std::string`-based `get()`
Let's examine what happens when get() is called from Java
```cpp
jint Java_org_rocksdb_RocksDB_get__JJ_3BII_3BIIJ(
JNIEnv* env, jobject, jlong jdb_handle, jlong jropt_handle, jbyteArray jkey,
jint jkey_off, jint jkey_len, jbyteArray jval, jint jval_off, jint jval_len,
jlong jcf_handle)
```
1. Create an empty `std::string value`
2. Call `DB::Get()` using the `std::string` variant
3. Copy the resultant `std::string` into Java, using the JNI `SetByteArrayRegion()` method
So stage (3) costs us a copy into Java. It's mostly unavoidable that there will be at least the one copy from a C++ buffer into a Java buffer.
But what does stage 2 do ?
* Create a `PinnableSlice(std::string&)` which uses the value as the slice's backing buffer.
* Call `DB::Get()` using the PinnableSlice variant
* Work out if the slice has pinned data, in which case copy the pinned data into value and release it.
* ..or, if the slice has not pinned data, it is already in value (because we tried, but couldn't pin anything).
So stage (2) costs us a copy into a `std::string`. But! It's just a naive `std::string` that we have copied a large buffer into. And in RocksDB, the buffer is or can be large, so an extra copy something we need to worry about.
Luckily this is easy to fix. In the Java API (JNI) implementation:
1. Create a PinnableSlice() which uses its own default backing buffer.
2. Call `DB::Get()` using the `PinnableSlice` variant of the RocksDB API
3. Copy the data indicated by the `PinnableSlice` straight into the Java output buffer using the JNI `SetByteArrayRegion()` method, then release the slice.
4. Work out if the slice has successfully pinned data, in which case copy the pinned data straight into the Java output buffer using the JNI `SetByteArrayRegion()` method, then release the pin.
5. ..or, if the slice has not pinned data, it is in the pinnable slice's default backing buffer. All that is left, is to copy it straight into the Java output buffer using the JNI SetByteArrayRegion() method.
In the case where the `PinnableSlice` has succesfully pinned the data, this saves us the intermediate copy to the `std::string`. In the case where it hasn't, we still have the extra copy so the observed performance improvement depends on when the data can be pinned. Luckily, our benchmarking suggests that the pin is happening in a significant number of cases.
On discussion with the RocksDB core team we understand that the core `PinnableSlice` optimization is most likely to succeed when pages are loaded from the block cache, rather than when they are in `memtable`. And it might be possible to successfully pin in the `memtable` as well, with some extra coding effort. This would likely improve the results for these benchmarks.
Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

+8 -8
View File
@@ -57,10 +57,10 @@ static Env* GetTestEnv() {
static std::shared_ptr<Env> env_guard;
static Env* custom_env = nullptr;
if (custom_env == nullptr) {
const char* uri = getenv("TEST_ENV_URI");
if (uri != nullptr) {
EXPECT_OK(Env::CreateFromUri(ConfigOptions(), uri, "", &custom_env,
&env_guard));
const char* env_uri = getenv("TEST_ENV_URI");
if (env_uri != nullptr) {
EXPECT_OK(Env::CreateFromUri(ConfigOptions(), env_uri, /*fs_uri=*/"",
&custom_env, &env_guard));
}
}
EXPECT_NE(custom_env, nullptr);
@@ -71,10 +71,10 @@ static Env* GetTestFS() {
static std::shared_ptr<Env> fs_env_guard;
static Env* fs_env = nullptr;
if (fs_env == nullptr) {
const char* uri = getenv("TEST_FS_URI");
if (uri != nullptr) {
EXPECT_OK(
Env::CreateFromUri(ConfigOptions(), uri, "", &fs_env, &fs_env_guard));
const char* fs_uri = getenv("TEST_FS_URI");
if (fs_uri != nullptr) {
EXPECT_OK(Env::CreateFromUri(ConfigOptions(), /*env_uri=*/"", fs_uri,
&fs_env, &fs_env_guard));
}
}
EXPECT_NE(fs_env, nullptr);
+7
View File
@@ -2906,6 +2906,13 @@ TEST_F(CreateEnvTest, CreateEncryptedFileSystem) {
std::string base_opts =
std::string("provider=1://test; id=") + EncryptedFileSystem::kClassName();
// Rewrite the default FileSystem URI if the "TEST_FS_URI" environment
// variable is set. This is useful to test customer encryption plugins.
const char* uri = getenv("TEST_FS_URI");
if (uri != nullptr) {
base_opts = uri;
}
// The EncryptedFileSystem requires a "provider" option.
ASSERT_NOK(FileSystem::CreateFromString(
config_options_, EncryptedFileSystem::kClassName(), &fs));
+2 -1
View File
@@ -29,7 +29,8 @@
// For non linux platform, the following macros are used only as place
// holder.
#if !(defined OS_LINUX) && !(defined CYGWIN) && !(defined OS_AIX)
#if !(defined OS_LINUX) && !(defined OS_FREEBSD) && !(defined CYGWIN) && \
!(defined OS_AIX)
#define POSIX_FADV_NORMAL 0 /* [MC1] no further special treatment */
#define POSIX_FADV_RANDOM 1 /* [MC1] expect random page refs */
#define POSIX_FADV_SEQUENTIAL 2 /* [MC1] expect sequential page refs */
+1
View File
@@ -21,6 +21,7 @@
#ifdef __SSE4_2__
#ifdef _WIN32
#include <intrin.h>
#define _rdtsc() __rdtsc()
#else
#include <x86intrin.h>
#endif
+2
View File
@@ -144,6 +144,8 @@ int main() {
options.create_if_missing = true;
// Disable RocksDB background compaction.
options.compaction_style = ROCKSDB_NAMESPACE::kCompactionStyleNone;
// Small write buffer size for generating more sst files in level 0.
options.write_buffer_size = 4 << 20;
// Small slowdown and stop trigger for experimental purpose.
options.level0_slowdown_writes_trigger = 3;
options.level0_stop_writes_trigger = 5;
+24
View File
@@ -375,6 +375,14 @@ class Cache {
// Returns the helper for the specified entry.
virtual const CacheItemHelper* GetCacheItemHelper(Handle* handle) const = 0;
virtual Status GetSecondaryCacheCapacity(size_t& /*size*/) const {
return Status::NotSupported();
}
virtual Status GetSecondaryCachePinnedUsage(size_t& /*size*/) const {
return Status::NotSupported();
}
// Call this on shutdown if you want to speed it up. Cache will disown
// any underlying data and will not free it on delete. This call will leak
// memory - call this only if you're shutting down the process.
@@ -595,6 +603,14 @@ class CacheWrapper : public Cache {
return target_->HasStrictCapacityLimit();
}
size_t GetOccupancyCount() const override {
return target_->GetOccupancyCount();
}
size_t GetTableAddressCount() const override {
return target_->GetTableAddressCount();
}
size_t GetCapacity() const override { return target_->GetCapacity(); }
size_t GetUsage() const override { return target_->GetUsage(); }
@@ -630,6 +646,14 @@ class CacheWrapper : public Cache {
target_->WaitAll(async_handles, count);
}
uint32_t GetHashSeed() const override { return target_->GetHashSeed(); }
void ReportProblems(const std::shared_ptr<Logger>& info_log) const override {
target_->ReportProblems(info_log);
}
const std::shared_ptr<Cache>& GetTarget() { return target_; }
protected:
std::shared_ptr<Cache> target_;
};
+1 -1
View File
@@ -714,7 +714,7 @@ struct AdvancedColumnFamilyOptions {
//
// Dynamically changeable through SetOptions() API
std::vector<int> max_bytes_for_level_multiplier_additional =
std::vector<int>(num_levels, 1);
std::vector<int>(static_cast<size_t>(num_levels), 1);
// We try to limit number of bytes in one compaction to be lower than this
// threshold. But it's not guaranteed.
+46
View File
@@ -78,6 +78,7 @@ typedef struct rocksdb_lru_cache_options_t rocksdb_lru_cache_options_t;
typedef struct rocksdb_hyper_clock_cache_options_t
rocksdb_hyper_clock_cache_options_t;
typedef struct rocksdb_cache_t rocksdb_cache_t;
typedef struct rocksdb_write_buffer_manager_t rocksdb_write_buffer_manager_t;
typedef struct rocksdb_compactionfilter_t rocksdb_compactionfilter_t;
typedef struct rocksdb_compactionfiltercontext_t
rocksdb_compactionfiltercontext_t;
@@ -1060,6 +1061,8 @@ rocksdb_block_based_options_set_pin_top_level_index_and_filter(
rocksdb_block_based_table_options_t*, unsigned char);
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_block_based_table_factory(
rocksdb_options_t* opt, rocksdb_block_based_table_options_t* table_options);
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_write_buffer_manager(
rocksdb_options_t* opt, rocksdb_write_buffer_manager_t* wbm);
/* Cuckoo table options */
@@ -1143,6 +1146,8 @@ extern ROCKSDB_LIBRARY_API unsigned char rocksdb_options_get_paranoid_checks(
rocksdb_options_t*);
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_db_paths(
rocksdb_options_t*, const rocksdb_dbpath_t** path_values, size_t num_paths);
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_cf_paths(
rocksdb_options_t*, const rocksdb_dbpath_t** path_values, size_t num_paths);
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_env(rocksdb_options_t*,
rocksdb_env_t*);
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_info_log(rocksdb_options_t*,
@@ -1256,6 +1261,10 @@ rocksdb_options_set_max_bytes_for_level_multiplier_additional(
rocksdb_options_t*, int* level_values, size_t num_levels);
extern ROCKSDB_LIBRARY_API void rocksdb_options_enable_statistics(
rocksdb_options_t*);
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_periodic_compaction_seconds(
rocksdb_options_t*, uint64_t);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_options_get_periodic_compaction_seconds(rocksdb_options_t*);
enum {
rocksdb_statistics_level_disable_all = 0,
@@ -1668,6 +1677,10 @@ extern ROCKSDB_LIBRARY_API int rocksdb_options_get_wal_compression(
/* RateLimiter */
extern ROCKSDB_LIBRARY_API rocksdb_ratelimiter_t* rocksdb_ratelimiter_create(
int64_t rate_bytes_per_sec, int64_t refill_period_us, int32_t fairness);
extern ROCKSDB_LIBRARY_API rocksdb_ratelimiter_t*
rocksdb_ratelimiter_create_auto_tuned(int64_t rate_bytes_per_sec,
int64_t refill_period_us,
int32_t fairness);
extern ROCKSDB_LIBRARY_API void rocksdb_ratelimiter_destroy(
rocksdb_ratelimiter_t*);
@@ -2067,6 +2080,36 @@ rocksdb_cache_get_table_address_count(const rocksdb_cache_t* cache);
extern ROCKSDB_LIBRARY_API size_t
rocksdb_cache_get_occupancy_count(const rocksdb_cache_t* cache);
/* WriteBufferManager */
extern ROCKSDB_LIBRARY_API rocksdb_write_buffer_manager_t*
rocksdb_write_buffer_manager_create(size_t buffer_size, bool allow_stall);
extern ROCKSDB_LIBRARY_API rocksdb_write_buffer_manager_t*
rocksdb_write_buffer_manager_create_with_cache(size_t buffer_size,
const rocksdb_cache_t* cache,
bool allow_stall);
extern ROCKSDB_LIBRARY_API void rocksdb_write_buffer_manager_destroy(
rocksdb_write_buffer_manager_t* wbm);
extern ROCKSDB_LIBRARY_API bool rocksdb_write_buffer_manager_enabled(
rocksdb_write_buffer_manager_t* wbm);
extern ROCKSDB_LIBRARY_API bool rocksdb_write_buffer_manager_cost_to_cache(
rocksdb_write_buffer_manager_t* wbm);
extern ROCKSDB_LIBRARY_API size_t
rocksdb_write_buffer_manager_memory_usage(rocksdb_write_buffer_manager_t* wbm);
extern ROCKSDB_LIBRARY_API size_t
rocksdb_write_buffer_manager_mutable_memtable_memory_usage(
rocksdb_write_buffer_manager_t* wbm);
extern ROCKSDB_LIBRARY_API size_t
rocksdb_write_buffer_manager_dummy_entries_in_cache_usage(
rocksdb_write_buffer_manager_t* wbm);
extern ROCKSDB_LIBRARY_API size_t
rocksdb_write_buffer_manager_buffer_size(rocksdb_write_buffer_manager_t* wbm);
extern ROCKSDB_LIBRARY_API void rocksdb_write_buffer_manager_set_buffer_size(
rocksdb_write_buffer_manager_t* wbm, size_t new_size);
extern ROCKSDB_LIBRARY_API void rocksdb_write_buffer_manager_set_allow_stall(
rocksdb_write_buffer_manager_t* wbm, bool new_allow_stall);
/* HyperClockCache */
extern ROCKSDB_LIBRARY_API rocksdb_hyper_clock_cache_options_t*
@@ -2427,6 +2470,9 @@ extern ROCKSDB_LIBRARY_API char*
rocksdb_sst_file_metadata_get_relative_filename(
rocksdb_sst_file_metadata_t* file_meta);
extern ROCKSDB_LIBRARY_API char* rocksdb_sst_file_metadata_get_directory(
rocksdb_sst_file_metadata_t* file_meta);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_sst_file_metadata_get_size(rocksdb_sst_file_metadata_t* file_meta);
+4
View File
@@ -499,6 +499,10 @@ enum TieredAdmissionPolicy {
// allocations costed to the block cache, will be distributed
// proportionally across both the primary and secondary.
struct TieredCacheOptions {
// This should point to an instance of either LRUCacheOptions or
// HyperClockCacheOptions, depending on the cache_type. In either
// case, the capacity and secondary_cache fields in those options
// should not be set. If set, they will be ignored by NewTieredCache.
ShardedCacheOptions* cache_opts = nullptr;
PrimaryCacheType cache_type = PrimaryCacheType::kCacheTypeLRU;
TieredAdmissionPolicy adm_policy = TieredAdmissionPolicy::kAdmPolicyAuto;
+35 -2
View File
@@ -435,6 +435,10 @@ class DB {
virtual Status PutEntity(const WriteOptions& options,
ColumnFamilyHandle* column_family, const Slice& key,
const WideColumns& columns);
// Split and store wide column entities in multiple column families (a.k.a.
// AttributeGroups)
virtual Status PutEntity(const WriteOptions& options, const Slice& key,
const AttributeGroups& attribute_groups);
// Remove the database entry (if any) for "key". Returns OK on
// success, and a non-OK status on error. It is not an error if "key"
@@ -505,6 +509,15 @@ class DB {
ColumnFamilyHandle* column_family,
const Slice& begin_key, const Slice& end_key,
const Slice& ts);
virtual Status DeleteRange(const WriteOptions& options,
const Slice& begin_key, const Slice& end_key) {
return DeleteRange(options, DefaultColumnFamily(), begin_key, end_key);
}
virtual Status DeleteRange(const WriteOptions& options,
const Slice& begin_key, const Slice& end_key,
const Slice& ts) {
return DeleteRange(options, DefaultColumnFamily(), begin_key, end_key, ts);
}
// Merge the database entry for "key" with "value". Returns OK on success,
// and a non-OK status on error. The semantics of this operation is
@@ -602,6 +615,16 @@ class DB {
return Status::NotSupported("GetEntity not supported");
}
// Returns logically grouped wide-column entities per column family (a.k.a.
// attribute groups) for a single key. PinnableAttributeGroups is a vector of
// PinnableAttributeGroup. Each PinnableAttributeGroup will have
// ColumnFamilyHandle* as input, and Status and PinnableWideColumns as output.
virtual Status GetEntity(const ReadOptions& /* options */,
const Slice& /* key */,
PinnableAttributeGroups* /* result */) {
return Status::NotSupported("GetEntity not supported");
}
// Populates the `merge_operands` array with all the merge operands in the DB
// for `key`. The `merge_operands` array will be populated in the order of
// insertion. The number of entries populated in `merge_operands` will be
@@ -1636,7 +1659,17 @@ class DB {
virtual Status GetFullHistoryTsLow(ColumnFamilyHandle* column_family,
std::string* ts_low) = 0;
// Allow compactions to delete obsolete files.
// Enable deleting obsolete files.
// Usually users should only need to call this if they have previously called
// `DisableFileDeletions`.
// File deletions disabling and enabling is not controlled by a binary flag,
// instead it's represented as a counter to allow different callers to
// independently disable file deletion. Disabling file deletion can be
// critical for operations like making a backup. So the counter implementation
// makes the file deletion disabled as long as there is one caller requesting
// so, and only when every caller agrees to re-enable file deletion, it will
// be enabled. So be careful when calling this function with force = true as
// explained below.
// If force == true, the call to EnableFileDeletions() will guarantee that
// file deletions are enabled after the call, even if DisableFileDeletions()
// was called multiple times before.
@@ -1645,7 +1678,7 @@ class DB {
// enabling the two methods to be called by two threads concurrently without
// synchronization -- i.e., file deletions will be enabled only after both
// threads call EnableFileDeletions()
virtual Status EnableFileDeletions(bool force = true) = 0;
virtual Status EnableFileDeletions(bool force) = 0;
// Retrieves the creation time of the oldest file in the DB.
// This API only works if max_open_files = -1, if it is not then
+2 -2
View File
@@ -280,7 +280,7 @@ class Env : public Customizable {
const EnvOptions& options);
// Open `fname` for random read and write, if file doesn't exist the file
// will be created. On success, stores a pointer to the new file in
// will not be created. On success, stores a pointer to the new file in
// *result and returns OK. On failure returns non-OK.
//
// The returned file will only be accessed by one thread at a time.
@@ -1001,7 +1001,7 @@ class WritableFile {
/*
* Get the size of valid data in the file.
*/
virtual uint64_t GetFileSize() { return 0; };
virtual uint64_t GetFileSize() { return 0; }
/*
* Get and set the default pre-allocation block size for writes to
Loaded 100 of 182 files, more files were not shown because too many files have changed in this diff. Show more