Compare commits

...

199 Commits

Author SHA1 Message Date
Hui Xiao 5fbc1cd5bc Update for 10.9.1 patch 2025-12-11 19:04:28 -08:00
Hui Xiao 60f24d394d Fix resumable compaction to prevent resumption at truncated range deletion boundaries (#14184)
Summary:
**Context/Summary:**

Truncated range deletion in input files can be output by CompactionIterator with type kMaxValid instead of kTypeRangeDeletion, to satisfy ordering requirement between the truncated range deletion start key and a file's point keys. There was a plan to skip such key in https://github.com/facebook/rocksdb/pull/14122 but blockers remain to fulfill the plan.

Resumable compaction is not able to handle resumption from range deletion well at this point and should consider kMaxValid type same as kTypeRangeDeletion for resumption. Previously, it didn't and mistakenly allow resumption from a delete range. That led to an assertion failure, complaining about lacking information to update file boundaries in the presence of range deletion needed during cutting an output file, after the compaction resumes from that delete range and happens to cut the output file shortly after without any point keys in between.

```
frame https://github.com/facebook/rocksdb/issues/9: 0x00007f4f4743bc93 libc.so.6`__GI___assert_fail(assertion="meta.smallest.size() > 0", file="db/compaction/compaction_outputs.cc", line=530, function="rocksdb::Status rocksdb::CompactionOutputs::AddRangeDels(rocksdb::CompactionRangeDelAggregator&, const rocksdb::Slice*, const rocksdb::Slice*, rocksdb::CompactionIterationStats&, bool, const rocksdb::InternalKeyComparator&, rocksdb::SequenceNumber, std::pair<long unsigned int, long unsigned int>, const rocksdb::Slice&, const string&)") at assert.c:101:3
frame https://github.com/facebook/rocksdb/issues/10: 0x00007f4f4808c68c librocksdb.so.10.9`rocksdb::CompactionOutputs::AddRangeDels(this=0x00007f4f0c27e1a0, range_del_agg=0x00007f4f0c21ecc0, comp_start_user_key=0x0000000000000000, comp_end_user_key=0x0000000000000000, range_del_out_stats=0x00007f4f0dffa140, bottommost_level=false, icmp=0x00007f4ef4c93040, earliest_snapshot=13108729, keep_seqno_range=<unavailable>, next_table_min_key=0x00007f4ef4c8f540, full_history_ts_low="") at compaction_outputs.cc:530:7
frame https://github.com/facebook/rocksdb/issues/11: 0x00007f4f480480dd librocksdb.so.10.9`rocksdb::CompactionJob::FinishCompactionOutputFile(this=0x00007f4f0dffb890, input_status=<unavailable>, prev_table_last_internal_key=0x00007f4f0dffa650, next_table_min_key=0x00007f4ef4c8f540, comp_start_user_key=0x0000000000000000, comp_end_user_key=0x0000000000000000, c_iter=0x00007f4ef4c8f400, sub_compact=0x00007f4f0c27e000, outputs=0x00007f4f0c27e1a0) at compaction_job.cc:1917:31
```

This PR simply prevents  MaxValid from being a resumption point like regular range deletion - see commit 842d66eb18ea67e965d6acb1fce12c18eeb778d2

Besides that, the PR also improves the testing, variable naming, logging in resumable compaction codes that were needed to debug this assertion failure - see commit https://github.com/facebook/rocksdb/pull/14184/commits/aecd4e7f971f6dd4df672d9e5f1409fe4747c561. These improvements are covered by existing tests.

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

Test Plan:
- The stress initially surfaced the error. Using the exact same LSM shapes and files that were used in stress test but in a unit test, I'm able to get a deterministic repro and confirmed the fix resolves the error.  This is the repro test https://github.com/hx235/rocksdb/commit/1075936e693c68c960761855900c53f5b894f57a
```
./compaction_service_test --gtest_filter=ResumableCompactionServiceTest.CompactSpecificFilesFromExistingDBWithCancelAndResume
# Before fix
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from ResumableCompactionServiceTest
[ RUN      ] ResumableCompactionServiceTest.CompactSpecificFilesFromExistingDBWithCancelAndResume
compaction_service_test: db/compaction/compaction_outputs.cc:530: rocksdb::Status rocksdb::CompactionOutputs::AddRangeDels(rocksdb::CompactionRangeDelAggregator&, const rocksdb::Slice*, const rocksdb::Slice*, rocksdb::CompactionIterationStats&, bool, const rocksdb::InternalKeyComparator&, rocksdb::SequenceNumber, std::pair<long unsigned int, long unsigned int>, const rocksdb::Slice&, const string&): Assertion `meta.smallest.size() > 0' failed.
Received signal 6 (Aborted)
Invoking GDB for stack trace...
[New LWP 2621610]
[New LWP 2621611]
[New LWP 2621612]
[New LWP 2621613]
[New LWP 2621614]
[New LWP 2621630]
[New LWP 2621631]

# After fix
Note: Google Test filter = ResumableCompactionServiceTest.CompactSpecificFilesFromExistingDBWithCancelAndResume
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from ResumableCompactionServiceTest
[ RUN      ] ResumableCompactionServiceTest.CompactSpecificFilesFromExistingDBWithCancelAndResume
[       OK ] ResumableCompactionServiceTest.CompactSpecificFilesFromExistingDBWithCancelAndResume (4722 ms)
[----------] 1 test from ResumableCompactionServiceTest (4722 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (4722 ms total)
[  PASSED  ] 1 test.

```
- Follow-up: I tried a couple time to coerce the truncated range delete from scratch in the unit test but failed doing so. Considering kMaxValid may not be outputted by compaction iterator anymore after https://github.com/facebook/rocksdb/pull/14122/files gets landed again (and obsolete the bug) ADN the simple nature of this fix 842d66eb18ea67e965d6acb1fce12c18eeb778d2 AND the worst case of such fix going wrong is just less resumption, I decided to leave writing a unit test to coerce truncated ranged deletion from scratch a follow-up. Maybe I will draw inspiration from https://github.com/facebook/rocksdb/pull/14122/files.

Reviewed By: jaykorean

Differential Revision: D88912663

Pulled By: hx235

fbshipit-source-id: 80a01135684c8fea659650faaa00c2dc452c482a
2025-12-11 18:39:27 -08:00
Xingbo Wang 268a719a30 Fix missing const for arg of OptionChangeMigration (#14173)
Summary:
Fix missing const for arg of OptionChangeMigration

We switched from std::string to std::string & for API OptionChangeMigration, which caused const qualifier to be lost at call site, which causes compilation failure.

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

Test Plan: Unit test

Reviewed By: pdillinger

Differential Revision: D88431457

Pulled By: xingbowang

fbshipit-source-id: a705f3b80cc5ff56dab73aa6a31c940798d8df45
2025-12-04 19:06:26 -08:00
Xingbo Wang 9791103976 Revert #14122 "Fix a bug where compaction ..." (#14170)
Summary:
Revert "Fix a bug where compaction with range deletion can persist kTypeMaxValid in file metadata (https://github.com/facebook/rocksdb/issues/14122)"

Add a new unit test to capture the situation found by stress test

This reverts commit 8c7c8b8dab.

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

Test Plan: Unit Test

Reviewed By: anand1976

Differential Revision: D88395956

Pulled By: xingbowang

fbshipit-source-id: 226649dc79a86010ad326ffb2eae35109dc96bc4
2025-12-04 13:30:39 -08:00
Peter Dillinger 4982688c21 Fix AutoSkipCompressorWrapper with new logic (#14150)
Summary:
... from https://github.com/facebook/rocksdb/issues/14140. The assertion in the default implementation of CompressorWrapper::MaybeCloneSpecialized() could fail because this wrapper wasn't overriding it when it should. (See the NOTE on that implementation.)

Because this release already has a breaking modification to the Compressor API (adding Clone()), I took this opportunity to add 'const' to MaybeCloneSpecialized(). Also marked some compression classes as 'final' that could be marked as such.

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

Test Plan: unit test expanded to cover this case (verified failing before). Audited the rest of our CompressorWrappers.

Reviewed By: archang19

Differential Revision: D87793987

Pulled By: pdillinger

fbshipit-source-id: 61c4469b84e4a47451a9942df09277faeeccfe63
2025-11-24 13:40:36 -08:00
Xingbo Wang ef5a37aca8 Cut 10.9.0 branch.
Summary:
  Cut 10.9.0 branch.

Test Plan:

Reviewers:

Subscribers:

Tasks:

Tags:
2025-11-24 12:53:27 -08:00
Peter Dillinger 35148aca91 Improve distinct compression for index and data blocks (#14140)
Summary:
This change enables a custom CompressionManager / Compressor to adopt custom handling for data and index blocks. In particular, index blocks for format_version >= 4 use a distinct variant of the block format. Thus, a potentially format-aware compression algorithm such as OpenZL should be told which kind of block we are compressing. (And previously I avoided passing block type in CompressBlock for efficient handling of things like dictionaries but also avoiding checks on every CompressBlock call.)

Most of the change is in BlockBasedTableBuilder to call MaybeCloneSpecialized for both kDataBlock and for kIndexBlock. But I also needed some small tweaks/additions to the public API also:
* Require a Clone() function from Compressors, to support proper implementations of MaybeCloneSpecialized() in wrapper Compressors.
* Assert that the default implementation of CompressorWrapper::MaybeCloneSpecialized() is only used in allowable cases.
* Convenience function Compressor::CloneMaybeSpecialized()

This also fixes a serious bug/oversight in ManagedPtr for (ManagedWorkingArea) that somehow wasn't showing up before. It probably doesn't need a release note because CompressionManager stuff is still considered experimental.

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

Test Plan: Greatly expanded DBCompressionTest.CompressionManagerWrapper to make sure the distinction between data blocks and index blocks is properly communicated to a custom CompressionManager/Compressor. The test includes processing the expected structure of data and index blocks, to serve as a tested example for structure-aware compressors.

Reviewed By: hx235

Differential Revision: D87600019

Pulled By: pdillinger

fbshipit-source-id: 252ef78910073a0e45f2c81dd45ac87ff8a41fc6
2025-11-21 16:34:49 -08:00
Changyu Bi 8c7c8b8dab Fix a bug where compaction with range deletion can persist kTypeMaxValid in file metadata (#14122)
Summary:
Range deletion start keys are considered during compaction for cutting output files. Due to some ordering requirement (see comment above InsertNextValidRangeTombstoneAtLevel()) between truncated range deletion start key and a file's point keys, there was logic in https://github.com/facebook/rocksdb/blob/f6c9c3bf1cf05096e8ff8c03ded60c1e199edbb7/db/range_del_aggregator.cc#L39 that changes the value type to be kTypeMaxValid. However, kTypeMaxValid is not supposed to be persisted per https://github.com/facebook/rocksdb/blob/f6c9c3bf1cf05096e8ff8c03ded60c1e199edbb7/db/dbformat.h#L75-L76. This can cause forward compatibility issues reported in https://github.com/facebook/rocksdb/issues/14101. This PR fixes this issue by removing the logic that sets kTypeMaxValid and always skip truncated range deletion start key in CompactionMergingIterator.

For existing SST files, we want to avoid using this kTypeMaxValid, so this PR also introduces a new placeholder value type. This allows us to re-strengthen the relevant value type checks (IsExtendedValueType()) that was loosen for kTypeMaxValid.

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

Test Plan:
- a unit test that persists kTypeMaxValid before this fix
- crash test with frequent range deletion: `python3 ./tools/db_crashtest.py blackbox --delrangepercent=11 --readpercent=35`
- Generate SST files with 0x1A as value type (kTypeMaxValid before this change) in file metadata. Run ldb with the strengthened check in IsExtendedValueType() to dump the MANIFEST. It failed to parse MANIFEST as expected before this PR and succeeds after this PR.
```
Error in processing file /tmp/rocksdbtest-543376/db_range_del_test_2549357_6547198162080866792/MANIFEST-000005 Corruption: VersionEdit: new-file4 entry  The file /tmp/rocksdbtest-543376/db_range_del_test_2549357_6547198162080866792/MANIFEST-000005 may be corrupted.
```

Reviewed By: pdillinger

Differential Revision: D87016541

Pulled By: cbi42

fbshipit-source-id: 9957a095db2cd9947463b403f352bd9a1fd70a76
2025-11-21 14:18:38 -08:00
Jay Huh 2f583aed8f Move prepared_iter size assertion after cleanup (#14144)
Summary:
Fixing crash test failure caused by `prepared_iters_.size() == 0`

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

Test Plan:
```
python3 -u tools/db_crashtest.py --stress_cmd=./db_stress --cleanup_cmd='' --simple blackbox
```

Reviewed By: krhancoc

Differential Revision: D87656914

Pulled By: jaykorean

fbshipit-source-id: 9ef7cf4ea5d34fe9dee6219b32323e91a2ea3e5f
2025-11-21 13:30:31 -08:00
Jay Huh c4bbad4dfe Update format-diff script to add text to new files (#14143)
Summary:
Fixing internal validator failure

```
Every project specific source file must contain a doc block with an appropriate copyright header. Unrelated files must be listed as exceptions in the Copyright Headers Exceptions page in the repo dashboard.
A copyright header clearly indicates that the code is owned by Meta. Every open source file must start with a comment containing "Meta Platforms, Inc. and affiliates"
https://github.com/facebook/rocksdb/blob/main/buckifier/targets_cfg.py:
The first 16 lines of 'buckifier/targets_cfg.py' do not contain the patterns:
	(Meta Platforms, Inc. and affiliates)|(Facebook, Inc(\.|,)? and its affiliates)|([0-9]{4}-present(\.|,)? Facebook)|([0-9]{4}(\.|,)? Facebook)
```

While fixing the text to pass the linter, I took the opportunity to modify `format-diff.sh` script to add the copyright header automatically if missing in new files.

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

Test Plan:
```
$> make format
```
**new python file**
```
build_tools/format-diff.sh
Checking format of uncommitted changes...
Checking for copyright headers in new files...
Added copyright header to build_tools/test.py
Copyright headers were added to new files.
Nothing needs to be reformatted!
```
**new header file**
```
build_tools/format-diff.sh
Checking format of uncommitted changes...
Checking for copyright headers in new files...
Added copyright header to db/db_impl/db_impl_jewoongh.h
Copyright headers were added to new files.
Nothing needs to be reformatted!
```

Reviewed By: hx235

Differential Revision: D87653124

Pulled By: jaykorean

fbshipit-source-id: 164322cfcd2c162bb3b41bb8f3bafefa3f20b695
2025-11-21 11:32:10 -08:00
Hui Xiao dc33c1adaf Include verify_output_flags to check resumable compaction compatibility (#14139)
Summary:
**Context/Summary:**

.. because verify_output_flags contains information of usage of paranoid_file_check that is currently not yet compatible with resumable remote compaction

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

Test Plan: Existing tests

Reviewed By: jaykorean

Differential Revision: D87582635

Pulled By: hx235

fbshipit-source-id: ef21223da53a0696fa3ca9b1617c2c1ee2e19878
2025-11-21 11:32:00 -08:00
Hui Xiao c76cacc696 Fix overflow in MultiplyCheckOverflow() due to std::numeric_limits<uint64_t>::max()'s promotion to double (#14132)
Summary:
**Context/Summary:**
Due to double's 53-bit mantissa limitation, large uint64_t values lose precision when converted to double. Value equals to or smaller than UINT64_MAX (but greater than 2^64 - 1024) round up to 2^64 since rounding up results in less error than rounding down, which exceeds UINT64_MAX. `std::numeric_limits<uint64_t>::max() / op1 < op2` won't catch those cases. Casting such out-of-range doubles back to uint64_t causes undefined behavior. T

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14132
UndefinedBehaviorSanitizer: undefined-behavior options/cf_options.cc:1087:32 in
```
before the fix but not after.

Test Plan:
```
COMPILE_WITH_ASAN=1 COMPILE_WITH_UBSAN=1 CC=clang-18 CXX=clang++-18 ROCKSDB_DISABLE_ALIGNED_NEW=1 USE_CLANG=1 make V=1 -j55 db_stress

python3 tools/db_crashtest.py --simple blackbox --compact_range_one_in=5 --target_file_size_base=9223372036854775807 // Half of std::numeric_limits<uint64_t>::max()
```
It fails with
```
stderr:
 options/cf_options.cc:1087:32: runtime error: 1.84467e+19 is outside the range of representable values of type 'unsigned long'

Reviewed By: pdillinger

Differential Revision: D87434936

Pulled By: hx235

fbshipit-source-id: 65563edf9faf732410bdba8b9e4b7fd61b958169
2025-11-19 16:25:53 -08:00
Jay Huh 8c8586aa23 Add oncall to BUCK file (#14134)
Summary:
As title

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

Test Plan:
The following command generated the BUCK file correctly
```
python3 buckifier/buckify_rocksdb.py
```

Reviewed By: anand1976

Differential Revision: D87469877

Pulled By: jaykorean

fbshipit-source-id: 9ec330084cfe96ad9b71aa13c8eb16593256a5ac
2025-11-19 14:04:58 -08:00
Peter Dillinger 678690274d More options for sst_dump recompress (#14133)
Summary:
I have been using sst_dump --command=recompress for some ad hoc automation for compression engineering and these new options help with that.

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

Test Plan: manual

Reviewed By: hx235

Differential Revision: D87453635

Pulled By: pdillinger

fbshipit-source-id: 2ae54e13a9221ec27c6637fea16623465a9163ae
2025-11-19 13:16:06 -08:00
Peter Dillinger 0762586067 Relax an assertion related to parallel compression (#14130)
Summary:
Saw a mysterious failure of assertion
`assert(rep_->props.num_data_blocks == 0)` in
DBCompressionTest/CompressionFailuresTest.CompressionFailures/45. This seems to be caused by a parallel compression failure arriving after the emit thread has started Finish() but before the Flush() at the start of Finish(). We can fix this by relaxing the assertion to allow for the !ok() case. Testing revealed more ok() assertions that needed to be relaxed/moved.

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

Test Plan: Added a sync point to inject a failure status in the right place and added to unit test to be sure the case is essentially covered. It would arguably be a more realistic test to force a particular thread interleaving but I believe simple is good here.

Reviewed By: hx235

Differential Revision: D87377709

Pulled By: pdillinger

fbshipit-source-id: 4bd465673b084afcc235688503d1c2f464eed32d
2025-11-19 09:23:41 -08:00
Hui Xiao 57a6fb9e3a Refactor and support option migration for db with multiple CFs (#14059)
Summary:
**Context/Summary:**
This PR adds multi-cf support to option migration. The original implementation sets options, opens db, compacts files and reopens the db in almost all the three branches below. Such design makes expanding to multi-cf difficult as it needs to change all these places within each of the branch causing code redundancy.
```
Status OptionChangeMigration(std::string dbname, const Options& old_opts,
                             const Options& new_opts) {
  if (old_opts.compaction_style == CompactionStyle::kCompactionStyleFIFO) {
    // LSM generated by FIFO compaction can be opened by any compaction.
    return Status::OK();
  } else if (new_opts.compaction_style ==
             CompactionStyle::kCompactionStyleUniversal) {
    return MigrateToUniversal(dbname, old_opts, new_opts);
  } else if (new_opts.compaction_style ==
             CompactionStyle::kCompactionStyleLevel) {
    return MigrateToLevelBase(dbname, old_opts, new_opts);
  } else if (new_opts.compaction_style ==
             CompactionStyle::kCompactionStyleFIFO) {
    return CompactToLevel(old_opts, dbname, 0, 0 /* l0_file_size */, true);
  } else {
    return Status::NotSupported(
        "Do not how to migrate to this compaction style");
  }
}
```

Therefore this PR
-  Refactor the option migration implementation by moving the common parts into the high-level `OptionChangeMigration()` through `PrepareNoCompactionCFDescriptors()` and `OpenDBWithCFs()` so `MigrateAllCFs()` can focus on compaction only.
-  Treat the original OptionChangeMigration() API as a special case of the multi-cf version option migration
- Add multiple-cf support

A few notes:
- CompactToLevel() originally modifies the compaction-related options conditionally before doing compaction. This is moved into earlier steps through `ApplySpecialSingleLevelSettings()` in `PrepareNoCompactionCFDescriptors()`
- MigrateToUniversal() originally opens the db twice with essentially the same option. This PR reduces that to one open
- Option migration does not always use the old option to compact the db and reopen the db after migration, see `  return CompactToLevel(new_opts, dbname, new_opts.num_levels - 1,/*l0_file_size=*/0, false);`. `PrepareNoCompactionCFDescriptors()` is where we handle those decisions.

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

Test Plan:
- Existing UTs
- New UTs

Reviewed By: cbi42

Differential Revision: D84852970

Pulled By: hx235

fbshipit-source-id: 936b456cf9fb4c3ccb687e5d1387f2d67a1448be
2025-11-19 05:10:03 -08:00
Ryan Hancock b9951ded37 Introducing Prepare all iterators for LevelIterator (#14100)
Summary:
This diff introduces the async prepare of all iterators within a MultiScan. The current state has each iterator be prepared as its needed, and with this diff, we prepare all iterators during the prepare phase of the Level Iterator, this will allow more time for each IO to be dispatched and serviced, increasing the odds that a block is ready as the scan seeks to it.

Benchmark is prefilled using
```
KEYSIZE=64
VALUESIZE=512
NUMKEYS=5000000
SCAN_SIZE=100
DISTANCE=25000
NUM_SCANS=15
THREADS=1

./db_bench --db=$DB \
    --benchmarks="fillseq" \
    --write_buffer_size=5242880 \
    --max_write_buffer_number=4 \
    --target_file_size_base=5242880 \
    --disable_wal=1 --key_size=$KEYSIZE \
    --value_size=$VALUESIZE --num=$NUMKEYS --threads=32

}
```

And benchmark ran is
```
run() {
echo 1 | sudo tee /proc/sys/vm/drop_caches
./db_bench --db=$DB --use_existing_db=1 \
    --benchmarks=multiscan \
    --disable_auto_compactions=1 --seek_nexts=$SCAN_SIZE \
    --multiscan-use-async-io=1 \
    --multiscan-size=$NUM_SCANS --multiscan-stride=$DISTANCE \
    --key_size=$KEYSIZE --value_size=$VALUESIZE \
    --num=$NUMKEYS --threads=$THREADS --duration=60 --statistics
}
```

The benchmark uses large stride sides to ensure that two scans would touch separate files. We reduce the size of the block cache to increase likelyhood of reads (and simulate larger data sets)

**Branch:**

```
Integrated BlobDB: blob cache disabled
RocksDB:    version 10.8.0
Date:       Tue Nov 11 13:26:29 2025
CPU:        166 * AMD EPYC-Milan Processor
CPUCache:   512 KB
Keys:       64 bytes each (+ 0 bytes user-defined timestamp)
Values:     512 bytes each (256 bytes after compression)
Entries:    5000000
Prefix:    0 bytes
Keys per prefix:    0
RawSize:    2746.6 MB (estimated)
FileSize:   1525.9 MB (estimated)
Write rate: 0 bytes/second
Read rate: 0 ops/second
Compression: Snappy
Compression sampling rate: 0
Memtablerep: SkipListFactory
Perf Level: 1
------------------------------------------------
multiscan_stride = 25000
multiscan_size = 15
seek_nexts = 100
DB path: [/data/rocksdb/mydb]
multiscan    :     837.941 micros/op 1193 ops/sec 60.001 seconds 71605 operations; (multscans:71605)
```

**Baseline:**

```
Set seed to 1762898809121995 because --seed was 0
Initializing RocksDB Options from the specified file
Initializing RocksDB Options from command-line flags
Integrated BlobDB: blob cache disabled
RocksDB:    version 10.9.0
Date:       Tue Nov 11 14:06:49 2025
CPU:        166 * AMD EPYC-Milan Processor
CPUCache:   512 KB
Keys:       64 bytes each (+ 0 bytes user-defined timestamp)
Values:     512 bytes each (256 bytes after compression)
Entries:    5000000
Prefix:    0 bytes
Keys per prefix:    0
RawSize:    2746.6 MB (estimated)
FileSize:   1525.9 MB (estimated)
Write rate: 0 bytes/second
Read rate: 0 ops/second
Compression: Snappy
Compression sampling rate: 0
Memtablerep: SkipListFactory
Perf Level: 1
------------------------------------------------
multiscan_stride = 25000
multiscan_size = 15
seek_nexts = 100
DB path: [/data/rocksdb/mydb]
multiscan    :    1129.916 micros/op 885 ops/sec 60.001 seconds 53102 operations; (multscans:53102)
```
Repeated for confirmation.

This introduces a ~20% improvement in latency and op/s.

Note: Benchmarks are single threaded as, when increasing thread count, we start seeing large amounts of overhead being induced by block cache contention, finally resulting in both baseline and branch becoming equal.

Further on network attached storage with high latency, the level iterator, preparing all iterators so a 20% improvement even at high thread counts.

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

Reviewed By: anand1976

Differential Revision: D86913584

Pulled By: krhancoc

fbshipit-source-id: da9d0c890e25e392a33389ce6b80f9bfb84d3f85
2025-11-18 15:57:03 -08:00
Peter Dillinger f6c9c3bf1c Use AutoHCC by default in tools (#14120)
Summary:
Oversight in https://github.com/facebook/rocksdb/issues/13964. More detail:
* Applies to cache_bench and db_bench (db_stress already using it)
* Make sure those along with db_stress treat "hyper_clock_cache" as "auto_hyper_clock_cache" because this is now the blessed implementation.

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

Test Plan: manual runs of the tools

Reviewed By: krhancoc

Differential Revision: D86913202

Pulled By: pdillinger

fbshipit-source-id: 07b425d3522103417f4b034735376b9d759af5fb
2025-11-12 21:40:15 -08:00
Viraj Thakur 2cf81e0a20 fix compiler warning for mutex->AssertHeld (#14115)
Summary:
We are seeing Github actions failures due to a compiler error:

https://github.com/facebook/rocksdb/actions/runs/19190877461/job/54865138898?fbclid=IwY2xjawN_Hc9leHRuA2FlbQIxMQBicmlkETFZeGlpZXZXMGlDTVhTYldwc3J0YwZhcHBfaWQBMAABHp6JoIoMBbZq-8Kgfc1honBdkAbHAZzW2ORiCM2Br2D9utxtMlq6IIqUUQnu_aem_SOU-DDsjDDMB3mTncKfLwQ&brid=VRqQ-asf2myW425wX1qqhg

When UpdatedMutableDbOptions is called from the VersionSet constructor, manifest_file_size_ is 0, and mu is nullptr. This is expected and fine, and we never enter the block where AssertHeld is called.

All other times UpdatedMutableDbOptions is called, the mutex must be held. This PR just checks that mu is not null, to satisfy the compiler. We could alternatively intentionally crash if there is concern over a silent failure if mu is passed as nullptr

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

Reviewed By: pdillinger

Differential Revision: D86733318

Pulled By: virajthakur

fbshipit-source-id: ce9ed6275c9495a3ea2a12f984dbceef7b441e24
2025-11-12 10:29:44 -08:00
Siying Dong c757f5b4e3 Java's Get() to directly return for NotFound (#14095)
Summary:
Right now, in Java's Get() calls, the way Get() is treated is inefficient. Status.NotFound is turned into an exception in the JNI layer, and is caught in the same function to turn into not found return. This causes significant overhead in the scenario where most of the queries ending up with not found. For example, in Spark's deduplication query, this exception creation overhead is higher than Get() itself. With the proposed change, if return status is NotFound, we directly return, rather than going through the exception path

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

Test Plan: Existing tests should cover all Get() cases, and they are passing.

Reviewed By: jaykorean

Differential Revision: D86797594

Pulled By: cbi42

fbshipit-source-id: 1202d24e46a2358976bb7c8ff38a2fd4783d0f99
2025-11-11 15:58:00 -08:00
Ranjan Banerjee 9fbb68be17 Api to get SST file with key ranges for a particular level and key range (startKey, EndKey)rocksdb [Internal version] (#14009)
Summary:
There are instances where  an application might be interested in knowing the distribution in SST files for a key range in a particular level.

This implementation creates an overloaded GetColumnFamilyMetaData api where  (startKey, EndKey) can be passed along with level information to filter the necessary sst files along with the keyranges for each sst file

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

Reviewed By: anand1976

Differential Revision: D83389707

fbshipit-source-id: 6df1dc1f9233efe9000b03cc1831b3c618cbcef3
2025-11-10 17:13:34 -08:00
Xingbo Wang b33c547b06 Add trivial move support in CompactFiles API (#14112)
Summary:
Support trivial move in CompactFiles API, which is not supported previously.

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

Test Plan: Unit test

Reviewed By: cbi42

Differential Revision: D86546150

Pulled By: xingbowang

fbshipit-source-id: 08a3ae9a055f3d3d41711403b1695f44977e6ea8
2025-11-10 15:20:50 -08:00
ngina b897c3789b Merge BuiltinFilterBitsBuilder into FilterBitsBuilder for accurate filter size estimation (#14111)
Summary:
**Summary:**
Merge the BuiltinFilterBitsBuilder into FilterBitsBuilder.  This enables using
CalculateSpace() for accurate filter size estimation instead of hardcoded
bits-per-key which could result in incorrect estimations for different filter types.
The previous hardcoded estimate of 15 bits per key was in the filter block builders UpdateFilterSizeEstimate().

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

Test Plan: - Existing filter tests pass (bloom_test, full_filter_block_test, filter_bench, db_bloom_filter_test)

Reviewed By: pdillinger

Differential Revision: D86473287

Pulled By: nmk70

fbshipit-source-id: cd4a47351e67444e944d5b1b375b3b13274dd6e3
2025-11-10 14:47:36 -08:00
Jay Huh 5879f8b62b Add option to verify block checksums of output files (#14103)
Summary:
For all compactions, RocksDB performs a lightweight sanity check on output SST files before installation (in `CompactionJob::VerifyOutputFiles()`). However, this lightweight check may not catch corruption that is small enough to allow the SST files to still be opened.

There is an existing feature, `paranoid_file_check`, which opens the SST file, iterates through all keys, and checks the hash of each key. While this provides the ultimate level of data integrity checking, it comes at a high computational cost.

In this PR, we introduce a new mutable CF option, `verify_output_flags`. The `verify_output_flags` is a bitmask enum that allows users to select various verification types, including block checksum verification, full key iteration, and file checksum verification (to be added in subsequent PRs). Note that the existing `paranoid_file_check` option is equivalent to a full key iteration check. Block-level checksum verification is much lighter than the full key iteration check.

Please note that the previously deprecated `verify_checksums_in_compaction` option (removed in version 5.3.0) was for verifying the checksum of **input SST files**. RocksDB continues to perform this verification for both local and remote compactions, and this behavior remains unchanged. In contrast, this PR focuses on verifying the **output SST files**.

## To follow up
- File-level Checksum verification for output SST files
- Deprecate `paranoid_file_checks` option in favor of the new option
- Add to stress test / db_bench

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

Test Plan:
New Unit Test added. The corruption is both detected by `paranoid_file_check` and various types of verification set by this new option, `verify_output_flags`
```
./compaction_service_test --gtest_filter="*CompactionServiceTest.CorruptedOutput*"
```

Reviewed By: pdillinger

Differential Revision: D86357924

Pulled By: jaykorean

fbshipit-source-id: a9e04798f249c7e977231e179622a0830d6675fe
2025-11-07 14:22:00 -08:00
Changyu Bi ea75cdc493 Fix a bug in MultiScan that moves iterator backward (#14106)
Summary:
MultiScanUnexpectedSeekTarget() currently uses user key comparison to decide on the next data block for multiscan. This can cause a multiscan to move backward in the following scenario:

data block 1: ..., k@7, k@6
data block 2: k@5, ...

DB iter scan through k@7, k@6 and k@5 and decides to seek to k@0 due to option [`max_sequential_skip_in_iterations`](https://github.com/facebook/rocksdb/blob/d56da8c112b4e6968fd79ce2bf15e6435df40656/include/rocksdb/advanced_options.h#L621-L629). Multiscan was on data block 2, but moves to data block 1 after the seek.

This can cause assertion failure in debug mode and seg fault in prod since older data blocks are unpinned and freed as we advanced a multiscan. This PR fixes the issue by forcing a multiscan to never go backward.

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

Test Plan: - added a new unit test that reproduces the scenario: `./db_iterator_test --gtest_filter="*ReseekAcrossBlocksSameUserKey*"`

Reviewed By: xingbowang

Differential Revision: D86428845

Pulled By: cbi42

fbshipit-source-id: ab623f93e73298a60857fb2ff268366f289092a0
2025-11-07 11:04:57 -08:00
Peter Dillinger 2bee29729a CI: move valgrind to weekly (#14110)
Summary:
This test is now taking > 6 hours, timing out, and has low signal, so creating a weekly job for it, with an explicit timeout of 12 hours.

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

Test Plan: watch CI

Reviewed By: virajthakur

Differential Revision: D86428262

Pulled By: pdillinger

fbshipit-source-id: 44103518064ca378f3fd2ff8d21967ede698c8ea
2025-11-07 10:36:34 -08:00
Peter Dillinger 37176a4a44 Auto-tune manifest file size (#14076)
Summary:
Adds auto-tuning of manifest file size to avoid the need to scale `max_manifest_file_size` in proportion to things like number of SST files to properly balance (a) manifest file write amp and new file creation, vs. (b) manifest file space amp and replay time, including non-incremental space usage in backups. (Manifest file write amp comes from re-writing a "live" record when the manifest file is re-created, or "compacted"; space amp is usage beyond what would be used by a compacted manifest file.) In more detail,

* Add new option `max_manifest_space_amp_pct` with default value of 500, which defaults to 0.2 write amp and up to roughly 5.0 space amp, except `max_manifest_file_size` is treated as the "minimum" size before re-creating ("compacting") the manifest file.
* `max_manifest_file_size` in a way means the same thing, with the same default of 1GB, but in a way has taken on a new role. What is the same is that we do not re-create the manifest file before reaching this size (except for DB re-open), and so users are very unlikely to see a change in default behavior (auto-tuning only kicking in if auto-tuning would exceed 1GB for effective max size for the current manifest file). The new role is as a file size lower bound before auto-tuning kicks in, to minimize churn in files considered "negligibly small." We recommend a new setting of around 1MB or even smaller like 64KB, and expect something like this to become the default soon.
* These two options along with `manifest_preallocation_size` are now mutable with SetDBOptions. The effect is nearly immediate, affecting the next write to the current manifest file.

Also in this PR:
* Refactoring of VersionSet to allow it to get (more) settings from MutableDBOptions. This touches a number of files in not very interesting ways, but notably we have to be careful about thread-safe access to MutableDBOptions fields, and even fields within VersionSet. I have decided to save copies of relevant fields from MutableDBOptions to simplify testing, etc. by not saving a reference to MutableDBOptions but getting notified of updates.
* Updated some logging in VersionSet to provide some basic data about final and compacted manifest sizes (effects of auto-tuning), making sure to avoid I/O while holding DB mutex.
* Added db_etc3_test.cc which is intended as a successor to db_test and db_test2, but having "test.cc" in its name for easier exclusion of test files when using `git grep`. Intended follow-up: rename db_test2 to db_etc2_test
* Moved+updated `ManifestRollOver` test to the new file to be closer to other manifest file rollover testing.

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

Test Plan:
As for correctness, new unit test AutoTuneManifestSize is pretty thorough. Some other unit tests updated appropriately. Manual tests in the performance section were also audited for expected behavior based on the new logging in the DB LOG. Example LOG data with -max_manifest_file_size=2048 -max_manifest_space_amp_pct=500:

```
2025/10/24-11:12:48.979472 2150678 [/version_set.cc:5927] Created manifest 5, compacted+appended from 52 to 116
2025/10/24-11:12:49.626441 2150682 [/version_set.cc:5927] Created manifest 24, compacted+appended from 2169 to 1801
2025/10/24-11:12:52.194592 2150682 [/version_set.cc:5927] Created manifest 91, compacted+appended from 10913 to 8707
2025/10/24-11:13:02.969944 2150682 [/version_set.cc:5927] Created manifest 362, compacted+appended from 52259 to 13321
2025/10/24-11:13:18.815120 2150681 [/version_set.cc:5927] Created manifest 765, compacted+appended from 80064 to 13304
2025/10/24-11:13:35.590905 2150681 [/version_set.cc:5927] Created manifest 1167, compacted+appended from 79863 to 13304
```

As you can see, it only took a few iterations of ramp-up to settle on the auto-tuned max manifest size for tracking ~122 live SST files, around 80KB and compacting down to about 13KB. (13KB * (500 + 100) / 100 = 78KB). With the default large setting for max_manifest_file_size, we end up with a 232KB manifest, which is more than 90% wasted space. (A long-running DB would be much worse.)

As for performance, we don't expect a difference, even with TransactionDB because actual writing of the manifest is done without holding the DB mutex. I was not able to see a performance regression using db_bench with FIFO compaction and >1000 ~10MB SST files, including settings of -max_manifest_file_size=2048 -max_manifest_space_amp_pct={500,10,0}. No "hiccups" visible with -histogram either.

I also tried seeding a 1 second delay in writing new manifest files (other than the first). This had no significant effect at -max_manifest_space_amp_pct=500 but at 100 started causing write stalls in my test. In many ways this is kind of a worst case scenario and out-of-proportion test, but gives me more confidence that a higher number like 500 is probably the best balance in general.

Reviewed By: xingbowang

Differential Revision: D85445178

Pulled By: pdillinger

fbshipit-source-id: 1e6e07e89c586762dd65c65bb7cb2b8b719513f9
2025-11-07 09:04:52 -08:00
ngina 7603712a88 Introduce tail estimation to prevent oversized compaction files (#14051)
Summary:
**Summary:**
This change introduces tail size estimation during SST construction to improve compaction file cutting accuracy to prevent oversized files. The BlockBasedTableBuilder now estimates the SST tail size (index and filter blocks) and uses this estimate, in addition to the data size, to determine when to cut files during compaction.

**Problem:**
Currently, file cutting logic only considers data size when determining where to cut a file, failing to reserve space for index and filter blocks that are added when the file is finalized. This often leads to SST files that exceed target file size limits.

**Behavior Change:**
Implement size estimation methods for index and filter builders, and integrate these estimates into BlockBasedTableBuilder via a new EstimatedTailSize() method. This method aggregates estimates from all tail components and is used for file cutting decisions during compaction.

**Performance Considerations:**
To minimize CPU overhead, size estimates are updated when data blocks are finalized rather than on every key add. For index builders, estimates are updated when index entries are added (one per data block). For filter builders, the OnDataBlockFinalized() hook triggers estimate updates when data blocks are cut/finalized.

This approach provides:
* Minimal impact to compaction hot path (key additions)
* Near real-time estimates for file cutting decisions
* Meaningful estimate changes only when data blocks are finalized

**Usage:**
* Set true mutable cf option `compaction_use_tail_size_estimation`
to use tail size estimation for compaction file cutting decisions.

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

Test Plan:
* Assert tail size estimate is an overestimate in BlockBasedTableBuilder::Finish
* Add new test to verify compaction output file is below target file size

**Next steps:**
* Enable tail size estimation for compaction file cutting by default (and other improvements)

Reviewed By: pdillinger, cbi42

Differential Revision: D84852285

Pulled By: nmk70

fbshipit-source-id: c43cf5dbd2cb2f623a0622591ef24eee30ce0c87
2025-11-05 20:00:00 -08:00
Peter Dillinger d56da8c112 More folly build updates (#14099)
Summary:
* Fix nightly build-linux-cmake-with-folly-lite-no-test for real this time
  with correct include directory. (CMakeLists.txt)
* Add test runs to that build (and rename)
* Improve folly build caching with a folly.mk file with most of the relevant
  parts of Makefile that contribute to the checkout_folly and
  build_folly builds. This reduces the risk of false passing of CI job with
  cache folly build. This caching is still only for folly debug builds, (which
  is probably OK with just a single nightly build relying on release folly
  build, which also serves as a rough canary against false passing
  because of caching).
* Use `make VERBOSE=1` after cmake calls for detailed output

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

Test Plan:
temporary CI change to put the relevant parts in pr-jobs,
then back to homes including in nightly

Reviewed By: mszeszko-meta

Differential Revision: D86243363

Pulled By: pdillinger

fbshipit-source-id: f7975fa190ef45195c6d0b74417f7886e551516a
2025-11-05 11:39:21 -08:00
Peter Dillinger befa6b8050 Fix and check for potential ODR violations (#14096)
Summary:
... caused by public headers depending on build parameters (macro definitions). This change also adds a check under 'make check-headers' (already in CI) looking for potential future violations.

I've audited the uses of '#if' in public headers and either
* Eliminated them
* Systematically excluded them because they are intentional or similar (details in comments in check-public-header.sh
* Manually excluded them as being ODR-SAFE

In the case of ROCKSDB_USING_THREAD_STATUS, there was no good reason for this to appear in public headers so I've replaced it with a static bool ThreadStatus::kEnabled. I considered getting rid of the ability to disable this code but some relatively recent PRs have been submitted for fixing that case. I've added a release note and updated one of the CI jobs to use this build configuration. (I didn't want to combine with some jobs like no_compression and status_checked because the interaction might limit what is checked.

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

Test Plan: manual 'make check-headers' + manual cmake as in new CI config + CI

Reviewed By: jaykorean

Differential Revision: D86241864

Pulled By: pdillinger

fbshipit-source-id: d16addc9e3480706b174a006720a4def0740bf2e
2025-11-04 19:47:42 -08:00
Peter Dillinger 9577b92b55 Fix ODR violation from open source folly build, update (#14094)
Summary:
Following up on https://github.com/facebook/rocksdb/pull/14071, updating folly to
https://github.com/facebook/folly/commit/8a9fc1e80a18cafadbec85e33d5042ce13a7c634 or beyond was failing an F14Table assertion for a very subtle reason: ODR violation between the folly build and RocksDB build because folly build was release mode and RocksDB build was debug mode. What was happening was that folly change introduced a dependence on kDebug (whether build is debug) in a hashing implementation in a .h file, and the inconsistency between the inlined implementation during RocksDB build and the linked-to implementation from the folly build was leading to inconsistencies in the data structure.

The primary fix is to ensure we build folly in debug mode for debug mode RocksDB builds. Also,

* Needed to use the `patchelf` tool in `build_folly` to ensure the glog dependency shared library can always find its own gflags dependency. I explored many options for working around this, and this is what would work without reworking folly's own build.
* Updated folly to latest commit.
* Thrown in an ad hoc folly patch to use ftp.gnu.org mirrors (the canonical is super slow)
* Moved the placement of GETDEPS_USE_WGET=1 to apply to local builds also, to avoid the issue of a large download almost reaching completion and then stalling indefinitely.
* Fix failing nightly build-linux-cmake-with-folly-lite-no-test with fmt includes in cmake build (as was done with make build)
* Add a release mode folly+RocksDB to nightly CI, including both cmake and make. This also serves as a non-cached folly build to detect potential problems with PR jobs working from cached folly build.
* Move build-linux-cmake-with-folly to nightly because it's mostly covered by build-linux-cmake-with-folly-coroutines

Intended follow-up:
* folly-lite build with tests
* Make the folly build caching more friendly+accurate by hashing the relevant Makefile parts and tagging whether debug or release. Not in this PR because then you wouldn't be able to see what changed in the folly build steps themselves.

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

Test Plan: manual + CI

Reviewed By: mszeszko-meta

Differential Revision: D85864871

Pulled By: pdillinger

fbshipit-source-id: 50009b33422d5781074fcbbdf18089be9e36800d
2025-11-02 16:08:09 -08:00
Peter Dillinger 94d91daddb Update folly (part way), fix USE_FOLLY_LITE (#14071)
Summary:
Resolving this folly upgrade required fixing the FOLLY_LITE build with header include from the 'fmt' library.

I was close to timing out on fixing USE_FOLLY_LITE and removing it altogether - it could be considered obsolete and/or not worth the maintenance cost.

Follow-up: make the folly build caching more friendly by hashing the relevant makefile parts. Not in this PR because then you wouldn't be able to see what changed in the folly build steps themselves.

UPDATE/NOTE: I wasn't able to fully update to latest due to a failure seen in F14, using the next folly commit or later. The source of the bug is likely outside of F14 but investigation is in progress.

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

Test Plan: CI

Reviewed By: jaykorean

Differential Revision: D85268833

Pulled By: pdillinger

fbshipit-source-id: 1d0a2d61f095524a20e6ec796ef46c02d0696f4e
2025-10-29 16:02:10 -07:00
anand76 0eb5b43b4f Change PosixWritableFile Truncate to reseek to new end of file (#14088)
Summary:
Change PosixWritableFile's Truncate to the new end offset. This ensures that future appends are written with no holes or overwrites. RocksDB doesn't guarantee this in the FileSystem contract, and its left up to the specific implementation.

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

Reviewed By: cbi42

Differential Revision: D85786398

Pulled By: anand1976

fbshipit-source-id: 3520d9d6336362f5128a17bbf396297d821a5da3
2025-10-29 12:58:03 -07:00
zaidoon 1bb704b6e0 optimize memory allocations and vector overhead in RocksDB C API using unique_ptr and PinnableSlice (#14036)
Summary:
Comprehensive performance optimizations for the RocksDB C API that eliminate unnecessary memory allocations and copies.

## Key Changes

### 1. PinnableSlice for Get Operations (50% reduction in copies)
- Changed all `rocksdb_get*` functions to use `PinnableSlice` internally instead of `std::string`
- **Before:** RocksDB → std::string → malloc'd buffer (2 copies)
- **After:** RocksDB → malloc'd buffer (1 copy)
- Affects: Get, Transaction Get, TransactionDB Get, WriteBatch Get variants

### 2. Array-Based MultiGet with PinnableSlice (30% allocation reduction)
- Switched MultiGet operations to use optimized array-based RocksDB API with `PinnableSlice`
- Eliminates vector overhead and string allocations
- Affects: MultiGet, Transaction MultiGet, TransactionDB MultiGet variants

### New Zero-Copy APIs
Added high-performance zero-copy functions for applications that can use them:
- `rocksdb_iter_key_slice()` / `value_slice()` / `timestamp_slice()` - Return slices by value (eliminates output param overhead)
- `rocksdb_batched_multi_get_cf_slice()` - Batched get with slice array input
- `rocksdb_slice_t` - ABI-compatible slice type

Note that this pr builds on top of https://github.com/facebook/rocksdb/pull/13911

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

Reviewed By: pdillinger

Differential Revision: D85604919

Pulled By: jaykorean

fbshipit-source-id: 7f04b935eea79af1d45b3125a79b90e4706666f6
2025-10-29 12:57:49 -07:00
Changyu Bi 64817ae604 Disable internal reseeking for multiscan stress test (#14087)
Summary:
Stress test can fail with assertion inside MultiScan in some reseek scenario. E.g., data block 1 ends with k@9, data block 2 starts with k@8, when a DB iter seeks to k@0 (see option `max_sequential_skip_in_iterations`), MultiScan will land in data block 1 due to https://github.com/facebook/rocksdb/blob/fd0b4e0cf08315f6a644d54d585fe70ca958d4ba/table/block_based/block_based_table_iterator.cc#L1258-L1263.

We can't just use internal key as separator since index block might not use it. I plan to follow up with a fix that never moves `cur_data_block_idx` backward within a MultiScan.

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

Test Plan: CI and internal crash tests

Reviewed By: anand1976

Differential Revision: D85701668

Pulled By: cbi42

fbshipit-source-id: d3f1aaff40a12be4e3d1b4b7160bf2547f43b849
2025-10-29 12:42:34 -07:00
Jay Huh fd0b4e0cf0 Disable mmap_read in Stress Test (#14083)
Summary:
All remote compaction test failures had `mmap_read=1` in common. Unfortunately, the failure hasn't been very reproducible. Try disabling `mmap_read` to see if that shed some light.

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

Test Plan: CI

Reviewed By: hx235

Differential Revision: D85622229

Pulled By: jaykorean

fbshipit-source-id: bbe9e08efc369813f0fec388c910446089e43650
2025-10-28 12:59:00 -07:00
Changyu Bi 12b85c8ce9 Fix timestamp handling in LevelIterator MultiScan seeks (#14085)
Summary:
As titled, this fixes some internal crash test failures when UDT is enabled.

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

Test Plan: monitor crash tests.

Reviewed By: anand1976

Differential Revision: D85617949

Pulled By: cbi42

fbshipit-source-id: da6fb21c0ca5803ea24e8daf7de8558321babcf4
2025-10-28 11:15:42 -07:00
Jay Huh a3aa44a716 Fix regression test script for internal use (#14079)
Summary:
Due to some internal requirements, what's being used for`$SSH` and `$SCP` has changed and it broke the regression test. (e.g. tarball streaming to remote host no longer works)

Minor behavior changes to the script to make the internal workflow work.

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

Test Plan:
```
./tools/regression_test.sh
```
Meta Internal automation

Reviewed By: pdillinger

Differential Revision: D85502798

Pulled By: jaykorean

fbshipit-source-id: d294c2ee47661fbe368ccc318062e891f3ac7c81
2025-10-27 14:22:47 -07:00
zaidoon 32f66712c8 optimize C API to reduce memory allocations and using PinnableSlice for zero-copy reads (#13911)
Summary:
### Problem
The current C API implementation has inefficiencies that impact performance in production environments:

1. **Double allocations in Get operations**: Values are first copied into a `std::string`, then copied again into a malloc'd buffer
2. **Unnecessary string temporaries**: Using `std::string` as intermediate storage adds allocation/deallocation overhead
3. **No zero-copy read path**: All read operations require at least one allocation and copy
4. **Redundant operations**: CopyString performed unnecessary `sizeof(char)` multiplication

### Solution

#### 1. Use PinnableSlice for Get Operations
- **Before**: `DB::Get() → std::string → malloc'd buffer` (2 allocations, 2 copies)
- **After**: `DB::Get() → PinnableSlice → malloc'd buffer` (1 allocation, 1 copy)
- **Impact**: 50% reduction in allocations and copies

#### 2. Optimize CopyString Helper
- Removed redundant `sizeof(char)` multiplication
- Single implementation using `Slice` parameter (works with all types via implicit conversion)
- Added `inline` for better optimization

#### 3. New Zero-Copy API Functions
Added high-performance alternatives for allocation-sensitive workloads:
- rocksdb_get_pinned_v2/ rocksdb_get_pinned_cf_v2 - Zero-copy read access
- rocksdb_get_into_buffer/ rocksdb_get_into_buffer_cf - Copy into user-provided buffer
- `rocksdb_pinnable_handle_*` - Handle management functions

### Performance Improvements

| Operation | Allocations | Improvement |
|-----------|------------|-------------|
| [rocksdb_get](cci:1://file:///Users/zaidoon/public%20repos/rocksdb/db/c.cc:1391:0-1411:1) | 2 → 1 | **50% reduction** |
| [rocksdb_get_cf](cci:1://file:///Users/zaidoon/public%20repos/rocksdb/db/c.cc:1411:0-1431:1) | 2 → 1 | **50% reduction** |
| [rocksdb_multi_get](cci:1://file:///Users/zaidoon/public%20repos/rocksdb/db/c.cc:1495:0-1520:1) (per key) | 2 → 1 | **50% reduction** |
| [rocksdb_transaction_get](cci:1://file:///Users/zaidoon/public%20repos/rocksdb/db/c.cc:6730:0-6748:1) | 2 → 1 | **50% reduction** |
| [rocksdb_writebatch_wi_get_from_batch](cci:1://file:///Users/zaidoon/public%20repos/rocksdb/db/c.cc:2714:0-2732:1) | 2 → 1 | **50% reduction** |
| [rocksdb_get_pinned_v2](cci:1://file:///Users/zaidoon/public%20repos/rocksdb/db/c.cc:7761:0-7775:1) (new) | 0 | **100% reduction** |

### Functions Optimized (30+)
- All Get variants (regular, CF, with timestamps)
- All MultiGet variants
- All Transaction Get/MultiGet operations
- All WriteBatch Get operations
- KeyMayExist operations
- Metadata getters (column family names, SST file keys, transaction names, DB identity)

### Testing
- Added tests for new zero-copy functions
- Added tests for previously untested functions rocksdb_column_family_handle_get_name, rocksdb_transaction_get_name

### Migration Path
Applications can adopt improvements in three ways:
1. **No changes needed** - Existing code automatically benefits from 50% allocation reduction
2. **Incremental adoption** - Replace hot-path calls with zero-copy variants
3. **Full optimization** - Use rocksdb_get_into_buffer

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

Reviewed By: cbi42

Differential Revision: D83508431

Pulled By: jaykorean

fbshipit-source-id: 96146a59b0f9e839f6603b376d4e51f0e97c3a8c
2025-10-27 13:16:33 -07:00
Andrew Kryczka 10478b98a5 Fix unsigned underflow in WAL TTL logic when system clock goes backwards (#14016)
Summary:
The TTL-based WAL archive cleanup logic could incorrectly delete an archived WAL if the system clock moved backwards between the last write to that WAL and `WALManager::PurgeObsoleteWALFiles()`. This happened due to unsigned underflow in subtraction of two wall clock based timestamps: `now_seconds - file_m_time`.

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

Test Plan: unit test repro

Reviewed By: pdillinger

Differential Revision: D83879806

Pulled By: hx235

fbshipit-source-id: 643e7f623c6b5c31711565854314cfd6cbbcf3a7
2025-10-24 17:10:48 -07:00
Andrew Kryczka e687ca79b4 Fix a missing CV signal in FindObsoleteFiles() (#14069)
Summary:
Fixed a missing CV signal when `FindObsoleteFiles()` decides there is nothing to purge and then decrements `pending_purge_obsolete_files_` to zero.  This bug could cause `DB::GetSortedWalFiles()` to hang, at least.

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

Test Plan: unit test repro

Reviewed By: hx235

Differential Revision: D85453534

Pulled By: cbi42

fbshipit-source-id: cf5cfe7f5087459ca1f1f28ce81ea6afc84178f0
2025-10-24 13:11:26 -07:00
Changyu Bi 2edc660e28 Fix multiscan assert failure in stress test (#14077)
Summary:
should not use async_io when not supported to avoid the assert failure here: https://github.com/facebook/rocksdb/blob/dce33f9443815dcbe1d9a98d4d34776dfdf1112e/table/block_based/block_based_table_iterator.cc#L1710.

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

Test Plan: monitor future CI failure.

Reviewed By: anand1976

Differential Revision: D85456447

Pulled By: cbi42

fbshipit-source-id: dccc865a5aedf194029a53616f4bbc99d0162691
2025-10-24 12:51:43 -07:00
Xingbo Wang dce33f9443 Follow up on MultiScan change in #14040 (#14055)
Summary:
* Address feedback from https://github.com/facebook/rocksdb/issues/14040
* Add additional test for MultiScan
* Fix a bug when del range and data are in same file for multi-scan
* Rewrite the cases need to be handled in SeekMultiScan

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

Test Plan: Unit test

Reviewed By: cbi42, anand1976

Differential Revision: D84851788

Pulled By: xingbowang

fbshipit-source-id: 0f69632733afb99685f6341badbf239681010c38
2025-10-23 20:34:21 -07:00
Jay Huh fac8222bfe Make Meta Internal Linter happy (#14074)
Summary:
Linter complains like this
```
  void foo(Arg parameter_name) {}
    void bar() {
    Arg a;
    foo(/*some_other_name=*/ a); // Wrong! Comment/parameter name mismatch
    foo(/*parameter_name=*/ a);  // This is OK; the names match.
  }
```
```
Argument name in comment (`read_only`) does not match parameter name (`unchanging`).
```

This used to be warning, but now treated as an error :(

Fixing a few other linter warnings before they become errors in the future.

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

Test Plan: CI

Reviewed By: archang19

Differential Revision: D85370353

Pulled By: jaykorean

fbshipit-source-id: 20e96aad740d516a29c0424282674e655f99c0a2
2025-10-23 18:10:12 -07:00
Changyu Bi 144e9f1e42 Fix compaction picking with L0 standalone range deletion file (#14061)
Summary:
When a standalone range deletion file is ingested in L0, currently it is compacted with any overlapping L0 files. This is not desirable when we ingest new data on top of the range deletion file. This PR fixes the compaction picking logic to only consider L0 files older than the standalone range deletion file.

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

Test Plan: added a new unit test and updated an existing one.

Reviewed By: xingbowang

Differential Revision: D84930780

Pulled By: cbi42

fbshipit-source-id: 65f4403ccb40ba964b9e65b09e2f7f7efebe81df
2025-10-23 13:34:07 -07:00
Jay Huh e691965558 Start 10.9.0 development (#14067)
Summary:
10.8.0 branch has been cut.

Updated
- HISTORY.md
- include/rocksdb/version.h
- tools/check_format_compatible.sh

To follow up
- folly update

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

Test Plan: CI

Reviewed By: pdillinger

Differential Revision: D85186398

Pulled By: jaykorean

fbshipit-source-id: 44920156aa2a62ba40626766dc4ebdbc02f23fa8
2025-10-22 12:48:31 -07:00
Hui Xiao e32c14eb56 Stress/crash test improvement to remote compaction with resumable compaction (#14041)
Summary:
**Context/Summary:**
- Add resumable compaction to stress test with adaptive progress cancellation
- Add fault injection to remote compaction
- Fix a real minor bug in a couple testing framework bugs with remote compaction

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

Test Plan: - Rehearsal stress test, finding bugs for https://github.com/facebook/rocksdb/pull/13984 effectively and did not create new failures.

Reviewed By: jaykorean

Differential Revision: D84524194

Pulled By: hx235

fbshipit-source-id: 42b4264e428c6739631ed9aa5eb02723367510bc
2025-10-21 12:13:57 -07:00
Hui Xiao 6d9b526551 Add OpenAndCompact() to db_bench (#14003)
Summary:
**Context/Summary:** as titled.

This can be used to benchmark OpenAndCompact() and OpenAndCompactionOptions::allow_resumption. See below for usage.

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

Test Plan:
1. Simple OpenAndCompact()
```
openandcompact_allow_resumption=false

./db_bench --use_existing_db=true --db=$db --benchmarks=openandcompact --openandcompact_test_cancel_on_odd=false --openandcompact_cancel_after_millseconds=0 --openandcompact_allow_resumption=$openandcompact_allow_resumption  --disable_auto_compactions=true --compression_type=none --secondary_path=$secondary_path

...
DB path: [/dev/shm/test]

Input files: 101 files, 10000 keys
OpenAndCompact() API call : 39746440.000 micros/op 39.746 seconds/op
OpenAndCompact status: OK
Output: 358 files, average size: 69835396 bytes (66.60 MB)
openandcompact : 39977603.000 micros/op 0 ops/sec 39.978 seconds 1 operations;
```

2. OpenAndCompact() with cancellation (after the whole compaction essentially finishes) and resumption
```
openandcompact_allow_resumption=true
./db_bench --use_existing_db=true --db=$db --benchmarks=openandcompact[X2] --openandcompact_test_cancel_on_odd=false --openandcompact_cancel_after_millseconds=0 --openandcompact_allow_resumption=$openandcompact_allow_resumption  --disable_auto_compactions=true --compression_type=none --secondary_path=$secondary_path

..
DB path: [/dev/shm/test]
Running benchmark for 2 times

Input files: 101 files, 10000 keys
OpenAndCompact() API call : 40095045.000 micros/op 40.095 seconds/op
OpenAndCompact status: OK
Output: 358 files, average size: 69835396 bytes (66.60 MB)
openandcompact : 41471226.000 micros/op 0 ops/sec 41.471 seconds 1 operations;

Input files: 101 files, 10000 keys
OpenAndCompact() API call : 336588.000 micros/op 0.337 seconds/op // Resume
OpenAndCompact status: OK
Output: 358 files, average size: 69835396 bytes (66.60 MB)
openandcompact :  573885.000 micros/op 1 ops/sec 0.574 seconds 1 operations;
openandcompact [AVG 2 runs] : 0 (± 1) ops/sec

openandcompact [AVG    2 runs] : 0 (± 1) ops/sec; 1132.236 ms/op
openandcompact [MEDIAN 2 runs] : 0 ops/sec
```

3. OpenAndCompact() with cancellation at a fixed point and resumption
```
openandcompact_allow_resumption=true
./db_bench --use_existing_db=true --db=$db --benchmarks=openandcompact[X2] --openandcompact_test_cancel_on_odd=true --openandcompact_cancel_after_millseconds=6000 --openandcompact_allow_resumption=$openandcompact_allow_resumption  --disable_auto_compactions=true --compression_type=none --secondary_path=$secondary_path

...
DB path: [/dev/shm/test]
Running benchmark for 2 times

 --- Run 1 (odd - will cancel) ---

Input files: 101 files, 10000 keys
OpenAndCompact() API call : 6005787.000 micros/op 6.006 seconds/op // Cancel accordingly
OpenAndCompact status: Result incomplete: Manual compaction paused
openandcompact : 7255346.000 micros/op 0 ops/sec 7.255 seconds 1 operations;

 --- Run 2 (even - resume) ---

Input files: 101 files, 10000 keys
OpenAndCompact() API call : 33013725.000 micros/op 33.014 seconds/op // Resume
OpenAndCompact status: OK
Output: 358 files, average size: 69835396 bytes (66.60 MB)
openandcompact : 33244026.000 micros/op 0 ops/sec 33.244 seconds 1 operations;
openandcompact [AVG 2 runs] : 0 (± 0) ops/sec

openandcompact [AVG    2 runs] : 0 (± 0) ops/sec; 11911.234 ms/op
openandcompact [MEDIAN 2 runs] : 0 ops/sec
```

Reviewed By: jaykorean

Differential Revision: D84839965

Pulled By: hx235

fbshipit-source-id: 21c4cd01be67da0a128e2de1c3aae93aa97662bd
2025-10-20 15:11:45 -07:00
Xingbo Wang f343f7ecdc Use ccache to accelerate windows build (#14064)
Summary:
With cache hit and compiler option optimization, the compilation time build time is reduced from 40 min to 2 min. Overall build time is reduced from 60 min to less 20 minutes on cache hit on majority of the source file. On 100% cache miss, it would be around 40 minutes.

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

Test Plan: Github CI

Reviewed By: mszeszko-meta

Differential Revision: D85023882

Pulled By: xingbowang

fbshipit-source-id: 98551880c98f14d36133ff43e6af8c3be94ab465
2025-10-20 10:37:08 -07:00
Xingbo Wang a8a5ade6fa Fix a nullptr access bug in MultiScan (#14062)
Summary:
Fixing a nullptr access in multiscan, under following situation.

```
Block Based Table: blk1:[k1,k2], blk2:[k3,                k8], blk3:[k9]
Scan ranges:            [k1,             k4), [k5,k6), [k7,            k10)
Prepared block ranges:  [0,2],                [2,2],   [1,3]
```

1. Seek key k1 on the first range, read key k1, k2.
2. Seek key k4 on the 2nd range, blocks 0,1 would be unpinned.
3. Seek key k9, block 1 would be accessed, but it is unpinned, which trigger assert failure in debug mode and nullptr access on release build.

This fix changes how blocks are unpinned. It is now only unpinning the block, when the cur_data_block_idx has passed it.

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

Test Plan:
Unit Test
rand_seed 304010984 on UserDefinedIndexStressTest

Reviewed By: cbi42

Differential Revision: D84976410

Pulled By: xingbowang

fbshipit-source-id: 6b99bf85fc9d4108c5267ae77be77ccfe08923cd
2025-10-19 21:24:17 -07:00
ngina 3687dc4ad3 Add prefetch feature enum to FSSupportedOps (#13917)
Summary:
**Problem:** RocksDB was making unnecessary prefetch system calls on file systems that don't support prefetch operations, potentially leading to wasted CPU cycles.

**Fix:** Add kFSPrefetch to FSSupportedOps enum to allow file systems to indicate prefetch support capability. File systems can now opt out of prefetch calls by not setting this field.

**Backwards compatibility:** File systems that don't override SupportedOps() continue to receive prefetch calls exactly as before. Only file systems that explicitly opt out by not setting kFSPrefetch will avoid the calls.

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

Test Plan:
- Added a new test in block_based_table_reader.
- Run existing tests: ```make prefetch_test && ./prefetch_test```

Reviewed By: anand1976

Differential Revision: D81607145

Pulled By: nmk70

fbshipit-source-id: 3bbefa05919034e8776ea4e4540cdc695cdc6d3f
2025-10-17 19:54:49 -07:00
Hui Xiao 8edb99f904 Statistics for successfully resumed compaction output bytes (#14054)
Summary:
Context/Summary: as titled

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

Test Plan: new UT, manually checking

Reviewed By: jaykorean

Differential Revision: D84828431

Pulled By: hx235

fbshipit-source-id: 56e1a9159f7597a10d6c549657d8b22788aa0599
2025-10-17 11:38:20 -07:00
Andrew Chang 622186adec Update error message for plain table reader max file size (#14056)
Summary:
Currently we return `File is too large for PlainTableReader!` when the file size exceeds our pre-defined constant. There was a request to have the file size information logged when this error is returned.

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

Reviewed By: nmk70

Differential Revision: D84834869

Pulled By: archang19

fbshipit-source-id: 8f332b6a31d51f320c7e2db06ad49f50798ff70e
2025-10-17 11:12:35 -07:00
Hui Xiao ad83352c39 Support dumping compaction progress file in ldb (#14058)
Summary:
**Context/Summary:**

This PR adds support to dump compaction progress file in ldb for debugging resumable compaction issue

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

Test Plan:
```
/data/users/huixiao/rocksdb$ ./ldb compaction_progress_dump --path=/home/huixiao/COMPACTION_PROGRESS-123
Compaction Progress File: /home/huixiao/COMPACTION_PROGRESS-123
============================================
Progress Record 0:
SubcompactionProgress{ next_internal_key_to_compact=user_key="b" (hex:62), seq=kMaxSequenceNumber, type=24, num_processed_input_records=1, output_level_progress=SubcompactionProgressPerLevel{ num_processed_output_records=1, output_files_count=1, last_persisted_output_files_count=0 }, proximal_output_level_progress=SubcompactionProgressPerLevel{ num_processed_output_records=0, output_files_count=0, last_persisted_output_files_count=0 } }
Progress Record 1:
SubcompactionProgress{ next_internal_key_to_compact=user_key="bb" (hex:6262), seq=kMaxSequenceNumber, type=24, num_processed_input_records=2, output_level_progress=SubcompactionProgressPerLevel{ num_processed_output_records=2, output_files_count=1, last_persisted_output_files_count=0 }, proximal_output_level_progress=SubcompactionProgressPerLevel{ num_processed_output_records=0, output_files_count=0, last_persisted_output_files_count=0 } }
Progress Record 2:
SubcompactionProgress{ next_internal_key_to_compact=user_key="cancel_before_this_key" (hex:63616E63656C5F6265666F72655F746869735F6B6579), seq=kMaxSequenceNumber, type=24, num_processed_input_records=3, output_level_progress=SubcompactionProgressPerLevel{ num_processed_output_records=3, output_files_count=1, last_persisted_output_files_count=0 }, proximal_output_level_progress=SubcompactionProgressPerLevel{ num_processed_output_records=0, output_files_count=0, last_persisted_output_files_count=0 } }

Total records: 3
```

Reviewed By: jaykorean

Differential Revision: D84840680

Pulled By: hx235

fbshipit-source-id: 8e448c50348eb1dba92c4ffdbd2d1bb6306288d6
2025-10-17 10:54:25 -07:00
Xingbo Wang a1dad12c8c Reduce github CI build time (#14057)
Summary:
* Reduce build time of folly from 45m~1hr down to 25m. This is achieved by caching folly build artifact from previous build.
* Reduce windows build time of folly from 1hr 15m down to 50m. This is done by increase windows build machine size.
* Fix build on macos on other macos target.

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

Test Plan: github CI

Reviewed By: archang19, nmk70

Differential Revision: D84848041

Pulled By: xingbowang

fbshipit-source-id: 00306750737070e7e446ee436d607ed6ecae79ae
2025-10-16 17:51:55 -07:00
Jay Huh 42842edc8d Use new TableFactory for each remote compaction in stress test (#14050)
Summary:
We simulate remote compaction in our stress test by running a separate set of worker threads to run compactions. In reality, these remote compactions run on a different host or (at least in a different process) where we cannot share the TableFactory and BlockCache with the main DB process.

To make this simulated remote compaction closer to reality, create a new TableFactory for each remote compaction in stress test.

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

Test Plan:
```
python3 -u tools/db_crashtest.py --cleanup_cmd='' --simple blackbox --remote_compaction_worker_threads=8 --interval=10
```

Reviewed By: hx235

Differential Revision: D84775656

Pulled By: jaykorean

fbshipit-source-id: d6203fcbe0eca3539e008a19fd47b742553537ed
2025-10-15 22:01:49 -07:00
Xingbo Wang 1d18c4ed01 Reduce macos github CI build time (#14048)
Summary:
We are adding more and more tests, so we need to increase the number of shards in macos build to reduce overall CI time.

macos-15-xlarge image is ARM, which has 5 vCPU cores, but is still 50% faster than the intel x86 12 vCPU.

Test time reduced from 1h 37m to 14m.

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

Reviewed By: archang19

Differential Revision: D84741917

Pulled By: xingbowang

fbshipit-source-id: 9ba9bd696d3b2152f11dec2fb4280572b98233d5
2025-10-15 20:40:05 -07:00
Hui Xiao f7e4009de1 Integrate compaction resumption with DB::OpenAndCompact() (#13984)
Summary:
### Context/Summary:
This is stacked on top of https://github.com/facebook/rocksdb/pull/13983 and integrate compaction resumption with OpenAndCompact().

Flow of resuming: DB::OpenAndCompact() -> Compaction progress file  -> SubcompactionProgress -> CompactionJob
Flow of persistence: CompactionJob -> SubcompactionProgress -> Compaction progress file  -> DB that is called with OpenAndCompact()

This PR focuses on DB::OpenAndCompact() -> Compaction progress file  -> SubcompactionProgress  and Compaction progress file -> DB that is called with OpenAndCompact()

**Resume Flow**
1. Check configuration. Right now paranoid_file_check=true (by default false) is not yet compatible with allow_resumption=true. Also only single subcompaction is supported as OpenAndCompact() does not partition compaction anyway
2. Scan compaction output files for latest, old and temporary compaction progress file and output files. If latest compaction progress file exists, we should resume.
3. Clean up older or temporary progress files if any. They can exist if the last OpenAndCompact() crashed during resume flow
4. If any, parse the latest progress file into CompactionProgress and clean up extra compaction output files that are not yet tracked. These compaction output files can exist as tracking every output file is just best-effort and interrupted output files in the middle is not tracked as progress yet.
5. If allow_resumption=false or no valid compaction progress is found or parsed, clean up the latest progress file and existing compaction output files to start fresh compaction. If the clean up itself fails, fail the OpenAndCompact() call to prevent resuming with inconsistency between output files and progress file.

 **Progress File Creation**
1. Create temporary progress file
2. Persist the progress from latest compaction progress file to the temporary progress file. This is to simplify resuming from an interrupted compaction that was just resumed. Similar to how manifest recovery works.
3. Rename the temporary progress file to the newer compaction progress so it atomically becomes the "new" latest progress file
4. Delete the "old" latest progress file since it's useless now.

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

Test Plan:
- Integrated unit tests to simulate OpenAndCompact gets canceled and optionally resumed for remote compaction
- Existing UTs and stress/crash test
- Manual stress test with https://github.com/facebook/rocksdb/pull/14041

### Performance testing:
**1. Latency**
Using
```
./db_bench --benchmarks=OpenAndCompact[X5] --openandcompact_test_cancel_on_odd=false --openandcompact_cancel_after_seconds=0 --openandcompact_allow_resumption=$openandcompact_allow_resumption  --use_existing_db=true --db=$db --disable_auto_compactions=true --compression_type=none --secondary_path=$secondary_path  --target_file_size_base=268435456
```
**allow_resumption = false**
Input files: 101 files, 10000 keys
OpenAndCompact() API call : 26766256.000 micros/op 0 ops/sec 26.766 seconds 1 operations;
OpenAndCompact status: OK
Output: 92 files, average size: 271747380 bytes (259.16 MB)
OpenAndCompact : 27837249.000 micros/op 0 ops/sec 27.837 seconds 1 operations;

Input files: 101 files, 10000 keys
OpenAndCompact() API call : 26546234.000 micros/op 0 ops/sec 26.546 seconds 1 operations;
OpenAndCompact status: OK
Output: 92 files, average size: 271747380 bytes (259.16 MB)
OpenAndCompact : 27918621.000 micros/op 0 ops/sec 27.919 seconds 1 operations;
OpenAndCompact [AVG 2 runs] : 0 (± 0) ops/sec

Input files: 101 files, 10000 keys
OpenAndCompact() API call : 42243571.000 micros/op 0 ops/sec 42.244 seconds 1 operations;
OpenAndCompact status: OK
Output: 92 files, average size: 271747380 bytes (259.16 MB)
OpenAndCompact : 43497581.000 micros/op 0 ops/sec 43.498 seconds 1 operations;
OpenAndCompact [AVG 3 runs] : 0 (± 0) ops/sec

Input files: 101 files, 10000 keys
OpenAndCompact() API call : 34241357.000 micros/op 0 ops/sec 34.241 seconds 1 operations;
OpenAndCompact status: OK
Output: 92 files, average size: 271747380 bytes (259.16 MB)
OpenAndCompact : 35655346.000 micros/op 0 ops/sec 35.655 seconds 1 operations;
OpenAndCompact [AVG 4 runs] : 0 (± 0) ops/sec

Input files: 101 files, 10000 keys
OpenAndCompact() API call : 27083361.000 micros/op 0 ops/sec 27.083 seconds 1 operations;
OpenAndCompact status: OK
Output: 92 files, average size: 271747380 bytes (259.16 MB)
OpenAndCompact : 28487999.000 micros/op 0 ops/sec 28.488 seconds 1 operations;
OpenAndCompact [AVG 5 runs] : 0 (± 0) ops/sec

OpenAndCompact [AVG    5 runs] : 0 (± 0) ops/sec; 31669.681 ms/op
OpenAndCompact [MEDIAN 5 runs] : 0 ops/sec

**allow_resumption= true**
Input files: 101 files, 10000 keys
OpenAndCompact() API call : 25446470.000 micros/op 0 ops/sec 25.446 seconds 1 operations;
OpenAndCompact status: OK
Output: 92 files, average size: 271747380 bytes (259.16 MB)
OpenAndCompact : 26833415.000 micros/op 0 ops/sec 26.833 seconds 1 operations;

Input files: 101 files, 10000 keys
OpenAndCompact() API call : 240745.000 micros/op 0 ops/sec 0.241 seconds 1 operations;
OpenAndCompact status: OK
Output: 92 files, average size: 271747380 bytes (259.16 MB)
OpenAndCompact :  244934.000 micros/op 4 ops/sec 0.245 seconds 1 operations;
OpenAndCompact [AVG 2 runs] : 2 (± 3) ops/sec

Input files: 101 files, 10000 keys
OpenAndCompact() API call : 24843383.000 micros/op 0 ops/sec 24.843 seconds 1 operations;
OpenAndCompact status: OK
Output: 92 files, average size: 271747380 bytes (259.16 MB)
OpenAndCompact : 26192235.000 micros/op 0 ops/sec 26.192 seconds 1 operations;
OpenAndCompact [AVG 3 runs] : 1 (± 2) ops/sec

Input files: 101 files, 10000 keys
OpenAndCompact() API call : 270819.000 micros/op 0 ops/sec 0.271 seconds 1 operations;
OpenAndCompact status: OK
Output: 92 files, average size: 271747380 bytes (259.16 MB)
OpenAndCompact :  275140.000 micros/op 3 ops/sec 0.275 seconds 1 operations;
OpenAndCompact [AVG 4 runs] : 1 (± 2) ops/sec

Input files: 101 files, 10000 keys
OpenAndCompact() API call : 23038311.000 micros/op 0 ops/sec 23.038 seconds 1 operations;
OpenAndCompact status: OK
Output: 92 files, average size: 271747380 bytes (259.16 MB)
OpenAndCompact : 24439097.000 micros/op 0 ops/sec 24.439 seconds 1 operations;
OpenAndCompact [AVG 5 runs] : 1 (± 1) ops/sec

OpenAndCompact [AVG    5 runs] : 1 (± 1) ops/sec; 638.417 ms/op
OpenAndCompact [MEDIAN 5 runs] : 0 ops/sec

**Persistence cost:** If we compare the odd number of OpenAndCompact() API, it's actually faster.
**Resumption saving:** (0.2 - 26.766 ) / 26.766 * 100 = 99.25% improvement when all the compaction progress is redone without the allow_resumption feature

**2.  Memory usage** (in case SubcompactionProgress storing its own memory copies of output filemetadata in https://github.com/facebook/rocksdb/pull/13983/files is a trouble)
 ```
// ~= 90 output files
/usr/bin/time -f "
Resource Summary:
  Wall time: %e seconds
  CPU time: %U user + %S system (%P total)
  Peak memory: %M KB
  Page faults: %F major + %R minor
" ./db_bench --benchmarks=OpenAndCompact[X1] --openandcompact_test_cancel_on_odd=false --openandcompact_cancel_after_seconds=0 --openandcompact_allow_resumption=$openandcompact_allow_resumption  --use_existing_db=true --db=$db --disable_auto_compactions=true --compression_type=none --secondary_path=$secondary_path  --target_file_size_base=268435456
```
**allow_resumption = false**
Peak memory: 275828 KB
**allow_resumption = true**
Peak memory: 277204 KB (regress 0.49% memory usage, most likely due to storing own copies of output files' file metadata in subcompaction progress)

### Near-term follow up:
- Add statistics to record the successfully resumed compaction output files bytes
- Add stress/crash test support to cover error paths (including progress file sync error), crash/cancel OpenAndCompact() at random compaction progress point and surface feature incompatibility
   - See https://github.com/facebook/rocksdb/pull/14041
- Resolve the TODO https://github.com/facebook/rocksdb/pull/13984/files#diff-17fbdec07244b1f07d1a4e5aed0a6feecf4474d20b3129818c10fc0ff9f3d547R1303-R1314
   - See https://github.com/facebook/rocksdb/pull/14042

Reviewed By: jaykorean

Differential Revision: D84299662

Pulled By: hx235

fbshipit-source-id: 69bbf395401604172a1a5c557ca834011a3d51d7
2025-10-15 13:43:53 -07:00
anand76 112ff5bb70 Allow empty MultiScan result in BlockBasedTableIterator Prepare (#14046)
Summary:
Currently in BlockBasedTableIterator's Prepare(), the index lookup for a MultiScan range is expected to return atleast 1 data block (unless UDI is in use). This is because there's an implicit assumption that only ranges intersecting with the keys in the file will be prepared. This assumption, however, doesn't hold if there are range deletions and the smallest and/or largest keys in the file extend beyond the keys in the file. The LevelIterator prunes the MultiScan ranges based on the smallest/largest key, so its possible for a range to only overlap the range deletion portion of the file and not overlap any of the data blocks. Furthermore, the BlockBasedTableIterator is now much more forgiving of Seek to targets outside of prepared ranges after https://github.com/facebook/rocksdb/issues/14040 .

Keeping the above in mind, this PR removes the check in BlockBasedTableIterator for non-empty index result. It adds assertions in LevelIterator to verify that ranges are being properly pruned. Another side effect is we can no longer rely solely on a scan range having 0 data blocks (i.e cur_scan_start_idx >= cur_scan_end_idx) to decide if the iterator is out of bound. We can only do so for all but the last range prepared range.

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

Test Plan:
1. Add unit test in db_iterator_test
2. Run crash test

Reviewed By: xingbowang

Differential Revision: D84623871

Pulled By: anand1976

fbshipit-source-id: 2418e629f92b1c46c555ddea3761140f700819e4
2025-10-14 14:22:29 -07:00
Xingbo Wang 1585f2240c Move the MultiScan seek key check to upper layer (#14040)
Summary:
The current seek key validation is too strict. This change relaxes it at block iterator level, and add additional check at DB iterator level. The new contract is that when MultiScan is used, after prepared is called, each following seek must seek the start key of the prepared scan range in order. Otherwise, the iterator is set with error status.

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

Test Plan: Unit test

Reviewed By: anand1976

Differential Revision: D84292297

Pulled By: xingbowang

fbshipit-source-id: 7b31f727e67e7c0bfc53c2f9a6552e0c3d324869
2025-10-13 12:48:04 -07:00
anand76 04c085a3fa Disable skip_stats_update_on_db_open in crash tests for multiscan (#14039)
Summary:
Multi scan crash/stress tests are failing when skip_stats_update_on_db_open is true, because LevelIterator::Prepare relies on these stats in FileMetaData to make decisions. Disable it in crash tests until the proper fix is ready.

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

Reviewed By: archang19

Differential Revision: D84280059

Pulled By: anand1976

fbshipit-source-id: f9f58b94c24d1f455432b05f3bf97f25c7233e3c
2025-10-09 17:31:54 -07:00
Peter Dillinger 2d331cc125 Blog post for parallel compression revamp (#14035)
Summary:
self-explanatory. First drafts using AI then heavily revised. AI help with diagram also.

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

Test Plan: https://pdillinger.github.io/rocksdb/blog/2025/10/08/parallel-compression-revamp.html

Reviewed By: hx235

Differential Revision: D84277660

Pulled By: pdillinger

fbshipit-source-id: 4d76f60f3f7304392836fa4df7a819e67d531a52
2025-10-09 16:55:27 -07:00
Hui Xiao f722e68d88 New FlushWAL() API to take extra fields such as rate limiter priority (#14037)
Summary:
**Context/Summary:**
There is no way to tag or rate-limit write IO occurs during FlushWAL() with priority. Under `Options::manual_wal_flush=true`, it is the major source of write IO during user writes so we decide to add that support. A new option struct `FlushWALOptions` is introduced to avoid making the API ugly for future new fields.

Also, we can't use the WriteOptions (https://github.com/facebook/rocksdb/blob/main/include/rocksdb/options.h#L2293-L2302 i) since is associated with that particular Put/Merge/.. associated with that option but FlushWAL() can happen after that write. There is no way to carry that write option over in RocksDB. I also avoided using the WriteOptions since it's mostly for live write.

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

Test Plan: New UTs `TEST_P(DBRateLimiterOnManualWALFlushTest, ManualWALFlush)`

Reviewed By: archang19

Differential Revision: D84193522

Pulled By: hx235

fbshipit-source-id: 18feb5235672010d19a101ce52c8abdcc4a789f2
2025-10-09 14:31:47 -07:00
Jay Huh cbfcac8d1d Stress Test Improvement (#14022)
Summary:
- Include Status in RemoteCompactionResultMap in SharedState so that we can directly check the status of the remote compaction in `DbStressCompactionService::Wait()`
- If result is empty, populate the result with the status that was returned from `GetRemoteCompactionResult()` so that the status can be bubbled up to the primary (main db thread)
- Get rid of Timeout in `Wait()`

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

Test Plan:
With fall-back
```
python3 -u tools/db_crashtest.py blackbox --remote_compaction_worker_threads=8 --remote_compaction_failure_fall_back_to_local=1
```

Without fall-back
```
python3 -u tools/db_crashtest.py blackbox --remote_compaction_worker_threads=8 --remote_compaction_failure_fall_back_to_local=0
```

Reviewed By: hx235

Differential Revision: D83789172

Pulled By: jaykorean

fbshipit-source-id: 08f710c4ece5fcc1d4b95b3f9c353831882851b7
2025-10-07 17:31:59 -07:00
anand76 5ace84ebae Pass the correct comparator to MultiScanArgs (#14033)
Summary:
Fix assertion failure in crash tests with timestamp due to the wrong comparator passed to MultiScanArgs

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

Reviewed By: xingbowang

Differential Revision: D84036954

Pulled By: anand1976

fbshipit-source-id: 526be21c0754dcccf8e4d2b9fba33716fe35860a
2025-10-07 08:35:28 -07:00
anand76 194160d534 Use wget for folly dependency download (#14030)
Summary:
Fix the binutils truncated download issue by switching to wget in the folly build scripts for downloading dependencies.

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

Test Plan: make build_folly

Reviewed By: jaykorean

Differential Revision: D84033126

Pulled By: anand1976

fbshipit-source-id: bc6706d7e57c97d6edff149a965aa12c7959825f
2025-10-07 02:13:35 -07:00
anand76 4ab1bc865c Disable standlone delete range file ingest in db_crashtest.py if multiscan enabled (#14026)
Summary:
MultiScan currently doesn't handle delete range properly. In this specific case, a file with only delete range will have an empty index resulting in BlockBasedTableIterator wrongly thinking that a scan doesn't intersect the file due to empty result.

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

Test Plan: Run crash test

Reviewed By: xingbowang

Differential Revision: D83881266

Pulled By: anand1976

fbshipit-source-id: dc1faa494ea23f36391b700dd1ee0430a1f20ac5
2025-10-06 18:47:24 -07:00
Xingbo Wang 27625f4fc2 Fix range delete file caused MultiScan issue (#14028)
Summary:
When there is an ingested SST file that only contains delete range operations, MultiScan may return error "Scan does not intersect with file". This is due to file selection during Prepare uses the file smallest and largest key without considering whether there is any key in the file. This is only a temporary fix.

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

Test Plan: Unit test

Reviewed By: anand1976

Differential Revision: D83986964

Pulled By: xingbowang

fbshipit-source-id: e0961ca854e2062c2457be4324817ba073ae785d
2025-10-06 14:35:15 -07:00
anand76 bdf5a8fffb Avoid reseeking upon skipping too many keys in crash tests (#14015)
Summary:
Implicit reseek in the middle of an iteration is not supported with MultiScan. Avoid this for now in crash tests by setting max_sequential_skip_in_iterations to an absurdly high value.

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

Reviewed By: xingbowang

Differential Revision: D83761612

Pulled By: anand1976

fbshipit-source-id: 16f4e856374b79170c0a79c11c275cbb0fc83a70
2025-10-03 18:16:33 -07:00
Pierre Moulon 2fab774697 Typo fix (#14024)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14024

Fix some typo found along the codebase

Reviewed By: pdillinger

Differential Revision: D83789182

fbshipit-source-id: feb24d7d47a6faaf735fcfd50dd3ecce4a6c8cd5
2025-10-03 14:28:37 -07:00
Xingbo Wang 7c22fbe0d5 Disable a param combo in crash test to fix a data race (#14023)
Summary:
When inplace_update_support and memtable_veirfy_per_key_checksum_on_seek are enabled at the same time, it would cause data race in memtable.

inplace_update_support allows key/value pair in place update in memtable.

memtable_veirfy_per_key_checksum_on_seek performs key checksum verification during seek. It is possible that one thread is updating the key/value pair in place, while another thread is reading the key/value pair for checksum verification during seek.

Therefore, there these 2 configurations could not be enabled at the same time

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

Test Plan: local stress test run stops reporting race condition

Reviewed By: anand1976

Differential Revision: D83812322

Pulled By: xingbowang

fbshipit-source-id: 6cb9f0f3faa8deba97305bfe87266f2fe78e0501
2025-10-03 00:01:02 -07:00
Peter Dillinger 9d3afcf543 Fix regression in LZ4 compression performance since 10.6 (#14017)
Summary:
In RocksDB 10.6 with https://github.com/facebook/rocksdb/issues/13805, due to inaccurate testing of an async system, it went undetected at the time that LZ4 compression was using more CPU despite making a change to reuse stream objects which dramatically improved LZ4HC compression efficiency.

This change switches to using a basic LZ4 compress API which appears to be faster than all of these:
* Legacy behavior of creating LZ4_stream_t for each compression
* 10.6-10.7 behavior of re-using streams between compressions for the same file (with stream-as-WorkingArea)
* using LZ4's extState APIs without streams (with extState-as-WorkingArea) (data not shown in below results)

Also in this PR: more improvements to sst_dump --recompress, which is arguably the best SST construction benchmark right now since db_bench seems to be so noisy due to backgroun flush+compaction, even with no compaction (FIFO). Streamlined some output and added a SST read time test, mostly for decompression performance.

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

Test Plan:
Performance test using sst_dump --recompress with newer sst_dump back-ported to 10.5:
```
./sst_dump --command=recompress --compression_types=kLZ4Compression
test5.sst --compression_level_from=-6 --compression_level_to=-1
```
and with default compression level.

10.5:
```
Cx level: -6    Cx size:   61608137 Write usec:     880404
Cx level: -5    Cx size:   60793749 Write usec:     840903
Cx level: -4    Cx size:   58134030 Write usec:     836365
Cx level: -3    Cx size:   55193773 Write usec:     857113
Cx level: -2    Cx size:   54013891 Write usec:     855642
Cx level: -1    Cx size:   50400393 Write usec:     865194
Cx level: 32767 Cx size:   50400393 Write usec:     886310
```

Before this change (showing the regression, more time, from 10.6:
```
Cx level: -6    Cx size:   61608137 Write usec:     933448
Cx level: -5    Cx size:   60793749 Write usec:     893826
Cx level: -4    Cx size:   58134030 Write usec:     891138
Cx level: -3    Cx size:   55193773 Write usec:     898461
Cx level: -2    Cx size:   54013891 Write usec:     897485
Cx level: -1    Cx size:   50400393 Write usec:     936970
Cx level: 32767 Cx size:   50400393 Write usec:     958764
```

After this change (faster than both the above):
```
Cx level: -6    Cx size:   63641883 Write usec:     874190
Cx level: -5    Cx size:   58860032 Write usec:     834662
Cx level: -4    Cx size:   57150188 Write usec:     832707
Cx level: -3    Cx size:   58791894 Write usec:     850305
Cx level: -2    Cx size:   53145885 Write usec:     839574
Cx level: -1    Cx size:   49809139 Write usec:     845639
Cx level: 32767 Cx size:   49809139 Write usec:     875199
```

Similar tests with dictionary compression show essentially no difference (need to use stream APIs and reuse doesn't seem to matter). LZ4HC also unaffected (still improved vs. 10.5)

Reviewed By: hx235

Differential Revision: D83722880

Pulled By: pdillinger

fbshipit-source-id: 30149dd187686d5dd98321e6aa7d74bd7653a905
2025-10-02 08:34:08 -07:00
Xingbo Wang 742741b175 Support Super Block Alignment (#13909)
Summary:
Pad block based table based on super block alignment

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

Test Plan:
Unit Test

No impact on perf observed due to change in the inner loop of flush.

upstream/main branch 202.15 MB/s
```
for i in `seq 1 10`; do ./db_bench --benchmarks=fillseq -num=10000000 -compaction_style=2 -fifo_compaction_max_table_files_size_mb=1000 -fifo_compaction_allow_compaction=0 -disable_wal -write_buffer_size=12000000 -format_version=7 >> /tmp/x1 2>&1; grep fillseq /tmp/x1 | grep -Po "\d+\.\d+ MB/s" | grep -Po "\d+\.\d+" | awk '{sum+=$1} END {print sum/NR}'
```

After the change without super block alignment 203.44 MB/s
```
for i in `seq 1 10`; do ./db_bench --benchmarks=fillseq -num=10000000 -compaction_style=2 -fifo_compaction_max_table_files_size_mb=1000 -fifo_compaction_allow_compaction=0 -disable_wal -write_buffer_size=12000000 -format_version=7 >> /tmp/x1 2>&1
```

After the change with super block alignment 204.47 MB/s
```
for i in `seq 1 10`; do ./db_bench --benchmarks=fillseq -num=10000000 -compaction_style=2 -fifo_compaction_max_table_files_size_mb=1000 -fifo_compaction_allow_compaction=0 -disable_wal -write_buffer_size=12000000 -format_version=7 --super_block_alignment_size=131072 --super_block_alignment_max_padding_size=4096 >> /tmp/x1 2>&1;
```

Reviewed By: pdillinger

Differential Revision: D83068913

Pulled By: xingbowang

fbshipit-source-id: eecd65088ab3e9dbc7902aab8c2580f1bc8575df
2025-10-01 18:20:35 -07:00
Hui Xiao 1e5fa69c99 Resuming and persisting subcompaction progress in CompactionJob (#13983)
Summary:
### Context/Summary:
Flow of resuming: DB::OpenAndCompact() -> Compaction progress file  -> SubcompactionProgress -> CompactionJob
Flow of persistence: CompactionJob -> SubcompactionProgress -> Compaction progress file  -> DB that is called with OpenAndCompact()

This PR focuses on SubcompactionProgress -> CompactionJob and  CompactionJob -> SubcompactionProgress -> Compaction progress file. For now only single subcompaction is supported as OpenAndCompact() does not partition compaction anyway.

The actual triggering of progress persistence and resuming (i.e, integration) is through DB::OpenAndCompact() in the upcoming PR.

**Resume Flow**
1. input_iter->Seek(next_internal_key_to_compact)  // Position iterator
2. ReadTableProperties()                           // Validate existing outputs
3. RestoreCompactionOutputs() in CompactionOutputs                     // Rebuild output file metadata
4. Restore critical statistics about processed input and output records count for verification later
5. AdvanceFileNumbers()                            // Prevent file number conflicts
6. Continue normal compaction from positioned iterator or fallback to not resuming compaction in limited case or fail the compaction entirely

**Persistence Strategy**
1. When: At each SST file completion (FinishCompactionOutputFile()). This is the simplest but most expensive frequency. See below for benchmarking and potential follow-up items
2. What: Serialize, write and sync the in-memory SubcompactionProgress to a dedicated manifest-like file
3. For simplicity: Only persist at "clean" boundaries (no overlapping user keys, no range deletions, no timestamp for now)

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

Test Plan:
- New unit test in CompactionJob level to cover basic compaction progress resumption
- Existing UTs and stress/crash test to test no correctness regression to existing compaction code
- Run benchmark to ensure no performance regression to existing compaction code
```
./db_bench --benchmarks=fillseq[-X10] --db=$db --disable_auto_compactions=true --num=100000 --value_size=25000 --compression_type=none --target_file_size_base=268435456 --write_buffer_size=268435456
```
Pre-PR:
fillseq [AVG    10 runs] : 45127 (± 799) ops/sec; 1076.6 (± 19.1) MB/sec
fillseq [MEDIAN 10 runs] : 45375 ops/sec; 1082.5 MB/sec
Post-PR (regressed 0.057%, ignorable)
fillseq [AVG    10 runs] : 45101 (± 920) ops/sec; 1076.0 (± 22.0) MB/sec
fillseq [MEDIAN 10 runs] : 45385 ops/sec; 1082.8 MB/sec

Reviewed By: jaykorean

Differential Revision: D82889188

Pulled By: hx235

fbshipit-source-id: 8553fd478f134969d331af2c5a125b94bd747268
2025-10-01 14:21:55 -07:00
ngina 13172e2be3 Add method to estimate index size (#14010)
Summary:
This method will be used to improve the compaction logic by accounting for the tail size, in addition to the data size,  when determining when to cut a file.

Problem: Currently the file cutting logic only considers data size when determining where to cut a file, failing to reserve space for index and filter blocks that are added when the file is finalized.

Key changes:
- Add EstimateCurrentIndexSize() to IndexBuilder interface
- Implement in ShortenedIndexBuilder with buffer that accounts for the next index entry. The buffer addresses under-estimation where the current index size doesn't account for the next index entry associated with the data block currently being built. The 2x multiplier bounds the estimate in the right direction and handles outlier cases with large keys.
- Add num_index_entries_ member to track added index entries (== data blocks emitted). This is thread-safe since it's updated/read in the serialized emit step.

Next steps:
- Partitioned index size estimation implementation
- Update compaction file cutting logic to consider index size estimation

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

Test Plan: Added a new test class with unit tests for new builder size estimation across all IndexBuilder implementations.

Reviewed By: pdillinger

Differential Revision: D83501741

Pulled By: nmk70

fbshipit-source-id: d58fc2a9e92e12a162f6244d4abd707a9c9e1885
2025-10-01 07:38:08 -07:00
anand76 035242415f Fix incorrect MultiScan handling of range limit between files (#14011)
Summary:
This PR fixes a bug in how MultiScan handled a scan range limit falling in the key range between files. The bug was in LevelIterator, where Prepare() relied on FindFile to determine the lower bound file for the range limit. FindFile returns the smallest file index with `range.limit < file.largest_key`. However, that doesn't guarantee that the range overlaps the file, as the `range.limit` could be smaller than `file.smallest_key`.

This also fixes a bug in BlockBasedTableIterator of Valid() returning true even if status() returned error. This was exposed by the previous bug.

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

Test Plan: Add unit tests in db_iterator_test and table_test

Reviewed By: cbi42

Differential Revision: D83496439

Pulled By: anand1976

fbshipit-source-id: a9d2d138d69d0c816d9f4160a984b273d00d683f
2025-09-30 11:45:49 -07:00
Peter Dillinger f5fb597bac Resolve missing/inconsistent tickers in Java (#14012)
Summary:
Pretty self-explanatory from the changes, including re-arranging the "COOL" entries for easier tracking of which values are used.

I'm not touching the TICKER_ENUM_MAX issue because IIRC we've gotten in trouble in the past for changing any Java ticker values.

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

Test Plan: CI, sufficient prompts to get AI to discover the known issues relayed by hx235, to help ensure we found any other outstanding issues.

Reviewed By: hx235

Differential Revision: D83497503

Pulled By: pdillinger

fbshipit-source-id: ec0bd7e28188e0430fb03fc5bd79c2ed7b28f3ad
2025-09-29 14:21:00 -07:00
Hui Xiao d8c058c5fe Blog about unified memory limit (#14002)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/14002

Test Plan: verify according to https://github.com/facebook/rocksdb/tree/main/docs

Reviewed By: jaykorean

Differential Revision: D83209262

Pulled By: hx235

fbshipit-source-id: 688c855387e08c9b22644d4de3bc539e51a0ba0a
2025-09-29 10:55:16 -07:00
Jay Huh feb1486e37 No StandaloneRangeDeletionFile Optimization for Leveled Compaction (#14007)
Summary:
In https://github.com/facebook/rocksdb/pull/13816, we added `earliest_snapshot` in the Compaction object picked by remote compaction which is required for Standalone Range Deletion Optimization (introduced in https://github.com/facebook/rocksdb/pull/13078)

The Standalone Range Deletion Optimization was supposed to be supported by Universal Compaction only. This PR properly skips the feature when the compaction style is not kUniversal

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

Test Plan:
Unit Test updated to include Leveled Compaction
```
./compaction_service_test --gtest_filter="*CompactionServiceTest.StandaloneDeleteRangeTombstoneOptimization*"
```

In Stress Test, we were able to repro before, but not anymore
```
./db_stress --WAL_size_limit_MB=0 --WAL_ttl_seconds=60 --acquire_snapshot_one_in=10000 --adaptive_readahead=1 --adm_policy=2 --advise_random_on_open=0 --allow_data_in_errors=True --allow_fallocate=1 --allow_setting_blob_options_dynamically=0 --allow_unprepared_value=1 --async_io=1 --atomic_flush=1 --auto_readahead_size=1 --auto_refresh_iterator_with_snapshot=1 --avoid_flush_during_recovery=0 --avoid_flush_during_shutdown=0 --avoid_unnecessary_blocking_io=0 --backup_max_size=104857600 --backup_one_in=1000 --batch_protection_bytes_per_key=0 --bgerror_resume_retry_interval=100 --block_align=0 --block_protection_bytes_per_key=0 --block_size=16384 --bloom_before_level=2147483647 --bloom_bits=3.4547746144863423 --bottommost_compression_type=lz4hc --bottommost_file_compaction_delay=3600 --bytes_per_sync=262144 --cache_index_and_filter_blocks=0 --cache_index_and_filter_blocks_with_high_priority=0 --cache_size=8388608 --cache_type=tiered_fixed_hyper_clock_cache --charge_compression_dictionary_building_buffer=1 --charge_file_metadata=0 --charge_filter_construction=1 --charge_table_reader=0 --check_multiget_consistency=0 --check_multiget_entity_consistency=0 --checkpoint_one_in=0 --checksum_type=kxxHash64 --clear_column_family_one_in=0 --compact_files_one_in=1000000 --compact_range_one_in=1000 --compaction_pri=3 --compaction_readahead_size=1048576 --compaction_style=0 --compaction_ttl=2 --compress_format_version=1 --compressed_secondary_cache_ratio=0.5 --compressed_secondary_cache_size=0 --compression_checksum=1 --compression_manager=mixed --compression_max_dict_buffer_bytes=15 --compression_max_dict_bytes=16384 --compression_parallel_threads=1 --compression_type=lz4hc --compression_use_zstd_dict_trainer=1 --compression_zstd_max_train_bytes=0 --continuous_verification_interval=0 --daily_offpeak_time_utc= --data_block_index_type=1 --db=/tmp/jewoongh/rocksdb_crashtest_blackbox_remote_compaction --db_write_buffer_size=1048576 --decouple_partitioned_filters=1 --default_temperature=kWarm --default_write_temperature=kWarm --delete_obsolete_files_period_micros=21600000000 --delpercent=4 --delrangepercent=1 --destroy_db_initially=0 --detect_filter_construct_corruption=0 --disable_file_deletions_one_in=1000000 --disable_manual_compaction_one_in=10000 --disable_wal=1 --dump_malloc_stats=0 --enable_blob_files=0 --enable_blob_garbage_collection=0 --enable_checksum_handoff=1 --enable_compaction_filter=0 --enable_compaction_on_deletion_trigger=1 --enable_custom_split_merge=0 --enable_do_not_compress_roles=0 --enable_index_compression=0 --enable_memtable_insert_with_hint_prefix_extractor=0 --enable_pipelined_write=0 --enable_sst_partitioner_factory=1 --enable_thread_tracking=1 --enable_write_thread_adaptive_yield=0 --error_recovery_with_no_fault_injection=0 --exclude_wal_from_write_fault_injection=0 --expected_values_dir=/tmp/jewoongh/rocksdb_crashtest_expected_remote_compaction --fifo_allow_compaction=1 --file_checksum_impl=none --file_temperature_age_thresholds= --fill_cache=1 --flush_one_in=1000 --format_version=6 --get_all_column_family_metadata_one_in=1000000 --get_current_wal_file_one_in=0 --get_live_files_apis_one_in=1000000 --get_properties_of_all_tables_one_in=100000 --get_property_one_in=1000000 --get_sorted_wal_files_one_in=0 --hard_pending_compaction_bytes_limit=274877906944 --high_pri_pool_ratio=0 --index_block_restart_interval=7 --index_shortening=2 --index_type=2 --ingest_external_file_one_in=0 --ingest_wbwi_one_in=500 --initial_auto_readahead_size=0 --inplace_update_support=0 --iterpercent=10 --key_len_percent_dist=1,30,69 --key_may_exist_one_in=100 --last_level_temperature=kUnknown --level_compaction_dynamic_level_bytes=0 --lock_wal_one_in=1000000 --log_file_time_to_roll=60 --log_readahead_size=16777216 --long_running_snapshots=1 --low_pri_pool_ratio=0 --lowest_used_cache_tier=2 --manifest_preallocation_size=5120 --manual_wal_flush_one_in=0 --mark_for_compaction_one_file_in=10 --max_auto_readahead_size=16384 --max_background_compactions=2 --max_bytes_for_level_base=10485760 --max_key=100000 --max_key_len=3 --max_log_file_size=1048576 --max_manifest_file_size=1073741824 --max_sequential_skip_in_iterations=8 --max_total_wal_size=0 --max_write_batch_group_size_bytes=16 --max_write_buffer_number=3 --max_write_buffer_size_to_maintain=4194304 --memtable_avg_op_scan_flush_trigger=2 --memtable_insert_hint_per_batch=0 --memtable_max_range_deletions=0 --memtable_op_scan_flush_trigger=100 --memtable_prefix_bloom_size_ratio=0.1 --memtable_protection_bytes_per_key=4 --memtable_veirfy_per_key_checksum_on_seek=0 --memtable_whole_key_filtering=1 --metadata_charge_policy=1 --metadata_read_fault_one_in=1000 --metadata_write_fault_one_in=0 --min_write_buffer_number_to_merge=1 --mmap_read=1 --mock_direct_io=False --multiscan_use_async_io=0 --nooverwritepercent=1 --num_bottom_pri_threads=20 --num_file_reads_for_auto_readahead=1 --open_files=-1 --open_metadata_read_fault_one_in=0 --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_hits=1 --optimize_filters_for_memory=1 --optimize_multiget_for_io=1 --paranoid_file_checks=1 --paranoid_memory_checks=0 --partition_filters=1 --partition_pinning=1 --pause_background_one_in=10000 --periodic_compaction_seconds=0 --prefix_size=-1 --prefixpercent=0 --prepopulate_block_cache=0 --preserve_internal_time_seconds=36000 --progress_reports=0 --promote_l0_one_in=0 --read_amp_bytes_per_bit=0 --read_fault_one_in=32 --readahead_size=16384 --readpercent=50 --recycle_log_file_num=0 --remote_compaction_failure_fall_back_to_local=1 --remote_compaction_worker_threads=8 --reopen=0 --report_bg_io_stats=1 --reset_stats_one_in=1000000 --sample_for_compression=5 --secondary_cache_fault_one_in=32 --secondary_cache_uri= --set_options_one_in=1000 --skip_stats_update_on_db_open=0 --snapshot_hold_ops=100000 --soft_pending_compaction_bytes_limit=68719476736 --sqfc_name=foo --sqfc_version=2 --sst_file_manager_bytes_per_sec=104857600 --sst_file_manager_bytes_per_truncate=0 --statistics=1 --stats_dump_period_sec=600 --stats_history_buffer_size=1048576 --strict_bytes_per_sync=0 --subcompactions=4 --sync=0 --sync_fault_injection=0 --table_cache_numshardbits=6 --target_file_size_base=524288 --target_file_size_multiplier=2 --test_batches_snapshots=0 --test_ingest_standalone_range_deletion_one_in=0 --test_secondary=0 --top_level_index_pinning=2 --track_and_verify_wals=0 --uncache_aggressiveness=72 --universal_max_read_amp=0 --universal_reduce_file_locking=1 --unpartitioned_pinning=1 --use_adaptive_mutex=0 --use_adaptive_mutex_lru=1 --use_attribute_group=0 --use_delta_encoding=0 --use_direct_io_for_flush_and_compaction=0 --use_direct_reads=0 --use_full_merge_v1=1 --use_get_entity=1 --use_merge=1 --use_multi_cf_iterator=1 --use_multi_get_entity=0 --use_multiget=0 --use_multiscan=0 --use_put_entity_one_in=0 --use_sqfc_for_range_queries=1 --use_timed_put_one_in=0 --use_write_buffer_manager=1 --user_timestamp_size=0 --value_size_mult=32 --verification_only=0 --verify_checksum=1 --verify_checksum_one_in=1000000 --verify_compression=0 --verify_db_one_in=100000 --verify_file_checksums_one_in=0 --verify_iterator_with_expected_state_one_in=5 --verify_sst_unique_id_in_manifest=1 --wal_bytes_per_sync=0 --wal_compression=zstd --write_buffer_size=1048576 --write_dbid_to_manifest=1 --write_fault_one_in=0 --write_identity_file=0 --writepercent=35
```

Reviewed By: hx235

Differential Revision: D83375779

Pulled By: jaykorean

fbshipit-source-id: 6dad06e3a825c4e9a7101ab8603d1c966be6a4f4
2025-09-29 09:26:59 -07:00
Hui Xiao c0e484c36e Blog about IO tagging (#14005)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/14005

Test Plan: verify according to https://github.com/facebook/rocksdb/tree/main/docs

Reviewed By: archang19

Differential Revision: D83365540

Pulled By: hx235

fbshipit-source-id: b674aca6a9977721b64cafcdfaf8690d1c5940b7
2025-09-26 15:57:06 -07:00
Xingbo Wang 3d53af9746 Allow passing comparator in UDI (#14001)
Summary:
Pass the comparator to UDI interface for both reader and builder.

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

Test Plan: Unit test

Reviewed By: anand1976

Differential Revision: D83339943

Pulled By: xingbowang

fbshipit-source-id: 7f6541776b0995260e28224329f0cca37f13b3d4
2025-09-26 15:32:50 -07:00
Peter Dillinger e859c3b7af Improve version macros (#14004)
Summary:
* Delete obsolete double-underscore version macros, `__ROCKSDB_MAJOR__` etc.
* Add convenient ROCKSDB_VERSION_GE(x, y, z) macro for conditional compilation

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

Test Plan: Unit test added

Reviewed By: jaykorean

Differential Revision: D83264938

Pulled By: pdillinger

fbshipit-source-id: 23dcfb2760751fb87e232b8e0bbda610fd4ac73c
2025-09-25 17:35:23 -07:00
Changyu Bi 862438a7a1 Fix handling of out-of-range scan option (#13995)
Summary:
currently BlockBasedTableIterator::Prepare() fails the iterator with non-ok status if an out-of-range scan option is detected. This is due to the interaction between LevelIterator and BlockBasedTableIterator, see added comment above BlockBasedTableIterator::Prepare(). This can fail stress test for L0 files since it doesn't use LevelIterator and scan options are not pruned. This PR fixes this by adding an internal option to MultiScanArgs that enables this check.

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

Test Plan:
- new unit test
- stress test that fails before this pr: `python3 -u ./tools/db_crashtest.py whitebox --iterpercent=60 --prefix_size=-1 --prefixpercent=0 --readpercent=0 --test_batches_snapshots=0 --use_multiscan=1 --read_fault_one_in=0 --kill_random_test=88888 --interval=60 --multiscan_use_async_io=0 --mmap_read=0 --level0_file_num_compaction_trigger=20`

Reviewed By: anand1976

Differential Revision: D83166088

Pulled By: cbi42

fbshipit-source-id: 241a7d43c8c00d9a98eea0cabb03d2174d51aae5
2025-09-25 17:33:57 -07:00
Peter Dillinger 1c8a012727 Add kCool Temperature (#14000)
Summary:
also requested by internal user, like kIce in https://github.com/facebook/rocksdb/issues/13927

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

Test Plan: unit tests updated

Reviewed By: archang19

Differential Revision: D83200479

Pulled By: pdillinger

fbshipit-source-id: 31f2842d87bcad40227aeee9687ff5772393689c
2025-09-25 11:27:00 -07:00
Andrew Chang 90241e18c8 Add shared mutex field to IODebugContext (#13993)
Summary:
There can be concurrent reads/writes to fields in `IODebugContext`. One example we have seen is for the `cost_info` field which is of type `std::any`. In fact, in RocksDB's async MultiRead implementation, the same `IODebugContext` is re-used across separate async read requests.

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

Test Plan: Update code which reads/writes to `cost_data` to first acquire shared/exclusive lock on the `mutex` field. There should not be any race conditions when async MultiRead is used.

Reviewed By: pdillinger

Differential Revision: D83091423

Pulled By: archang19

fbshipit-source-id: 4db86d33cf162ed39114b1cd115fcd8964c8ff9b
2025-09-24 16:31:13 -07:00
anand76 169f90cdea Allow UDIs with non BytewiseComparator (#13999)
Summary:
Remove the restriction of only using BytewiseComparator(). In a follow on PR, the UDI interface will be updated to take the Comparator as a parameter.

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

Test Plan: Add a unit test in table_test.cc

Reviewed By: cbi42

Differential Revision: D83179747

Pulled By: anand1976

fbshipit-source-id: 60222533c71022aa0701ac61c39268d36ca86338
2025-09-24 14:59:20 -07:00
Peter Dillinger 134cfb6b22 Speed up AutoHCC check in dtor (#13998)
Summary:
In https://github.com/facebook/rocksdb/issues/13964 I changed an expensive DEBUG check in ~AutoHyperClockTable to only run in ASAN builds. It's still expensive so I'm modifying it to scan only about one page beyond what we expect to have written to the anonymous mmap, rather than scanning the whole thing.

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

Test Plan: manually checked that lru_cache_test running time went from 5.0s to 4.0s after the change. Verified that existing unit test ClockCacheTest.Limits uses the full anonymous mmap to be sure it is sized as expected, by temporarily breaking AutoHyperClockTable::Grow() to allow slightly exceeding the anonymous mmap size.

Reviewed By: cbi42

Differential Revision: D83178493

Pulled By: pdillinger

fbshipit-source-id: a2bf093e98bf68b540c073800be7e193021f2692
2025-09-24 14:06:56 -07:00
anand76 6051d843d5 Prohibit unsupported multiscan + delrange combo in crash tests (#13992)
Summary:
This combination causes MultiScan iteration to fail due to internal reseek by the iterator.

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

Reviewed By: cbi42

Differential Revision: D83094631

Pulled By: anand1976

fbshipit-source-id: 96410747d88de391e6d65857d39063d4fb113d65
2025-09-23 20:09:47 -07:00
Xingbo Wang bbd8f0d4bf Bug fix in random seed override support in stress test (#13991)
Summary:
Fix the bug in Improve random seed override support in stress test.

The Bug:
`parser.parse_known_args()` is used to parse command line argument. When it is called without any argument, it uses sys.argv as input parameter. In sys.argv, the first argument is the command itself, so parser.parse_known_args skip the first argument. Meantime, the return value `remain_argv` of `parser.parse_known_args()` does not contain the command itself. When `remain_arg` replaces `sys.argv`, the first argument is treated as the command itself, which is skipped by `parser.parse_known_args()`. In the internal stress test tool, the first argument is `--stress_cmd`, therefore, it is skipped. Instead, the default value `./stress_db` is used. This is why `./stress_db` showed up in the error message. This is also why it works in local, as stress_db is located in the local folder.

The Fix:
When `parser.parse_known_args()` is called first time, the remain_argv is saved as a global variable. It is used in the second call of the `parser.parse_known_args(remain_argv)`. When argument is passed to `parser.parse_known_args` directly, the first argument will not be skipped.

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

Test Plan:
The the value of first argument `--stress_cmd` is parsed correctly, and shown up in the error message.

```
/usr/local/bin/python3 -u tools/db_crashtest.py --stress_cmd=/data/sandcastle/boxes/trunk-hg-full-fbsource/buck-out/v2/gen/fbcode/d7db8b24dd42e2db/internal_repo_rocksdb/repo/__db_stress__/db_stress --cleanup_cmd='' --simple blackbox  --print_stderr_separately
Start with random seed 11107847853133580500
Running blackbox-crash-test with
interval_between_crash=120
total-duration=6000

Use random seed for iteration 8577470137673434540
Traceback (most recent call last):
  File "/home/xbw/workspace/ws1/rocksdb/tools/db_crashtest.py", line 1650, in <module>
    main()
  File "/home/xbw/workspace/ws1/rocksdb/tools/db_crashtest.py", line 1639, in main
    blackbox_crash_main(args, unknown_args)
  File "/home/xbw/workspace/ws1/rocksdb/tools/db_crashtest.py", line 1358, in blackbox_crash_main
    hit_timeout, retcode, outs, errs = execute_cmd(cmd, cmd_params["interval"])
                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/xbw/workspace/ws1/rocksdb/tools/db_crashtest.py", line 1294, in execute_cmd
    child = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/fbcode/platform010/lib/python3.12/subprocess.py", line 1028, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File "/usr/local/fbcode/platform010/lib/python3.12/subprocess.py", line 1957, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: '/data/sandcastle/boxes/trunk-hg-full-fbsource/buck-out/v2/gen/fbcode/d7db8b24dd42e2db/internal_repo_rocksdb/repo/__db_stress__/db_stress'
```

Reviewed By: hx235

Differential Revision: D83068960

Pulled By: xingbowang

fbshipit-source-id: 28334d38a444c6f8525444e15f460ec6b257ef38
2025-09-23 12:35:35 -07:00
anand76 afbbc90b06 Fail multi scan upon Prepare failure or bad scan options (#13974)
Summary:
Return a failure status for multi scan if Prepare fails, or if the scan options are unsupported, instead of falling back on a regular scan. This PR also fixes a bug in LevelIterator that caused max_prefetch_size to be ignored.

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

Test Plan: Add new test in db_iterator_test and table_test

Reviewed By: xingbowang

Differential Revision: D82843944

Pulled By: anand1976

fbshipit-source-id: f12756c40ebd38d8d4e4425e97438b6e766a4663
2025-09-22 18:13:10 -07:00
Hui Xiao eaeafa7819 Revert "Improve random seed override support in stress test (#13952)" (#13989)
Summary:
**Context/Summary**
This reverts commit 73432a3f36. This is due to it mysteriously fails our internal CI running with this change to db_crashtest.py. The root-cause is unknown but the error only reproed with this commit frequently but not the one before it. The error message appears to be the command parsing leading to the db_stress binary can't be found

```
Traceback (most recent call last):
  File "/data/sandcastle/boxes/trunk-hg-full-fbsource/fbcode/internal_repo_rocksdb/repo/tools/db_crashtest.py", line 1638, in <module>
    main()
  File "/data/sandcastle/boxes/trunk-hg-full-fbsource/fbcode/internal_repo_rocksdb/repo/tools/db_crashtest.py", line 1627, in main
    blackbox_crash_main(args, unknown_args)
  File "/data/sandcastle/boxes/trunk-hg-full-fbsource/fbcode/internal_repo_rocksdb/repo/tools/db_crashtest.py", line 1347, in blackbox_crash_main
    hit_timeout, retcode, outs, errs = execute_cmd(cmd, cmd_params["interval"])
                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/data/sandcastle/boxes/trunk-hg-full-fbsource/fbcode/internal_repo_rocksdb/repo/tools/db_crashtest.py", line 1283, in execute_cmd
    child = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/fbcode/platform010/lib/python3.12/subprocess.py", line 1028, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File "/usr/local/fbcode/platform010/lib/python3.12/subprocess.py", line 1957, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: './db_stress'
```

**Test plan**
- Rehearsal crash test

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

Reviewed By: xingbowang

Differential Revision: D83010751

Pulled By: hx235

fbshipit-source-id: d8cfc70564074065b6bb8a3986d6c1011064dd5e
2025-09-22 17:44:16 -07:00
Changyu Bi 54373ba0e8 Revert "Create a new API FileSystem::SyncFile for file sync (#13762)" (#13987)
Summary:
This is causing some internal failure, we decide to revert this for now until we have a proper fix.

This reverts commit 961880b458.

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

Reviewed By: anand1976

Differential Revision: D82990294

Pulled By: cbi42

fbshipit-source-id: 5f5b4d18d0afe47599738d27e11e3eb2d08d88a0
2025-09-22 15:30:24 -07:00
Hui Xiao ab10ea0aac Add in-memory data structures and (de)serialization support for subcompaction progress (#13928)
Summary:
**Context**
Resuming compaction is designed to periodically record the progress of an ongoing compaction and can resume from that saved progress after interruptions such as cancellation, database shutdown, or crashes.

This PR introduces the data structures needed to store  subcompaction progress in memory, along with serialization and deserialization support to persist and parse this progress to/from "a manifest-like compaction progress file" (the actual creation of such file is in upcoming PRs).

Flow of resuming: DB::OpenAndCompact() -> Compaction progress file  -> SubcompactionProgress -> CompactionJob
Flow of persistence: CompactionJob -> SubcompactionProgress -> Compaction progress file  -> DB that is called with OpenAndCompact()

**Summary**
Progress represented by `SubcompactionProgress` will be tracked at the scope of a subcompaction, which is the smallest independent unit of compaction work.

The frequency of recording this progress is once every N compaction output files (to be detailed in future PRs).

When recording, all fields, except for the output files metadata in `SubcompactionProgress`, will directly overwrite the corresponding fields from the last saved progress (See `SubcompactionProgress` and `SubcompactionProgressBuilder` for more).

As a bonus, this PR refactors the file metadata encoding and decoding utilities into two static helper functions, EncodeToNewFile4() and DecodeNewFile4From(), to support subcompaction progress usage.

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

Test Plan:
- Added various `SubcompactionProgressTest` unit tests in version_edit_test.cc to verify basic serialization/deserialization and forward compatibility handling
- Existing UTs and stress/crash test

**Follow up:**
- Move output entry number and file verification to after each file creation so we can remove kNumProcessedOutputRecords persistence support and make resuming compaction work with `paranoid_file_checks=true` (by default false). Output verification will be done before persistence of progress. As long as this follow-up is done before the landing of the integration PR to create the progress file, we can change the manifest-like compaction progress file format freely.

Reviewed By: jaykorean

Differential Revision: D81986583

Pulled By: hx235

fbshipit-source-id: b42766da7d9c2e2f596c892d050c753238d1039f
2025-09-22 15:03:46 -07:00
Changyu Bi eb1d924308 Fix an assertion failure in stress test (#13988)
Summary:
for MultiScan and UDI we start to use bound check from index iterator, so removing this assert here.

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

Test Plan: existing test

Reviewed By: hx235

Differential Revision: D82993180

Pulled By: cbi42

fbshipit-source-id: 442b2e83cb3aef96fc1a825bf733af9ce59c21c1
2025-09-22 14:28:38 -07:00
Josh Kang 7ae602e80a Support output temperature in CompactFiles (#13955)
Summary:
It is useful to be able to specify output temperatures in the CompactFiles API. For example it may be useful to store small L0 files produced by flushes locally, while larger intra-L0 compactions can store the compacted L0 file remotely.

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

Test Plan: New unit tests

Reviewed By: jaykorean

Differential Revision: D82492503

Pulled By: joshkang97

fbshipit-source-id: e1225fe572a15d7c5c30a265762b048a4a9e7f0b
2025-09-22 13:36:26 -07:00
Changyu Bi 3cdd3281ba Update main for 10.8 (#13980)
Summary:
- updated release note
- updated version to 10.8 in version.h
- added 10.7 to check_format_compatible.sh
- did not updated folly commit hash due to some build failure.

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

Reviewed By: xingbowang

Differential Revision: D82882035

Pulled By: cbi42

fbshipit-source-id: b5e0e78570fdd492d592ee77bd3901e4b39c25fb
2025-09-22 08:51:17 -07:00
Changyu Bi 841e364238 Fix flaky unit test IngestDBGeneratedFileTest2.NonZeroSeqno (#13979)
Summary:
the test did not consider the ingestion_option settings that can result in different error message. This PR fixes the relevant check and ensure we have enough randomness in this test.

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

Test Plan: `gtest-parallel --repeat=20 --workers=20 ./external_sst_file_test --gtest_filter="*VaryingOptions/IngestDBGeneratedFileTest2.NonZeroSeqno/*"`

Reviewed By: hx235

Differential Revision: D82873439

Pulled By: cbi42

fbshipit-source-id: b0d74bf26a502ca3db59b4a0ea9717bf7d027400
2025-09-20 00:08:12 -07:00
Xingbo Wang 73432a3f36 Improve random seed override support in stress test (#13952)
Summary:
Support random seed for white box test
Support per iteration random seed override, so that we could skip previous iterations, as sometimes failure happens after a few iterations.
The reason we still need initial random seed is that some of the parameter is initialized before each iteration, and not all of the parameters are randomized again in each iteration. The reason is that we want some of the parameters to be stable across the run.

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

Test Plan:
Example for using per iteration random seed override to jump the to second iteration.

Simulate a normal run. 4205502355970671733 is the seed used for the second iteration.
```
[xbw@devvm16622.vll0 ~/workspace/ws2/rocksdb (plm_stress_fix)]$ /usr/local/bin/python3 -u tools/db_crashtest.py --stress_cmd=./db_stress --cleanup_cmd='' --cf_consistency blackbox --duration=96000 --max_key=2500000 --interval=10 --initial_random_seed_override=10
Start with random seed 10
Running blackbox-crash-test with
interval_between_crash=10
total-duration=96000

Use random seed for iteration 13278846177722289202
Running db_stress with pid=2102945: ./db_stress --WAL_size_limit_MB=1 --WAL_ttl_seconds=0 --acquire_snapshot_one_in=10000 --adaptive_readahead=0 --adm_policy=2 --advise_random_on_open=0 --allow_data_in_errors=True --allow_fallocate=0 --allow_setting_blob_options_dynamically=1 --allow_unprepared_value=0 --async_io=1 --atomic_flush=1 --auto_readahead_size=1 --auto_refresh_iterator_with_snapshot=0 --avoid_flush_during_recovery=0 --avoid_flush_during_shutdown=0 --avoid_unnecessary_blocking_io=0 --backup_max_size=104857600 --backup_one_in=1000 --batch_protection_bytes_per_key=0 --bgerror_resume_retry_interval=1000000 --blob_cache_size=2097152 --blob_compaction_readahead_size=4194304 --blob_compression_type=snappy --blob_file_size=1073741824 --blob_file_starting_level=3 --blob_garbage_collection_age_cutoff=0.0 --blob_garbage_collection_force_threshold=0.5 --block_align=0 --block_protection_bytes_per_key=1 --block_size=16384 --bloom_before_level=1 --bloom_bits=27.321469575655275 --bottommost_compression_type=zstd --bottommost_file_compaction_delay=86400 --bytes_per_sync=0 --cache_index_and_filter_blocks=0 --cache_index_and_filter_blocks_with_high_priority=1 --cache_size=8388608 --cache_type=lru_cache --charge_compression_dictionary_building_buffer=1 --charge_file_metadata=1 --charge_filter_construction=0 --charge_table_reader=0 --check_multiget_consistency=0 --check_multiget_entity_consistency=1 --checkpoint_one_in=0 --checksum_type=kXXH3 --clear_column_family_one_in=0 --compact_files_one_in=1000000 --compact_range_one_in=1000 --compaction_pri=4 --compaction_readahead_size=1048576 --compaction_style=0 --compaction_ttl=0 --compress_format_version=1 --compressed_secondary_cache_ratio=0.0 --compressed_secondary_cache_size=0 --compression_checksum=1 --compression_manager=autoskip --compression_max_dict_buffer_bytes=8589934591 --compression_max_dict_bytes=16384 --compression_parallel_threads=1 --compression_type=lz4 --compression_use_zstd_dict_trainer=1 --compression_zstd_max_train_bytes=0 --continuous_verification_interval=0 --daily_offpeak_time_utc= --data_block_index_type=1 --db=/tmp/rocksdb_crashtest_blackboxs39kubu3 --db_write_buffer_size=0 --decouple_partitioned_filters=1 --default_temperature=kHot --default_write_temperature=kUnknown --delete_obsolete_files_period_micros=30000000 --delpercent=5 --delrangepercent=0 --destroy_db_initially=0 --detect_filter_construct_corruption=0 --disable_file_deletions_one_in=10000 --disable_manual_compaction_one_in=10000 --disable_wal=1 --dump_malloc_stats=0 --enable_blob_files=1 --enable_blob_garbage_collection=0 --enable_checksum_handoff=0 --enable_compaction_filter=0 --enable_compaction_on_deletion_trigger=1 --enable_custom_split_merge=1 --enable_do_not_compress_roles=0 --enable_index_compression=1 --enable_memtable_insert_with_hint_prefix_extractor=0 --enable_pipelined_write=0 --enable_sst_partitioner_factory=1 --enable_thread_tracking=0 --enable_write_thread_adaptive_yield=1 --error_recovery_with_no_fault_injection=1 --exclude_wal_from_write_fault_injection=0 --expected_values_dir=/tmp/rocksdb_crashtest_expected_rvq7p3ow --fifo_allow_compaction=0 --file_checksum_impl=xxh64 --file_temperature_age_thresholds= --fill_cache=1 --flush_one_in=1000000 --format_version=6 --get_all_column_family_metadata_one_in=1000000 --get_current_wal_file_one_in=0 --get_live_files_apis_one_in=1000000 --get_properties_of_all_tables_one_in=1000000 --get_property_one_in=1000000 --get_sorted_wal_files_one_in=0 --hard_pending_compaction_bytes_limit=274877906944 --high_pri_pool_ratio=0 --index_block_restart_interval=15 --index_shortening=2 --index_type=2 --ingest_external_file_one_in=0 --ingest_wbwi_one_in=500 --initial_auto_readahead_size=16384 --inplace_update_support=0 --iterpercent=10 --key_len_percent_dist=1,30,69 --key_may_exist_one_in=100 --last_level_temperature=kWarm --level_compaction_dynamic_level_bytes=0 --lock_wal_one_in=10000 --log_file_time_to_roll=0 --log_readahead_size=0 --long_running_snapshots=1 --low_pri_pool_ratio=0 --lowest_used_cache_tier=2 --manifest_preallocation_size=5120 --manual_wal_flush_one_in=0 --mark_for_compaction_one_file_in=10 --max_auto_readahead_size=0 --max_background_compactions=20 --max_bytes_for_level_base=10485760 --max_key=2500000 --max_key_len=3 --max_log_file_size=1048576 --max_manifest_file_size=32768 --max_sequential_skip_in_iterations=1 --max_total_wal_size=0 --max_write_batch_group_size_bytes=16 --max_write_buffer_number=3 --max_write_buffer_size_to_maintain=1048576 --memtable_avg_op_scan_flush_trigger=20 --memtable_insert_hint_per_batch=1 --memtable_max_range_deletions=0 --memtable_op_scan_flush_trigger=1000 --memtable_prefix_bloom_size_ratio=0.001 --memtable_protection_bytes_per_key=0 --memtable_whole_key_filtering=0 --memtablerep=skip_list --metadata_charge_policy=1 --metadata_read_fault_one_in=0 --metadata_write_fault_one_in=0 --min_blob_size=8 --min_write_buffer_number_to_merge=1 --mmap_read=0 --mock_direct_io=False --nooverwritepercent=1 --num_bottom_pri_threads=1 --num_file_reads_for_auto_readahead=0 --open_files=100 --open_metadata_read_fault_one_in=8 --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_hits=1 --optimize_filters_for_memory=1 --optimize_multiget_for_io=1 --paranoid_file_checks=0 --paranoid_memory_checks=0 --partition_filters=1 --partition_pinning=2 --pause_background_one_in=10000 --periodic_compaction_seconds=0 --prefix_size=1 --prefixpercent=5 --prepopulate_blob_cache=1 --prepopulate_block_cache=1 --preserve_internal_time_seconds=36000 --progress_reports=0 --promote_l0_one_in=0 --read_amp_bytes_per_bit=32 --read_fault_one_in=0 --readahead_size=524288 --readpercent=45 --recycle_log_file_num=0 --remote_compaction_worker_threads=0 --reopen=0 --report_bg_io_stats=1 --reset_stats_one_in=10000 --sample_for_compression=0 --secondary_cache_fault_one_in=32 --secondary_cache_uri=compressed_secondary_cache://capacity=8388608;enable_custom_split_merge=true --set_options_one_in=1000 --skip_stats_update_on_db_open=0 --snapshot_hold_ops=100000 --soft_pending_compaction_bytes_limit=1048576 --sqfc_name=foo --sqfc_version=0 --sst_file_manager_bytes_per_sec=104857600 --sst_file_manager_bytes_per_truncate=0 --statistics=1 --stats_dump_period_sec=0 --stats_history_buffer_size=0 --strict_bytes_per_sync=0 --subcompactions=3 --sync=0 --sync_fault_injection=1 --table_cache_numshardbits=-1 --target_file_size_base=524288 --target_file_size_multiplier=2 --test_batches_snapshots=1 --test_cf_consistency=1 --test_ingest_standalone_range_deletion_one_in=0 --top_level_index_pinning=0 --track_and_verify_wals=0 --uncache_aggressiveness=3225 --universal_max_read_amp=-1 --universal_reduce_file_locking=0 --unpartitioned_pinning=1 --use_adaptive_mutex=1 --use_adaptive_mutex_lru=1 --use_attribute_group=1 --use_blob_cache=0 --use_delta_encoding=1 --use_direct_io_for_flush_and_compaction=0 --use_direct_reads=1 --use_full_merge_v1=0 --use_get_entity=0 --use_merge=0 --use_multi_cf_iterator=0 --use_multi_get_entity=0 --use_multiget=1 --use_multiscan=0 --use_put_entity_one_in=10 --use_shared_block_and_blob_cache=0 --use_sqfc_for_range_queries=1 --use_timed_put_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=1000 --verify_compression=0 --verify_db_one_in=10000 --verify_file_checksums_one_in=1000000 --verify_iterator_with_expected_state_one_in=0 --verify_sst_unique_id_in_manifest=1 --wal_bytes_per_sync=0 --wal_compression=none --write_buffer_size=1048576 --write_dbid_to_manifest=0 --write_fault_one_in=0 --write_identity_file=1 --writepercent=35

KILLED 2102945

stdout:

Use random seed for iteration 4205502355970671733
Running db_stress with pid=2107447: ./db_stress --WAL_size_limit_MB=0 --WAL_ttl_seconds=0 --acquire_snapshot_one_in=10000 --adaptive_readahead=0 --adm_policy=3 --advise_random_on_open=0 --allow_data_in_errors=True --allow_fallocate=0 --allow_setting_blob_options_dynamically=1 --allow_unprepared_value=1 --async_io=1 --auto_readahead_size=0 --auto_refresh_iterator_with_snapshot=0 --avoid_flush_during_recovery=0 --avoid_flush_during_shutdown=0 --avoid_unnecessary_blocking_io=0 --backup_max_size=104857600 --backup_one_in=100000 --batch_protection_bytes_per_key=0 --bgerror_resume_retry_interval=100 --blob_cache_size=4194304 --blob_compaction_readahead_size=1048576 --blob_compression_type=snappy --blob_file_size=1048576 --blob_file_starting_level=2 --blob_garbage_collection_age_cutoff=1.0 --blob_garbage_collection_force_threshold=0.75 --block_align=0 --block_protection_bytes_per_key=8 --block_size=16384 --bloom_before_level=2147483647 --bloom_bits=0 --bottommost_compression_type=disable --bottommost_file_compaction_delay=600 --bytes_per_sync=262144 --cache_index_and_filter_blocks=1 --cache_index_and_filter_blocks_with_high_priority=0 --cache_size=33554432 --cache_type=auto_hyper_clock_cache --charge_compression_dictionary_building_buffer=1 --charge_file_metadata=0 --charge_filter_construction=1 --charge_table_reader=0 --check_multiget_consistency=1 --check_multiget_entity_consistency=0 --checkpoint_one_in=1000000 --checksum_type=kxxHash --clear_column_family_one_in=0 --compact_files_one_in=1000000 --compact_range_one_in=1000 --compaction_pri=4 --compaction_readahead_size=1048576 --compaction_style=0 --compaction_ttl=2 --compress_format_version=1 --compressed_secondary_cache_ratio=0.0 --compressed_secondary_cache_size=0 --compression_checksum=1 --compression_manager=randommixed --compression_max_dict_buffer_bytes=34359738367 --compression_max_dict_bytes=16384 --compression_parallel_threads=4 --compression_type=none --compression_use_zstd_dict_trainer=1 --compression_zstd_max_train_bytes=0 --continuous_verification_interval=0 --daily_offpeak_time_utc= --data_block_index_type=0 --db=/tmp/rocksdb_crashtest_blackboxs39kubu3 --db_write_buffer_size=0 --decouple_partitioned_filters=1 --default_temperature=kHot --default_write_temperature=kUnknown --delete_obsolete_files_period_micros=30000000 --delpercent=5 --delrangepercent=0 --destroy_db_initially=0 --detect_filter_construct_corruption=1 --disable_file_deletions_one_in=10000 --disable_manual_compaction_one_in=1000000 --disable_wal=0 --dump_malloc_stats=1 --enable_blob_files=1 --enable_blob_garbage_collection=1 --enable_checksum_handoff=1 --enable_compaction_filter=0 --enable_compaction_on_deletion_trigger=0 --enable_custom_split_merge=0 --enable_do_not_compress_roles=0 --enable_index_compression=1 --enable_memtable_insert_with_hint_prefix_extractor=0 --enable_pipelined_write=0 --enable_sst_partitioner_factory=1 --enable_thread_tracking=0 --enable_write_thread_adaptive_yield=1 --error_recovery_with_no_fault_injection=0 --exclude_wal_from_write_fault_injection=1 --expected_values_dir=/tmp/rocksdb_crashtest_expected_rvq7p3ow --fifo_allow_compaction=1 --file_checksum_impl=none --file_temperature_age_thresholds= --fill_cache=1 --flush_one_in=1000000 --format_version=5 --get_all_column_family_metadata_one_in=10000 --get_current_wal_file_one_in=0 --get_live_files_apis_one_in=10000 --get_properties_of_all_tables_one_in=100000 --get_property_one_in=100000 --get_sorted_wal_files_one_in=0 --hard_pending_compaction_bytes_limit=274877906944 --high_pri_pool_ratio=0.5 --index_block_restart_interval=11 --index_shortening=2 --index_type=2 --ingest_external_file_one_in=0 --ingest_wbwi_one_in=0 --initial_auto_readahead_size=0 --inplace_update_support=0 --iterpercent=10 --key_len_percent_dist=1,30,69 --key_may_exist_one_in=100000 --last_level_temperature=kCold --level_compaction_dynamic_level_bytes=1 --lock_wal_one_in=0 --log_file_time_to_roll=0 --log_readahead_size=16777216 --long_running_snapshots=0 --low_pri_pool_ratio=0.5 --lowest_used_cache_tier=1 --manifest_preallocation_size=5120 --manual_wal_flush_one_in=1000 --mark_for_compaction_one_file_in=0 --max_auto_readahead_size=524288 --max_background_compactions=2 --max_bytes_for_level_base=10485760 --max_key=2500000 --max_key_len=3 --max_log_file_size=1048576 --max_manifest_file_size=1073741824 --max_sequential_skip_in_iterations=2 --max_total_wal_size=0 --max_write_batch_group_size_bytes=16 --max_write_buffer_number=3 --max_write_buffer_size_to_maintain=2097152 --memtable_avg_op_scan_flush_trigger=20 --memtable_insert_hint_per_batch=1 --memtable_max_range_deletions=0 --memtable_op_scan_flush_trigger=10 --memtable_prefix_bloom_size_ratio=0.01 --memtable_protection_bytes_per_key=0 --memtable_whole_key_filtering=1 --memtablerep=skip_list --metadata_charge_policy=1 --metadata_read_fault_one_in=0 --metadata_write_fault_one_in=0 --min_blob_size=16 --min_write_buffer_number_to_merge=1 --mmap_read=1 --mock_direct_io=False --nooverwritepercent=1 --num_bottom_pri_threads=20 --num_file_reads_for_auto_readahead=1 --open_files=-1 --open_metadata_read_fault_one_in=8 --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_hits=0 --optimize_filters_for_memory=0 --optimize_multiget_for_io=0 --paranoid_file_checks=1 --paranoid_memory_checks=0 --partition_filters=1 --partition_pinning=2 --pause_background_one_in=10000 --periodic_compaction_seconds=1000 --prefix_size=7 --prefixpercent=5 --prepopulate_blob_cache=1 --prepopulate_block_cache=0 --preserve_internal_time_seconds=36000 --progress_reports=0 --promote_l0_one_in=0 --read_amp_bytes_per_bit=0 --read_fault_one_in=0 --readahead_size=16384 --readpercent=45 --recycle_log_file_num=0 --remote_compaction_worker_threads=0 --reopen=0 --report_bg_io_stats=0 --reset_stats_one_in=1000000 --sample_for_compression=0 --secondary_cache_fault_one_in=0 --secondary_cache_uri=compressed_secondary_cache://capacity=8388608;enable_custom_split_merge=true --set_options_one_in=1000 --skip_stats_update_on_db_open=1 --snapshot_hold_ops=100000 --soft_pending_compaction_bytes_limit=68719476736 --sqfc_name=foo --sqfc_version=0 --sst_file_manager_bytes_per_sec=104857600 --sst_file_manager_bytes_per_truncate=1048576 --statistics=1 --stats_dump_period_sec=600 --stats_history_buffer_size=1048576 --strict_bytes_per_sync=0 --subcompactions=1 --sync=0 --sync_fault_injection=1 --table_cache_numshardbits=6 --target_file_size_base=524288 --target_file_size_multiplier=2 --test_batches_snapshots=1 --test_cf_consistency=1 --test_ingest_standalone_range_deletion_one_in=0 --top_level_index_pinning=1 --track_and_verify_wals=0 --uncache_aggressiveness=136 --universal_max_read_amp=-1 --universal_reduce_file_locking=1 --unpartitioned_pinning=2 --use_adaptive_mutex=0 --use_adaptive_mutex_lru=1 --use_attribute_group=0 --use_blob_cache=0 --use_delta_encoding=1 --use_direct_io_for_flush_and_compaction=0 --use_direct_reads=0 --use_full_merge_v1=0 --use_get_entity=0 --use_merge=0 --use_multi_cf_iterator=1 --use_multi_get_entity=0 --use_multiget=0 --use_multiscan=0 --use_put_entity_one_in=10 --use_shared_block_and_blob_cache=1 --use_sqfc_for_range_queries=1 --use_timed_put_one_in=5 --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_compression=0 --verify_db_one_in=100000 --verify_file_checksums_one_in=0 --verify_iterator_with_expected_state_one_in=0 --verify_sst_unique_id_in_manifest=1 --wal_bytes_per_sync=0 --wal_compression=zstd --write_buffer_size=1048576 --write_dbid_to_manifest=0 --write_fault_one_in=0 --write_identity_file=1 --writepercent=35
```

Override the per iteration random seed directly 4205502355970671733, to jump to the second iteration parameter set. Only the file path name is different. The rest of the parameters are all same
```
[xbw@devvm16622.vll0 ~/workspace/ws2/rocksdb (plm_stress_fix)]$ /usr/local/bin/python3 -u tools/db_crashtest.py --stress_cmd=./db_stress --cleanup_cmd='' --cf_consistency blackbox --duration=96000 --max_key=2500000 --interval=10 --initial_random_seed_override=10 --per_iteration_random_seed_override=4205502355970671733
Start with random seed 10
Running blackbox-crash-test with
interval_between_crash=10
total-duration=96000

Use random seed for iteration 4205502355970671733
Running db_stress with pid=2110794: ./db_stress --WAL_size_limit_MB=0 --WAL_ttl_seconds=0 --acquire_snapshot_one_in=10000 --adaptive_readahead=0 --adm_policy=3 --advise_random_on_open=0 --allow_data_in_errors=True --allow_fallocate=0 --allow_setting_blob_options_dynamically=1 --allow_unprepared_value=1 --async_io=1 --auto_readahead_size=0 --auto_refresh_iterator_with_snapshot=0 --avoid_flush_during_recovery=0 --avoid_flush_during_shutdown=0 --avoid_unnecessary_blocking_io=0 --backup_max_size=104857600 --backup_one_in=100000 --batch_protection_bytes_per_key=0 --bgerror_resume_retry_interval=100 --blob_cache_size=4194304 --blob_compaction_readahead_size=1048576 --blob_compression_type=snappy --blob_file_size=1048576 --blob_file_starting_level=2 --blob_garbage_collection_age_cutoff=1.0 --blob_garbage_collection_force_threshold=0.75 --block_align=0 --block_protection_bytes_per_key=8 --block_size=16384 --bloom_before_level=2147483647 --bloom_bits=0 --bottommost_compression_type=disable --bottommost_file_compaction_delay=600 --bytes_per_sync=262144 --cache_index_and_filter_blocks=1 --cache_index_and_filter_blocks_with_high_priority=0 --cache_size=33554432 --cache_type=auto_hyper_clock_cache --charge_compression_dictionary_building_buffer=1 --charge_file_metadata=0 --charge_filter_construction=1 --charge_table_reader=0 --check_multiget_consistency=1 --check_multiget_entity_consistency=0 --checkpoint_one_in=1000000 --checksum_type=kxxHash --clear_column_family_one_in=0 --compact_files_one_in=1000000 --compact_range_one_in=1000 --compaction_pri=4 --compaction_readahead_size=1048576 --compaction_style=0 --compaction_ttl=2 --compress_format_version=1 --compressed_secondary_cache_ratio=0.0 --compressed_secondary_cache_size=0 --compression_checksum=1 --compression_manager=randommixed --compression_max_dict_buffer_bytes=34359738367 --compression_max_dict_bytes=16384 --compression_parallel_threads=4 --compression_type=none --compression_use_zstd_dict_trainer=1 --compression_zstd_max_train_bytes=0 --continuous_verification_interval=0 --daily_offpeak_time_utc= --data_block_index_type=0 --db=/tmp/rocksdb_crashtest_blackboxo1xvo_2n --db_write_buffer_size=0 --decouple_partitioned_filters=1 --default_temperature=kHot --default_write_temperature=kUnknown --delete_obsolete_files_period_micros=30000000 --delpercent=5 --delrangepercent=0 --destroy_db_initially=0 --detect_filter_construct_corruption=1 --disable_file_deletions_one_in=10000 --disable_manual_compaction_one_in=1000000 --disable_wal=0 --dump_malloc_stats=1 --enable_blob_files=1 --enable_blob_garbage_collection=1 --enable_checksum_handoff=1 --enable_compaction_filter=0 --enable_compaction_on_deletion_trigger=0 --enable_custom_split_merge=0 --enable_do_not_compress_roles=0 --enable_index_compression=1 --enable_memtable_insert_with_hint_prefix_extractor=0 --enable_pipelined_write=0 --enable_sst_partitioner_factory=1 --enable_thread_tracking=0 --enable_write_thread_adaptive_yield=1 --error_recovery_with_no_fault_injection=0 --exclude_wal_from_write_fault_injection=1 --expected_values_dir=/tmp/rocksdb_crashtest_expected_s0kmvlrj --fifo_allow_compaction=1 --file_checksum_impl=none --file_temperature_age_thresholds= --fill_cache=1 --flush_one_in=1000000 --format_version=5 --get_all_column_family_metadata_one_in=10000 --get_current_wal_file_one_in=0 --get_live_files_apis_one_in=10000 --get_properties_of_all_tables_one_in=100000 --get_property_one_in=100000 --get_sorted_wal_files_one_in=0 --hard_pending_compaction_bytes_limit=274877906944 --high_pri_pool_ratio=0.5 --index_block_restart_interval=11 --index_shortening=2 --index_type=2 --ingest_external_file_one_in=0 --ingest_wbwi_one_in=0 --initial_auto_readahead_size=0 --inplace_update_support=0 --iterpercent=10 --key_len_percent_dist=1,30,69 --key_may_exist_one_in=100000 --last_level_temperature=kCold --level_compaction_dynamic_level_bytes=1 --lock_wal_one_in=0 --log_file_time_to_roll=0 --log_readahead_size=16777216 --long_running_snapshots=0 --low_pri_pool_ratio=0.5 --lowest_used_cache_tier=1 --manifest_preallocation_size=5120 --manual_wal_flush_one_in=1000 --mark_for_compaction_one_file_in=0 --max_auto_readahead_size=524288 --max_background_compactions=2 --max_bytes_for_level_base=10485760 --max_key=2500000 --max_key_len=3 --max_log_file_size=1048576 --max_manifest_file_size=1073741824 --max_sequential_skip_in_iterations=2 --max_total_wal_size=0 --max_write_batch_group_size_bytes=16 --max_write_buffer_number=3 --max_write_buffer_size_to_maintain=2097152 --memtable_avg_op_scan_flush_trigger=20 --memtable_insert_hint_per_batch=1 --memtable_max_range_deletions=0 --memtable_op_scan_flush_trigger=10 --memtable_prefix_bloom_size_ratio=0.01 --memtable_protection_bytes_per_key=0 --memtable_whole_key_filtering=1 --memtablerep=skip_list --metadata_charge_policy=1 --metadata_read_fault_one_in=0 --metadata_write_fault_one_in=0 --min_blob_size=16 --min_write_buffer_number_to_merge=1 --mmap_read=1 --mock_direct_io=False --nooverwritepercent=1 --num_bottom_pri_threads=20 --num_file_reads_for_auto_readahead=1 --open_files=-1 --open_metadata_read_fault_one_in=8 --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_hits=0 --optimize_filters_for_memory=0 --optimize_multiget_for_io=0 --paranoid_file_checks=1 --paranoid_memory_checks=0 --partition_filters=1 --partition_pinning=2 --pause_background_one_in=10000 --periodic_compaction_seconds=1000 --prefix_size=7 --prefixpercent=5 --prepopulate_blob_cache=1 --prepopulate_block_cache=0 --preserve_internal_time_seconds=36000 --progress_reports=0 --promote_l0_one_in=0 --read_amp_bytes_per_bit=0 --read_fault_one_in=0 --readahead_size=16384 --readpercent=45 --recycle_log_file_num=0 --remote_compaction_worker_threads=0 --reopen=0 --report_bg_io_stats=0 --reset_stats_one_in=1000000 --sample_for_compression=0 --secondary_cache_fault_one_in=0 --secondary_cache_uri=compressed_secondary_cache://capacity=8388608;enable_custom_split_merge=true --set_options_one_in=1000 --skip_stats_update_on_db_open=1 --snapshot_hold_ops=100000 --soft_pending_compaction_bytes_limit=68719476736 --sqfc_name=foo --sqfc_version=0 --sst_file_manager_bytes_per_sec=104857600 --sst_file_manager_bytes_per_truncate=1048576 --statistics=1 --stats_dump_period_sec=600 --stats_history_buffer_size=1048576 --strict_bytes_per_sync=0 --subcompactions=1 --sync=0 --sync_fault_injection=1 --table_cache_numshardbits=6 --target_file_size_base=524288 --target_file_size_multiplier=2 --test_batches_snapshots=1 --test_cf_consistency=1 --test_ingest_standalone_range_deletion_one_in=0 --top_level_index_pinning=1 --track_and_verify_wals=0 --uncache_aggressiveness=136 --universal_max_read_amp=-1 --universal_reduce_file_locking=1 --unpartitioned_pinning=2 --use_adaptive_mutex=0 --use_adaptive_mutex_lru=1 --use_attribute_group=0 --use_blob_cache=0 --use_delta_encoding=1 --use_direct_io_for_flush_and_compaction=0 --use_direct_reads=0 --use_full_merge_v1=0 --use_get_entity=0 --use_merge=0 --use_multi_cf_iterator=1 --use_multi_get_entity=0 --use_multiget=0 --use_multiscan=0 --use_put_entity_one_in=10 --use_shared_block_and_blob_cache=1 --use_sqfc_for_range_queries=1 --use_timed_put_one_in=5 --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_compression=0 --verify_db_one_in=100000 --verify_file_checksums_one_in=0 --verify_iterator_with_expected_state_one_in=0 --verify_sst_unique_id_in_manifest=1 --wal_bytes_per_sync=0 --wal_compression=zstd --write_buffer_size=1048576 --write_dbid_to_manifest=0 --write_fault_one_in=0 --write_identity_file=1 --writepercent=35
```

Reviewed By: jaykorean

Differential Revision: D82399857

Pulled By: xingbowang

fbshipit-source-id: 38f3bfefdd0adc7f527fd68982e2edc22b2304f4
2025-09-19 19:52:55 -07:00
Peter Dillinger f9f408f536 Start migration of HCC implementation to BitFields (#13965)
Summary:
Start the process of migrating the HCC implementation over to my new system of "bit field atomics" to clean up the code. Here I took on the simplest of the three "bit field atomic" formats in HCC, but ended up moving some things around to end up with less plumbing of definitions and values overall.

In the process, updated BitFields to use the CRTP pattern to simplify some things (see updated example, etc.)
https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

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

Test Plan: existing tests. ClockCacheTest.ClockEvictionEffortCapTest caught a regression during my development, and the crash test has a history of finding subtle HCC bugs.

Reviewed By: xingbowang

Differential Revision: D82669582

Pulled By: pdillinger

fbshipit-source-id: b73dd47361cbe9fbd334413dd4ce01b3c667159e
2025-09-19 17:34:48 -07:00
Peter Dillinger a843991930 Allow standalone file and directory arguments to sst_dump (#13978)
Summary:
longtime wanted e.g. for easy tab-completion, now implemented

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

Test Plan: pretty good unit test updates, manual testing

Reviewed By: cbi42

Differential Revision: D82857671

Pulled By: pdillinger

fbshipit-source-id: d2b63b7d15e61ebf22c58a6ecd3003311e2d03cb
2025-09-19 16:01:43 -07:00
Peter Dillinger fa3e61cce2 Improve sst_dump --command=recompress (#13977)
Summary:
* There was a bug where the compression manager would actually not be used for recompress because the options passed to SstFileDumper were not respected. That is now fixed by respecting the Options.
* Refactored SstFileDumper not to take explicit options that could naturally be embedded in Options.
* Report compressed and uncompressed data block sizes (and ratio) instead of total file size (without a useful ratio). Needed to add a new table property to support that.
* Allow --block_size instead of --set_block_size to be consistent with other tools
* Allow --compression_level as shorthand for both _from and _to options, for simplicity and consistency with other tools
* Support --compression_parallel_threads option

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

Test Plan:
* sst_dump manual testing
* TableProperties unit tests updated
* Made it much easier to detect when a functional change requires an update to ParseTablePropertiesString() (rather than causing cryptic downstream failures)

Reviewed By: cbi42

Differential Revision: D82841412

Pulled By: pdillinger

fbshipit-source-id: 8d3421be4d2a3e25b7590cd59d204a3779c2a928
2025-09-19 13:52:05 -07:00
Peter Dillinger 19c8d1b7ed (Re-)fix initialization order dep on kPageSize (#13976)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/13976

Missed an occurrence of kPageSize in the last PR
https://github.com/facebook/rocksdb/pull/13973

Reviewed By: mszeszko-meta

Differential Revision: D82826713

fbshipit-source-id: b112cd7c94b7d6604623ee80274b2b25911245eb
2025-09-19 10:56:50 -07:00
Changyu Bi 798373975c Unpin skipped data blocks in MultiScan (#13972)
Summary:
Currently in MultiScan we only unpins a block after we scan through it. This PR adds unpinning during Seek to release all blocks pinned by the previous scan range. This is useful when users do not scan through the entire scan range. I plan to follow up with support for aborting async IOs from the previous scan.

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

Test Plan: new test MultiScanUnpinPreviousBlocks validates unpinning behavior

Reviewed By: xingbowang

Differential Revision: D82779504

Pulled By: cbi42

fbshipit-source-id: 17ba7d1e5a6d8ff09ceea57b79c18febfba75584
2025-09-19 10:21:38 -07:00
Pavel Tcholakov e9fc03eed7 Expose C bindings for Column Family export/import (#13874)
Summary:
This change adds FFI support for exporting column family checkpoints, basic access to the export/import files metadata, and creating column families by import.

I've been able to successfully use this to [add checkpoint export and import support to `rust-rocksdb`](https://github.com/pcholakov/rust-rocksdb/pull/2), a forked version of which has been successfully used in production for some time.

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

Reviewed By: hx235

Differential Revision: D82343565

Pulled By: jaykorean

fbshipit-source-id: fb4182bdfd5cce10743c021a1ac636fd6ac48df3
2025-09-19 09:52:15 -07:00
Peter Dillinger ef6fbe7ff9 Attempt fix initialization order dep on kPageSize (#13973)
Summary:
If there's a static initialization of Options() this could now instantiate an AutoHyperClockTable before kPageSize is initialized. Break the dependency because it's a very minor optimization.

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

Test Plan: internal CI (not able to reproduce locally)

Reviewed By: hx235

Differential Revision: D82789849

Pulled By: pdillinger

fbshipit-source-id: 3f32b5779a4f56d2071be5aadacda2bf0f4b895d
2025-09-19 01:55:06 -07:00
Xingbo Wang 94e65a2e0b Add option to validate key during seek in SkipList Memtable (#13902)
Summary:
Add a new CF immutable option `paranoid_memory_check_key_checksum_on_seek` that allows additional data integrity validations during seek on SkipList Memtable. When this option is enabled and memtable_protection_bytes_per_key is non zero, skiplist-based memtable will validate the checksum of each key visited during seek operation. The option is opt-in due to performance overhead. This is an enhancement on top of paranoid_memory_checks option.

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

Test Plan:
* new unit test added for paranoid_memory_check_key_checksum_on_seek=true.
    * existing unit test for paranoid_memory_check_key_checksum_on_seek=false.
    * enable in stress test.

Performance Benchmark: we check for performance regression in read path where data is in memtable only. For each benchmark, the script was run at the same time for main and this PR:

### Memtable-only randomread ops/sec:

* Value size = 100 Bytes
```
for B in 0 1 2 4 8; do (for I in $(seq 1 50);do  ./db_bench --benchmarks=fillseq,readrandom --write_buffer_size=268435456 --writes=250000 --value_size=100 --num=250000 --reads=500000  --seed=1723056275 --paranoid_memory_check_key_checksum_on_seek=true --memtable_protection_bytes_per_key=$B 2>&1 | grep "readrandom"; done;) | awk '{ t += $5; c++; print } END { print 1.0 * t / c }'; done;
```

1. Main: 928999
2. PR with paranoid_memory_check_key_checksum_on_seek=false: 930993 (+0.2%)
3. PR with paranoid_memory_check_key_checksum_on_seek=true:
3.1 memtable_protection_bytes_per_key=1: 464577 (-50%)
3.2 memtable_protection_bytes_per_key=2: 470319 (-49%)
3.3 memtable_protection_bytes_per_key=4: 468457 (-50%)
3.4 memtable_protection_bytes_per_key=8: 465061 (-50%)

* Value size = 1000 Bytes
```
for B in 0 1 2 4 8; do (for I in $(seq 1 50);do  ./db_bench --benchmarks=fillseq,readrandom --write_buffer_size=268435456 --writes=250000 --value_size=1000 --num=250000 --reads=500000  --seed=1723056275 --paranoid_memory_check_key_checksum_on_seek=true --memtable_protection_bytes_per_key=$B 2>&1 | grep "readrandom"; done;) | awk '{ t += $5; c++; print } END { print 1.0 * t / c }'; done;
```

1. Main: 601321
2. PR with paranoid_memory_check_key_checksum_on_seek=false: 607885 (+1.1%)
3. PR with paranoid_memory_check_key_checksum_on_seek=true:
3.1 memtable_protection_bytes_per_key=1: 185742 (-69%)
3.2 memtable_protection_bytes_per_key=2: 177167 (-71%)
3.3 memtable_protection_bytes_per_key=4: 185908 (-69%)
3.4 memtable_protection_bytes_per_key=8: 183639 (-69%)

Reviewed By: pdillinger

Differential Revision: D81199245

Pulled By: xingbowang

fbshipit-source-id: e3c29552ab92f2c5f360361366a293fa26934913
2025-09-18 16:15:50 -07:00
Xingbo Wang 5a1ff2cb14 Force caller to pass comparator in MultiScanArgs (#13970)
Summary:
Force caller of MultiScanArgs to pass comparator. Pass comparator from CF handle to MultiScanArgs in NewMultiScan.
Expand MultiScanArgs unit test with different comparator.

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

Test Plan: unit test

Reviewed By: cbi42

Differential Revision: D82739270

Pulled By: xingbowang

fbshipit-source-id: e709f4a333ad547c0ba6d24d8fb2b22e50e8a12f
2025-09-18 15:18:18 -07:00
Hui Xiao 6a202c5570 Fix nullptr access in IsInjectedError() for stress test (#13968)
Summary:
**Context/Summary:**
`Status::state` can be nullptr when created with no specific error message. std::strstr on nullptr caused some segfault in our stress test.

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

Test Plan: Monitor stress test

Reviewed By: jaykorean

Differential Revision: D82695541

Pulled By: hx235

fbshipit-source-id: cf08f70163a9ee6c911cdc3a3d79acd3429f0d15
2025-09-18 15:10:04 -07:00
Peter Dillinger 6127a42f98 Use/endorse (Auto)HyperClockCache by default over LRUCache (#13964)
Summary:
After seeing more people hit issues with thrashing small LRUCache shards and AutoHCC running fully in production for a while on a very large service, here I make these updates:

* In the public API, mark the case of `estimated_entry_charge = 0` (which is how you select AutoHCC) as production-ready and generally preferred. That means devoting a lot less space to how to tune FixedHCC (`estimated_entry_charge > 0`) because it is not generally recommended anymore even though in theory it is the fastest (conditional on a fragile configuration).
* In the public API, add more detail about potential problems with LRUCache and explicitly endorse HCC.
* When a default block cache is created, use AutoHCC instead of LRUCache. It's still a 32MB cache but that's just one cache shard for AutoHCC so the risk of issues with small cache shards is dramatically reduced. And a single AutoHCC shard is still essentially wait-free.
* Improve the handling of the hypothetical scenario of a failed anonymous mmap. This is hardly a concern for 64-bit Linux and likely most other OSes. It would in theory be possible to fall back on LRUCache in that case but the code structure makes that annoying/challenging. Instead we crash with an appropriate message.
* Cleaned up some includes
* Fixed some previously unreported leaks (better assertions on HCC perhaps, some subtle behavior changes)
* Added a new mode to cache_bench (detailed below)
* Avoid a particularly costly sanity check in `~AutoHyperClockTable()` even in debug builds so that unit testing, etc., isn't bogged down, except keep it in ASAN build.

Planned follow-up:
* Update HCC implementation to use my new "bit field atomics" API introduced in https://github.com/facebook/rocksdb/issues/13910 to make it easier to read and maintain

Possible follow-up:
* Re-engineer table cache to use AutoHCC also, instead of LRUCache and a single mutex to ensure no duplication across threads. (a) Pad table cache key to 128 bits for AutoHCC. (b) Stripe/shard the no-duplication mutex. (HCC's consistency model is too weak for concurrent threads to use its API to agree on a winner, even if entries could be inserted in an "open in progress" state.)

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

Test Plan:
existing tests. ClockCacheTest.ClockEvictionEffortCapTest caught a regression during my development, and the crash test has a history of finding subtle HCC bugs.

## Performance

Although we've validated AutoHCC performance under high load, etc., before we haven't really considered whether there will be unacceptable overheads for small DBs and CFs, e.g. in unit tests. For this, I have added a new mode to cache_bench: with the -stress_cache_instances=n parameter, it will create and destroy n empty cache instances several times. In the debug build, this found that a particular check in `~AutoHyperClockTable()` was extremely costly for short-lived caches (fixed). Beyond that, we can answer the question of whether it is feasible for a single process to host 1000 DBs each with 1000 CFs with default block cache instances, after moving LRUCache -> AutoHCC, for example:

```
/usr/bin/time ./cache_bench -stress_
cache_instances=1000000 -cache_type=auto_hyper_clock_cache -cache_size=33554432
```

Release build:
Average 9.8 us per 32MB LRUCache creation, 2.9 us per destruction, 24.6GB max RSS (~25KB each)
->
Average 4.3 us per  32MB AutoHCC creation, 4.9 us per destruction, 4.8GB max RSS (~5KB each)

Debug build:
Average 10.9 us per 32MB LRUCache creation, 3.5 us per destruction, 28.7GB max RSS (~29KB each)
->
Average 4.5 us per 32MB AutoHCC creation, 4.9 us per destruction, 4.7GB max RSS (~5KB each)

Despite the anonymous mmaps, it's apparently more efficient for default/small/empty structures. This is likely due to the dramatically low number of cache shards at this size. If we switch to `-stress_cache_instances=10000  -cache_size=1073741824`:

Release build:
Average 10.6 us per 1GB LRUCache, 2.8 us per destruction, 2.3 GB max RSS (~230KB each)
->
Average 130 us per 1GB AutoHCC creation, 153 us per destruction, 1.5 GB max RSS (~150KB each)

Debug build:
Average 11.2 us per 1GB LRUCache, 3.6 us per destruction, 2.4 GB max RSS (~240KB each)
->
Average 130 us per 1GB AutoHCC creation, 150 us per destruction, 1.6 GB max RSS (~160KB each)

Here it's clear that we are paying a price in time for setting up all those mmaps for the good number of cache shards and potential table growth, even though the RSS is well under control. However, I am not concerned about this at all, as it's unlikely to slow down anything notably such as unit tests. Before and after full testsuite runs confirm:

3327.73user 5188.71system 3:38.88elapsed -> 3312.07user 5704.77system 3:41.61elapsed

There is increased kernel time but acceptable. With ASAN+UBSAN:

11618.70user 15671.30system 5:54.68elapsed -> 12595.81user 16159.67system 6:32.77elapsed

Acceptable given that our ASAN+UBSAN builds are not the slowest in CI

Reviewed By: hx235

Differential Revision: D82661067

Pulled By: pdillinger

fbshipit-source-id: ab25c766ca70f2b8664849c2a838b9e1b4e72d3b
2025-09-18 13:27:51 -07:00
Changyu Bi 20bcd01758 Record smallest seqno in table properties for faster file ingestion (#13942)
Summary:
when ingesting DB generated file with non-zero sequence number, we need smallest seqno of each file for file meta data. To avoid full table scan, we record this information in table property and use it during file ingestion.

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

Test Plan: new unit test and updated existing unit test.

Reviewed By: hx235

Differential Revision: D82331802

Pulled By: cbi42

fbshipit-source-id: 3009a6801ca7092cd0fde33692db1a13567068a9
2025-09-17 20:20:33 -07:00
anand76 631fb8670b Correctly handle upper bound iteration result from a UDI (#13960)
Summary:
This PR fixes a bug in BlockBasedTableIterator::Prepare in conjunction with a user defined index (UDI). If the UDI determines a scan range to be empty and thus returns the kOutOfBound iteration result during Seek, the iteration result is not propagated up and Prepare() assumes end of file and aborts the remaining scans. This results in incorrect behavior and unpredictable multi scan results.

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

Test Plan: Add unit test to table_test.cc

Reviewed By: xingbowang

Differential Revision: D82590892

Pulled By: anand1976

fbshipit-source-id: 8cfaaae2bb1a9509ddf8ec967cb8a8801748413d
2025-09-17 09:59:18 -07:00
Peter Dillinger 3c85aa8a69 Some follow-up to parallel compression revamp (#13959)
Summary:
* Fix compaction/flush CPU usage stats to include CPU usage by parallel compression workers. (Validated with manual db_bench testing.)
* Disable the parallel compression framework when compression is disabled. See new code comment for details, because in theory it could be useful to hide SST write latency, but manual testing with db_bench and -rate_limiter_bytes_per_sec or -simulate_hdd options shows no useful increase in throughput, just more CPU usage.
* Fix some minor clean-up items in the implementation

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

Test Plan: Also ran some tests like in https://github.com/facebook/rocksdb/issues/13910 to ensure the new CPU usage tracking did not regress performance, all good.

Reviewed By: xingbowang

Differential Revision: D82556686

Pulled By: pdillinger

fbshipit-source-id: 77c522159a7e6ab0ab6f7fb1d662070a46661557
2025-09-17 08:43:19 -07:00
Xingbo Wang 95813a84cd Fix error from transactiondb layer in stress test (#13950)
Summary:
The stress test runs concurrent transactions through many threads at the same time on a shared key space. It is possible that a dead lock or a timeout is detected from the transactiondb layer. When this happens, simply return from the function and continue the test, instead of fail the test.

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

Test Plan: Stress test pass locally with the same random seed from stress test 14723229280871643749.

Reviewed By: hx235

Differential Revision: D82373959

Pulled By: xingbowang

fbshipit-source-id: 5d72e89998171c5844fb22f13d8f061f81014c7d
2025-09-16 17:43:02 -07:00
Peter Dillinger 7c3472b4d9 Work around GCC TSAN bug (#13958)
Summary:
... reporting false positive double-lock on some of the new parallel compression code. Switching from std::condition_variable to condition_variable_any simply changes the FP from double-lock to lock inversion. In addition, leaking ParallelCompressionRep instances to avoid memory location reuse fails to fix the FP reports. Thus, I've decided to disable the watchdog with GCC+TSAN.

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

Test Plan: local crash test runs could reproduce, now don't reproduce. CLANG TSAN doesn't seem to be reporting the same supposed issues

Reviewed By: xingbowang

Differential Revision: D82555968

Pulled By: pdillinger

fbshipit-source-id: 537fbc3a787f917915a6faf0bdedd1449a7f378a
2025-09-16 16:51:33 -07:00
Changyu Bi 2620c85638 Support async IO for MultiScan (#13932)
Summary:
add option MultiScanArgs::use_async_io option and implementation for using ReadAsync() for multiscan. Read requests are submitted during Prepare() and polled during actual scanning.

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

Test Plan:
- updated existing unit test to use async_io.
- crash test: `python3 -u ./tools/db_crashtest.py whitebox --iterpercent=60 --prefix_size=-1 --prefixpercent=0 --readpercent=0 --test_batches_snapshots=0 --use_multiscan=1 --read_fault_one_in=0 --kill_random_test=88888 --interval=60 --multiscan_use_async_io=1 --mmap_read=0`

Benchmark:
- Default multiscan benchmark:
```
Set up: /db_bench --benchmarks="fillseq,compact" --disable_wal=1 --threads=1 --num_levels=1 --compaction_style=2 --fifo_compaction_max_table_files_size_mb=1000 --write_buffer_size=268435456

Without async IO:
./db_bench --db="/tmp/rocksdbtest-543376/dbbench" --use_existing_db=1 --benchmarks=multiscan --disable_auto_compactions=1 --seek_nexts=100 --threads=32 --duration=10 --statistics=1 --use_direct_reads=1 --multiscan_use_async_io=0

multiscan    :     415.569 micros/op 75805 ops/sec 10.355 seconds 784968 operations; (multscans:24999)
rocksdb.read.async.micros COUNT : 0

With asycn IO:
./db_bench --db="/tmp/rocksdbtest-543376/dbbench" --use_existing_db=1 --benchmarks=multiscan --disable_auto_compactions=1 --seek_nexts=100 --threads=32 --duration=10 --statistics=1 --use_direct_reads=1 --multiscan_use_async_io=1

multiscan    :     413.236 micros/op 76044 ops/sec 10.375 seconds 788968 operations; (multscans:24999)
rocksdb.read.async.micros COUNT : 3916499

Similar performance.
```

- Larger scan, more scans per multiscan, do not coalesce IO so that async IO can progress while scanning, and use one thread:
```
multiscan_stride = 1000
multiscan_size = 100
seek_nexts = 1000

./db_bench --db="/tmp/rocksdbtest-543376/dbbench" --use_existing_db=1 --benchmarks=multiscan --disable_auto_compactions=1 --threads=1 --duration=10 --statistics=0 --use_direct_reads=1  --cache_size=2097152 --multiscan_size=100 --multiscan_stride=1000 --seek_nexts=1000 --seed=1 --multiscan_coalesce_threshold=0  --multiscan_use_async_io=0

Without async IO:
multiscan    :   20495.205 micros/op 48 ops/sec 10.002 seconds 488 operations; (multscans:488)

With async IO:
multiscan    :   18337.883 micros/op 54 ops/sec 10.013 seconds 546 operations; (multscans:546)

~10% improvement in throughput
```

Reviewed By: xingbowang

Differential Revision: D82077818

Pulled By: cbi42

fbshipit-source-id: 66e32cf4039183c4841827409286dfbaa6dfbcd8
2025-09-15 11:39:45 -07:00
Peter Dillinger 29d9798ae8 Revamp of parallel compression (#13910)
Summary:
Complete redo of parallel compression in block_based_table_builder.cc to greatly reduce cross-thread hand-off and blocking. A ring buffer of blocks-in-progress is used to essentially bound working memory while enabling high throughput. Unlike before, all threads can participate in compression work, for a kind of work-stealing algorithm that reduces the need for threads to block. This builds on improvements in https://github.com/facebook/rocksdb/pull/13850

Previously, there was either
* parallel_threads==1, the *emit thread* (caller from flush/compaction) doing all the work
* parallel_threads > 1, the emit thread generates uncompressed blocks, `parallel_threads` worker threads compress blocks, and a writer thread writes to the SST file. Total of `parallel_threads + 2` threads participating. (Other bookkeeping in emit and write steps omitted from description for simplicity.)

Now we have either
* parallel_threads==1 (same), the emit thread doing all the work
* parallel_threads > 1, the emit thread generates uncompressed blocks and can take up compression work when the ring buffer is full; `parallel_threads` worker threads have as their top priority to write compressed blocks to the SST file but also take up compression work in priority order of next-to-write. Total of `parallel_threads + 1` threads participating. In some cases, this could result in less throughput than before, but arguably the previous implementation was using more threads than explicitly allowed.

## Future/alternate considerations
Although we could likely have used some framework for micro-work sharing across threads, that could be difficult with the asymmetry of work loads and thread affinity. Specifically, (a) it would be quite challenging to allow emit work in other threads, because it happens in the caller of BlockBasedTableBuilder, (b) async programming is unlikely to pay off until we have an async interface for writing SST files, and (c) this implementation will nevertheless serve as a benchmark for what we lose or gain in such a framework vs. a hand-tuned system.

This implementation still creates and destroys threads for each SST file created. We hope in the future to have more governance and/or pooling of worker threads across various flushes and compactions, but that is not available currently and would require significant design and implementation work.

## More details
* This implementation makes use of semaphores for idling and re-waking threads. `std::counting_semaphore` and `binary_semaphore` offer the best performance (see benchmark results below) but some implementations are known to have correctness bugs. Also, my attempt at upgrading CI for C++20 support (required for these) in https://github.com/facebook/rocksdb/pull/13904 is actually incomplete. Therefore, using these structures is opt-in with `-DROCKSDB_USE_STD_SEMAPHORES` at compile time, and a naive semaphore implementation based on mutex and condvar is used by default. A folly alternative (folly::fibers::Semaphore) was dropped in during development and found to be less efficient than the naive implementation. One CI job is upgraded to test with the new opt-in.
* One of the biggest concerns about correctness/reliability for this implementation is the possibility of hitting a deadlock, in part because that is not well checked in the DB crash test (a challenging problem!). Note also that with the parallel compression improvements in this release, I am calling the feature production-ready, so there is an extra level of confidence needed in the reliability of the feature. Thus, for DEBUG builds including crash test, I have added a watchdog thread to each parallel SST construction that heuristically checks for the most likely kinds of deadlock that could happen, including for the case of buggy semaphore implementations. It periodically verifies that some thread is outside of its "idle" state, and if the watchdog wakes up repeatedly to see all live threads stuck in their idle state (even if wake-up was attempted) then it declares a deadlock. This feature was manually verified for several seeded deadlock bugs. (More details in code comments.)
* For CPU efficiency, this implementation greatly simplifies the logic to estimate the outstanding or "inflight" size not yet written to the SST file. I expect this size to generally be insignificant relative to the full SST file size so is not worth careful engineering. And based on Meta's current needs, landing under-size for an SST file is better than over-size. See comments on `estimated_inflight_size` for details.
* Some other existing atomics in block_based_table_builder.cc modified to use safe atomic wrappers.
* Status handling in BlockBasedTableBuilder was streamlined to get rid of essentially redundant `status`+`io_status` fields and associated code. Made small optimizations to reduce unnecessary IOStatus copies (with StatusOk()) and mark status conditional branches as LIKELY or UNLIKELY.
* Prefer inline field initialization to initialization in constructor.
* Minimize references to the `parallel_threads` configuration parameter for better separation of concerns / sanitization / etc.  For example, use non-nullity of `pc_rep` to indicate that parallel compression is enabled (and active).
* Some other refactoring to aid the new implementation.

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

Test Plan:
## Correctness
Already integrated into unit tests and crash test. CI updated for opt-in semaphore implementation. Basic semaphore unit tests added/updated.

As for the tremendous simplification of logic relating to hitting target SST file size, as expected, the new behavior could under-shoot the single-threaded behavior by a small number of blocks, which will typically affect the file size by ~1/1000th or less. I think that's a good trade-off for cutting out unnecessarily complex code with non-trivial CPU cost (FileSizeEstimator).
```
./db_bench -db=/dev/shm/dbbench_filesize_after8 -benchmarks=fillseq,compact -num=10000000 -compression_type=zstd -compression_level=8 -compression_parallel_threads=8
```

Before, PT=8 & PT=1, and After PT=1 the same or very similar
```
-rw-r--r-- 1 peterd users 67474097 Sep 12 15:32 000052.sst
-rw-r--r-- 1 peterd users 67474214 Sep 12 15:32 000053.sst
-rw-r--r-- 1 peterd users 67473834 Sep 12 15:32 000054.sst
-rw-r--r-- 1 peterd users 67473437 Sep 12 15:32 000055.sst
-rw-r--r-- 1 peterd users 67473835 Sep 12 15:32 000056.sst
-rw-r--r-- 1 peterd users 67473204 Sep 12 15:33 000057.sst
-rw-r--r-- 1 peterd users 67473294 Sep 12 15:33 000058.sst
-rw-r--r-- 1 peterd users 67473839 Sep 12 15:33 000059.sst
```

After, PT=8 (worst case here ~0.05% smaller)
```
-rw-r--r-- 1 peterd users 67463189 Sep 12 14:55 000052.sst
-rw-r--r-- 1 peterd users 67465233 Sep 12 14:55 000053.sst
-rw-r--r-- 1 peterd users 67466822 Sep 12 14:55 000054.sst
-rw-r--r-- 1 peterd users 67466221 Sep 12 14:55 000055.sst
-rw-r--r-- 1 peterd users 67441675 Sep 12 14:55 000056.sst
-rw-r--r-- 1 peterd users 67467855 Sep 12 14:55 000057.sst
-rw-r--r-- 1 peterd users 67455132 Sep 12 14:55 000058.sst
-rw-r--r-- 1 peterd users 67458334 Sep 12 14:55 000059.sst
```

## Performance, modest load
We are primarily interested in balancing throughput in building SST files and CPU usage in doing so. (For example, we could maximize throughput by having worker threads only spin waiting for work, but that would likely be extra CPU usage we want to avoid to allow other productive CPU work to be scheduled.) No read path code has been touched.

A benchmark script running "before" and "after" configurations at the same time to minimize random machine load effects:
```
$ SUFFIX=`tty | sed 's|/|_|g'`; for CT in none lz4 zstd; do for PT in 1 2 3 4 6 8; do echo -n "$CT pt=$PT -> "; (for I in `seq 1 10`; do BIN=/tmp/dbbench${SUFFIX}.bin; rm -f $BIN; cp db_bench $BIN; /usr/bin/time $BIN -db=/dev/shm/dbbench$SUFFIX --benchmarks=fillseq -num=10000000 -compaction_style=2 -fifo_compaction_max_table_files_size_mb=1000 -fifo_compaction_allow_compaction=0 -disable_wal -write_buffer_size=12000000 -format_version=7 -compression_type=$CT -compression_parallel_threads=$PT 2>&1; done) | awk '/micros.op/ {n++; sum += $5;} /system / { cpu += $1 + $2; } END { print "ops/s: " int(sum/n) " cpu*s: " cpu; }'; done; done
```

Before this change:
```
none pt=1 -> ops/s: 1999603 cpu*s: 72.08
none pt=2 -> ops/s: 1871094 cpu*s: 148.3
none pt=3 -> ops/s: 1882907 cpu*s: 147.7
lz4  pt=1 -> ops/s: 1987858 cpu*s: 94.74
lz4  pt=2 -> ops/s: 1590192 cpu*s: 182.65
lz4  pt=3 -> ops/s: 1896294 cpu*s: 174.7
lz4  pt=4 -> ops/s: 1949174 cpu*s: 172.26
lz4  pt=6 -> ops/s: 1912517 cpu*s: 175.91
lz4  pt=8 -> ops/s: 1930585 cpu*s: 176.71
zstd pt=1 -> ops/s: 1239379 cpu*s: 129.85
zstd pt=2 -> ops/s: 1171742 cpu*s: 226.12
zstd pt=3 -> ops/s: 1832574 cpu*s: 214.21
zstd pt=4 -> ops/s: 1887124 cpu*s: 212.51
zstd pt=6 -> ops/s: 1920936 cpu*s: 211.7
zstd pt=8 -> ops/s: 1885544 cpu*s: 214.87
```

After this change:
```
none pt=1 -> ops/s: 1964361 cpu*s: 72.66
none pt=2 -> ops/s: 1914033 cpu*s: 104.95
none pt=3 -> ops/s: 1978567 cpu*s: 100.24
lz4  pt=1 -> ops/s: 2041703 cpu*s: 92.88
lz4  pt=2 -> ops/s: 1903210 cpu*s: 121.64
lz4  pt=3 -> ops/s: 1973906 cpu*s: 122.22
lz4  pt=4 -> ops/s: 1952605 cpu*s: 123.05
lz4  pt=6 -> ops/s: 1957524 cpu*s: 124.31
lz4  pt=8 -> ops/s: 1986274 cpu*s: 129.06
zstd pt=1 -> ops/s: 1233748 cpu*s: 130.43
zstd pt=2 -> ops/s: 1675226 cpu*s: 158.41
zstd pt=3 -> ops/s: 1929878 cpu*s: 159.77
zstd pt=4 -> ops/s: 1916403 cpu*s: 160.99
zstd pt=6 -> ops/s: 1942526 cpu*s: 166.21
zstd pt=8 -> ops/s: 1966704 cpu*s: 171.56
```

For parallel_threads=1, results are very similar, as expected.

For parallel_threads>1, throughput is usually improved a bit, but cpu consumption is dramatically reduced. For zstd, maximum throughput is essentially achieved with pt=3 rather than the previous roughly pt=4 to 6. And the old used about 30% more CPU.

We can also compare with more expensive compression by raising the compression level.
```
SUFFIX=`tty | sed 's|/|_|g'`; CT=zstd; for CL in 4 6 8; do for PT in 1 4 8; do echo -n "$CT@$CL pt=$PT -> "; (for I in `seq 1 10`; do BIN=/tmp/dbbench${SUFFIX}.bin; rm -f $BIN; cp db_bench $BIN; /usr/bin/time $BIN -db=/dev/shm/dbbench$SUFFIX --benchmarks=fillseq -num=10000000 -compaction_style=2 -fifo_compaction_max_table_files_size_mb=1000 -fifo_compaction_allow_compaction=0 -disable_wal -write_buffer_size=12000000 -format_version=7 -compression_type=$CT -compression_parallel_threads=$PT -compression_level=$CL 2>&1; done) | awk '/micros.op/ {n++; sum += $5;} /system / { cpu += $1 + $2; } END { print "ops/s: " int(sum/n) " cpu*s: " cpu; }'; done; done
```

Before:
```
zstd@4 pt=1 -> ops/s:  883630 cpu*s: 161.12
zstd@4 pt=4 -> ops/s: 1878206 cpu*s: 243.25
zstd@4 pt=8 -> ops/s: 1885002 cpu*s: 245.89
zstd@6 pt=1 -> ops/s:  710767 cpu*s: 189.44
zstd@6 pt=4 -> ops/s: 1706377 cpu*s: 277.29
zstd@6 pt=8 -> ops/s: 1866736 cpu*s: 275.07
zstd@8 pt=1 -> ops/s:  529047 cpu*s: 237.87
zstd@8 pt=4 -> ops/s: 1401379 cpu*s: 330.61
zstd@8 pt=8 -> ops/s: 1895601 cpu*s: 321.59
```

After:
```
zstd@4 pt=1 -> ops/s:  889905 cpu*s: 161.03
zstd@4 pt=4 -> ops/s: 1942240 cpu*s: 193.18
zstd@4 pt=8 -> ops/s: 1922367 cpu*s: 205.21
zstd@6 pt=1 -> ops/s:  713870 cpu*s: 188.91
zstd@6 pt=4 -> ops/s: 1832314 cpu*s: 219.66
zstd@6 pt=8 -> ops/s: 1949631 cpu*s: 229.34
zstd@8 pt=1 -> ops/s:  530324 cpu*s: 238.02
zstd@8 pt=4 -> ops/s: 1479767 cpu*s: 271.65
zstd@8 pt=8 -> ops/s: 1949631 cpu*s: 275.6
```

And we can also look at the cumulative effect of this change and  https://github.com/facebook/rocksdb/pull/13850 that will combine for the parallel compression improvements in the upcoming 10.7 release:

Before both:
```
lz4 pt=1 -> ops/s: 1954445 cpu*s: 95.14
lz4 pt=3 -> ops/s: 1687043 cpu*s: 186.62
lz4 pt=5 -> ops/s: 1708196 cpu*s: 188.33
zstd pt=1 -> ops/s: 1220649 cpu*s: 131.2
zstd pt=3 -> ops/s: 1658100 cpu*s: 227.08
zstd pt=5 -> ops/s: 1685074 cpu*s: 226.08
```

After:
```
lz4 pt=1 -> ops/s: 2048214 cpu*s: 93.24
lz4 pt=3 -> ops/s: 1922049 cpu*s: 122.9
lz4 pt=5 -> ops/s: 1980165 cpu*s: 122.49
zstd pt=1 -> ops/s: 1245165 cpu*s: 128.84
zstd pt=3 -> ops/s: 1956961 cpu*s: 158.73
zstd pt=5 -> ops/s: 1970458 cpu*s: 161.02
```

In summary, before with zstd default level, you could see only
* about 38% increase in throughput for about 73% increase in CPU usage

Now you can get
* about 58% increase in throughput for about 25% increase in CPU usage

## Performance, high load
To validate this for usage on remote compaction workers, we also need to test whether it falls over at high load or anything concerning like that. For this I did a lot of testing with concurrent db_bench and zstd compression_level=8 and parallel_thread (PT) in {1,8} trying to observe "bad" behaviors such as stalls due to preempted threads and such. On a 166 core machine where a "job" is a db_bench process running a fillseq benchmark similar to above in parallel with others, I could summarize the results like this:

10 jobs PT=8 vs. PT=1 -> 12% more CPU usage, 75% reduction in wall time, 1.9 jobs/sec (vs. 0.5)
50 jobs PT=8 vs. PT=1 -> 89% more CPU usage, 27% reduction in wall time, 3.1 jobs/sec (vs. 2.3)
100 jobs PT=8 vs. PT=1 -> 24% more CPU usage, 5% reduction in wall time, 3.25 jobs/sec (vs. 3.1)
150 jobs PT=8 vs. PT=1 -> 4% more CPU usage, 2% increase in wall time, 3.3 jobs/sec (vs. 3.4)
500 jobs PT=8 vs. PT=1 -> 1% more CPU usage, insignificant difference in wall time, 3.3 jobs/sec

Even when there are 4000 threads potentially competing for 166 cores, the throughput (3.3 jobs / sec) is still very close to maximum (3.4). Enabling parallel compression didn't result in notably less throughput (based on wall clock time for all jobs to complete) in any case tested above, and much higher throughput for many cases. If parallel compression causes us to tip from comfortably under-saturating to over-saturating the cores (as in the 50 jobs case), the overall CPU usage can be much higher, presumably due to lower CPU cache hit rates and maybe clock throttling, but parallel compression still has the throughput advantage in those cases.

In other words, what would we stand to gain from being able to intelligently share worker threads between compaction jobs? It doesn't seem that much.

Reviewed By: xingbowang

Differential Revision: D81365623

Pulled By: pdillinger

fbshipit-source-id: 5db5151a959b5d25b84dbe185bc208bd188f2d1c
2025-09-14 07:38:00 -07:00
Changyu Bi acf9d4e445 Fix UDT handling in MultiScan (#13938)
Summary:
we saw some crash test failure at https://github.com/facebook/rocksdb/blob/f46242cef631351a5c8f4a7b0fb0935ec7fa61c8/table/block_based/block_based_table_iterator.cc#L964-L965. This is likely due to timestamp not being considered properly in some places in MultiScan code paths. This PR fixes the issue.

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

Test Plan: crash test with timestamp and multiscan: `python3 -u ./tools/db_crashtest.py whitebox --enable_ts --iterpercent=60 --prefix_size=-1 --prefixpercent=0 --readpercent=0 --test_batches_snapshots=0 --use_multiscan=1 --read_fault_one_in=0 --kill_random_test=88888 --interval=60`

Reviewed By: anand1976

Differential Revision: D82175263

Pulled By: cbi42

fbshipit-source-id: 5d40ede1aec15f8faeaa7fd041b939e68611ff73
2025-09-12 15:56:49 -07:00
Hui Xiao 54941a8d42 Fix a race condition in FIFO size-based compaction where concurrent threads could select the same non-L0 file (#13946)
Summary:
**Context/Summary:**
Fix a race condition (illustrated below) in FIFO size-based compaction where concurrent threads could select the same non-L0 file, causing assertion failures in debug builds or "Cannot delete table file from LSM tree" errors in release builds.
```
Thread 1                           Thread 2
--------                           --------
FIFO size-based compaction
   ↓
Pick L2 file
   ↓
Mark: file.being_compacted = true (file.being_compacted was false)
   ↓
WriteManifestStart (unlock mutex) ─→ FIFO size-based compaction starts
   ↓                                   ↓
Continue manifest write...          Pick SAME L2 file
   ↓                                Mark: file.being_compacted = true  (file.being_compacted was true) 
   ↓                                   ↓
   ↓                                Unlock mutex, wait for manifest
   ↓                                   ↓
Lock mutex ←─────────────────────────────────┘
   ↓
Delete L2 file 
   ↓
Complete ─────────────────────────────→ Try delete same file 
                                        ↓
                                     ERROR: "file not in LSM tree"

🐛 BUG: Both threads pick the same file!
    Thread 2 doesn't properly check file.being_compacted flag
```

**Test**
New test that fails before the fix and passes after

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

Reviewed By: xingbowang

Differential Revision: D82279731

Pulled By: hx235

fbshipit-source-id: b426517f2d1b23dd7d4951157822a2d322fe1435
2025-09-12 13:52:10 -07:00
Jay Huh 4f12c55e3e Make Remote Compaction Failures fall back to local in Stress Test (#13945)
Summary:
This PR enables Stress Test to fall back to local compaction when a remote compaction fails, allowing the compaction to be retried on the main thread.

If the local compaction succeeds, the stress test will continue without failing. The main thread will log that the remote compaction failed and was retried locally, while detailed failure logs from the remote compaction attempt will still be printed by the worker thread for further investigation.

This approach allows us to keep collecting useful logs for diagnosing remote compaction failures in Stress Test, while ensuring the test continues to run with remote compaction enabled.

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

Test Plan:
```
python3 -u tools/db_crashtest.py --cleanup_cmd='' --simple blackbox --remote_compaction_worker_threads=8 --interval=10
```

# Internal Only

https://www.internalfb.com/sandcastle/workflow/1315051091202224133

https://www.internalfb.com/sandcastle/workflow/3382203320165521367

https://www.internalfb.com/sandcastle/workflow/2616591383512372892

https://www.internalfb.com/sandcastle/workflow/4607182418810099066

Reviewed By: hx235

Differential Revision: D82279337

Pulled By: jaykorean

fbshipit-source-id: 6f663ec2eeb642fd4ad885a90efb344432a32f89
2025-09-12 11:42:48 -07:00
Hui Xiao 799f83a934 Rename and clarify CompactionJobStats::has_num_input_records for clarity and set true by default (#13929)
Summary:
**Context/Summary:**
Internally `CompactionJobStats ::num_input_records` is only used for input record count [verification](https://github.com/facebook/rocksdb/blob/1aca60c089a48857930b4191b0c84b6dd98a221c/db/compaction/compaction_job.cc#L2535) and such verification always checks for `CompactionJobStats::has_num_input_records` (now renamed) before using this field. This is needed because the `CompactionJobStats::num_input_records` gets its number from `CompactionIterator::NumInputEntryScanned()` in a subcompaction and this number can be inaccurate purposefully to increase performance, see [CompactionIterator::must_count_input_entries](https://github.com/facebook/rocksdb/pull/13929/files#diff-e6c876f655a21865c0f3dff94b9763f1bd40cf88a8a86f04868201b2e845a890R186-R199) for more.
- This PR renames the `CompactionJobStats::has_num_input_records` to more explicit naming and adds more comments. Not a behavior change.

Also, aggregation of  `CompactionJobStats::has_num_input_records` among all subcompactions is done by [AND](https://github.com/facebook/rocksdb/blob/1aca60c089a48857930b4191b0c84b6dd98a221c/util/compaction_job_stats_impl.cc#L62) operation so it's false if any of the subcompaction has this field being false. The default value of this field should be "true" in order to not mistakenly "false" by default. We are currently fine because `CompactionJobStats::Reset()` that [sets the value to be true](https://github.com/facebook/rocksdb/blob/1aca60c089a48857930b4191b0c84b6dd98a221c/util/compaction_job_stats_impl.cc#L14) is always called before such aggregation.
 - This PR changes the default value to be true.
 - Resumable compaction development plans to set `CompactionJobStats::has_num_input_records` to be false if the previous compaction carries inaccurate records. In order for this not be overwritten by the subsequent progress in [here](https://github.com/facebook/rocksdb/blob/1aca60c089a48857930b4191b0c84b6dd98a221c/db/compaction/compaction_job.cc#L1540-L1543), this PR also changes this = to AND operation and +=. With the default value `CompactionJobStats::has_num_input_records` now to be true (or Reset() already called) and `CompactionJobStats::num_input_records=0` already, this will not a behavior change.

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

Test Plan: - Existing UT to test "...changes the default value to be true" is safe.

Reviewed By: jaykorean

Differential Revision: D82014912

Pulled By: hx235

fbshipit-source-id: 6f211c3b2c9eb7d39abf37271d21a4d3f407b934
2025-09-11 12:19:11 -07:00
Andrew Chang d87e598f70 Update error logging and status reporting for unsupported iouring (#13936)
Summary:
We should add error logging to be able to pinpoint why RocksDB is returning status `NotSupported` for `ReadAsync`.

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

Test Plan: Look at logs (and client logs of error status)

Reviewed By: anand1976

Differential Revision: D82141529

Pulled By: archang19

fbshipit-source-id: c71b70967457be35ef5168321d449f96b2b9441d
2025-09-10 17:54:26 -07:00
Xingbo Wang f46242cef6 Fix uninitialized value complaint in valgrind (#13934)
Summary:
Fix uninitialized value complaint in valgrind due to gtest print padded struct.

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

Test Plan: CI. Verified that valgrind no longer complains about it.

Reviewed By: pdillinger

Differential Revision: D82124983

Pulled By: xingbowang

fbshipit-source-id: 99eb7bab99726c45affe0a231777e5951844d73b
2025-09-10 10:42:07 -07:00
Peter Dillinger 67af5bdc38 Add Temperature::kIce (#13927)
Summary:
... and associated statistics, etc. Someone needs it, so here it is.

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

Test Plan: Updated / extended / added some unit tests

Reviewed By: cbi42

Differential Revision: D81981469

Pulled By: pdillinger

fbshipit-source-id: 52558c08741890b781310906acbc18d9eb479363
2025-09-10 10:29:49 -07:00
Xingbo Wang 8b8a3de2c6 Fix PointLockManager in C++20 (#13933)
Summary:
Fix broken build in PointLockManager change with C++20

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

Test Plan: CI

Reviewed By: pdillinger

Differential Revision: D82073490

Pulled By: xingbowang

fbshipit-source-id: 0bd4936fe0a27a28db61ca5f23d3bea90bce73ef
2025-09-09 21:45:50 -07:00
anand76 0e59c3864f Add copyright to header file (#13930)
Summary:
Add copyright notice to any_lock_manager_test.h

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

Reviewed By: xingbowang

Differential Revision: D82035581

Pulled By: anand1976

fbshipit-source-id: 2275f7c8b41fbd4384bdae011d244bfa117225f7
2025-09-09 15:57:13 -07:00
Andrew Chang 85f1ba572e Add support for custom IOActivity types (#13924)
Summary:
There are some internal use cases that do not map cleanly onto the existing `IOActivity` enums. This PR creates new custom IOActivity types that internal users can use as they see fit.

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

Test Plan: Wrote a simple unit test

Reviewed By: pdillinger

Differential Revision: D82029992

Pulled By: archang19

fbshipit-source-id: a3e23c360baa96cd2e9adf570e71c6e43947bfc8
2025-09-09 14:47:29 -07:00
Xingbo Wang 1aca60c089 Improve efficiency in PointLockManager by using separate Condvar (#13731)
Summary:
PointLockManager manages point lock per key. The old implementation partition the per key lock into 16 stripes. Each stripe handles the point lock for a subset of keys. Each stripe have only one conditional variable. This conditional variable is used by all the transactions that are waiting for its turn to acquire a lock of a key that belongs to this stripe.

In production, we notified that when there are multiple transactions trying to write to the same key, all of them will wait on the same conditional variables. When the previous lock holder released the key, all of the transactions are woken up, but only one of them could proceed, and the rest goes back to sleep. This wasted a lot of CPU cycles. In addition, when there are other keys being locked/unlocked on the same lock stripe, the problem becomes even worse.

In order to solve this issue, we implemented a new PerKeyPointLockManager that keeps a transaction waiter queue at per key level. When a transaction could not acquire a lock immediately, it joins the waiter queue of the key and waits on a dedicated conditional variable. When previous lock holder released the lock, it wakes up the next set of transactions that are eligible to acquire the lock from the waiting queue. The queue respect FIFO order, except it prioritizes lock upgrade/downgrade operation.

However, this waiter queue change increases the deadlock detection cost, because the transaction waiting in the queue also needs to be considered during deadlock detection. To resolve this issue, a new deadlock_timeout_us (microseconds) configuration is introduced in transaction option. Essentially, when a transaction is waiting on a lock, it will join the wait queue and wait for the duration configured by deadlock_timeout_us without perform deadlock detection. If the transaction didn't get the lock after the deadlock_timeout_us timeout is reached, it will then perform deadlock detection and wait until lock_timeout is reached. This optimization takes the heuristic where majority of the transaction would be able to get the lock without perform deadlock detection.

The deadlock_timeout_us configuration needs to be tuned for different workload, if the likelihood of deadlock is very low, the deadlock_timeout_us could be configured close to a big higher than the average transaction execution time, so that majority of the transaction would be able to acquire the lock without performing deadlock detection. If the likelihood of deadlock is high, deadlock_timeout_us could be configured with lower value, so that deadlock would get detected faster.

The new PerKeyPointLockManager is disabled by default. It can be enabled by TransactionDBOptions.use_per_key_point_lock_mgr. The deadlock_timeout_us is only effective when PerKeyPointLockManager is used. When deadlock_timeout_us is set to 0, transaction will perform deadlock detection immediately before wait.

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

Test Plan:
Unit test.
Stress unit test that validates deadlock detection and exclusive, shared lock guarantee.
A new point_lock_bench binary is created to help perform performance test.

Reviewed By: pdillinger

Differential Revision: D77353607

Pulled By: xingbowang

fbshipit-source-id: 21cf93354f9a367a78c8666596ed14013ac7240b
2025-09-08 15:52:54 -07:00
Peter Dillinger 86bb0c0d1b Use C++20 in public API, fix CI (#13915)
Summary:
A follow-up to https://github.com/facebook/rocksdb/issues/13904 which was incomplete in updating CI jobs to support C++20 because the C++20 usage was only in tests. Here we add subtle C++20 usage in the public API ("using enum" feature in db.h) to force the issue.

A lot of the work for this PR was in updating the Ubuntu22 docker image, for earlier compiler/runtime versions supporting C++20, and generating a new Ubuntu24 docker image, for later compiler/runtime versions. The Ubuntu22 image needed to be updated because there are incompatibilities with clang-13 + c++20 + libstdc++ for gcc 11, seen on these examples

```
#include <chrono>

int main(int argc, char *argv[]) {
  std::chrono::microseconds d = {}; return 0;
}
```

and

```
#include <coroutine>

int main() { return 0; }
```

The second was causing recurring failures in build-linux-clang-13-asan-ubsan-with-folly, now fixed.

So we have to install clang's libc++ to compile with clang-13. I haven't been able to get this to work with some of the libraries like benchmark, glog, and/or gflags, but I'm able to compile core RocksDB with clang-13. On this docker image, an extra compiler parameter is needed to compile with gcc and glog because it's built from source perhaps not perfectly, because the ubuntu package transitively conflicts with libc++.

The Ubuntu24 image seems to be low-drama and generally work for testing out newer compiler versions. The mingw build uses Ubuntu24 because the mingw package on Ubuntu22 uses a gcc version that is too old.

And the mass of other code changes are trying to work around new warnings, mostly from clang-analyze, which I upgraded to clang-18 in CI.

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

Test Plan: CI, including temporarily including the nightly jobs in the PR jobs in earlier revisions to test and stabilize

Reviewed By: archang19

Differential Revision: D81933067

Pulled By: pdillinger

fbshipit-source-id: 7e33823006a79d5f3cf5bc1d625f0a3c08a7d74c
2025-09-08 13:11:28 -07:00
Hui Xiao 6b02f137a4 Turn on stats collection in crash test (#13926)
Summary:
**Context/Summary:** it's for formal testing to cover statistics in our stress test

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

Reviewed By: anand1976, jaykorean

Differential Revision: D81943762

Pulled By: hx235

fbshipit-source-id: 4186be0b35839976b7299667492d0cc722128a06
2025-09-08 13:03:42 -07:00
Jay Huh 5a498bf688 Disable Remote Compaction In Stress Test (#13925)
Summary:
After running stress test over a week, we've identified more failures to fix. While we work on the fix, disable the remote compaction temporarily to reduce noise and avoid these failures hiding other failures.

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

Test Plan: CI

Reviewed By: anand1976

Differential Revision: D81934248

Pulled By: jaykorean

fbshipit-source-id: 9ac11926429eebe1aebf7b520a548dc5987b7d76
2025-09-08 11:30:42 -07:00
Andrew Chang 96f796f93a Add logging for errors in external file ingestion path (#13905)
Summary:
This diff adds logging in various places in the external file ingestion code where we check for non-OK status codes.

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

Test Plan: Debugging external file ingestion should be easier with additional logging.

Differential Revision: D81814033

Pulled By: archang19

fbshipit-source-id: 77f8b342cbad892acedc4603c02865c38886f2f4
2025-09-08 09:25:34 -07:00
anand76 0044a76d36 Make failure to load UDI when opening an SST a soft failure (#13921)
Summary:
If user_defined_index_factory in BlockBasedTableOptions is configured and we try to open an SST file without the corresponding UDI (either during DB open or file ingestion), ignore a failure to load the UDI by default. If fail_if_no_udi_on_open in BlockBasedTableOptions is true, then treat it as a fatal error.

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

Test Plan: Update unit tests

Reviewed By: xingbowang

Differential Revision: D81826054

Pulled By: anand1976

fbshipit-source-id: f4fe0b13ccb02b9448622af487680131e349c52b
2025-09-05 19:06:28 -07:00
Changyu Bi a805c9b9a8 Add option to limit max prefetching in MultiScan (#13920)
Summary:
Add a new option `MultiScanArgs::max_prefetch_size` that limits the memory usage of per file pinning of prefetched blocks. Note that this only accounts for compressed block size. This is intended to be a stopgap until we implement some kind of global prefetch manager that limits the global multiscan memory usage.

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

Test Plan: new unit test `./block_based_table_reader_test --gtest_filter="*MultiScanPrefetchSizeLimit/*"`

Reviewed By: xingbowang

Differential Revision: D81630629

Pulled By: cbi42

fbshipit-source-id: 9f66678915242fe1220620531a4b9fd22747cdea
2025-09-05 12:40:32 -07:00
Jay Huh dfbcdaf70e Disable Remote Compaction in UDT enabled Stress Tests (#13919)
Summary:
# Summary

Until we get WAL + Remote Compaction in Stress Test working, temporarily disable this

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

Test Plan: Meta Internal CI run

Reviewed By: anand1976

Differential Revision: D81605621

Pulled By: jaykorean

fbshipit-source-id: 6e1f9a0a7a0f27e7465512689b51364b63ef3e2b
2025-09-03 12:33:44 -07:00
Jay Huh a34683bf54 Disable Remote Compaction when Integrated BlobDB is enabled in Stress Test (#13916)
Summary:
Fixing "Integrated BlobDB is currently incompatible with Remote Compaction" error

https://github.com/facebook/rocksdb/actions/runs/17417658959/job/49449586139

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

Test Plan: CI

Reviewed By: anand1976

Differential Revision: D81537676

Pulled By: jaykorean

fbshipit-source-id: f5e2c40cd498a17cb08486a1cb9404ccf1d812e0
2025-09-02 21:23:11 -07:00
Jay Huh 8fa2aae7f4 Re-enable Remote Compaction Stress Test (#13913)
Summary:
Re-enabling Remote Compaction Stress Test with some changes to stress test feature combo sanitization changes

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

Test Plan:
Ran Meta Internal Tests for a few days

# Follow up
- Skip recovering from WAL in remote worker and re-enable WAL
- Investigate and fix races with Integrated BlobDB

Reviewed By: hx235

Differential Revision: D81509225

Pulled By: jaykorean

fbshipit-source-id: 949762c48ece0a25e3d0281e3510f1e7d3fe3667
2025-09-02 15:32:12 -07:00
Hui Xiao fc8bc60f2d Avoid overwriting non-okay status due to shutdown or manual compaction pause (#13891)
Summary:
**Context/Summary:**
A small change as titled.

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

Test Plan: - Existing UT and rehearsal stress test

Reviewed By: jaykorean

Differential Revision: D80588011

Pulled By: hx235

fbshipit-source-id: 6987e08a4855782305ad742eef6c0196da0d67ca
2025-09-02 12:37:16 -07:00
Xingbo Wang ac4d563dd1 Add random seed to db_crashtest.py to make reproduce test easier. (#13906)
Summary:
Add a new argument --random_seed to script db_crashtest.py to allow reusing the same random seed to produce exactly same test argument. When the argument is missing, a random seed is used, and printed. When developer wants to reproduce the exactly same setup, they could use the same seed with --random_seed for reproduction. The example below shows running the command without and with the argument. All of the arguments are same, except --db and --expected_values_dir, which does not use python random.

* Without --random_seed, a new seed is generated and printed.
```
[xbw@devvm16622.vll0 ~/workspace/ws1/rocksdb (crashtest)]$ /usr/local/bin/python3 -u tools/db_crashtest.py --stress_cmd=./db_stress --cleanup_cmd='' --cf_consistency blackbox --duration=960 --max_key=2500000
Start with random seed 17953760416546706382
Running blackbox-crash-test with
interval_between_crash=120
total-duration=960

Running db_stress with pid=2957716: ./db_stress --WAL_size_limit_MB=0 --WAL_ttl_seconds=60 --acquire_snapshot_one_in=10000 --adaptive_readahead=0 --adm_policy=0 --advise_random_on_open=1 --allow_data_in_errors=True --allow_fallocate=0 --allow_setting_blob_options_dynamically=1 --allow_unprepared_value=0 --async_io=1 --atomic_flush=1 --auto_readahead_size=0 --auto_refresh_iterator_with_snapshot=1 --avoid_flush_during_recovery=0 --avoid_flush_during_shutdown=1 --avoid_unnecessary_blocking_io=0 --backup_max_size=104857600 --backup_one_in=1000 --batch_protection_bytes_per_key=0 --bgerror_resume_retry_interval=100 --blob_cache_size=2097152 --blob_compaction_readahead_size=4194304 --blob_compression_type=zstd --blob_file_size=1073741824 --blob_file_starting_level=0 --blob_garbage_collection_age_cutoff=0.5 --blob_garbage_collection_force_threshold=0.75 --block_align=1 --block_protection_bytes_per_key=0 --block_size=16384 --bloom_before_level=1 --bloom_bits=12 --bottommost_compression_type=none --bottommost_file_compaction_delay=3600 --bytes_per_sync=262144 --cache_index_and_filter_blocks=1 --cache_index_and_filter_blocks_with_high_priority=0 --cache_size=8388608 --cache_type=auto_hyper_clock_cache --charge_compression_dictionary_building_buffer=0 --charge_file_metadata=0 --charge_filter_construction=0 --charge_table_reader=0 --check_multiget_consistency=0 --check_multiget_entity_consistency=1 --checkpoint_one_in=10000 --checksum_type=kxxHash64 --clear_column_family_one_in=0 --compact_files_one_in=1000000 --compact_range_one_in=1000000 --compaction_pri=0 --compaction_readahead_size=1048576 --compaction_style=0 --compaction_ttl=100 --compress_format_version=1 --compressed_secondary_cache_ratio=0.0 --compressed_secondary_cache_size=0 --compression_checksum=0 --compression_manager=none --compression_max_dict_buffer_bytes=0 --compression_max_dict_bytes=0 --compression_parallel_threads=1 --compression_type=none --compression_use_zstd_dict_trainer=1 --compression_zstd_max_train_bytes=0 --continuous_verification_interval=0 --daily_offpeak_time_utc=23:30-03:15 --data_block_index_type=0 --db=/tmp/rocksdb_crashtest_blackboxqishhgdc --db_write_buffer_size=0 --decouple_partitioned_filters=1 --default_temperature=kWarm --default_write_temperature=kCold --delete_obsolete_files_period_micros=30000000 --delpercent=4 --delrangepercent=1 --destroy_db_initially=0 --detect_filter_construct_corruption=0 --disable_file_deletions_one_in=10000 --disable_manual_compaction_one_in=10000 --disable_wal=1 --dump_malloc_stats=1 --enable_blob_files=1 --enable_blob_garbage_collection=1 --enable_checksum_handoff=0 --enable_compaction_filter=0 --enable_custom_split_merge=1 --enable_do_not_compress_roles=1 --enable_index_compression=0 --enable_memtable_insert_with_hint_prefix_extractor=0 --enable_pipelined_write=0 --enable_sst_partitioner_factory=1 --enable_thread_tracking=0 --enable_write_thread_adaptive_yield=0 --error_recovery_with_no_fault_injection=0 --exclude_wal_from_write_fault_injection=0 --expected_values_dir=/tmp/rocksdb_crashtest_expected_udz8mw68 --fifo_allow_compaction=0 --file_checksum_impl=crc32c --file_temperature_age_thresholds= --fill_cache=0 --flush_one_in=1000 --format_version=4 --get_all_column_family_metadata_one_in=1000000 --get_current_wal_file_one_in=0 --get_live_files_apis_one_in=10000 --get_properties_of_all_tables_one_in=1000000 --get_property_one_in=1000000 --get_sorted_wal_files_one_in=0 --hard_pending_compaction_bytes_limit=2097152 --high_pri_pool_ratio=0 --index_block_restart_interval=1 --index_shortening=2 --index_type=0 --ingest_external_file_one_in=0 --ingest_wbwi_one_in=500 --initial_auto_readahead_size=16384 --inplace_update_support=0 --iterpercent=10 --key_len_percent_dist=1,30,69 --key_may_exist_one_in=100000 --last_level_temperature=kUnknown --level_compaction_dynamic_level_bytes=0 --lock_wal_one_in=0 --log_file_time_to_roll=0 --log_readahead_size=16777216 --long_running_snapshots=1 --low_pri_pool_ratio=0 --lowest_used_cache_tier=2 --manifest_preallocation_size=0 --manual_wal_flush_one_in=0 --mark_for_compaction_one_file_in=10 --max_auto_readahead_size=16384 --max_background_compactions=20 --max_bytes_for_level_base=10485760 --max_key=2500000 --max_key_len=3 --max_log_file_size=0 --max_manifest_file_size=1073741824 --max_sequential_skip_in_iterations=8 --max_total_wal_size=0 --max_write_batch_group_size_bytes=16777216 --max_write_buffer_number=3 --max_write_buffer_size_to_maintain=1048576 --memtable_avg_op_scan_flush_trigger=2 --memtable_insert_hint_per_batch=0 --memtable_max_range_deletions=100 --memtable_op_scan_flush_trigger=10 --memtable_prefix_bloom_size_ratio=0.5 --memtable_protection_bytes_per_key=8 --memtable_whole_key_filtering=0 --memtablerep=skip_list --metadata_charge_policy=1 --metadata_read_fault_one_in=32 --metadata_write_fault_one_in=128 --min_blob_size=16 --min_write_buffer_number_to_merge=2 --mmap_read=0 --mock_direct_io=False --nooverwritepercent=1 --num_bottom_pri_threads=1 --num_file_reads_for_auto_readahead=2 --open_files=-1 --open_metadata_read_fault_one_in=0 --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_hits=1 --optimize_filters_for_memory=0 --optimize_multiget_for_io=0 --paranoid_file_checks=1 --paranoid_memory_checks=0 --partition_filters=0 --partition_pinning=1 --pause_background_one_in=10000 --periodic_compaction_seconds=1000 --prefix_size=5 --prefixpercent=5 --prepopulate_blob_cache=1 --prepopulate_block_cache=1 --preserve_internal_time_seconds=36000 --progress_reports=0 --promote_l0_one_in=0 --read_amp_bytes_per_bit=32 --read_fault_one_in=1000 --readahead_size=0 --readpercent=45 --recycle_log_file_num=0 --remote_compaction_worker_threads=0 --reopen=0 --report_bg_io_stats=1 --reset_stats_one_in=1000000 --sample_for_compression=5 --secondary_cache_fault_one_in=0 --secondary_cache_uri=compressed_secondary_cache://capacity=8388608;enable_custom_split_merge=true --set_options_one_in=1000 --skip_stats_update_on_db_open=0 --snapshot_hold_ops=100000 --soft_pending_compaction_bytes_limit=68719476736 --sqfc_name=foo --sqfc_version=2 --sst_file_manager_bytes_per_sec=0 --sst_file_manager_bytes_per_truncate=0 --stats_dump_period_sec=10 --stats_history_buffer_size=1048576 --strict_bytes_per_sync=0 --subcompactions=2 --sync=0 --sync_fault_injection=0 --table_cache_numshardbits=0 --target_file_size_base=524288 --target_file_size_multiplier=2 --test_batches_snapshots=0 --test_cf_consistency=1 --test_ingest_standalone_range_deletion_one_in=0 --top_level_index_pinning=0 --track_and_verify_wals=0 --uncache_aggressiveness=211 --universal_max_read_amp=4 --universal_reduce_file_locking=0 --unpartitioned_pinning=0 --use_adaptive_mutex=1 --use_adaptive_mutex_lru=1 --use_attribute_group=1 --use_blob_cache=0 --use_delta_encoding=0 --use_direct_io_for_flush_and_compaction=1 --use_direct_reads=0 --use_full_merge_v1=0 --use_get_entity=0 --use_merge=0 --use_multi_cf_iterator=0 --use_multi_get_entity=0 --use_multiget=1 --use_multiscan=0 --use_put_entity_one_in=0 --use_shared_block_and_blob_cache=0 --use_sqfc_for_range_queries=1 --use_timed_put_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=1000 --verify_compression=0 --verify_db_one_in=10000 --verify_file_checksums_one_in=1000000 --verify_iterator_with_expected_state_one_in=0 --verify_sst_unique_id_in_manifest=1 --wal_bytes_per_sync=0 --wal_compression=none --write_buffer_size=1048576 --write_dbid_to_manifest=0 --write_fault_one_in=0 --write_identity_file=1 --writepercent=35
```

* With --random_seed, the seed specified in the argument is used.
```
[xbw@devvm16622.vll0 ~/workspace/ws1/rocksdb (crashtest)]$ /usr/local/bin/python3 -u tools/db_crashtest.py --stress_cmd=./db_stress --cleanup_cmd='' --cf_consistency blackbox --duration=960 --max_key=2500000 --random_seed=17953760416546706382
Start with random seed 17953760416546706382
Running blackbox-crash-test with
interval_between_crash=120
total-duration=960

Running db_stress with pid=2959006: ./db_stress --WAL_size_limit_MB=0 --WAL_ttl_seconds=60 --acquire_snapshot_one_in=10000 --adaptive_readahead=0 --adm_policy=0 --advise_random_on_open=1 --allow_data_in_errors=True --allow_fallocate=0 --allow_setting_blob_options_dynamically=1 --allow_unprepared_value=0 --async_io=1 --atomic_flush=1 --auto_readahead_size=0 --auto_refresh_iterator_with_snapshot=1 --avoid_flush_during_recovery=0 --avoid_flush_during_shutdown=1 --avoid_unnecessary_blocking_io=0 --backup_max_size=104857600 --backup_one_in=1000 --batch_protection_bytes_per_key=0 --bgerror_resume_retry_interval=100 --blob_cache_size=2097152 --blob_compaction_readahead_size=4194304 --blob_compression_type=zstd --blob_file_size=1073741824 --blob_file_starting_level=0 --blob_garbage_collection_age_cutoff=0.5 --blob_garbage_collection_force_threshold=0.75 --block_align=1 --block_protection_bytes_per_key=0 --block_size=16384 --bloom_before_level=1 --bloom_bits=12 --bottommost_compression_type=none --bottommost_file_compaction_delay=3600 --bytes_per_sync=262144 --cache_index_and_filter_blocks=1 --cache_index_and_filter_blocks_with_high_priority=0 --cache_size=8388608 --cache_type=auto_hyper_clock_cache --charge_compression_dictionary_building_buffer=0 --charge_file_metadata=0 --charge_filter_construction=0 --charge_table_reader=0 --check_multiget_consistency=0 --check_multiget_entity_consistency=1 --checkpoint_one_in=10000 --checksum_type=kxxHash64 --clear_column_family_one_in=0 --compact_files_one_in=1000000 --compact_range_one_in=1000000 --compaction_pri=0 --compaction_readahead_size=1048576 --compaction_style=0 --compaction_ttl=100 --compress_format_version=1 --compressed_secondary_cache_ratio=0.0 --compressed_secondary_cache_size=0 --compression_checksum=0 --compression_manager=none --compression_max_dict_buffer_bytes=0 --compression_max_dict_bytes=0 --compression_parallel_threads=1 --compression_type=none --compression_use_zstd_dict_trainer=1 --compression_zstd_max_train_bytes=0 --continuous_verification_interval=0 --daily_offpeak_time_utc=23:30-03:15 --data_block_index_type=0 --db=/tmp/rocksdb_crashtest_blackbox0kxvhzbm --db_write_buffer_size=0 --decouple_partitioned_filters=1 --default_temperature=kWarm --default_write_temperature=kCold --delete_obsolete_files_period_micros=30000000 --delpercent=4 --delrangepercent=1 --destroy_db_initially=0 --detect_filter_construct_corruption=0 --disable_file_deletions_one_in=10000 --disable_manual_compaction_one_in=10000 --disable_wal=1 --dump_malloc_stats=1 --enable_blob_files=1 --enable_blob_garbage_collection=1 --enable_checksum_handoff=0 --enable_compaction_filter=0 --enable_custom_split_merge=1 --enable_do_not_compress_roles=1 --enable_index_compression=0 --enable_memtable_insert_with_hint_prefix_extractor=0 --enable_pipelined_write=0 --enable_sst_partitioner_factory=1 --enable_thread_tracking=0 --enable_write_thread_adaptive_yield=0 --error_recovery_with_no_fault_injection=0 --exclude_wal_from_write_fault_injection=0 --expected_values_dir=/tmp/rocksdb_crashtest_expected_hhk9kcgo --fifo_allow_compaction=0 --file_checksum_impl=crc32c --file_temperature_age_thresholds= --fill_cache=0 --flush_one_in=1000 --format_version=4 --get_all_column_family_metadata_one_in=1000000 --get_current_wal_file_one_in=0 --get_live_files_apis_one_in=10000 --get_properties_of_all_tables_one_in=1000000 --get_property_one_in=1000000 --get_sorted_wal_files_one_in=0 --hard_pending_compaction_bytes_limit=2097152 --high_pri_pool_ratio=0 --index_block_restart_interval=1 --index_shortening=2 --index_type=0 --ingest_external_file_one_in=0 --ingest_wbwi_one_in=500 --initial_auto_readahead_size=16384 --inplace_update_support=0 --iterpercent=10 --key_len_percent_dist=1,30,69 --key_may_exist_one_in=100000 --last_level_temperature=kUnknown --level_compaction_dynamic_level_bytes=0 --lock_wal_one_in=0 --log_file_time_to_roll=0 --log_readahead_size=16777216 --long_running_snapshots=1 --low_pri_pool_ratio=0 --lowest_used_cache_tier=2 --manifest_preallocation_size=0 --manual_wal_flush_one_in=0 --mark_for_compaction_one_file_in=10 --max_auto_readahead_size=16384 --max_background_compactions=20 --max_bytes_for_level_base=10485760 --max_key=2500000 --max_key_len=3 --max_log_file_size=0 --max_manifest_file_size=1073741824 --max_sequential_skip_in_iterations=8 --max_total_wal_size=0 --max_write_batch_group_size_bytes=16777216 --max_write_buffer_number=3 --max_write_buffer_size_to_maintain=1048576 --memtable_avg_op_scan_flush_trigger=2 --memtable_insert_hint_per_batch=0 --memtable_max_range_deletions=100 --memtable_op_scan_flush_trigger=10 --memtable_prefix_bloom_size_ratio=0.5 --memtable_protection_bytes_per_key=8 --memtable_whole_key_filtering=0 --memtablerep=skip_list --metadata_charge_policy=1 --metadata_read_fault_one_in=32 --metadata_write_fault_one_in=128 --min_blob_size=16 --min_write_buffer_number_to_merge=2 --mmap_read=0 --mock_direct_io=False --nooverwritepercent=1 --num_bottom_pri_threads=1 --num_file_reads_for_auto_readahead=2 --open_files=-1 --open_metadata_read_fault_one_in=0 --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_hits=1 --optimize_filters_for_memory=0 --optimize_multiget_for_io=0 --paranoid_file_checks=1 --paranoid_memory_checks=0 --partition_filters=0 --partition_pinning=1 --pause_background_one_in=10000 --periodic_compaction_seconds=1000 --prefix_size=5 --prefixpercent=5 --prepopulate_blob_cache=1 --prepopulate_block_cache=1 --preserve_internal_time_seconds=36000 --progress_reports=0 --promote_l0_one_in=0 --read_amp_bytes_per_bit=32 --read_fault_one_in=1000 --readahead_size=0 --readpercent=45 --recycle_log_file_num=0 --remote_compaction_worker_threads=0 --reopen=0 --report_bg_io_stats=1 --reset_stats_one_in=1000000 --sample_for_compression=5 --secondary_cache_fault_one_in=0 --secondary_cache_uri=compressed_secondary_cache://capacity=8388608;enable_custom_split_merge=true --set_options_one_in=1000 --skip_stats_update_on_db_open=0 --snapshot_hold_ops=100000 --soft_pending_compaction_bytes_limit=68719476736 --sqfc_name=foo --sqfc_version=2 --sst_file_manager_bytes_per_sec=0 --sst_file_manager_bytes_per_truncate=0 --stats_dump_period_sec=10 --stats_history_buffer_size=1048576 --strict_bytes_per_sync=0 --subcompactions=2 --sync=0 --sync_fault_injection=0 --table_cache_numshardbits=0 --target_file_size_base=524288 --target_file_size_multiplier=2 --test_batches_snapshots=0 --test_cf_consistency=1 --test_ingest_standalone_range_deletion_one_in=0 --top_level_index_pinning=0 --track_and_verify_wals=0 --uncache_aggressiveness=211 --universal_max_read_amp=4 --universal_reduce_file_locking=0 --unpartitioned_pinning=0 --use_adaptive_mutex=1 --use_adaptive_mutex_lru=1 --use_attribute_group=1 --use_blob_cache=0 --use_delta_encoding=0 --use_direct_io_for_flush_and_compaction=1 --use_direct_reads=0 --use_full_merge_v1=0 --use_get_entity=0 --use_merge=0 --use_multi_cf_iterator=0 --use_multi_get_entity=0 --use_multiget=1 --use_multiscan=0 --use_put_entity_one_in=0 --use_shared_block_and_blob_cache=0 --use_sqfc_for_range_queries=1 --use_timed_put_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=1000 --verify_compression=0 --verify_db_one_in=10000 --verify_file_checksums_one_in=1000000 --verify_iterator_with_expected_state_one_in=0 --verify_sst_unique_id_in_manifest=1 --wal_bytes_per_sync=0 --wal_compression=none --write_buffer_size=1048576 --write_dbid_to_manifest=0 --write_fault_one_in=0 --write_identity_file=1 --writepercent=35
```

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

Test Plan: stress test

Reviewed By: hx235

Differential Revision: D81201034

Pulled By: xingbowang

fbshipit-source-id: 0bb4e0cbcdcf2de9b730492342dcfa18f07e93d6
2025-08-28 23:04:13 -07:00
Peter Dillinger 2950e99219 Require C++20 (#13904)
Summary:
I am wanting to use std::counting_semaphore for something and the timing seems good to require C++20 support. The internets suggest:

* GCC >= 10 is adequate, >= 11 preferred
* Clang >= 10 is needed
* Visual Studio >= 2019 is adquate

And popular linux distributions look like this:
* CentOS Stream 9 -> GCC 11.2  (CentOS 8 is EOL)
* Ubuntu 22.04 LTS -> GCC 11.x  (Ubuntu 20 just ended standard support)
* Debian 12 (oldstable) -> GCC 12.2
  * (Debian 11 has ended security updates, uses GCC 10.2)

This required generating a new docker image based on Ubuntu 22 for CI using gcc. The existing Ubuntu 20 image works for covering appropriate clang versions (though we should maybe add a much later version as well, in the next increment of our Ubuntu 22 image; however the minimum available clang build from apt.llvm.org for Ubuntu 22 is clang 13).

Update to SetDumpFilter is to quiet a mysterious gcc-13 warning-as-error.

Removed --compile-no-warning-as-error from a cmake command line because cmake in the new docker image is too old for this option.

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

Test Plan: CI, one minor unit test added to verify std::counting_semaphor works

Reviewed By: xingbowang

Differential Revision: D81266435

Pulled By: pdillinger

fbshipit-source-id: 26040eeccca7004416e29a6ff4f6ea93f2052684
2025-08-28 16:59:16 -07:00
Hui Xiao 68efd6fd8e Refactor ProcessKeyValueCompaction into smaller functions (#13879)
Summary:
**Context/Summary:**
`ProcessKeyValueCompaction()` has grown too long to resonate or add any logic to resume from some key and save progress for resumable compaction. This PR breaks this function into smaller functions. Almost all of them are cosmetic changes, except for one thing pointed out in below PR conversation.

Specially, this PR did the following:
- Added `SubcompactionInternalIterators`, `SubcompactionKeyBoundaries` and `BlobFileResources` to manage the lifetime of the local variables of the original functions to be used across smaller functions
- Moved AutoThreadOperationStageUpdater, some IO stats measurement to a different place that makes more sense

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

Test Plan: Existing UT

Reviewed By: jaykorean

Differential Revision: D80216092

Pulled By: hx235

fbshipit-source-id: 515615906e5e5fd5ec191bcdd4126f17d282cac2
2025-08-28 13:46:54 -07:00
Peter Dillinger e59bbd7241 First step to improve parallel compression efficiency (#13850)
Summary:
The implementation of parallel compression has historically scaled rather poorly, or perhaps modestly with heavy compression, topping out around 3x throughput vs. serial and incurring big overheads in CPU consumption relative to the throughput.

This change addresses one source of that extra CPU consumption: stashing all the keys of a block for later processing into building index and filter blocks. Historically with parallel compression, the index and filter block updates were handled in the last stage of processing along with writing each data block to the file writer. This was because the index blocks needed to know the BlockHandle of the new data block, which could only be known after every preceeding data block was compressed, to know the starting location for the BlockHandle. And because index and filter partitions were historically coupled (see decouple_partitioned_filters), filter updates had to happen at the same time.

Here we get rid of stashing the keys for later processing and the extra CPU associated with it, by
* Creating a two stage process of adding to index blocks ("prepare" and "finish" each entry; one entry per data block). The two stages must be executable in parallel for separate index entries. NOTE: not yet supported by UserDefinedIndex
* Requiring decouple_partitioned_filters=true for parallel compression, because we now add to filters in the first stage of processing when each key is readily available and we cannot couple that with finalizing index entries in the last stage of processing.

It might seem like adding to filters is something that is expensive (hashing etc.) and should be kept out of the bottle-neck first stage of processing (which includes walking the compaction iterator) but it's probably similar cost to simply stashing the keys away for later processing. (We might be able to reduce a bottle-neck by stashing hashes, but we're not to a point where that is worth the effort.)

And it makes sense to make two more simple public API updates in conjunction with this:
* Set decouple_partitioned_filters=true by default. No signs of problems in production.
* Mark parallel compression as production-ready. It's being thoroughly tested in the crash test, successfully, and in limited production uses.

Follow-up:
* Improve the threading/sychronization model of parallel compression for the next major efficiency improvement
* Consider supporting the parallel-compatible index building APIs with UserDefinedIndex, unless it's considered too dangerous to expect users to safely handle the multi-threading.
* (In a subsequent release) remove all the code associated with coupling filter and index partitions and mark the option as ignored.

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

Test Plan:
for correctness, existing tests

## Performance Data

The "before" data here includes revert of https://github.com/facebook/rocksdb/issues/13828 for combined performance measurement of this change and that one.

```
SUFFIX=`tty | sed 's|/|_|g'`; for CT in lz4 zstd lz4; do for PT in 1 2 3 4 6 8; do echo "$CT pt=$PT"; (for I in `seq 1 1`; do BIN=/dev/shm/dbbench${SUFFIX}.bin; rm -f $BIN; cp db_bench $BIN; /usr/bin/time $BIN -db=/dev/shm/dbbench$SUFFIX --benchmarks=fillseq -num=30000000 -compaction_style=2 -fifo_compaction_max_table_files_size_mb=1000 -fifo_compaction_allow_compaction=0 -disable_wal -write_buffer_size=12000000 -format_version=7 -compression_type=$CT -compression_parallel_threads=$PT 2>&1 | tail -n 3 | head -n 2; done); done; done
```

To get a sense of the overall performance relative to number of parallel threads, we vary that with popular fast compression and popular heavier weight compression (some noise in this data, don't interpret each data point too strongly)

lz4 pt=1
2107431 -> 2112941 ops/sec (+0.3% - improvement)
(26.51 + 0.75) = 27.26 CPU sec -> (26.63 + 0.79) = 27.42 CPU sec (+0.6% - regression)
lz4 pt=2
1606660 -> 1580333 ops/sec (-1.6% - regression)
(47.10 + 8.37) = 55.47 CPU sec -> (45.05 + 9.23) = 54.28 CPU sec (-2.2% - improvement)
lz4 pt=3
1701353 -> 1889283 ops/sec (+11.1% - improvement)
(47.23 + 8.29) = 55.52 CPU sec -> (43.89 + 8.33) = 52.22 CPU sec (-6.0% - improvement)
lz4 pt=4
1651504 -> 1817890 ops/sec (+10.1% - improvement)
(48.07 + 8.31) = 56.38 CPU sec -> (44.77 + 8.45) = 53.22 CPU sec (-5.6% - improvement)
lz4 pt=6
1716099 -> 1888523 ops/sec (+10.1% - improvement)
(47.50 + 8.45) = 55.95 CPU sec -> (44.25 + 8.73) = 52.98 CPU sec (-5.3% - improvement)
lz4 pt=8
1696840 -> 1797256 ops/sec (+5.9% - improvement)
(48.09 + 8.61) = 56.70 CPU sec -> (45.90 + 8.68) = 54.58 CPU sec (-3.8% - improvement)

Clearly parallel threads do not help with fast compression like LZ4, but it's not as bad as it was before.

zstd pt=1
1214258 -> 1202863 ops/sec (-0.9% - regression)
(38.26 + 0.66) = 38.92 CPU sec -> (39.37 + 0.69) = 40.06 CPU sec (+2.9% - regression)
zstd pt=2
1194673 -> 1152746 ops/sec (-3.5% - regression)
(61.01 + 9.85) = 70.86 CPU sec -> (58.28 + 9.99) = 68.27 CPU sec (-3.7% - improvement)
zstd pt=3
1653661 -> 1825618 ops/sec (+10.4% - improvement)
(60.07 + 8.45) = 68.52 CPU sec -> (56.03 + 8.43) = 64.46 CPU sec (-5.9% - improvement)
zstd pt=4
1691723 -> 1890976 ops/sec (+11.8% - improvement)
(59.72 + 8.46) = 68.18 CPU sec -> (55.96 + 8.27) = 64.23 CPU sec (-5.7% - improvement)
zstd pt=6
1684982 -> 1900002 ops/sec (+12.8% - improvement)
(58.89 + 8.26) = 67.15 CPU sec -> (55.98 + 8.48) = 64.46 CPU sec (-4.0% - improvement)
zstd pt=8
1648282 -> 1892531 ops/sec (+14.8% - improvement)
(59.43 + 8.63) = 68.06 CPU sec -> (56.49 + 8.32) = 64.81 CPU sec (-4.8% - improvement)

The throughput is now able to increase by *more than half* with lots of parallelism, rather than only *about a third*.

Scalability is a bit better with higher compression level, and we still see a benefit from this change. (We've also enabled partitioned indexes and filters here, which sees essentially the same benefits):

zstd pt=1 compression_level=7
595720 -> 597359 ops/sec (+0.3% - improvement)
(63.45 + 0.73) = 64.18 CPU sec -> (63.25 + 0.71) = 63.96 CPU sec (-0.3% - improvement)
zstd pt=4 compression_level=7
1527116 -> 1501779 ops/sec (-1.7% - regression)
(85.00 + 8.14) = 93.14 CPU sec -> (81.85 + 9.02) = 90.87 CPU sec (-2.5% - improvement)
zstd pt=6 compression_level=7
1678239 -> 1956070 ops/sec (+16.5% - improvement)
(83.77 + 8.11) = 91.88 CPU sec -> (79.87 + 7.78) = 87.65 CPU sec (-4.6% - improvement)
zstd pt=8 compression_level=7
1696132 -> 1953041 ops/sec (+15.1% - improvement)
(83.97 + 8.14) = 92.11 CPU sec -> (80.61 + 7.78) = 88.39 CPU sec (-4.1% - improvement)

With more tests, not really seeing any consistent differences with no parallelism (despite some micro-optimizations thrown in)

Reviewed By: hx235

Differential Revision: D79853111

Pulled By: pdillinger

fbshipit-source-id: 7a34fd7811217fb74fa6d3efaea7ffcce72beec7
2025-08-27 18:57:44 -07:00
ngina 749e11f0ad Add compaction on deletion-trigger test to db stress test (#13894)
Summary:
Enable stress testing of deletion-triggered compaction.

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

Test Plan:
```
 python3 -u tools/db_crashtest.py --simple whitebox --enable_compaction_on_deletion_trigger=true
```

Reviewed By: jaykorean

Differential Revision: D81175559

Pulled By: nmk70

fbshipit-source-id: c5128b7c1e2d07833b0e9385e04b342bc42c65cf
2025-08-27 17:08:15 -07:00
Hui Xiao b67149a55e Skip DumpStats() on dropped CF (#13900)
Summary:
**Context/Summary:**

DumpStats() do not skip dropped CF and can run into a seg fault like below
```
2025-08-23T06:44:05.0469230Z �[0;32m[ RUN      ] �[mFormatLatest/ColumnFamilyTest.LiveIteratorWithDroppedColumnFamily/0
2025-08-23T06:44:05.0470050Z Received signal 11 (Segmentation fault: 11)
2025-08-23T06:44:05.0470510Z #0   0x7000069305e0
2025-08-23T06:44:05.0471070Z https://github.com/facebook/rocksdb/issues/1   rocksdb::DBImpl::DumpStats() (in librocksdb.10.6.0.dylib) (db_impl.cc:1076)
```

This PR skipped it.

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

Test Plan:
- Deterministically repro-ed the seg fault before the fix and ensure it doesn't happen after the fix
```
 diff --git a/db/column_family_test.cc b/db/column_family_test.cc
index 3a2ca0617..f57d6f757 100644
 --- a/db/column_family_test.cc
+++ b/db/column_family_test.cc
@@ -2372,11 +2372,17 @@ TEST_P(ColumnFamilyTest, LiveIteratorWithDroppedColumnFamily) {
   int kKeysNum = 10000;
   PutRandomData(1, kKeysNum, 100);
   {
+    ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
+        {{"PostDrop", "BeforeAccessCFD"}, {"PostAccessCFD", "BeforeGo"}});
+
+    ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
     std::unique_ptr<Iterator> iterator(
         db_->NewIterator(ReadOptions(), handles_[1]));
     iterator->SeekToFirst();

     DropColumnFamilies({1});
+    TEST_SYNC_POINT("PostDrop");
+    TEST_SYNC_POINT("BeforeGo");

     // Make sure iterator created can still be used.
     int count = 0;
@@ -2386,6 +2392,9 @@ TEST_P(ColumnFamilyTest, LiveIteratorWithDroppedColumnFamily) {
     }
     ASSERT_OK(iterator->status());
     ASSERT_EQ(count, kKeysNum);
+
+    ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+    ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
   }

   Reopen();
 diff --git a/db/db_impl/db_impl.cc b/db/db_impl/db_impl.cc
index a8e4f5f8f..a8a0499c0 100644
 --- a/db/db_impl/db_impl.cc
+++ b/db/db_impl/db_impl.cc
@@ -1073,8 +1073,10 @@ void DBImpl::DumpStats() {
         continue;
       }

-      auto* table_factory =
-          cfd->GetCurrentMutableCFOptions().table_factory.get();
+      TEST_SYNC_POINT("BeforeAccessCFD");
+      auto moptions = cfd->GetCurrentMutableCFOptions();
+      auto* table_factory = moptions.table_factory.get();
+      TEST_SYNC_POINT("PostAccessCFD");
       assert(table_factory != nullptr);
       // FIXME: need to a shared_ptr if/when block_cache is going to be mutable
       Cache* cache =
~
```

Reviewed By: archang19

Differential Revision: D81003739

Pulled By: hx235

fbshipit-source-id: bdf3c4cc45988f43e79ebc191a20af5b70ac289f
2025-08-26 11:20:41 -07:00
Hui Xiao d399165109 Ignore IOActivity check for ManagedSnapshot snapshot_guard(db_); for TestMultiScan (#13898)
Summary:
**Context/Summary:**

RocksDB stress test verifies IOActivity is set correctly through reusing the pass-in Read/Write options through assertion. This is too strict for API that does not take or do not need to take Read/WriteOptions yet hence assertion failure.
```
stderr:
 db_stress: ... db_stress_tool/db_stress_env_wrapper.h:24: void rocksdb::(anonymous namespace)::CheckIOActivity(const IOOptions &): Assertion `io_activity == Env::IOActivity::kUnknown || io_activity == options.io_activity' failed.
Received signal 6 (Aborted)
```
An example is ManagedSnapshot snapshot_guard(db_); in TestMultiScan().

This PR ignores such check.

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

Test Plan: The same command repro-ed this assertion failure passes after this fix

Reviewed By: archang19

Differential Revision: D80983214

Pulled By: hx235

fbshipit-source-id: d8b660f8c8771198bc7fa0e805c3e86d2584f03e
2025-08-26 11:03:13 -07:00
Hui Xiao 8d2f420db2 Shorten the lifetime of statistics object in db stress (#13899)
Summary:
**Context/Summary:**
Clear statistics reference from options_ to intentionally shorten the statistics object lifetime to be same as the db object (which is the common case in practice) and detect if RocksDB access the statistics beyond its lifetime.

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

Test Plan: - [Ongoing] Stress test rehearsal

Reviewed By: pdillinger

Differential Revision: D80985435

Pulled By: hx235

fbshipit-source-id: ab238231cd81f47fa451aea12a0c85fa11d9ac81
2025-08-26 11:01:12 -07:00
anand76 1842a4029f Update main for 10.7 (#13897)
Summary:
* Release notes from 10.6 branch
* Update version.h
* Add [10.6.fb](https://github.com/facebook/rocksdb/tree/10.4.fb) (to check_format_compatible.sh
* No update to folly commit hash due to build failures

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

Reviewed By: mszeszko-meta

Differential Revision: D80971628

Pulled By: anand1976

fbshipit-source-id: a24dbe90b5c54f781b2d017497ea3a22fcf6e148
2025-08-25 16:13:13 -07:00
Changyu Bi 82b5a2d3fc Allow ingestion of any DB generated SST file (#13878)
Summary:
`IngestExternalFileOptions::allow_db_generated_files` requires SST files to have zero sequence number. This PR opens it up for any DB generated SST files. Currently we don't do global sequence number assignment when `allow_db_generated_files` is true, so we require that files do not overlap with any key in the CF. One behavior difference is that now we allow ingesting overlapping files when `allow_db_generated_files` is true. Users need to ensure that files are ordered such that later files have more recent updates.

Intended follow ups:
- Record smallest seqno in table property, so that we don't need to scan the file for it.
- Cover allow_db_generated_files in crash test. We may create a new DB and ingest all files from a CF for verification.
- Add APIs that uses allow_db_generated_files. For example, an API for ingesting SST files from a source CF, so that we take care of ingestion file ordering for user. If we are already getting metadata from the source CF, we may be use it as a hint for level placement instead of dividing input files into batches again (`ExternalSstFileIngestionJob::DivideInputFilesIntoBatches`).

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

Test Plan: two new unit tests.

Reviewed By: hx235, xingbowang

Differential Revision: D80233727

Pulled By: cbi42

fbshipit-source-id: 74209386d8426c434bff2d9a734f06db537eb50c
2025-08-22 16:05:56 -07:00
Changyu Bi 439e1707fc Fix MultiScan Prepare() to support dictionary compression (#13896)
Summary:
I saw failure when added some asserts near https://github.com/facebook/rocksdb/blob/b9957c991cae44959f96888369caf1b145398132/table/block_based/block_based_table_iterator.cc#L1201-L1205 in stress test. The decompression failed with error message like "Corruption: Failed zlib inflate: -3". This PR fixes the issue to use the right decompressor for dictionary compression.

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

Test Plan: updated unit test that checks no I/O is done after Prepare(), this would fail before this change.

Reviewed By: anand1976

Differential Revision: D80821500

Pulled By: cbi42

fbshipit-source-id: a4322c0da99a2d10e9787d0ec168668567c0c19a
2025-08-22 13:32:10 -07:00
Andrew Chang 239b06cefb Retry on some io_uring_wait_cqe error codes (#13890)
Summary:
RocksDB currently aborts whenever `io_uring_wait_cqe` returns an error code. It also does not log what error code was returned.

While experimenting with `IO_URING`, my application crashed because of this.

I asked the Linux Kernel user group the best way to handle unsuccessful `io_uring_wait_cqe`.

It was recommended to retry on `EINTR`, `EAGAIN`, and `ETIME`. `ETIME` only happens when waiting with a timeout, so I am not handling it.

I also write to `stderr` so that we have some debugging information if we abort.

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

Test Plan: Unfortunately this is hard to cover through unit/stress tests. We have to see what sort of errors get encountered in production.

Reviewed By: anand1976

Differential Revision: D80639955

Pulled By: archang19

fbshipit-source-id: e3a230bd37552ec0f36be34e6a4e53cfd2a254f1
2025-08-22 12:31:50 -07:00
zaidoon b9957c991c actually expose rocksdb_status_ptr_get_error via c api (#13875)
Summary:
the function implementation is here: https://github.com/facebook/rocksdb/blob/8f0ab1598effd4b05f6f88310c7bd9aaf5d418c6/db/c.cc#L928-L930 but it wasn't fully exposed

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

Reviewed By: hx235

Differential Revision: D80717828

Pulled By: cbi42

fbshipit-source-id: d6aaa984f24e469aa8ddb81524dc156b85e891f2
2025-08-21 14:50:22 -07:00
zaidoon 444f1ed07f expose compact on deletion factory with min file size via C api (#13887)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/13887

Reviewed By: hx235

Differential Revision: D80717735

Pulled By: cbi42

fbshipit-source-id: efecf436188d473a18359e715df979ff24f2fd2e
2025-08-21 11:51:28 -07:00
anand76 a5d4db64e2 Fix multiscan crash when fill_cache=false (#13889)
Summary:
When fill_cache is ReadOptions is false, multi scan Prepare crashes with the following assertion failure. In this case, CreateAndPibBlockInCache needs to directly create a block with full ownership.

https://github.com/facebook/rocksdb/issues/9  0x00007f2fc003bc93 in __GI___assert_fail (assertion=0x7f2fc2147361 "pinned_data_blocks_guard[block_idx].GetValue()", file=0x7f2fc2146e08 "table/block_based/block_based_table_iterator.cc", line=1178, function=0x7f2fc2147262 "virtual void rocksdb::BlockBasedTableIterator::Prepare(const rocksdb::MultiScanArgs *)") at assert.c:101
101 in assert.c
https://github.com/facebook/rocksdb/issues/10 0x00007f2fc1d73088 in rocksdb::BlockBasedTableIterator::Prepare(rocksdb::MultiScanArgs const*) () from /data/users/anand76/rocksdb_anand76/librocksdb.so.10.6

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

Test Plan: Parameterize the DBMultiScanIteratorTest tests with fill_cache

Reviewed By: cbi42

Differential Revision: D80552069

Pulled By: anand1976

fbshipit-source-id: 1a0b64af1e14c63d826add1f994a832ebff12757
2025-08-21 08:55:47 -07:00
Changyu Bi 0b426ff58d Enable multiscan in crash test (#13888)
Summary:
I ran multiple runs of crash test jobs internally, so far I've seen one iterator mismatch and one assertion failure. I've added relevant logging improvements to help debugging them. use_multiscan will be stable within a crash test run to make it easier to triage.

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

Test Plan: `python3 tools/db_crashtest.py whitebox --prefix_size=-1 --test_batches_snapshots=0 --use_multiscan=1 --read_fault_one_in=0 --kill_random_test=88888`

Reviewed By: anand1976

Differential Revision: D80627399

Pulled By: cbi42

fbshipit-source-id: 2fa3f77e730f5bc7d1d200dc122cf84e3558c588
2025-08-20 12:02:20 -07:00
Changyu Bi 618f660eab Configurable multiscan IO coalescing threshold (#13886)
Summary:
Add a new filed `io_coalesce_threshold` to MultiScanArgs to make IO coalescing threshold configurable.

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

Test Plan:
db_bench showing less IO requests with higher io_coalesce_threshold
```
Single L0 file, iterator uses BlockBasedTableIterator directly, skipping LevelIterator

DB Set up: ./db_bench --benchmarks="fillseq,compact" --disable_wal=1 --threads=1 --num_levels=1 --compaction_style=2 --fifo_compaction_max_table_files_size_mb=1000 --write_buffer_size=268435456

./db_bench --db="/tmp/rocksdbtest-543376/dbbench" --use_existing_db=1 --benchmarks=multiscan --disable_auto_compactions=1 --seek_nexts=100 --threads=32 --duration=10 --statistics=1 --use_direct_reads=1 ..

--multiscan_coalesce_threshold=0
rocksdb.non.last.level.read.bytes COUNT : 54591304136
rocksdb.non.last.level.read.count COUNT : 7680204
multiscan    :     397.197 micros/op 79401 ops/sec 10.377 seconds 823968 operations; (multscans:24999)

--multiscan_coalesce_threshold=16384
rocksdb.non.last.level.read.bytes COUNT : 95960989272
rocksdb.non.last.level.read.count COUNT : 912008
multiscan    :     389.099 micros/op 81064 ops/sec 10.312 seconds 835968 operations; (multscans:25999)

--multiscan_coalesce_threshold=163840
rocksdb.non.last.level.read.bytes COUNT : 98805008718
rocksdb.non.last.level.read.count COUNT : 827893
multiscan    :     392.831 micros/op 80357 ops/sec 10.353 seconds 831968 operations; (multscans:25999)

DB with multiple files in a level, iterator will use LevelIterator
./db_bench --benchmarks="fillseq,compact" --disable_wal=1 --threads=1 --num_levels=6 --num=10000000

./db_bench --db="/tmp/rocksdbtest-543376/dbbench" --use_existing_db=1 --benchmarks=multiscan --disable_auto_compactions=1 --seek_nexts=100 --threads=32 --duration=10 --statistics=1 --use_direct_reads=1 --num=10000000

--multiscan_coalesce_threshold=0
multiscan    :    1161.734 micros/op 26995 ops/sec 10.667 seconds 287968 operations; (multscans:8999)
rocksdb.non.last.level.read.bytes COUNT : 23917753523
rocksdb.non.last.level.read.count COUNT : 2868907

--multiscan_coalesce_threshold=16384
rocksdb.non.last.level.read.bytes COUNT : 35022281853
rocksdb.non.last.level.read.count COUNT : 287375
multiscan    :    1195.336 micros/op 26265 ops/sec 10.850 seconds 284968 operations; (multscans:8999)

```

Reviewed By: anand1976

Differential Revision: D80381441

Pulled By: cbi42

fbshipit-source-id: 57cc67df4a808e27c3a48ddf3ef6907bec131ee9
2025-08-18 10:56:16 -07:00
Maciej Szeszko 84f814454a Remove reservation mismatch assert in cache adapter destructor (#13885)
Summary:
The assert occasionally throws off the stress test runs. We already have sufficient logging in place to collect the signal about secondary cache capacity exceeding primary cache reservation for further investigation.

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

Reviewed By: anand1976

Differential Revision: D80355513

Pulled By: mszeszko-meta

fbshipit-source-id: b36926f0493a3aca19818a1980ef79277db9fe7e
2025-08-15 15:41:01 -07:00
anand76 772e342a92 Add an option to sst_dump to list all metadata blocks (#13838)
Summary:
Add the --list_meta_blocks option to sst_dump. This PR also refactors some of the test code in sst_dump_test.

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

Reviewed By: cbi42

Differential Revision: D80320812

Pulled By: anand1976

fbshipit-source-id: 921b6560fbd756f5f8b364893700d240d3b7ad00
2025-08-15 09:42:42 -07:00
Peter Dillinger b3fdb9b3cc Use safer atomic APIs for some memtable code (#13844)
Summary:
Two instances of change that are not just cosmetic:

* InlineSkipList<>::Node::CASNext() was implicitly using memory_order_seq_cst to access `next_` while it's intended to be accessed with acquire/release. This is probably not a correctness issue for compare_exchange_strong but potentially a previously missed optimization.
* Similar for `max_height_` in Insert which is otherwise accessed with relaxed memory order.
* One non-relaxed access to `is_range_del_table_empty_` in a function only used in assertions. Access to this atomic is otherwise relaxed (and should be - comment added)

Didn't do all of memtable.h because some of them are more complicated changes and I should probably add FetchMin and FetchMax functions to simplify and take advantage of C++27 functions where available (intended follow-up).

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

Test Plan: existing tests

Reviewed By: xingbowang

Differential Revision: D79742552

Pulled By: pdillinger

fbshipit-source-id: d97ce72ba9af6c105694b7d40622db9e994720cd
2025-08-14 21:54:52 -07:00
Peter Dillinger 5c7162da27 Set decouple_partitioned_filters=true by default (#13881)
Summary:
This is an important feature for avoiding (reducing) unfair block cache treatment for a lot of blocks. It should also unlock some parallel optimizations (https://github.com/facebook/rocksdb/issues/13850) and code simplification.

Consider for follow-up:
* Feature to avoid majorly under0sized data blocks and filter and index partition blocks

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

Test Plan: existing tests, been looking good in production

Reviewed By: hx235

Differential Revision: D80288192

Pulled By: pdillinger

fbshipit-source-id: 5e274ffffb044713278d2a286db6bceaab2dadec
2025-08-14 21:03:47 -07:00
Changyu Bi 972fd9adf1 Remove expect_valid_internal_key parameter from CompactionIterator (#13882)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/13882

The `expect_valid_internal_key` parameter was always passed as true, with false only used in one unit test. This change removes the parameter and always fail compaction when encountering corrupted internal keys, which is the expected production behavior.

Reviewed By: mszeszko-meta

Differential Revision: D80287672

fbshipit-source-id: e30a282ac30d7fded677504cec11173de8d15167
2025-08-14 16:40:25 -07:00
anand76 1369c7b169 Allow a user defined index to be configured from a string (#13880)
Summary:
Allow a user defined index to be configured from a string

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

Test Plan: Add a unit test in table_test.cc

Reviewed By: bikash-c

Differential Revision: D80237701

Pulled By: anand1976

fbshipit-source-id: 8b3d0bcdfbb4bb76803916ea1b1f940a4d985dfd
2025-08-14 09:05:39 -07:00
Hui Xiao 7e9c96020b Improve two error messages on WAL recovery (#13876)
Summary:
**Context/Summary:** ... for better readability

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

Test Plan: Existing UT

Reviewed By: mszeszko-meta

Differential Revision: D80185817

Pulled By: hx235

fbshipit-source-id: 534d37dd747369da48fc5903acc66bb9c8f5206d
2025-08-13 12:02:12 -07:00
anand76 8f0ab1598e Make UDI interface consistently use the user key (#13865)
Summary:
The original intention of the User Defined Index interface was to use the user key. However, the implementation mixed user and internal key usage. This PR makes it consistent. It also clarifies the UDI contract.

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

Test Plan: Update tests in table_test.cc

Reviewed By: pdillinger

Differential Revision: D80050344

Pulled By: anand1976

fbshipit-source-id: ace47737d21684ec19709640a09e198cee2d98bd
2025-08-12 14:00:40 -07:00
Hui Xiao e12734d51f Disable track_and_verify_wals temporarily (#13869)
Summary:
... as we see some issues that rehearsal stress test didn't surface.

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

Reviewed By: cbi42

Differential Revision: D80103341

Pulled By: hx235

fbshipit-source-id: 8b2c1d76d4c3099727ba3a69de44de67afd64369
2025-08-12 11:57:29 -07:00
Karthik Krishnamurthy 99bbc2d7fa Fix bug in the generation of index and meta blocks when constructing UDI (#13846)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/13846

This diff addresses few issues that was identified during testing of the user defined index.

1. During the finishing of the index blocks, we run into an infinite loop because the user defined index wrapper returns
early on incomplete status. This happens because the wrapper blindly returns the status if it not OK. But, the status
could legitimately be `Incomplete()` for some indices like Partitioned Index (serving as the internal index for the UDI
wrapper). Fix is to exclude `Incomplete()` check from the status check early in the UDI wrapper's finish.

2. Once we fixed (1), we noticed that the meta blocks for the UDI-based index writer were not written out to the final
SST file. This is because the UDI's meta blocks are created after the internal index's meta blocks and the block-based
index builder didn't account for this. The fix is to finish the UDI wrapper first which will create the necessary meta blocks
and then finish the internal index. If the internal index is incomplete, the block-based index builder should still continue
to write out the meta blocks.

3. OnKeyAdded when delegating to the user-defined index should only pass the user key. The UDI builder doesn't
understand RocksDB's internal key format and while that poses interesting challenges when the UDI is used for non
last level SST files, our plan is to restrict the usage of the UDI to last level files only (for now).

Reviewed By: pdillinger

Differential Revision: D79781453

fbshipit-source-id: 2239c8fc016da55df5c24be6aacc8f6357cab029
2025-08-12 08:41:55 -07:00
Changyu Bi 496eebaee8 Fix compilation error using CLANG (#13864)
Summary:
fix the following error showing up in continuous tests:
```
Makefile:186: Warning: Compiling in debug mode. Don't use the resulting binary in production
port/mmap.cc:46:15: error: first argument in call to 'memcpy' is a pointer to non-trivially copyable type 'rocksdb::MemMapping' [-Werror,-Wnontrivial-memcall]
   46 |   std::memcpy(this, &other, sizeof(*this));
      |               ^
port/mmap.cc:46:15: note: explicitly cast the pointer to silence this warning
   46 |   std::memcpy(this, &other, sizeof(*this));
      |               ^
      |               (void*)
1 error generated.
make: *** [Makefile:2580: port/mmap.o] Error 1
make: *** Waiting for unfinished jobs....
```

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

Test Plan: `make USE_CLANG=1 j=150 check` with https://github.com/facebook/rocksdb/blob/13f054febb26100184eeefaac11877d735d45ac2/build_tools/build_detect_platform#L61-L70 commented out.

Reviewed By: mszeszko-meta

Differential Revision: D80033441

Pulled By: cbi42

fbshipit-source-id: b2330eea71fe28243236b75128ec6f3f1e971873
2025-08-11 15:15:26 -07:00
Hui Xiao d8835f918c Enable track_and_verify_wal in stress test (#13853)
Summary:
**Context/Summary:**
https://github.com/facebook/rocksdb/pull/13508 accidentally didn't enable track_and_verify_wal back and this PR will enable it.

**Test**
[ongoing] Rehearsal stress test

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

Reviewed By: pdillinger

Differential Revision: D79909991

Pulled By: hx235

fbshipit-source-id: aea91c98e43f26dec9a8988c837a6ed821979a3c
2025-08-11 13:13:21 -07:00
Changyu Bi 13f054febb Support DbStressCustomCompressionManager in ldb and sst_dump (#13827)
Summary:
while debugging stress test failure, I noticed that sst_dump and ldb do not work if custom db_stress compression manager is used. This PR adds support for it.

```
 ./sst_dump --command=raw --show_properties --file=/tmp/rocksdb_crashtest_whitebox4ny5mass/000589.sst
options.env is 0x7f2b1f4b9000
Process /tmp/rocksdb_crashtest_whitebox4ny5mass/000589.sst
Sst file format: block-based
/tmp/rocksdb_crashtest_whitebox4ny5mass/000589.sst: Not implemented: Could not load CompressionManager: DbStressCustom1
/tmp/rocksdb_crashtest_whitebox4ny5mass/000589.sst is not a valid SST file

./ldb idump --db=/tmp/rocksdb_crashtest_whiteboxy_emah11 --ignore_unknown_options  --hex >> /tmp/i_dump
Failed: Not implemented: Could not load CompressionManager: DbStressCustom1
```

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

Test Plan: manually tested that ldb and sst_dump work with DbStressCustomCompressionManager after this PR

Reviewed By: pdillinger

Differential Revision: D79461175

Pulled By: cbi42

fbshipit-source-id: c8c092b10b4fde3a295b00751057749e8f0cf095
2025-08-08 11:04:14 -07:00
Ryan Hancock 0b44282a9d Introduction of MultiScanOptions (#13837)
Summary:
To better support future options, and changes, we need to convert the std::vector<ScanOptions> to something more malleable.

This diff introduces the MultiScanOptions structure and pipes it through the various points in the code in the Prepare path.

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

Test Plan:
Ensure all associated tests pass
```
make check all
```

Reviewed By: cbi42

Differential Revision: D79655229

Pulled By: krhancoc

fbshipit-source-id: 3a90fb7420e9655021de85ed0158b866f8bfba05
2025-08-08 10:33:36 -07:00
Hui Xiao b8b42b7a68 Simple cleanup to CompactionJob::Run() (#13851)
Summary:
**Context/Summary:**
This update, which should have been part of a previous refactoring [PR](https://github.com/facebook/rocksdb/commit/d2ac955881e856fc69d5b15427d742fc635aaead), involves simple renaming for clarity and ensures output table properties are only set when compaction succeeds. Output properties are not meaningful if compaction fails, so this change prevents their population in such cases. Additionally, subsequent statistics updates already do not rely on output file table properties, maintaining correctness regardless of compaction success.

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

Test Plan: Existing unit tests

Reviewed By: jaykorean

Differential Revision: D79862244

Pulled By: hx235

fbshipit-source-id: 1db16b8dc7b820fab3ec1d5c8a4b757466590e2c
2025-08-08 10:09:55 -07:00
Hui Xiao d2ac955881 Refactor CompactionJob::Run() into smaller focused methods (#13849)
Summary:
**Context/Summary:**
The `CompactionJob::Run()` method has grown too large and complex, making it difficult to implement moderate changes or reason about the code flow (e.g., determining where to save compaction progress for resuming). This PR refactors the method into smaller, more focused functions to improve readability and maintainability.

The refactoring consists mostly of cosmetic changes that extract logical sections into separate methods, with two notable functional improvements:

1.  **Relocated output processing logic**: Moved code under `RemoveEmptyOutputs()` and `HasNewBlobFiles()` to where it's actually needed, rather than piggy-backing on the subcompaction state loop. While this introduces 2 additional loops over subcompactions, the performance impact should be negligible given the improved code clarity.

2.  **Repositioned statistics updates**: Moved `UpdateCompactionJobInputStats()` and `UpdateCompactionJobOutputStats()` from the record verification section to the end `FinalizeCompactionRun()` methods. This change is safe since record verification is a read-only operation that doesn't modify any statistics.

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

Test Plan: Existing unit tests

Reviewed By: jaykorean

Differential Revision: D79824429

Pulled By: hx235

fbshipit-source-id: 6b73136f32ecc6842a04a77502b7dbb0bbf507f7
2025-08-07 17:22:01 -07:00
Jay Huh b43a84fc37 Temporarily Disable Remote Compaction In Stress Test (#13848)
Summary:
Previous attempts were not enough keep the stress test running with remote compaction enabled - https://github.com/facebook/rocksdb/pull/13845, https://github.com/facebook/rocksdb/pull/13843, https://github.com/facebook/rocksdb/pull/13835

We will disable the remote compaction in stress test and address this with a better strategy (using internal Meta infra)

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

Test Plan: CI

Reviewed By: cbi42

Differential Revision: D79816733

Pulled By: jaykorean

fbshipit-source-id: e93b037adf4f775202e06c3fd4aa8a3b4b85c274
2025-08-07 11:02:33 -07:00
Jay Huh d0051d9314 Disable other incompatible features when disabled WAL + Remote Compaction in Stress Test (#13845)
Summary:
We temporarily disabled WAL when Remote Compaction is enabled in Stress Test (https://github.com/facebook/rocksdb/pull/13843). There are few others to incompatible features when WAL is disabled. Due to the sanitization order, WAL was disabled at the end of the sanitization and these incompatible features weren't set properly. Stress Test failed with an error like the following.

e.g. `reopen` stress test is not compatible with `disable_wal` - `Error: Db cannot reopen safely with disable_wal set!`

This PR changes the order of sanitization so that `disable_wal` is set earlier when `remote_compaction_worker_threads > 0`

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

Test Plan:
```
python3 -u tools/db_crashtest.py blackbox --remote_compaction_worker_threads=8 --interval=5 --duration=6000 --continuous_verification_interval=10 --disable_wal=1 --use_txn=1 --txn_write_policy=2 --enable_pipelined_write=0 --checkpoint_one_in=0 --use_timed_put_one_in=0
```

Reviewed By: cbi42

Differential Revision: D79758670

Pulled By: jaykorean

fbshipit-source-id: aa6f4a74cc86c23f442928c301187b06e8137f53
2025-08-07 09:22:29 -07:00
zaidoon f2b646713e allow setting sst file manager via c api (#13826)
Summary:
https://github.com/facebook/rocksdb/pull/13404 exposed pretty much everything via c api except allowing the user to set the sst file manager that was created

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

Reviewed By: hx235

Differential Revision: D79733147

Pulled By: cbi42

fbshipit-source-id: 6a18741581717a8b8b644b9f85bcd8fbeba94e6a
2025-08-06 16:08:21 -07:00
Peter Dillinger 1bba680ebb Improve handling of GetFileSize failure (#13842)
Summary:
https://github.com/facebook/rocksdb/issues/13676 unfortunately treated some IOErrors as corruption, which is not appropriate when remote storage is involved. To help enforce this, our crash test injects errors that are expected to be propagated back to the user rather than causing some other failure.

Saw crash test failures like this:
```
TestMultiGetEntity (AttributeGroup) error: Corruption: Failed to get file size: Not implemented: GetFileSize Not Supported for file ...
```

So fixing this handling by not injecting a false Corruption failure and allowing smooth fallback from FSRandomAccessFile::GetFileSize to FileSystem::GetFileSize

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

Test Plan: unit test added

Reviewed By: xingbowang

Differential Revision: D79728861

Pulled By: pdillinger

fbshipit-source-id: 33f7dfc85d86d88cb4ab24a8defd26618c95c954
2025-08-06 15:20:07 -07:00
Jay Huh 3dd6c6f9cb Disable Incompatible Tests with Remote Compaction (#13843)
Summary:
To reduce the noise, disable the incompatible ones for now when `remote_compaction_worker_threads > 0`. We will investigate each, fix as needed and re-enable them as follow up.

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

Test Plan:
```
python3 -u tools/db_crashtest.py blackbox --remote_compaction_worker_threads=8 --interval=5 --duration=6000 --continuous_verification_interval=10 --disable_wal=1 --use_txn=1 --enable_pipelined_write=0 --checkpoint_one_in=0 --use_timed_put_one_in=0
```

Reviewed By: cbi42

Differential Revision: D79735166

Pulled By: jaykorean

fbshipit-source-id: ae3be38a21073fd3282d6e8cd7d71f0363df3590
2025-08-06 11:54:23 -07:00
ngina dfb4efaae3 Add test for deletion-triggered compaction with min file size (#13825)
Summary:
**Summary:**
This test verifies that compaction respects the min_file_size parameter when triggered by deletions, preventing the compaction of files with deletions smaller than the threshold. The test logic includes two scenarios:
1. Verify that a large L0 file with deletions exceeding the minimum file size threshold triggers deletion-triggered compaction (DTC) and compacts to L1.
2. Verify that a small L0 file with deletions, but below the minimum file size threshold, does not trigger DTC and remains at L0.

Added the DeletionTriggeredCompactionWithMinFileSizeTestListener, which verifies that files selected for compaction based on deletion triggers meet the minimum file size threshold. The listener validates in OnCompactionBegin that all input files have sizes greater than or equal to the configured min_file_size parameter.

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

Test Plan:
Tested this feature on our devserver using the following commands:
```
DEBUG_LEVEL=2 make -j64 db_compaction_test && KEEP_DB=1 ./db_compaction_test --gtest_filter="*DBCompactionTest.CompactionWith*"
```

Test output confirms the expected behavior:
```
2025/07/31-11:24:49.473181 1431671 [/compaction/compaction_job.cc:2291] [default] [JOB 6] Compacting 2@0 files to L1, score 0.04
2025/07/31-11:24:49.473240 1431671 [/compaction/compaction_job.cc:2297] [default]: Compaction start summary: Base version 6 Base level 0, inputs: [15(52KB) 9(103KB)]
2025/07/31-11:24:49.473304 1431671 EVENT_LOG_v1 {"time_micros": 1753986289473273, "job": 6, "event": "compaction_started", "cf_name": "default", "compaction_reason": "FilesMarkedForCompaction", "files_L0": [15, 9], "score": 0.04, "input_data_size": 159848, "oldest_snapshot_seqno": -1}

```

**Tasks:**
T228156639

Reviewed By: cbi42

Differential Revision: D79395851

Pulled By: nmk70

fbshipit-source-id: 4c2a80a95521b40543981dd81b347f3984cd2a8b
2025-08-06 11:40:09 -07:00
Jay Huh 9c0a0c0058 Fix remote compaction stress test (#13835)
Summary:
Remote Compaction in the stress test previously failed with the following error, so we temporarily disabled it in PR https://github.com/facebook/rocksdb/issues/13815 :

```
reference std::vector<rocksdb::ThreadState *>::operator[](size_type) [_Tp = rocksdb::ThreadState *, _Alloc = std::allocator<rocksdb::ThreadState *>]: Assertion '__n < this->size()' failed.
```

The error was from accessing `remote_compaction_worker_threads[i]` when `i < remote_compaction_worker_threads.size()` which leads to an undefined behavior. This PR fixes the issue by properly setting the worker thread pointers in `remote_compaction_worker_threads`.

Note: We are still encountering errors when both BlobDB and Remote Compaction are enabled. It appears to be a race condition. For now, BlobDB is temporarily disabled if remote compaction is enabled. We will fix the race condition and re-enable BlobDB as a follow-up.

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

Test Plan:
```
python3 -u tools/db_crashtest.py blackbox --remote_compaction_worker_threads=16 --interval=2 --duration=180
```

Reviewed By: hx235

Differential Revision: D79684447

Pulled By: jaykorean

fbshipit-source-id: 65f5809f651865c3df76c2cf3b9e7b8d654bb90a
2025-08-06 06:59:51 -07:00
Changyu Bi 3bd7d968e1 Introduce column family option cf_allow_ingest_behind (#13810)
Summary:
this option has the same functionality as DBOptions::allow_ingest_behind but allows the feature at per CF level. `DBOptions::allow_ingest_behind` is deprecated after this PR and users should use `cf_allow_ingest_behind` instead.

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

Test Plan: updated some existing tests to use the new option.

Reviewed By: xingbowang

Differential Revision: D79191969

Pulled By: cbi42

fbshipit-source-id: 0da45f6be472ace6754ad15df93d45ac86313837
2025-08-05 23:19:09 -07:00
Hui Xiao d0a412d962 Disable RoundRobinSubcompactionsAgainstResources.SubcompactionsUsingResources (#13839)
Summary:
**Context/Summary:**

The `RoundRobinSubcompactionsAgainstResources` test, specifically the `SubcompactionsUsingResources` case, is now disabled. This decision was made because the test's reliability depends on the absence of any concurrent compactions other than the round-robin compaction. Addressing this issue while maintaining the test's focus on resource reservation requires a deeper investigation, which is currently beyond my available bandwidth. Given the increased frequency of test failures, it has been temporarily disabled to prevent further disruptions.

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

Test Plan: - Should be no test failure from RoundRobinSubcompactionsAgainstResources.SubcompactionsUsingResources anymore.

Reviewed By: cbi42

Differential Revision: D79686366

Pulled By: hx235

fbshipit-source-id: 3a226cfd2b67cabc6c585ea567e2b0c25aa5f345
2025-08-05 17:51:54 -07:00
Jay Huh b6e804b7de Rename CompactFiles() and CompactRange() in CompactionPickers (#13831)
Summary:
#Summary

Quick follow-up from https://github.com/facebook/rocksdb/pull/13816: `CompactFiles()` and `CompactRange()` in CompactionPickers do not run compaction as their names might suggest. What they actually do is create the Compaction object that will be passed to `CompactionJob` to run the compaction.

Renaming these two functions to better represent their purposes.

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

Test Plan: No functional change. Existing CI should be sufficient.

Reviewed By: hx235

Differential Revision: D79660196

Pulled By: jaykorean

fbshipit-source-id: ca831dbef5120e7115b52fd07b0059ca16c8f1e8
2025-08-05 13:11:01 -07:00
Maciej Szeszko 799079cac5 Handle drop column family version edit in file checksum retriever (#13832)
Summary:
... by ensuring that files in dropped column family are not returned to the caller upon successful, offline MANIFEST iteration.

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

Test Plan: `DBTest2, GetFileChecksumsFromCurrentManifest_CRC32`

Reviewed By: pdillinger

Differential Revision: D79607298

Pulled By: mszeszko-meta

fbshipit-source-id: e7948e086ba6e6fb953a3959fdcc81300613d73e
2025-08-05 10:48:49 -07:00
Jay Huh a88d367096 Minor Refactor - VerifyOutputRecordCount (#13830)
Summary:
Introduce `CompactionJob::VerifyOutputRecordCount()` and make it align with `VerifyInputRecordCount()`.

Functionality-wise, it should be the same except when `db_options_.compaction_verify_record_count` is false. RocksDB will only print WARN message upon verification failure and not return `Status::Corruption()`.

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

Test Plan:
Existing tests cover both
```
 ./compaction_service_test --gtest_filter="*CompactionServiceTest.VerifyInputRecordCount*"
```

```
 ./compaction_service_test --gtest_filter="*CompactionServiceTest.CorruptedOutput*"
```

Reviewed By: hx235

Differential Revision: D79584795

Pulled By: jaykorean

fbshipit-source-id: 5851328999005601b28504085b688b80880bca7c
2025-08-04 17:16:25 -07:00
Peter Dillinger 53c39c2b01 Refactor/improve PartitionedIndexBuilder::AddIndexEntry (#13828)
Summary:
In anticipation of an enhancement related to parallel compression
* Rename confusing state variables `seperator_is_key_plus_seq_` -> `must_use_separator_with_seq_`
* Eliminate copy-paste code in `PartitionedIndexBuilder::AddIndexEntry`
* Optimize/simplify `PartitionedIndexBuilder::flush_policy_` by allowing a single policy to be re-targetted to different block builders. Added some additional internal APIs to make this work, and it only works because the FlushBlockBySizePolicy is otherwise stateless (after creation).
* Improve some comments, including another proposed optimization especially for the common case of no live snapshots affecting a large compaction

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

Test Plan:
existing tests are pretty exhaustive, especially with crash test

Planning to validate performance in combination with next change. (This change is saving some extra allocate/deallocate with partitioned index.)

Reviewed By: cbi42

Differential Revision: D79570576

Pulled By: pdillinger

fbshipit-source-id: f7a16f0e6e6ad2023a3d1a2ebaa3cc22aac717af
2025-08-04 14:15:38 -07:00
Ryan Hancock 7c5c37a1a4 IntervalSet Data Structure (#13787)
Summary:
This diff introduces the IntervalSet data structure, which will be used to help create sets of non overlapping sets of intervals for MultiScan scan options. Specifically, we add specializations for Slices to assist in this.

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

Test Plan: Added test to catch various cases within adding intervals.

Reviewed By: anand1976

Differential Revision: D78624970

Pulled By: krhancoc

fbshipit-source-id: 9a3e4a28738ab8428788467540fc05ab5c1a1b67
2025-08-04 14:14:16 -07:00
Jay Huh 3829750b70 Make CompactionPicker::CompactFiles() take earliest_snapshot and snapshot_checker (#13816)
Summary:
One of the parameters for constructing a Compaction object is `earliest_snapshot`, which is required for Standalone Range Deletion Optimization (introduced in [https://github.com/facebook/rocksdb/pull/13078](https://github.com/facebook/rocksdb/pull/13078)). Remote Compaction has been using the `CompactionPicker::CompactFiles()` API to create the Compaction object, but this API never sets the `earliest_snapshot` parameter. To address this, update `CompactionPicker::CompactFiles()` to optionally accept `earliest_snapshot` and pass it during the call in `DBImplSecondary::CompactWithoutInstallation()`.

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

Test Plan:
```
./compaction_service_test --gtest_filter="*CompactionServiceTest.StandaloneDeleteRangeTombstoneOptimization*"
```

\+ Tested in Meta's internal offload infra.

Reviewed By: hx235

Differential Revision: D79284769

Pulled By: jaykorean

fbshipit-source-id: 164834ef6972d5e0ddfc2970bb9234ef166d6e52
2025-08-04 13:20:49 -07:00
Changyu Bi ccd850fa56 Bug fix in MultiScan and stress test (#13822)
Summary:
Fix a bug in MultiScan where BlockBasedTableIterator should not return out-of-bound when the all blocks of the last scan are exhausted. This prevented LevelIterator from entering the next file so iterator is returning less keys than expected.

Also fixed stress testing to specify iterate_upper_bound correctly.

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

Test Plan:
- the following fails quickly before this PR and finishes after this PR
```python3 tools/db_crashtest.py whitebox --iterpercent=60 --prefix_size=-1 --prefixpercent=0 --readpercent=0 --test_batches_snapshots=0 --use_multiscan=1 --seed=1 --fill_cache=1 --read_fault_one_in=0 --column_families=1 --allow_unprepared_value=0 --kill_random_test=88888```
- new unit test that fails before this PR

Reviewed By: krhancoc

Differential Revision: D79308957

Pulled By: cbi42

fbshipit-source-id: c9eafd1c8750b959b0185d7c63199b503493cbd2
2025-07-31 13:28:17 -07:00
Peter Dillinger 0a169cea0e Compressor::CompressBlock API change and refactoring/improvement (#13805)
Summary:
The main motivation for this change is to more flexibly and efficiently support compressing data without extra copies when we do not want to support saving compressed data that is LARGER than the uncompressed. We believe pretty strongly that for the various workloads served by RocksDB, it is well worth a single byte compression marker so that we have the flexibility to save compressed or uncompressed data when compression is attempted. Why? Compression algorithms can add tens of bytes in fixed overheads and percents of bytes in relative overheads. It is also an advantage for the reader when they can bypass decompression, including at least a buffer copy in most cases, after reading just one byte.

The block-based table format in RocksDB follows this model with a single-byte compression marker, and at least after https://github.com/facebook/rocksdb/pull/13797 so does CompressedSecondaryCache. (Notably, the blob file format DOES NOT. This is left to follow-up work.)

In particular, Compressor::CompressBlock now takes in a fixed size buffer for output rather than a `std::string*`. CompressBlock itself rejects the compression if the output would not fit in the provided buffer. This also works well with `max_compressed_bytes_per_kb` option to reject compression even sooner if its ratio is insufficient (implemented in this change). In the future we might use this functionality to reduce a buffer copy (in many cases) into the WritableFileWriter buffer of the block based table builder.

This is a large change because we needed to (or were compelled to)
* Update all the existing callers of CompressBlock, sometimes with substantial changes. This includes introducing GrowableBuffer to reuse between calls rather than std::string, which (at least in C++17) requires zeroing out data when allocating/growing a buffer.
* Re-implement built-in Compressors (V2; V1 is obsolete) to efficiently implement the new version of the API, no longer wrapping the `OLD_CompressData()` function. The new compressors appropriately leverage the CompressBlock virtual call required for the customization interface and no rely on `switch` on compression type for each block. The implementations are largely adaptations of the old implementations, except
  * LZ4 and LZ4HC are notably upgraded to take advantage of WorkingArea (see performance tests). And for simplicity in the new implementation, we are dropping support for some super old versions of the library.
  * Getting snappy to work with limited-size output buffer required using the Sink/Source interfaces, which appear to be well supported for a long time and efficient (see performance tests).
* Replace awkward old CompressionManager::GetDecompressorForCompressor with Compressor::GetOptimizedDecompressor (which is optional to implement)
* Small behavior change where we treat lack of support for compression closer to not configuring compression, such as incompatibility with block_align. This is motivated by giving CompressionManager the freedom of determining when compression can be excluded for an entire file despite the configured "compression" type, and thus only surfacing actual incompatibilities not hypothetical ones that might be irrelevant to the CompressionManager (or build configuration). Unit tests in `table_test` and `compact_files_test` required update.
* Some lingering clean up of CompressedSecondaryCache and a re-optimization made possible by compressing into an existing buffer.

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

Test Plan:
for correctness, existing tests

## Performance Test

As I generally only modified compression paths, I'm using a db_bench write benchmark, with before & after configurations running at the same time. vc=1 means verify_compression=1

```
USE_CLANG=1 DEBUG_LEVEL=0 LIB_MODE=static make -j100 db_bench
SUFFIX=`tty | sed 's|/|_|g'`; for CT in zlib bzip2 none snappy zstd lz4 lz4hc none snappy zstd lz4 bzip2; do for VC in 0 1; do echo "$CT vc=$VC"; (for I in `seq 1 20`; do BIN=/dev/shm/dbbench${SUFFIX}.bin; rm -f $BIN; cp db_bench $BIN; $BIN -db=/dev/shm/dbbench$SUFFIX --benchmarks=fillseq -num=10000000 -compaction_style=2 -fifo_compaction_max_table_files_size_mb=1000 -fifo_compaction_allow_compaction=0 -disable_wal -write_buffer_size=12000000 -format_version=7 -compression_type=$CT -verify_compression=$VC 2>&1 | grep micros/op; done) | awk '{n++; sum += $5;} END { print int(sum / n); }'; done; done
```

zlib vc=0 524198 -> 524904 (+0.1%)
zlib vc=1 430521 -> 430699 (+0.0%)
bzip2 vc=0 61841 -> 60835 (-1.6%)
bzip2 vc=1 49232 -> 48734 (-1.0%)
none vc=0 1802375 -> 1906227 (+5.8%)
none vc=1 1837181 -> 1950308 (+6.2%)
snappy vc=0 1783266 -> 1901461 (+6.6%)
snappy vc=1 1799703 -> 1879660 (+4.4%)
zstd vc=0 1216779 -> 1230507 (+1.1%)
zstd vc=1 996370 -> 1015415 (+1.9%)
lz4 vc=0 1801473 -> 1943095 (+7.9%)
lz4 vc=1 1799155 -> 1935242 (+7.6%)
lz4hc vc=0 349719 -> 1126909 (+222.2%)
lz4hc vc=1 348099 -> 1108933 (+218.6%)
(Repeating the most important ones)
none vc=0 1816878 -> 1952221 (+7.4%)
none vc=1 1813736 -> 1904622 (+5.0%)
snappy vc=0 1794816 -> 1875062 (+4.5%)
snappy vc=1 1789363 -> 1873771 (+4.7%)
zstd vc=0 1202592 -> 1225164 (+1.9%)
zstd vc=1 994322 -> 1016688 (+2.2%)
lz4 vc=0 1786959 -> 1971518 (+10.3%)
lz4 vc=1 1829483 -> 1935871 (+5.8%)

I confirmed manually that the new WorkingArea for LZ4HC makes the huge difference on that one, but not as much difference for LZ4, presumably because LZ4HC uses much larger buffers/structures/whatever for better compression ratios.

Reviewed By: hx235

Differential Revision: D79111736

Pulled By: pdillinger

fbshipit-source-id: 1ce1b14af9f15365f1b6da49906b5073a8cecc14
2025-07-31 08:39:56 -07:00
Jay Huh 7f14960816 UnitTest for Remote Compaction Empty Result (#13812)
Summary:
Unit Test for a repro for the fix that was reported by https://github.com/facebook/rocksdb/pull/13743

There's potential dataloss when Remote Compaction entries are all removed due to various reasons (CompactionFilter, DeleteRange covering all keys of the SST file, etc)

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

Test Plan:
```
./compaction_service_test --gtest_filter="*CompactionServiceTest.EmptyResult*"
```

Failed before merging https://github.com/facebook/rocksdb/pull/13743, now passing

Reviewed By: cbi42

Differential Revision: D79192829

Pulled By: jaykorean

fbshipit-source-id: e200300c4a7993de21c63cd92bda65b692921b89
2025-07-30 14:13:31 -07:00
Peter Dillinger 3757e5479d Improve detection and reporting for fbcode build (#13820)
Summary:
We were seeing some internal builds apparently failing the `-d /mnt/gvfs/third-party` check. Although third-party2 is likely a better check (see dependencies_platform010.sh), that would create a big headache with check_format_compatible.sh which has to work across codebase versions.
* Report a WARNING when we detect on a Meta machine but the `-d /mnt/gvfs/third-party` check fails
* Let USE_CLANG influence default compiler choice so that things might still work in that case (e.g. `USE_CLANG=1 make -j24 check`)

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

Test Plan: manual, CI

Reviewed By: jaykorean

Differential Revision: D79277197

Pulled By: pdillinger

fbshipit-source-id: 19b2d45ed794f64bbf838f4414568d77ae9ca6f1
2025-07-30 13:00:37 -07:00
Changyu Bi e7a4505a2e Preserve tombstones for allow_ingest_behind (#13807)
Summary:
Preserve tombstone when allow_ingest_behind` is enabled so that they can be applied to ingested files. This can be useful when users use ingest_behind to buffer updates where Deletion needs to be preserved. This fixes https://github.com/facebook/rocksdb/issues/13571.

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

Test Plan: updated a unit test to verify that tombstones are not dropped during compaction.

Reviewed By: hx235

Differential Revision: D79016109

Pulled By: cbi42

fbshipit-source-id: c4d31ef32c88468ababcc1ea5af5db6de42a3b0d
2025-07-30 12:00:54 -07:00
Jay Huh 5435032c4c Temporarily Disable Remote Compaction in Stress Test (#13815)
Summary:
As title. We will re-enable it once fixed

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

Test Plan: N/A - Disabling the test.

Reviewed By: archang19

Differential Revision: D79172697

Pulled By: jaykorean

fbshipit-source-id: 936de3743816049cda811bde48b3b2207ed256ee
2025-07-29 08:48:19 -07:00
huangmengbin f66ac76938 prevent data loss when all entries are expired in Remote Compaction (#13743)
Summary:
**Issue**:
When running remote compaction, if all entries in the input files are expired, RocksDB incorrectly deletes an active file from the primary DB, leading to data loss and corruption.

**Root Cause**:
The current logic mistakenly mixed up the input and output file paths during the cleanup phase when no keys survive the compaction (all expired). This results in deleting the input files (which belong to the primary DB) instead of the output files (which belong to the SecondaryDB).

**Fix**:
Use `GetTableFileName` (virtual function) instead of `TableFileName`

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

Reviewed By: hx235

Differential Revision: D79108650

Pulled By: jaykorean

fbshipit-source-id: 1c9ba971a0e9a62c15ebc014436cb8fc961af95c
2025-07-28 19:17:45 -07:00
anand76 07f1520290 Add MultiScan to db_stress (#13803)
Summary:
Add the new MultiScan operation to db_stress (disabled by default)

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

Test Plan: python3 tools/db_crashtest.py whitebox --iterpercent=60 --prefix_size=-1 --prefixpercent=0 --readpercent=0 --test_batches_snapshots=0 --use_multiscan=1

Reviewed By: krhancoc

Differential Revision: D78938131

Pulled By: anand1976

fbshipit-source-id: 30fced56e46b79cebebc7ec4d4588c6c2fca232a
2025-07-28 15:39:58 -07:00
Peter Dillinger f8535fb955 Build fix and GitHub CI enhancements (#13813)
Summary:
Building db_bench with clang and DEBUG_LEVEL=0 was failing with unused variable. This was not caught by CI so I have added this to the build-linux-clang-13-no_test_run job.

Also, while I was touching CI:
* Fold build-linux-release-rtti into build-linux-release by reducing the number of combinations tested between static/dynamic lib and rtti/not. I don't expect these to interact meaningfully with an extremely mature compiler.
* Combine build-linux-clang10-asan and build-linux-clang10-ubsan because clang is extremely reliable running both together

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

Test Plan: manual builds, CI

Reviewed By: krhancoc

Differential Revision: D79112643

Pulled By: pdillinger

fbshipit-source-id: 4ffc672718c05fa4597d637aacbc5a179ad8a0cf
2025-07-28 14:40:32 -07:00
RROP 6ae1cb8837 Switch fragmented range tombstone cache to C++20 atomic<shared_ptr> API (#13744)
Summary:
• Guard on __cpp_lib_atomic_shared_ptr to use std::atomic<std::shared_ptr<T>>::load()/store()
• Fallback to std::atomic_load_explicit()/store_explicit() under C++17

When attempting to build with CXX 20 using clang in a Linux environment, the build fails due to deprecation of atomic_load_explicit.

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

Reviewed By: xingbowang

Differential Revision: D78997919

Pulled By: cbi42

fbshipit-source-id: f829c282cba878f072d4b0ad44192a87f73b8a90
2025-07-28 13:14:14 -07:00
Jay Huh 217e075df8 Simulate e2e flow in Stress Test (#13800)
Summary:
Simulate Remote Compaction in Stress Test by running a separate set of threads that runs remote compaction.
Queue and ResultMap for the remote compactions are stored in memory as part of the `SharedState`. They are shared across main worker threads and remote compaction worker threads.

`enable_remote_compaction` is replaced by `remote_compaction_worker_threads`.
If `remote_compaction_worker_threads` is set to 0, remote compaction is not enabled in Stress Test.

**To Follow up**

This PR covers happy path only. Failure injection in the remote worker thread will be added as a follow up.

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

Test Plan:
```
./db_stress --remote_compaction_worker_threads=4  --flush_one_in=1000 --writepercent=40 --readpercent=40 --iterpercent=10 --prefixpercent=0 --delpercent=10 --destroy_db_initially=0 --clear_column_family_one_in=0 --reopen=0
```
```
python3 -u tools/db_crashtest.py blackbox --remote_compaction_worker_threads=8
```

Reviewed By: hx235

Differential Revision: D78862084

Pulled By: jaykorean

fbshipit-source-id: b262058c92d7fecc5e014cef5df9cca4a209921b
2025-07-28 07:29:03 -07:00
Peter Dillinger ee6b0def55 Refactor, improve CompressedSecondaryCache (#13797)
Summary:
To be compatible with some upcoming compression change/refactoring where we supply a fixed size buffer to CompressBlock, we need to support CompressedSecondaryCache storing uncompressed values when the compression ratio is not suitable. It seems crazy that CompressedSecondaryCache currently stores compressed values that are *larger* than the uncompressed value, and even explicitly exercises that case (almost exclusively) in the existing unit tests. But it's true.

This change fixes that with some other nearby refactoring/improvement:
* Update the in-memory representation of these cache entries to support uncompressed entries even when compression is enabled. AFAIK this also allows us to safely get rid of "don't support custom split/merge for the tiered case".
* Use more efficient in-memory representation for non-split entries
  * For CompressionType and CacheTier, which are defined as single-byte data types, use a single byte instead of varint32. (I don't know if varint32 was an attempt at future-proofing for a memory-only schema or what.) Now using lossless_cast will raise a compiler error if either of these types is made too large for a single byte.
  * Don't wrap entries in a CacheAllocationPtr object; it's not necessary. We can rely on the same allocator being provided at delete time.
* Restructure serialization/deserialization logic, hopefully simpler or easier to read/understand.
* Use a RelaxedAtomic for disable_cache_ to avoid race.

Suggested follow-up on CompressedSecondaryCache:
* Refine the exact strategy for rejecting compressions
* Still have a lot of buffer copies; try to reduce
* Revisit the split-merge logic and try to make it more efficient overall, more unified with non-split case

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

Test Plan:
Unit tests updated to use actually compressible strings in many places and more testing around non-compressible string.

## Performance Test
There was some pre-existing issue causing decompression failures in compressed secondary cache with cache_bench that is somehow fixed in this change. This decompression failures were present before the new compression API, but since then cause assertion failures rather than being quietly ignored. For the "before" test here, they are back to quietly ignored. And the cache_bench changes here were  back-ported to the "before" configuration.

### No compressed secondary (setting expectations)
```
./cache_bench --cache_type=auto_hyper_clock_cache -cache_size=8000000000 -populate_cache
```
Max key             : 3906250

Before:
Complete in 12.784 s; Rough parallel ops/sec = 2503123
Thread ops/sec = 160329; Lookup hit ratio: 0.686771

After:
Complete in 12.745 s; Rough parallel ops/sec = 2510717 (in the noise)
Thread ops/sec = 159498; Lookup hit ratio: 0.68686

### Compressed secondary, no split/merge
Same max key and approximate total memory size
```
/usr/bin/time ./cache_bench --cache_type=auto_hyper_clock_cache -cache_size=4000000000 -populate_cache -resident_ratio=0.125 -compressible_to_ratio=0.4 --secondary_cache_uri=compressed_secondary_cache://capacity=4000000000
```
Before:
Complete in 18.690 s; Rough parallel ops/sec = 1712144
Thread ops/sec = 108683; Lookup hit ratio: 0.776683
Latency: P50: 4205.19 P75: 15281.76 P99: 43810.98 P99.9: 71487.41 P99.99: 165453.32
max RSS (according to /usr/bin/time): 9341856

After:
Complete in 17.878 s; Rough parallel ops/sec = 1789951 (+4.5%)
Thread ops/sec = 114957; Lookup hit ratio: 0.792998 (+0.016)
Latency: P50: 4012.70 P75: 14477.63 P99: 40039.70 P99.9: 62521.04 P99.99: 167049.18
max RSS (according to /usr/bin/time): 9235688

The improved hit ratio is probably from fixing the failed decompressions (somehow). And my modifications could have improved CPU efficiency, or it could be the small penalty the benchmark naturally imposes on most misses (generate another value and insert it).

### Compressed secondary, with split/merge
```
/usr/bin/time ./cache_bench --cache_type=auto_hyper_clock_cache -cache_size=4000000000 -populate_cache -resident_ratio=0.125 -compressible_to_ratio=0.4 --secondary_cache_uri='compressed_secondary_cache://capacity=4000000000;enable_custom_split_merge=true'
```
Before:
Complete in 20.062 s; Rough parallel ops/sec = 1595075
Thread ops/sec = 101759; Lookup hit ratio: 0.787129
Latency: P50: 5338.53 P75: 16073.46 P99: 46752.65 P99.9: 73459.11 P99.99: 201318.75
max RSS (according to /usr/bin/time): 9049852

After:
Complete in 18.564 s; Rough parallel ops/sec = 1723771 (+8.1%)
Thread ops/sec = 110724; Lookup hit ratio: 0.813414 (+0.026)
Latency: P50: 5234.75 P75: 14590.43 P99: 41401.03 P99.9: 65606.50 P99.99: 157248.04
max RSS (according to /usr/bin/time): 8917592

Looks like an improvement

Reviewed By: anand1976

Differential Revision: D78842120

Pulled By: pdillinger

fbshipit-source-id: 5f754b160c37ebee789279178ebb5e862071bdb2
2025-07-25 13:39:25 -07:00
Xingbo Wang 961880b458 Create a new API FileSystem::SyncFile for file sync (#13762)
Summary:
Create a new API FileSystem::SyncFile for file sync, so that we could use file sync directly in places where we need to sync file content to file system without any modification. This is mostly used combined with link file. In some file system link file does not guarantee the file content is synced to file system.

https://github.com/facebook/rocksdb/issues/13741

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

Test Plan:
Unit test
T229418750

Reviewed By: pdillinger

Differential Revision: D78121137

Pulled By: xingbowang

fbshipit-source-id: 0ea8a5a3b486e0b61636700400613fed6bbd3faa
2025-07-23 17:12:41 -07:00
jainpr 124dd30879 Remove yield in point lock manager (#13796)
Summary:
The yield is actually of not much use because waitFor should already be doing that.

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

Reviewed By: pdillinger

Differential Revision: D78823656

Pulled By: jainpr

fbshipit-source-id: 040eaf596938ce8db535bc810ad77a9e50b2d551
2025-07-23 11:43:03 -07:00
341 changed files with 30117 additions and 6547 deletions
+10
View File
@@ -1,7 +1,17 @@
name: build-folly
description: Build folly and dependencies (skipped if cache hit)
inputs:
cache-hit:
description: Whether the folly cache was hit
required: true
runs:
using: composite
steps:
- name: Build folly and dependencies
if: ${{ inputs.cache-hit != 'true' }}
run: make build_folly
shell: bash
- name: Skip folly build (using cached version)
if: ${{ inputs.cache-hit == 'true' }}
run: echo "Folly build skipped - using cached version"
shell: bash
+33
View File
@@ -0,0 +1,33 @@
name: cache-folly
description: Cache folly build to speed up CI
outputs:
cache-hit:
description: Whether the cache was hit
value: ${{ steps.cache-folly-build.outputs.cache-hit }}
runs:
using: composite
steps:
- name: Extract FOLLY_MK_HASH
id: extract-folly-hash
shell: bash
run: |
FOLLY_MK_HASH=$(md5sum folly.mk | cut -d' ' -f1)
echo "hash=$FOLLY_MK_HASH" >> $GITHUB_OUTPUT
- name: Extract FOLLY_INSTALL_DIR
id: extract-folly-install-dir
shell: bash
run: |
FOLLY_INSTALL_DIR=$(cd third-party/folly && python3 build/fbcode_builder/getdeps.py show-inst-dir)
echo "dir=$(echo $FOLLY_INSTALL_DIR | sed 's|installed/folly|installed|')" >> $GITHUB_OUTPUT
- name: Cache folly build
id: cache-folly-build
uses: actions/cache@v4
with:
# Cache the folly build directory
path: ${{ steps.extract-folly-install-dir.outputs.dir }}
# Key is based on:
# - OS and architecture
# - The docker image, which may not always be specified/known
# - Hash of folly.mk, which includes the folly repository commit hash
# NOTE: this is still only intended for DEBUG folly builds
key: folly-build-${{ runner.os }}-${{ runner.arch }}-${{ github.job_container.image }}-${{ steps.extract-folly-hash.outputs.hash }}
+5 -1
View File
@@ -3,5 +3,9 @@ runs:
using: composite
steps:
- name: Checkout folly sources
run: make checkout_folly
run: |
make checkout_folly
shell: bash
- name: Install patchelf
run: apt-get update -y && apt-get install -y patchelf
shell: bash
+17 -2
View File
@@ -4,6 +4,16 @@ runs:
steps:
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v1.3.1
- name: Cache ccache directory
id: ccache-cache
uses: actions/cache@v4
with:
path: C:\a\rocksdb\rocksdb\.ccache
key: rocksdb-build-${{ runner.os }}-${{ runner.arch }}-ccache-${{ hashFiles('CMakeLists.txt', 'cmake/**/*.cmake') }}-v1
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2
with:
max-size: "10GB"
- name: Custom steps
env:
THIRDPARTY_HOME: ${{ github.workspace }}/thirdparty
@@ -38,11 +48,12 @@ runs:
$env:Path = $env:JAVA_HOME + ";" + $env:Path
mkdir build
cd build
& cmake -G "$Env:CMAKE_GENERATOR" -DCMAKE_BUILD_TYPE=Debug -DOPTDBG=1 -DPORTABLE="$Env:CMAKE_PORTABLE" -DSNAPPY=1 -DXPRESS=1 -DJNI=1 ..
& cmake -G "$Env:CMAKE_GENERATOR" -DCMAKE_BUILD_TYPE=Debug -DWIN_CI=1 -DPORTABLE="$Env:CMAKE_PORTABLE" -DSNAPPY=1 -DXPRESS=1 -DJNI=1 ..
if(!$?) { Exit $LASTEXITCODE }
cd ..
echo "Building with VS version: $Env:CMAKE_GENERATOR"
msbuild build/rocksdb.sln -maxCpuCount -property:Configuration=Debug -property:Platform=x64
# use more parallel processes than the number of processes available, as most of the compile command would be cache hit
msbuild build/rocksdb.sln /m:32 /p:LinkIncremental=false -property:Configuration=Debug -property:Platform=x64
if(!$?) { Exit $LASTEXITCODE }
echo ========================= Test RocksDB =========================
build_tools\run_ci_db_test.ps1 -SuiteRun arena_test,db_basic_test,db_test,db_test2,db_merge_operand_test,bloom_test,c_test,coding_test,crc32c_test,dynamic_bloom_test,env_basic_test,env_test,hash_test,random_test -Concurrency 16
@@ -52,3 +63,7 @@ runs:
& ctest -C Debug -j 16
if(!$?) { Exit $LASTEXITCODE }
shell: pwsh
- name: Show ccache stats
shell: pwsh
run: |
ccache --show-stats -v
+36 -30
View File
@@ -10,7 +10,7 @@ jobs:
runs-on:
labels: 16-core-ubuntu
container:
image: zjay437/rocksdb:0.6
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
@@ -32,7 +32,7 @@ jobs:
runs-on:
labels: 16-core-ubuntu
container:
image: zjay437/rocksdb:0.6
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
env:
TEST_TMPDIR: "/tmp/rocksdb_test_tmp"
@@ -41,16 +41,16 @@ jobs:
- uses: "./.github/actions/pre-steps"
- run: make V=1 -j32 check
- uses: "./.github/actions/post-steps"
build-linux-clang-13-asan-ubsan-with-folly:
build-linux-clang-18-asan-ubsan-with-folly:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
image: zjay437/rocksdb:0.6
image: ghcr.io/facebook/rocksdb_ubuntu:24.0
options: --shm-size=16gb
env:
CC: clang-13
CXX: clang++-13
CC: clang-18
CXX: clang++-18
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -58,17 +58,38 @@ jobs:
- uses: "./.github/actions/build-folly"
- run: LIB_MODE=static USE_CLANG=1 USE_FOLLY=1 COMPILE_WITH_UBSAN=1 COMPILE_WITH_ASAN=1 make -j32 check
- uses: "./.github/actions/post-steps"
build-linux-valgrind:
build-linux-cmake-with-folly:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
image: zjay437/rocksdb:0.6
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- run: make V=1 -j32 valgrind_test
- uses: "./.github/actions/setup-folly"
- uses: "./.github/actions/cache-folly"
id: cache-folly
- uses: "./.github/actions/build-folly"
with:
cache-hit: ${{ steps.cache-folly.outputs.cache-hit }}
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 .. && make VERBOSE=1 -j20 && ctest -j20)"
- uses: "./.github/actions/post-steps"
build-linux-release-with-folly:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- uses: "./.github/actions/setup-folly"
- run: "DEBUG_LEVEL=0 make -j20 build_folly"
- run: "USE_FOLLY=1 LIB_MODE=static DEBUG_LEVEL=0 V=1 make -j20 release"
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 -DCMAKE_BUILD_TYPE=Release .. && make VERBOSE=1 -j20 && ctest -j20)"
- uses: "./.github/actions/post-steps"
build-windows-vs2022-avx2:
if: ${{ github.repository_owner == 'facebook' }}
@@ -94,7 +115,7 @@ jobs:
runs-on:
labels: 4-core-ubuntu
container:
image: zjay437/rocksdb:0.6
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
@@ -107,41 +128,26 @@ jobs:
runs-on:
labels: 4-core-ubuntu
container:
image: zjay437/rocksdb:0.6
image: ghcr.io/facebook/rocksdb_ubuntu:24.0
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- name: Build rocksdb lib
run: CC=clang-13 CXX=clang++-13 USE_CLANG=1 make -j4 static_lib
run: CC=clang-18 CXX=clang++-18 USE_CLANG=1 make -j4 static_lib
- name: Build fuzzers
run: cd fuzz && make sst_file_writer_fuzzer db_fuzzer db_map_fuzzer
- uses: "./.github/actions/post-steps"
build-linux-gcc-11-no_test_run:
build-linux-cmake-with-folly-lite:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
image: zjay437/rocksdb:0.6
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- run: LIB_MODE=static CC=gcc-11 CXX=g++-11 V=1 make -j32 all microbench
- uses: "./.github/actions/post-steps"
build-linux-cmake-with-folly-lite-no-test:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
image: zjay437/rocksdb:0.6
options: --shm-size=16gb
env:
CC: gcc-10
CXX: g++-10
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- uses: "./.github/actions/setup-folly"
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY_LITE=1 -DWITH_GFLAGS=1 .. && make V=1 -j20)"
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY_LITE=1 -DWITH_GFLAGS=1 -DCMAKE_CXX_FLAGS=-DGLOG_USE_GLOG_EXPORT .. && make VERBOSE=1 -j20 && ctest -j20)"
- uses: "./.github/actions/post-steps"
+91 -129
View File
@@ -66,7 +66,7 @@ jobs:
runs-on:
labels: 16-core-ubuntu
container:
image: zjay437/rocksdb:0.6
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
@@ -78,7 +78,7 @@ jobs:
runs-on:
labels: 4-core-ubuntu
container:
image: zjay437/rocksdb:0.6
image: ghcr.io/facebook/rocksdb_ubuntu:24.0
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
@@ -92,38 +92,22 @@ jobs:
which javac && javac -version
mkdir build && cd build && cmake -DJNI=1 -DWITH_GFLAGS=OFF .. -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++ -DCMAKE_SYSTEM_NAME=Windows && make -j4 rocksdb rocksdbjni
- uses: "./.github/actions/post-steps"
build-linux-cmake-with-folly:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
image: zjay437/rocksdb:0.6
options: --shm-size=16gb
env:
CC: gcc-10
CXX: g++-10
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- uses: "./.github/actions/setup-folly"
- uses: "./.github/actions/build-folly"
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 .. && make V=1 -j20 && ctest -j20)"
- uses: "./.github/actions/post-steps"
build-linux-make-with-folly:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
image: zjay437/rocksdb:0.6
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
env:
CC: gcc-10
CXX: g++-10
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- uses: "./.github/actions/setup-folly"
- uses: "./.github/actions/cache-folly"
id: cache-folly
- uses: "./.github/actions/build-folly"
with:
cache-hit: ${{ steps.cache-folly.outputs.cache-hit }}
- run: USE_FOLLY=1 LIB_MODE=static V=1 make -j32 check
- uses: "./.github/actions/post-steps"
build-linux-make-with-folly-lite-no-test:
@@ -131,52 +115,50 @@ jobs:
runs-on:
labels: 16-core-ubuntu
container:
image: zjay437/rocksdb:0.6
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
env:
CC: gcc-10
CXX: g++-10
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- uses: "./.github/actions/setup-folly"
- run: USE_FOLLY_LITE=1 V=1 make -j32 all
- run: USE_FOLLY_LITE=1 EXTRA_CXXFLAGS=-DGLOG_USE_GLOG_EXPORT V=1 make -j32 all
- uses: "./.github/actions/post-steps"
build-linux-cmake-with-folly-coroutines:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
image: zjay437/rocksdb:0.6
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
env:
CC: gcc-10
CXX: g++-10
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- uses: "./.github/actions/setup-folly"
- uses: "./.github/actions/cache-folly"
id: cache-folly
- uses: "./.github/actions/build-folly"
- run: "(mkdir build && cd build && cmake -DUSE_COROUTINES=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 .. && make V=1 -j20 && ctest -j20)"
with:
cache-hit: ${{ steps.cache-folly.outputs.cache-hit }}
- run: "(mkdir build && cd build && cmake -DUSE_COROUTINES=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 .. && make VERBOSE=1 -j20 && ctest -j20)"
- uses: "./.github/actions/post-steps"
build-linux-cmake-with-benchmark:
build-linux-cmake-with-benchmark-no-thread-status:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
image: zjay437/rocksdb:0.6
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- run: mkdir build && cd build && cmake -DWITH_GFLAGS=1 -DWITH_BENCHMARK=1 .. && make V=1 -j20 && ctest -j20
- run: mkdir build && cd build && cmake -DWITH_GFLAGS=1 -DWITH_BENCHMARK=1 -DCMAKE_CXX_FLAGS=-DNROCKSDB_THREAD_STATUS .. && make VERBOSE=1 -j20 && ctest -j20
- uses: "./.github/actions/post-steps"
build-linux-encrypted_env-no_compression:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
image: zjay437/rocksdb:0.6
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
@@ -190,102 +172,82 @@ jobs:
runs-on:
labels: 16-core-ubuntu
container:
image: zjay437/rocksdb:0.6
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- run: make V=1 -j32 LIB_MODE=shared release
- run: ls librocksdb.so
- run: "./db_stress --version"
- run: "./trace_analyzer --version" # A tool dependent on gflags that can run in release build
- run: make clean
- run: make V=1 -j32 release
- run: USE_RTTI=1 make V=1 -j32 release
- run: ls librocksdb.a
- run: "./db_stress --version"
- run: "./trace_analyzer --version"
- run: make clean
- run: apt-get remove -y libgflags-dev
- run: make V=1 -j32 LIB_MODE=shared release
- run: ls librocksdb.so
- run: if ./db_stress --version; then false; else true; fi
- run: if ./trace_analyzer --version; then false; else true; fi
- run: make clean
- run: make V=1 -j32 release
- run: USE_RTTI=1 make V=1 -j32 release
- run: ls librocksdb.a
- run: if ./db_stress --version; then false; else true; fi
- uses: "./.github/actions/post-steps"
build-linux-release-rtti:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 8-core-ubuntu
container:
image: zjay437/rocksdb:0.6
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- run: USE_RTTI=1 DEBUG_LEVEL=0 make V=1 -j16 static_lib tools db_bench
- run: "./db_stress --version"
- run: make clean
- run: apt-get remove -y libgflags-dev
- run: USE_RTTI=1 DEBUG_LEVEL=0 make V=1 -j16 static_lib tools db_bench
- run: if ./db_stress --version; then false; else true; fi
build-linux-clang-no_test_run:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 8-core-ubuntu
container:
image: zjay437/rocksdb:0.6
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- run: CC=clang CXX=clang++ USE_CLANG=1 PORTABLE=1 make V=1 -j16 all
- run: if ./trace_analyzer --version; then false; else true; fi
- uses: "./.github/actions/post-steps"
build-linux-clang-13-no_test_run:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
labels: 8-core-ubuntu
container:
image: zjay437/rocksdb:0.6
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- run: CC=clang-13 CXX=clang++-13 USE_CLANG=1 make -j32 all microbench
# FIXME: get back to "all microbench" targets
- run: CC=clang-13 CXX=clang++-13 USE_CLANG=1 EXTRA_CXXFLAGS=-stdlib=libc++ EXTRA_LDFLAGS=-stdlib=libc++ make -j32 shared_lib
- run: make clean
# FIXME: get back to "release" target
- run: CC=clang-13 CXX=clang++-13 USE_CLANG=1 EXTRA_CXXFLAGS=-stdlib=libc++ EXTRA_LDFLAGS=-stdlib=libc++ DEBUG_LEVEL=0 make -j32 shared_lib
- uses: "./.github/actions/post-steps"
build-linux-gcc-8-no_test_run:
build-linux-clang-18-no_test_run:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
image: zjay437/rocksdb:0.6
image: ghcr.io/facebook/rocksdb_ubuntu:24.0
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- run: CC=gcc-8 CXX=g++-8 V=1 make -j32 all
- run: CC=clang-18 CXX=clang++-18 USE_CLANG=1 make -j32 all microbench
- run: make clean
- run: CC=clang-18 CXX=clang++-18 USE_CLANG=1 DEBUG_LEVEL=0 make -j32 release
- uses: "./.github/actions/post-steps"
build-linux-gcc-10-cxx20-no_test_run:
build-linux-gcc-14-no_test_run:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
image: zjay437/rocksdb:0.6
image: ghcr.io/facebook/rocksdb_ubuntu:24.0
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- run: CC=gcc-10 CXX=g++-10 V=1 ROCKSDB_CXX_STANDARD=c++20 make -j32 all
- run: CC=gcc-14 CXX=g++-14 V=1 make -j32 all microbench
- uses: "./.github/actions/post-steps"
# ======================== Linux Other Checks ======================= #
build-linux-clang10-clang-analyze:
build-linux-clang18-clang-analyze:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
image: zjay437/rocksdb:0.6
image: ghcr.io/facebook/rocksdb_ubuntu:24.0
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- run: CC=clang-10 CXX=clang++-10 ROCKSDB_DISABLE_ALIGNED_NEW=1 CLANG_ANALYZER="/usr/bin/clang++-10" CLANG_SCAN_BUILD=scan-build-10 USE_CLANG=1 make V=1 -j32 analyze
- run: CC=clang-18 CXX=clang++-18 ROCKSDB_DISABLE_ALIGNED_NEW=1 CLANG_ANALYZER="/usr/bin/clang++-18" CLANG_SCAN_BUILD=scan-build-18 USE_CLANG=1 make V=1 -j32 analyze
- uses: "./.github/actions/post-steps"
- name: compress test report
run: tar -cvzf scan_build_report.tar.gz scan_build_report
@@ -313,7 +275,7 @@ jobs:
runs-on:
labels: 4-core-ubuntu
container:
image: zjay437/rocksdb:0.6
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
@@ -321,102 +283,96 @@ jobs:
- run: ulimit -S -n `ulimit -H -n` && make V=1 -j8 CRASH_TEST_EXT_ARGS='--duration=960 --max_key=2500000' blackbox_crash_test_with_atomic_flush
- uses: "./.github/actions/post-steps"
# ======================= Linux with Sanitizers ===================== #
build-linux-clang10-asan:
build-linux-clang18-asan-ubsan:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 32-core-ubuntu
container:
image: zjay437/rocksdb:0.6
image: ghcr.io/facebook/rocksdb_ubuntu:24.0
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- run: COMPILE_WITH_ASAN=1 CC=clang-10 CXX=clang++-10 ROCKSDB_DISABLE_ALIGNED_NEW=1 USE_CLANG=1 make V=1 -j32 check
- run: COMPILE_WITH_ASAN=1 COMPILE_WITH_UBSAN=1 CC=clang-18 CXX=clang++-18 ROCKSDB_DISABLE_ALIGNED_NEW=1 USE_CLANG=1 make V=1 -j40 check
- uses: "./.github/actions/post-steps"
build-linux-clang10-ubsan:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
image: zjay437/rocksdb:0.6
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- run: COMPILE_WITH_UBSAN=1 CC=clang-10 CXX=clang++-10 ROCKSDB_DISABLE_ALIGNED_NEW=1 USE_CLANG=1 make V=1 -j32 ubsan_check
- uses: "./.github/actions/post-steps"
build-linux-clang13-mini-tsan:
build-linux-clang18-mini-tsan:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 32-core-ubuntu
container:
image: zjay437/rocksdb:0.6
image: ghcr.io/facebook/rocksdb_ubuntu:24.0
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- run: COMPILE_WITH_TSAN=1 CC=clang-13 CXX=clang++-13 ROCKSDB_DISABLE_ALIGNED_NEW=1 USE_CLANG=1 make V=1 -j32 check
- run: COMPILE_WITH_TSAN=1 CC=clang-18 CXX=clang++-18 ROCKSDB_DISABLE_ALIGNED_NEW=1 USE_CLANG=1 make V=1 -j32 check
- uses: "./.github/actions/post-steps"
build-linux-static_lib-alt_namespace-status_checked:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
image: zjay437/rocksdb:0.6
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- run: ASSERT_STATUS_CHECKED=1 TEST_UINT128_COMPAT=1 ROCKSDB_MODIFY_NPHASH=1 LIB_MODE=static OPT="-DROCKSDB_NAMESPACE=alternative_rocksdb_ns" make V=1 -j24 check
- run: ASSERT_STATUS_CHECKED=1 TEST_UINT128_COMPAT=1 ROCKSDB_MODIFY_NPHASH=1 LIB_MODE=static OPT="-DROCKSDB_USE_STD_SEMAPHORES -DROCKSDB_NAMESPACE=alternative_rocksdb_ns" make V=1 -j24 check
- uses: "./.github/actions/post-steps"
# ========================= MacOS build only ======================== #
build-macos:
if: ${{ github.repository_owner == 'facebook' }}
runs-on: macos-13
runs-on: macos-15-xlarge
env:
ROCKSDB_DISABLE_JEMALLOC: 1
steps:
- uses: actions/checkout@v4.1.0
- uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: 14.3.1
xcode-version: 16.4.0
- uses: "./.github/actions/increase-max-open-files-on-macos"
- uses: "./.github/actions/install-gflags-on-macos"
- uses: "./.github/actions/pre-steps-macos"
- name: Build
run: ulimit -S -n `ulimit -H -n` && make V=1 J=16 -j16 all
run: ulimit -S -n `ulimit -H -n` && make V=1 J=16 -j8 all
- uses: "./.github/actions/post-steps"
# ========================= MacOS with Tests ======================== #
build-macos-cmake:
if: ${{ github.repository_owner == 'facebook' }}
runs-on: macos-13
runs-on: macos-15-xlarge
strategy:
matrix:
run_even_tests: [true, false]
run_sharded_tests: [0, 1, 2, 3]
steps:
- uses: actions/checkout@v4.1.0
- uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: 14.3.1
xcode-version: 16.4.0
- uses: "./.github/actions/increase-max-open-files-on-macos"
- uses: "./.github/actions/install-gflags-on-macos"
- uses: "./.github/actions/pre-steps-macos"
- name: cmake generate project file
run: ulimit -S -n `ulimit -H -n` && mkdir build && cd build && cmake -DWITH_GFLAGS=1 ..
- name: Build tests
run: cd build && make V=1 -j16
- name: Run even tests
run: ulimit -S -n `ulimit -H -n` && cd build && ctest -j16 -I 0,,2
if: ${{ matrix.run_even_tests }}
- name: Run odd tests
run: ulimit -S -n `ulimit -H -n` && cd build && ctest -j16 -I 1,,2
if: ${{ ! matrix.run_even_tests }}
run: cd build && make VERBOSE=1 -j8
- name: Run shard 0 out of 4 test shards
run: ulimit -S -n `ulimit -H -n` && cd build && ctest -j8 -I 0,,4
if: ${{ matrix.run_sharded_tests == 0 }}
- name: Run shard 1 out of 4 test shards
run: ulimit -S -n `ulimit -H -n` && cd build && ctest -j8 -I 1,,4
if: ${{ matrix.run_sharded_tests == 1 }}
- name: Run shard 2 out of 4 test shards
run: ulimit -S -n `ulimit -H -n` && cd build && ctest -j8 -I 2,,4
if: ${{ matrix.run_sharded_tests == 2 }}
- name: Run shard 3 out of 4 test shards
run: ulimit -S -n `ulimit -H -n` && cd build && ctest -j8 -I 3,,4
if: ${{ matrix.run_sharded_tests == 3 }}
- uses: "./.github/actions/post-steps"
# ======================== Windows with Tests ======================= #
# NOTE: some windows jobs are in "nightly" to save resources
build-windows-vs2022:
if: ${{ github.repository_owner == 'facebook' }}
runs-on: windows-2022
runs-on: windows-8-core
env:
CMAKE_GENERATOR: Visual Studio 17 2022
CMAKE_PORTABLE: 1
@@ -429,11 +385,13 @@ jobs:
runs-on:
labels: 4-core-ubuntu
container:
image: evolvedbinary/rocksjava:centos7_x64-be
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
# The docker image is intentionally based on an OS that has an older GLIBC version.
# That GLIBC is incompatibile with GitHub's actions/checkout. Thus we implement a manual checkout step.
# NOTE: replaced evolvedbinary/rocksjava:centos7_x64-be with ghcr.io/facebook/rocksdb_ubuntu:22.1
# until a more appropriate docker image with C++20 support is made.
- name: Checkout
env:
GH_TOKEN: ${{ github.token }}
@@ -450,18 +408,21 @@ jobs:
which java && java -version
which javac && javac -version
- name: Test RocksDBJava
run: scl enable devtoolset-7 'make V=1 J=8 -j8 jtest'
# NOTE: post-steps skipped because of compatibility issues with docker image
# NOTE: replaced scl enable devtoolset-7 'make V=1 J=8 -j8 jtest'
run: make V=1 J=8 -j8 jtest
# post-steps skipped because of compatibility issues with docker image
build-linux-java-static:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 4-core-ubuntu
container:
image: evolvedbinary/rocksjava:centos7_x64-be
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
# The docker image is intentionally based on an OS that has an older GLIBC version.
# That GLIBC is incompatibile with GitHub's actions/checkout. Thus we implement a manual checkout step.
# NOTE: replaced evolvedbinary/rocksjava:centos7_x64-be with ghcr.io/facebook/rocksdb_ubuntu:22.1
# until a more appropriate docker image with C++20 support is made.
- name: Checkout
env:
GH_TOKEN: ${{ github.token }}
@@ -478,11 +439,12 @@ jobs:
which java && java -version
which javac && javac -version
- name: Build RocksDBJava Static Library
run: scl enable devtoolset-7 'make V=1 J=8 -j8 rocksdbjavastatic'
# NOTE: post-steps skipped because of compatibility issues with docker image
# NOTE: replaced scl enable devtoolset-7 'make V=1 J=8 -j8 rocksdbjavastatic'
run: make V=1 J=8 -j8 rocksdbjavastatic
# post-steps skipped because of compatibility issues with docker image
build-macos-java:
if: ${{ github.repository_owner == 'facebook' }}
runs-on: macos-13
runs-on: macos-15-xlarge
env:
JAVA_HOME: "/Library/Java/JavaVirtualMachines/liberica-jdk-8.jdk/Contents/Home"
ROCKSDB_DISABLE_JEMALLOC: 1
@@ -490,7 +452,7 @@ jobs:
- uses: actions/checkout@v4.1.0
- uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: 14.3.1
xcode-version: 16.4.0
- uses: "./.github/actions/increase-max-open-files-on-macos"
- uses: "./.github/actions/install-gflags-on-macos"
- uses: "./.github/actions/install-jdk8-on-macos"
@@ -505,14 +467,14 @@ jobs:
- uses: "./.github/actions/post-steps"
build-macos-java-static:
if: ${{ github.repository_owner == 'facebook' }}
runs-on: macos-13
runs-on: macos-15-xlarge
env:
JAVA_HOME: "/Library/Java/JavaVirtualMachines/liberica-jdk-8.jdk/Contents/Home"
steps:
- uses: actions/checkout@v4.1.0
- uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: 14.3.1
xcode-version: 16.4.0
- uses: "./.github/actions/increase-max-open-files-on-macos"
- uses: "./.github/actions/install-gflags-on-macos"
- uses: "./.github/actions/install-jdk8-on-macos"
@@ -527,14 +489,14 @@ jobs:
- uses: "./.github/actions/post-steps"
build-macos-java-static-universal:
if: ${{ github.repository_owner == 'facebook' }}
runs-on: macos-13
runs-on: macos-15-xlarge
env:
JAVA_HOME: "/Library/Java/JavaVirtualMachines/liberica-jdk-8.jdk/Contents/Home"
steps:
- uses: actions/checkout@v4.1.0
- uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: 14.3.1
xcode-version: 16.4.0
- uses: "./.github/actions/increase-max-open-files-on-macos"
- uses: "./.github/actions/install-gflags-on-macos"
- uses: "./.github/actions/install-jdk8-on-macos"
+20
View File
@@ -0,0 +1,20 @@
name: facebook/rocksdb/weekly
on:
schedule:
- cron: 0 9 * * 0
workflow_dispatch:
permissions: {}
jobs:
build-linux-valgrind:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
timeout-minutes: 840
container:
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- run: make V=1 -j20 valgrind_test
- uses: "./.github/actions/post-steps"
+29 -3
View File
@@ -1,12 +1,14 @@
# This file @generated by:
#$ python3 buckifier/buckify_rocksdb.py
# --> DO NOT EDIT MANUALLY <--
# This file is a Facebook-specific integration for buck builds, so can
# only be validated by Facebook employees.
# This file is a Meta-specific integration for buck builds, so can
# only be validated by Meta employees.
load("//rocks/buckifier:defs.bzl", "cpp_library_wrapper","rocks_cpp_library_wrapper","cpp_binary_wrapper","cpp_unittest_wrapper","fancy_bench_wrapper","add_c_test_wrapper")
load("@fbcode_macros//build_defs:export_files.bzl", "export_file")
oncall("rocksdb_point_of_contact")
cpp_library_wrapper(name="rocksdb_lib", srcs=[
"cache/cache.cc",
"cache/cache_entry_roles.cc",
@@ -114,6 +116,7 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
"db/write_controller.cc",
"db/write_stall_stats.cc",
"db/write_thread.cc",
"db_stress_tool/db_stress_compression_manager.cc",
"env/composite_env.cc",
"env/env.cc",
"env/env_chroot.cc",
@@ -418,16 +421,19 @@ cpp_library_wrapper(name="rocksdb_tools_lib", srcs=[
cpp_library_wrapper(name="rocksdb_cache_bench_tools_lib", srcs=["cache/cache_bench_tool.cc"], deps=[":rocksdb_lib"], headers=[], link_whole=False, extra_test_libs=False)
cpp_library_wrapper(name="rocksdb_point_lock_bench_tools_lib", srcs=["utilities/transactions/lock/point/point_lock_bench_tool.cc"], deps=[":rocksdb_lib"], headers=[], link_whole=False, extra_test_libs=False)
rocks_cpp_library_wrapper(name="rocksdb_stress_lib", srcs=[
"db_stress_tool/batched_ops_stress.cc",
"db_stress_tool/cf_consistency_stress.cc",
"db_stress_tool/db_stress_common.cc",
"db_stress_tool/db_stress_compaction_service.cc",
"db_stress_tool/db_stress_compression_manager.cc",
"db_stress_tool/db_stress_driver.cc",
"db_stress_tool/db_stress_filters.cc",
"db_stress_tool/db_stress_gflags.cc",
"db_stress_tool/db_stress_listener.cc",
"db_stress_tool/db_stress_shared_state.cc",
"db_stress_tool/db_stress_stat.cc",
"db_stress_tool/db_stress_test_base.cc",
"db_stress_tool/db_stress_tool.cc",
"db_stress_tool/db_stress_wide_merge_operator.cc",
@@ -449,6 +455,8 @@ cpp_binary_wrapper(name="db_bench", srcs=["tools/db_bench.cc"], deps=[":rocksdb_
cpp_binary_wrapper(name="cache_bench", srcs=["cache/cache_bench.cc"], deps=[":rocksdb_cache_bench_tools_lib"], extra_preprocessor_flags=[], extra_bench_libs=False)
cpp_binary_wrapper(name="point_lock_bench", srcs=["utilities/transactions/lock/point/point_lock_bench.cc"], deps=[":rocksdb_point_lock_bench_tools_lib"], extra_preprocessor_flags=[], extra_bench_libs=False)
cpp_binary_wrapper(name="ribbon_bench", srcs=["microbench/ribbon_bench.cc"], deps=[], extra_preprocessor_flags=[], extra_bench_libs=True)
cpp_binary_wrapper(name="db_basic_bench", srcs=["microbench/db_basic_bench.cc"], deps=[], extra_preprocessor_flags=[], extra_bench_libs=True)
@@ -4838,6 +4846,12 @@ cpp_unittest_wrapper(name="db_encryption_test",
extra_compiler_flags=[])
cpp_unittest_wrapper(name="db_etc3_test",
srcs=["db/db_etc3_test.cc"],
deps=[":rocksdb_test_lib"],
extra_compiler_flags=[])
cpp_unittest_wrapper(name="db_flush_test",
srcs=["db/db_flush_test.cc"],
deps=[":rocksdb_test_lib"],
@@ -5194,6 +5208,12 @@ cpp_unittest_wrapper(name="inlineskiplist_test",
extra_compiler_flags=[])
cpp_unittest_wrapper(name="interval_test",
srcs=["util/interval_test.cc"],
deps=[":rocksdb_test_lib"],
extra_compiler_flags=[])
cpp_unittest_wrapper(name="io_posix_test",
srcs=["env/io_posix_test.cc"],
deps=[":rocksdb_test_lib"],
@@ -5374,6 +5394,12 @@ cpp_unittest_wrapper(name="plain_table_db_test",
extra_compiler_flags=[])
cpp_unittest_wrapper(name="point_lock_manager_stress_test",
srcs=["utilities/transactions/lock/point/point_lock_manager_stress_test.cc"],
deps=[":rocksdb_test_lib"],
extra_compiler_flags=[])
cpp_unittest_wrapper(name="point_lock_manager_test",
srcs=["utilities/transactions/lock/point/point_lock_manager_test.cc"],
deps=[":rocksdb_test_lib"],
+61 -18
View File
@@ -27,7 +27,7 @@
#
# Linux:
#
# 1. Install a recent toolchain if you're on a older distro. C++17 required (GCC >= 7, Clang >= 5)
# 1. Install a recent toolchain if you're on a older distro. C++20 required (GCC >= 11, Clang >= 10)
# 2. mkdir build; cd build
# 3. cmake ..
# 4. make -j
@@ -80,6 +80,7 @@ if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE STRING
"Default BUILD_TYPE is ${default_build_type}" FORCE)
endif()
message(STATUS "CMAKE_BUILD_TYPE is set to ${CMAKE_BUILD_TYPE}")
find_program(CCACHE_FOUND ccache)
if(CCACHE_FOUND)
@@ -100,7 +101,7 @@ endif()
option(ROCKSDB_BUILD_SHARED "Build shared versions of the RocksDB libraries" ON)
if( NOT DEFINED CMAKE_CXX_STANDARD )
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)
endif()
include(CMakeDependentOption)
@@ -132,7 +133,9 @@ else()
option(WITH_GFLAGS "build with GFlags" ON)
endif()
set(GFLAGS_LIB)
if(WITH_GFLAGS)
# Skip all gflags detection and setup when USE_FOLLY or USE_COROUTINES is enabled
# since Folly provides its own gflags (USE_COROUTINES automatically sets USE_FOLLY)
if(WITH_GFLAGS AND NOT USE_FOLLY AND NOT USE_COROUTINES)
# Config with namespace available since gflags 2.2.2
option(GFLAGS_USE_TARGET_NAMESPACE "Use gflags import target with namespace." ON)
find_package(gflags CONFIG)
@@ -151,6 +154,9 @@ else()
include_directories(${GFLAGS_INCLUDE_DIR})
list(APPEND THIRDPARTY_LIBS ${GFLAGS_LIB})
add_definitions(-DGFLAGS=1)
elseif(WITH_GFLAGS AND (USE_FOLLY OR USE_COROUTINES))
# Still set the DGFLAGS=1 define when using Folly since Folly provides gflags
add_definitions(-DGFLAGS=1)
endif()
if(WITH_SNAPPY)
@@ -203,9 +209,16 @@ if(WIN32 AND MSVC)
endif()
endif()
option(WIN_CI "Accelerate build speed and reduce build artifect size for github CI with MSVC" OFF)
if(MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi /nologo /EHsc /GS /Gd /GR /GF /fp:precise /Zc:wchar_t /Zc:forScope /errorReport:queue")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FC /d2Zi+ /W4 /wd4127 /wd4996 /wd4100 /wd4324")
if(WIN_CI)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /nologo /EHsc /Gd /GR /GF /fp:precise /Zc:wchar_t /Zc:forScope /errorReport:queue")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FC /W4 /wd4127 /wd4996 /wd4100 /wd4324 /wd4702")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi /nologo /EHsc /GS /Gd /GR /GF /fp:precise /Zc:wchar_t /Zc:forScope /errorReport:queue")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FC /d2Zi+ /W4 /wd4127 /wd4996 /wd4100 /wd4324")
endif()
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wextra -Wall -pthread")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-compare -Wshadow -Wno-unused-parameter -Wno-unused-variable -Woverloaded-virtual -Wnon-virtual-dtor -Wno-missing-field-initializers -Wno-strict-aliasing -Wno-invalid-offsetof")
@@ -313,8 +326,7 @@ if(NOT MSVC)
endif()
# Check if -latomic is required or not
if (NOT MSVC)
set(CMAKE_REQUIRED_FLAGS "--std=c++17")
if (NOT MSVC AND NOT APPLE)
CHECK_CXX_SOURCE_COMPILES("
#include <atomic>
std::atomic<uint64_t> x(0);
@@ -451,24 +463,33 @@ else()
endif()
endif()
# Used to run CI build and tests so we can run faster
# Used to run optimized debug build and tests so we can run faster
option(OPTDBG "Build optimized debug build with MSVC" OFF)
option(WITH_RUNTIME_DEBUG "build with debug version of runtime library" ON)
if(MSVC)
if(OPTDBG)
if (WIN_CI)
message(STATUS "Debug optimization is enabled")
set(CMAKE_CXX_FLAGS_DEBUG "/Oxt")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /DEBUG:FASTLINK")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG:FASTLINK")
else()
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /RTC1")
# Minimal Build is deprecated after MSVC 2015
if( MSVC_VERSION GREATER 1900 )
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Gm-")
if(OPTDBG)
message(STATUS "Debug optimization is enabled")
set(CMAKE_CXX_FLAGS_DEBUG "/Oxt")
else()
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Gm")
endif()
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /RTC1")
# Minimal Build is deprecated after MSVC 2015
if( MSVC_VERSION GREATER 1900 )
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Gm-")
else()
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Gm")
endif()
endif()
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /DEBUG")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG")
endif()
if(WITH_RUNTIME_DEBUG)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /${RUNTIME_LIBRARY}d")
else()
@@ -476,8 +497,6 @@ if(MSVC)
endif()
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Oxt /Zp8 /Gm- /Gy /${RUNTIME_LIBRARY}")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /DEBUG")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG")
endif()
if(CMAKE_COMPILER_IS_GNUCXX)
@@ -629,6 +648,12 @@ if(USE_FOLLY)
${FOLLY_INST_PATH}/lib/cmake/folly/folly-targets.cmake)
include(${FOLLY_INST_PATH}/lib/cmake/folly/folly-config.cmake)
# Fix gflags library name for debug builds
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath=${GFLAGS_INST_PATH}/lib")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GFLAGS_INST_PATH}/lib/libgflags_debug.so.2.2")
endif()
endif()
add_compile_definitions(USE_FOLLY FOLLY_NO_CONFIG HAVE_CXX11_ATOMIC)
@@ -747,6 +772,7 @@ set(SOURCES
db/write_controller.cc
db/write_stall_stats.cc
db/write_thread.cc
db_stress_tool/db_stress_compression_manager.cc
env/composite_env.cc
env/env.cc
env/env_chroot.cc
@@ -1068,12 +1094,21 @@ if(USE_FOLLY_LITE)
third-party/folly/folly/synchronization/DistributedMutex.cpp
third-party/folly/folly/synchronization/ParkingLot.cpp)
include_directories(${PROJECT_SOURCE_DIR}/third-party/folly)
# Add boost to the include path
exec_program(python3 ${PROJECT_SOURCE_DIR}/third-party/folly ARGS
build/fbcode_builder/getdeps.py show-source-dir boost OUTPUT_VARIABLE
BOOST_SOURCE_PATH)
exec_program(ls ARGS -d ${BOOST_SOURCE_PATH}/boost* OUTPUT_VARIABLE
BOOST_INCLUDE_DIR)
include_directories(${BOOST_INCLUDE_DIR})
# Add fmt to the include path
exec_program(python3 ${PROJECT_SOURCE_DIR}/third-party/folly ARGS
build/fbcode_builder/getdeps.py show-source-dir fmt OUTPUT_VARIABLE
FMT_SOURCE_PATH)
exec_program(ls ARGS -d ${FMT_SOURCE_PATH}/fmt*/include OUTPUT_VARIABLE
FMT_INCLUDE_DIR)
include_directories(${FMT_INCLUDE_DIR})
add_definitions(-DUSE_FOLLY -DFOLLY_NO_CONFIG)
list(APPEND THIRDPARTY_LIBS glog)
endif()
@@ -1345,6 +1380,7 @@ if(WITH_TESTS)
db/db_clip_test.cc
db/db_dynamic_level_test.cc
db/db_encryption_test.cc
db/db_etc3_test.cc
db/db_flush_test.cc
db/db_inplace_update_test.cc
db/db_io_failure_test.cc
@@ -1503,6 +1539,7 @@ if(WITH_TESTS)
utilities/transactions/optimistic_transaction_test.cc
utilities/transactions/transaction_test.cc
utilities/transactions/lock/point/point_lock_manager_test.cc
utilities/transactions/lock/point/point_lock_manager_stress_test.cc
utilities/transactions/write_committed_transaction_ts_test.cc
utilities/transactions/write_prepared_transaction_test.cc
utilities/transactions/write_unprepared_transaction_test.cc
@@ -1613,6 +1650,12 @@ if(WITH_BENCHMARK_TOOLS)
utilities/persistent_cache/hash_table_bench.cc)
target_link_libraries(hash_table_bench${ARTIFACT_SUFFIX}
${ROCKSDB_LIB} ${GFLAGS_LIB} ${FOLLY_LIBS})
add_executable(point_lock_bench${ARTIFACT_SUFFIX}
utilities/transactions/lock/point/point_lock_bench.cc
utilities/transactions/lock/point/point_lock_bench_tool.cc)
target_link_libraries(point_lock_bench${ARTIFACT_SUFFIX}
${ROCKSDB_LIB} ${GFLAGS_LIB} ${FOLLY_LIBS})
endif()
option(WITH_TRACE_TOOLS "build with trace tools" ON)
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<PropertyGroup>
<CLToolExe>ccache_msvc_compiler.bat</CLToolExe>
<CLToolPath>$(MSBuildThisFileDirectory)</CLToolPath>
<UseMultiToolTask>true</UseMultiToolTask>
<EnforceProcessCountAcrossBuilds>true</EnforceProcessCountAcrossBuilds>
</PropertyGroup>
</Project>
+111 -7
View File
@@ -1,12 +1,116 @@
# Rocksdb Change Log
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
## 10.9.1 (12/11/2025)
### Bug Fixes
* Fix resumable compaction incorrectly allowing resumption from a truncated range deletion that is not well handled currently.
## 10.9.0 (11/21/2025)
### New Features
* Added an auto-tuning feature for DB manifest file size that also (by default) improves the safety of existing configurations in case `max_manifest_file_size` is repeatedly exceeded. The new recommendation is to set `max_manifest_file_size` to something small like 1MB and tune `max_manifest_space_amp_pct` as needed to balance write amp and space amp in the manifest. Refer to comments on those options in `DBOptions` for details. Both options are (now) mutable.
* Added a new API to support option migration for multiple column families
* Added new option target_file_size_is_upper_bound that makes most compaction output SST files come close to the target file size without exceeding it, rather than commonly exceeding it by some fraction (current behavior). For now the new behavior is off by default, but we expect to enable it by default in the future.
* Add a new option allow_trivial_move in CompactionOptions to allow CompactFiles to perform trivial move if possible. By default the flag of allow_trivial_move is false, so it preserve the original behavior.
### Public API Changes
* To reduce risk of ODR violations or similar, `ROCKSDB_USING_THREAD_STATUS` has been removed from public headers and replaced with static `const bool ThreadStatus::kEnabled`. Some other uses of conditional compilation have been removed from public API headers to reduce risk of ODR violations or other issues.
### Behavior Changes
* PosixWritableFile now repositions the seek pointer to the new end of file after a call to Truncate.
* Updated standalone range deletion L0 file compaction behavior to avoid compacting with any newer L0 files (which is expensive and not useful).
### Bug Fixes
* Fix a bug where compaction with range deletion can persist kTypeMaxValid in MANIFEST as file metadata. kTypeMaxValid is not supposed to be persisted and can change as new value types are introduced. This can cause a forward compatibility issue where older versions of RocksDB don't recognize kTypeMaxValid from newer versions. A new placeholder value type kTypeTruncatedRangeDeletionSentinel is also introduced to replace kTypeMaxValid when reading existing SST files' metadata from MANIFEST. This allows us to strengthen some checks to avoid using kTypeMaxValid in the future.
* Fixed a bug where `DB::GetSortedWalFiles()` could hang when waiting for a purge operation that found nothing to do (potentially triggered by iterator release, flush, compaction, etc.).
* Fixed a bug in MultiScan where `max_sequential_skip_in_iterations` could cause the iterator to seek backward to already-unpinned blocks when the same user key spans multiple data blocks, leading to assertion failures or seg fault.
* Fixed a bug for `WAL_ttl_seconds > 0` use cases where the newest archived WAL files could be incorrectly deleted when the system clock moved backwards.
### Performance Improvements
* Added optimization that allowed for the asynchronous prefetching of all data outlined in a multiscan iterator. This optimization was applied to the level iterator, which prefetches all data through each of the block-based iterators.
## 10.8.0 (10/21/2025)
### New Features
* Add kFSPrefetch to FSSupportedOps enum to allow file systems to indicate prefetch support capability, avoiding unnecessary prefetch system calls on file systems that don't support them.
* Added experimental support `OpenAndCompactOptions::allow_resumption` for resumable compaction that persists progress during `OpenAndCompact()`, allowing interrupted compactions to resume from the last progress persitence. The default behavior is to not persist progress.
### Public API Changes
* Allow specifying output temperature in CompactionOptions
* Added `DB::FlushWAL(const FlushWALOptions&)` as an alternative to `DB::FlushWAL(bool sync)`, where `FlushWALOptions` includes a new `rate_limiter_priority` field (default `Env::IO_TOTAL`) that allows rate limiting and priority passing of manual WAL flush's IO operations.
* The MultiScan API contract is updated. After a multi scan range got prepared with Prepare API call, the following seeks must seek the start of each prepared scan range in order. In addition, when limit is set, upper bound must be set to the same value of limit before each seek
### Behavior Changes
* `kChangeTemperature` FIFO compaction will now honor `compaction_target_temp` to all levels regardless of `cf_options::last_level_temperature`
* Allow UDIs with a non BytewiseComparator
### Bug Fixes
* Fix incorrect MultiScan seek error status due to bugs in handling range limit falling between adjacent SST files key range.
* Fix a bug in Page unpinning in MultiScan
### Performance Improvements
* Fixed a performance regression in LZ4 compression that started in version 10.6.0
## 10.7.0 (09/19/2025)
### New Features
* Add the fail_if_no_udi_on_open flag in BlockBasedTableOption to control whether a missing user defined index block in a SST is a hard error or not.
* A new flag memtable_verify_per_key_checksum_on_seek is added to AdvancedColumnFamilyOptions. When it is enabled, it will validate key checksum along the binary search path on skiplist based memtable during seek operation.
* Introduce option MultiScanArgs::use_async_io to enable asynchronous I/O during MultiScan, instead of waiting for I/O to be done in Prepare().
* Add new option `MultiScanArgs::max_prefetch_size` that limits the memory usage of per file pinning of prefetched blocks.
* Improved `sst_dump` by allowing standalone file and directory arguments without `--file=`. Also added new options and better output for `sst_dump --command=recompress`. See `sst_dump --help`
### Public API Changes
* HyperClockCache with no `estimated_entry_charge` is now production-ready and is the preferred block cache implementation vs. LRUCache. Please consider updating your code to minimize the risk of hitting performance bottlenecks or anomalies from LRUCache. See cache.h for more detail.
* RocksDB now requires a C++20 compatible compiler (GCC >= 11, Clang >= 10, Visual Studio >= 2019), including for any code using RocksDB headers.
* MultiScanArgs used to have a default constructor with default parameter of BytewiseComparator. Now it always requires Comparator in its constructor.
### Behavior Changes
* The default provided block cache implementation is now HyperClockCache instead of LRUCache, when `block_cache` is nullptr (default) and `no_block_cache==false` (default). We recommend explicitly creating a HyperClockCache block cache based on memory budget and sharing it across all column families and even DB instances. This change could expose previously hidden memory or resource leaks.
### Bug Fixes
* Reported numbers for compaction and flush CPU usage now include time spent by parallel compression worker threads. This now means compaction/flush CPU usage could exceed the wall clock time.
* Fix a race condition in FIFO size-based compaction where concurrent threads could select the same non-L0 file, causing assertion failures in debug builds or "Cannot delete table file from LSM tree" errors in release builds.
* Fix a bug in RocksDB MultiScan with UDI when one of the scan ranges is determined to be empty by the UDI, which causes incorrect results.
### Performance Improvements
* Add a new table property "rocksdb.key.smallest.seqno" which records the smallest sequence number of all keys in file. It makes ingesting DB generated files faster by
avoiding scanning the whole file to find the smallest sequence number.
* Add a new experimental PerKeyPointLockManager to improve efficiency under high lock contention. PointLockManager was not efficient when there is high write contention on same key, as it uses a single conditional variable per lock stripe. PerKeyPointLockManager uses per thread conditional variable supporting fifo order. Although this is an experimental feature. By default, it is disabled. A new boolean flag TransactionDBOptions::use_per_key_point_lock_mgr is added to optionally enable it. Search the flag in code for more info.
Together, a new configuration TransactionOptions::deadlock_timeout_us is added, which allows the transaction to wait for a short period before perform deadlock detection. When the workload has low lock contention, the deadlock_timeout_us can be configured to be slightly higher than average transaction execution time, so that transaction would likely be able to take the lock before deadlock detection is performed when it is waiting for a lock. This allows transaction to reduce CPU cost on performing deadlock detection, which could be expensive in CPU time. When the workload has high lock contention, the deadlock_timeout_us can be configured to 0, so that transaction would perform deadlock detection immediately. By default the value is 0 to keep the behavior same as before.
* Majorly improved CPU efficiency and scalability of parallel compression (`CompressionOptions::parallel_threads` > 1), though this efficiency improvement makes parallel compression currently incompatible with UserDefinedIndex and with old setting of `decouple_partitioned_filters=false`. Parallel compression is now considered a production-ready feature. Maximum performance is available with `-DROCKSDB_USE_STD_SEMAPHORES` at compile time, but this is not currently recommended because of reported bugs in implementations of `std::counting_semaphore`/`binary_semaphore`.
## 10.6.0 (08/22/2025)
### New Features
* Introduce column family option `cf_allow_ingest_behind`. This option aims to replace `DBOptions::allow_ingest_behind` to enable ingest behind at the per-CF level. `DBOptions::allow_ingest_behind` is deprecated.
* Introduce `MultiScanArgs::io_coalesce_threshold` to allow a configurable IO coalescing threshold.
### Public API Changes
* `IngestExternalFileOptions::allow_db_generated_files` now allows files ingestion of any DB generated SST file, instead of only the ones with all keys having sequence number 0.
* `decouple_partitioned_filters = true` is now the default in BlockBasedTableOptions.
* GetTtl() API is now available in TTL DB
* Minimum supported version of LZ4 library is now 1.7.0 (r129 from 2015)
* Some changes to experimental Compressor and CompressionManager APIs
* A new Filesystem::SyncFile function is added for syncing a file that was already written, such as on file ingestion. The default implementation matches previous RocksDB behavior: re-open the file for read-write, sync it, and close it. We recommend overriding for FileSystems that do not require syncing for crash recovery or do not handle (well) re-opening for writes.
### Behavior Changes
* When `allow_ingest_behind` is enabled, compaction will no longer drop tombstones based on the absence of underlying data. Tombstones will be preserved to apply to ingested files.
### Bug Fixes
* Files in dropped column family won't be returned to the caller upon successful, offline MANIFEST iteration in `GetFileChecksumsFromCurrentManifest`.
* Fix a bug in MultiScan that causes it to fall back to a normal scan when dictionary compression is enabled.
* Fix a crash in iterator Prepare() when fill_cache=false
* Fix a bug in MultiScan where incorrect results can be returned when a Scan's range is across multiple files.
* Fixed a bug in remote compaction that may mistakenly delete live SST file(s) during the cleanup phase when no keys survive the compaction (all expired)
* Allow a user defined index to be configured from a string.
* Make the User Defined Index interface consistently use the user key format, fixing the previous mixed usage of internal and user key.
### Performance Improvements
* Small improvement to CPU efficiency of compression using built-in algorithms, and a dramatic efficiency improvement for LZ4HC, based on reusing data structures between invocations.
## 10.5.0 (07/18/2025)
### Public API Changes
* DB option skip_checking_sst_file_sizes_on_db_open is deprecated, in favor of validating file size in parallel in a thread pool, when db is opened. When DB is opened, with paranoid check enabled, a file with the wrong size would fail the DB open. With paranoid check disabled, the DB open would succeed, the column family with the corrupted file would not be read or write, while the other healthy column families could be read and write normally. When max_open_files option is not set to -1, only a subset of the files will be opened and checked. The rest of the files will be opened and checked when they are accessed.
### Behavior Changes
* PessimisticTransaction::GetWaitingTxns now returns waiting transaction information even if the current transaction has timed out. This allows the information to be surfaced to users for debugging purposes once it is known that the timeout has occured.
* PessimisticTransaction::GetWaitingTxns now returns waiting transaction information even if the current transaction has timed out. This allows the information to be surfaced to users for debugging purposes once it is known that the timeout has occurred.
* A new API GetFileSize is added to FSRandomAccessFile interface class. It uses fstat vs stat on the posix implementation which is more efficient. Caller could use it to get file size faster. This function might be required in the future for FileSystem implementation outside of the RocksDB code base.
* RocksDB now triggers eligible compactions every 12 hours when periodic compaction is configured. This solves a limitation of the compaction trigger mechanism, which would only trigger compaction after specific events like flush, compaction, or SetOptions.
@@ -63,7 +167,7 @@ system's prefetch) on SST file during compaction read
* Deprecated API `DB::MaxMemCompactionLevel()`.
* Deprecated `ReadOptions::ignore_range_deletions`.
* Deprecated API `experimental::PromoteL0()`.
* Added arbitrary string map for additional options to be overriden for remote compactions
* Added arbitrary string map for additional options to be overridden for remote compactions
* The fail_if_options_file_error option in DBOptions has been removed. The behavior now is to always return failure in any API that fails to persist the OPTIONS file.
### Behavior Changes
@@ -213,7 +317,7 @@ system's prefetch) on SST file during compaction read
* In FIFO compaction, compactions for changing file temperature (configured by option `file_temperature_age_thresholds`) will compact one file at a time, instead of merging multiple eligible file together (#13018).
* Support ingesting db generated files using hard link, i.e. IngestExternalFileOptions::move_files/link_files and IngestExternalFileOptions::allow_db_generated_files.
* Add a new file ingestion option `IngestExternalFileOptions::link_files` to hard link input files and preserve original files links after ingestion.
* DB::Close now untracks files in SstFileManager, making avaialble any space used
* DB::Close now untracks files in SstFileManager, making available any space used
by them. Prior to this change they would be orphaned until the DB is re-opened.
### Bug Fixes
@@ -409,7 +513,7 @@ MultiGetBenchmarks.multiGetList10 no_column_family 10000 16 100 1024 thrpt 25 76
* Removed deprecated option `ColumnFamilyOptions::check_flush_compaction_key_order`
* Remove the default `WritableFile::GetFileSize` and `FSWritableFile::GetFileSize` implementation that returns 0 and make it pure virtual, so that subclasses are enforced to explicitly provide an implementation.
* Removed deprecated option `ColumnFamilyOptions::level_compaction_dynamic_file_size`
* Removed tickers with typos "rocksdb.error.handler.bg.errro.count", "rocksdb.error.handler.bg.io.errro.count", "rocksdb.error.handler.bg.retryable.io.errro.count".
* Removed tickers with typos "rocksdb.error.handler.bg.error.count", "rocksdb.error.handler.bg.io.error.count", "rocksdb.error.handler.bg.retryable.io.error.count".
* Remove the force mode for `EnableFileDeletions` API because it is unsafe with no known legitimate use.
* Removed deprecated option `ColumnFamilyOptions::ignore_max_compaction_bytes_for_input`
* `sst_dump --command=check` now compares the number of records in a table with `num_entries` in table property, and reports corruption if there is a mismatch. API `SstFileDumper::ReadSequential()` is updated to optionally do this verification. (#12322)
@@ -436,7 +540,7 @@ MultiGetBenchmarks.multiGetList10 no_column_family 10000 16 100 1024 thrpt 25 76
* Exposed options ttl via c api.
### Behavior Changes
* `rocksdb.blobdb.blob.file.write.micros` expands to also measure time writing the header and footer. Therefore the COUNT may be higher and values may be smaller than before. For stacked BlobDB, it no longer measures the time of explictly flushing blob file.
* `rocksdb.blobdb.blob.file.write.micros` expands to also measure time writing the header and footer. Therefore the COUNT may be higher and values may be smaller than before. For stacked BlobDB, it no longer measures the time of explicitly flushing blob file.
* Files will be compacted to the next level if the data age exceeds periodic_compaction_seconds except for the last level.
* Reduced the compaction debt ratio trigger for scheduling parallel compactions
* For leveled compaction with default compaction pri (kMinOverlappingRatio), files marked for compaction will be prioritized over files not marked when picking a file from a level for compaction.
@@ -501,7 +605,7 @@ want to continue to use force enabling, they need to explicitly pass a `true` to
### 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.
* 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 occurred 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.
@@ -1289,7 +1393,7 @@ Note: The next release will be major release 7.0. See https://github.com/faceboo
### Public API change
* Extend WriteBatch::AssignTimestamp and AssignTimestamps API so that both functions can accept an optional `checker` argument that performs additional checking on timestamp sizes.
* Introduce a new EventListener callback that will be called upon the end of automatic error recovery.
* Add IncreaseFullHistoryTsLow API so users can advance each column family's full_history_ts_low seperately.
* Add IncreaseFullHistoryTsLow API so users can advance each column family's full_history_ts_low separately.
* Add GetFullHistoryTsLow API so users can query current full_history_low value of specified column family.
### Performance Improvements
+5 -5
View File
@@ -6,7 +6,7 @@ than release mode.
RocksDB's library should be able to compile without any dependency installed,
although we recommend installing some compression libraries (see below).
We do depend on newer gcc/clang with C++17 support (GCC >= 7, Clang >= 5).
We do depend on newer gcc/clang with C++20 support (GCC >= 11, Clang >= 10).
There are few options when compiling RocksDB:
@@ -60,7 +60,7 @@ most processors made since roughly 2013.
## Supported platforms
* **Linux - Ubuntu**
* Upgrade your gcc to version at least 7 to get C++17 support.
* Upgrade your gcc to version at least 11 to get C++20 support.
* Install gflags. First, try: `sudo apt-get install libgflags-dev`
If this doesn't work and you're using Ubuntu, here's a nice tutorial:
(http://askubuntu.com/questions/312173/installing-gflags-12-04)
@@ -72,7 +72,7 @@ most processors made since roughly 2013.
* Install zstandard: `sudo apt-get install libzstd-dev`.
* **Linux - CentOS / RHEL**
* Upgrade your gcc to version at least 7 to get C++17 support
* Upgrade your gcc to version at least 11 to get C++20 support
* Install gflags:
git clone https://github.com/gflags/gflags.git
@@ -122,7 +122,7 @@ most processors made since roughly 2013.
make && sudo make install
* **OS X**:
* Install latest C++ compiler that supports C++ 17:
* Install latest C++ compiler that supports C++20:
* Update XCode: run `xcode-select --install` (or install it from XCode App's settting).
* Install via [homebrew](http://brew.sh/).
* If you're first time developer in MacOS, you still need to run: `xcode-select --install` in your command line.
@@ -213,7 +213,7 @@ most processors made since roughly 2013.
export PATH=/opt/freeware/bin:$PATH
* **Solaris Sparc**
* Install GCC 7 and higher.
* Install GCC 11 and higher.
* Use these environment variables:
export CC=gcc
+24 -119
View File
@@ -148,10 +148,8 @@ ifeq ($(USE_COROUTINES), 1)
USE_FOLLY = 1
# glog/logging.h requires HAVE_CXX11_ATOMIC
OPT += -DUSE_COROUTINES -DHAVE_CXX11_ATOMIC
ROCKSDB_CXX_STANDARD = c++2a
USE_RTTI = 1
ifneq ($(USE_CLANG), 1)
ROCKSDB_CXX_STANDARD = c++20
PLATFORM_CXXFLAGS += -fcoroutines
endif
endif
@@ -448,83 +446,7 @@ else
PLATFORM_CXXFLAGS += -isystem $(GTEST_DIR)
endif
# This provides a Makefile simulation of a Meta-internal folly integration.
# It is not validated for general use.
#
# USE_FOLLY links the build targets with libfolly.a. The latter could be
# built using 'make build_folly', or built externally and specified in
# the CXXFLAGS and EXTRA_LDFLAGS env variables. The build_detect_platform
# script tries to detect if an external folly dependency has been specified.
# If not, it exports FOLLY_PATH to the path of the installed Folly and
# dependency libraries.
#
# USE_FOLLY_LITE cherry picks source files from Folly to include in the
# RocksDB library. Its faster and has fewer dependencies on 3rd party
# libraries, but with limited functionality. For example, coroutine
# functionality is not available.
ifeq ($(USE_FOLLY),1)
ifeq ($(USE_FOLLY_LITE),1)
$(error Please specify only one of USE_FOLLY and USE_FOLLY_LITE)
endif
ifneq ($(strip $(FOLLY_PATH)),)
BOOST_PATH = $(shell (ls -d $(FOLLY_PATH)/../boost*))
DBL_CONV_PATH = $(shell (ls -d $(FOLLY_PATH)/../double-conversion*))
GFLAGS_PATH = $(shell (ls -d $(FOLLY_PATH)/../gflags*))
GLOG_PATH = $(shell (ls -d $(FOLLY_PATH)/../glog*))
LIBEVENT_PATH = $(shell (ls -d $(FOLLY_PATH)/../libevent*))
XZ_PATH = $(shell (ls -d $(FOLLY_PATH)/../xz*))
LIBSODIUM_PATH = $(shell (ls -d $(FOLLY_PATH)/../libsodium*))
FMT_PATH = $(shell (ls -d $(FOLLY_PATH)/../fmt*))
# For some reason, glog and fmt libraries are under either lib or lib64
GLOG_LIB_PATH = $(shell (ls -d $(GLOG_PATH)/lib*))
FMT_LIB_PATH = $(shell (ls -d $(FMT_PATH)/lib*))
# AIX: pre-defined system headers are surrounded by an extern "C" block
ifeq ($(PLATFORM), OS_AIX)
PLATFORM_CCFLAGS += -I$(BOOST_PATH)/include -I$(DBL_CONV_PATH)/include -I$(GLOG_PATH)/include -I$(LIBEVENT_PATH)/include -I$(XZ_PATH)/include -I$(LIBSODIUM_PATH)/include -I$(FOLLY_PATH)/include -I$(FMT_PATH)/include
PLATFORM_CXXFLAGS += -I$(BOOST_PATH)/include -I$(DBL_CONV_PATH)/include -I$(GLOG_PATH)/include -I$(LIBEVENT_PATH)/include -I$(XZ_PATH)/include -I$(LIBSODIUM_PATH)/include -I$(FOLLY_PATH)/include -I$(FMT_PATH)/include
else
PLATFORM_CCFLAGS += -isystem $(BOOST_PATH)/include -isystem $(DBL_CONV_PATH)/include -isystem $(GLOG_PATH)/include -isystem $(LIBEVENT_PATH)/include -isystem $(XZ_PATH)/include -isystem $(LIBSODIUM_PATH)/include -isystem $(FOLLY_PATH)/include -isystem $(FMT_PATH)/include
PLATFORM_CXXFLAGS += -isystem $(BOOST_PATH)/include -isystem $(DBL_CONV_PATH)/include -isystem $(GLOG_PATH)/include -isystem $(LIBEVENT_PATH)/include -isystem $(XZ_PATH)/include -isystem $(LIBSODIUM_PATH)/include -isystem $(FOLLY_PATH)/include -isystem $(FMT_PATH)/include
endif
# Add -ldl at the end as gcc resolves a symbol in a library by searching only in libraries specified later
# in the command line
PLATFORM_LDFLAGS += $(FOLLY_PATH)/lib/libfolly.a $(BOOST_PATH)/lib/libboost_context.a $(BOOST_PATH)/lib/libboost_filesystem.a $(BOOST_PATH)/lib/libboost_atomic.a $(BOOST_PATH)/lib/libboost_program_options.a $(BOOST_PATH)/lib/libboost_regex.a $(BOOST_PATH)/lib/libboost_system.a $(BOOST_PATH)/lib/libboost_thread.a $(DBL_CONV_PATH)/lib/libdouble-conversion.a $(FMT_LIB_PATH)/libfmt.a $(GLOG_LIB_PATH)/libglog.so $(GFLAGS_PATH)/lib/libgflags.so.2.2 $(LIBEVENT_PATH)/lib/libevent-2.1.so -ldl
PLATFORM_LDFLAGS += -Wl,-rpath=$(GFLAGS_PATH)/lib -Wl,-rpath=$(GLOG_LIB_PATH) -Wl,-rpath=$(LIBEVENT_PATH)/lib -Wl,-rpath=$(LIBSODIUM_PATH)/lib -Wl,-rpath=$(LIBEVENT_PATH)/lib
endif
PLATFORM_CCFLAGS += -DUSE_FOLLY -DFOLLY_NO_CONFIG
PLATFORM_CXXFLAGS += -DUSE_FOLLY -DFOLLY_NO_CONFIG
endif
ifeq ($(USE_FOLLY_LITE),1)
# Path to the Folly source code and include files
FOLLY_DIR = ./third-party/folly
ifneq ($(strip $(BOOST_SOURCE_PATH)),)
BOOST_INCLUDE = $(shell (ls -d $(BOOST_SOURCE_PATH)/boost*/))
# AIX: pre-defined system headers are surrounded by an extern "C" block
ifeq ($(PLATFORM), OS_AIX)
PLATFORM_CCFLAGS += -I$(BOOST_INCLUDE)
PLATFORM_CXXFLAGS += -I$(BOOST_INCLUDE)
else
PLATFORM_CCFLAGS += -isystem $(BOOST_INCLUDE)
PLATFORM_CXXFLAGS += -isystem $(BOOST_INCLUDE)
endif
endif # BOOST_SOURCE_PATH
# AIX: pre-defined system headers are surrounded by an extern "C" block
ifeq ($(PLATFORM), OS_AIX)
PLATFORM_CCFLAGS += -I$(FOLLY_DIR)
PLATFORM_CXXFLAGS += -I$(FOLLY_DIR)
else
PLATFORM_CCFLAGS += -isystem $(FOLLY_DIR)
PLATFORM_CXXFLAGS += -isystem $(FOLLY_DIR)
endif
PLATFORM_CCFLAGS += -DUSE_FOLLY -DFOLLY_NO_CONFIG
PLATFORM_CXXFLAGS += -DUSE_FOLLY -DFOLLY_NO_CONFIG
# TODO: fix linking with fbcode compiler config
PLATFORM_LDFLAGS += -lglog
endif
include folly.mk
ifdef TEST_CACHE_LINE_SIZE
PLATFORM_CCFLAGS += -DTEST_CACHE_LINE_SIZE=$(TEST_CACHE_LINE_SIZE)
@@ -638,13 +560,14 @@ endif
TEST_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(TEST_LIB_SOURCES) $(MOCK_LIB_SOURCES)) $(GTEST)
BENCH_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(BENCH_LIB_SOURCES))
CACHE_BENCH_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(CACHE_BENCH_LIB_SOURCES))
POINT_LOCK_BENCH_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(POINT_LOCK_BENCH_LIB_SOURCES))
TOOL_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(TOOL_LIB_SOURCES))
ANALYZE_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(ANALYZER_LIB_SOURCES))
STRESS_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(STRESS_LIB_SOURCES))
# Exclude build_version.cc -- a generated source file -- from all sources. Not needed for dependencies
ALL_SOURCES = $(filter-out util/build_version.cc, $(LIB_SOURCES)) $(TEST_LIB_SOURCES) $(MOCK_LIB_SOURCES) $(GTEST_DIR)/gtest/gtest-all.cc
ALL_SOURCES += $(TOOL_LIB_SOURCES) $(BENCH_LIB_SOURCES) $(CACHE_BENCH_LIB_SOURCES) $(ANALYZER_LIB_SOURCES) $(STRESS_LIB_SOURCES)
ALL_SOURCES += $(TOOL_LIB_SOURCES) $(BENCH_LIB_SOURCES) $(CACHE_BENCH_LIB_SOURCES) $(POINT_LOCK_BENCH_LIB_SOURCES) $(ANALYZER_LIB_SOURCES) $(STRESS_LIB_SOURCES)
ALL_SOURCES += $(TEST_MAIN_SOURCES) $(TOOL_MAIN_SOURCES) $(BENCH_MAIN_SOURCES)
ALL_SOURCES += $(ROCKSDB_PLUGIN_SOURCES) $(ROCKSDB_PLUGIN_TESTS)
@@ -683,7 +606,8 @@ am__v_CCH_1 =
# user build settings
%.h.pub: %.h # .h.pub not actually created, so re-checked on each invocation
$(AM_V_CCH) cd include/ && echo '#include "$(patsubst include/%,%,$<)"' | \
$(CXX) -I. -DROCKSDB_NAMESPACE=42 -x c++ -c - -o /dev/null
$(CXX) -std=$(or $(ROCKSDB_CXX_STANDARD),c++20) -I. -DROCKSDB_NAMESPACE=42 -x c++ -c - -o /dev/null
build_tools/check-public-header.sh $<
check-headers: $(HEADER_OK_FILES)
@@ -1345,6 +1269,9 @@ block_cache_trace_analyzer: $(OBJ_DIR)/tools/block_cache_analyzer/block_cache_tr
cache_bench: $(OBJ_DIR)/cache/cache_bench.o $(CACHE_BENCH_OBJECTS) $(LIBRARY)
$(AM_LINK)
point_lock_bench: $(OBJ_DIR)/utilities/transactions/lock/point/point_lock_bench.o $(POINT_LOCK_BENCH_OBJECTS) $(LIBRARY)
$(AM_LINK)
persistent_cache_bench: $(OBJ_DIR)/utilities/persistent_cache/persistent_cache_bench.o $(LIBRARY)
$(AM_LINK)
@@ -1357,6 +1284,9 @@ filter_bench: $(OBJ_DIR)/util/filter_bench.o $(LIBRARY)
db_stress: $(OBJ_DIR)/db_stress_tool/db_stress.o $(STRESS_LIBRARY) $(TOOLS_LIBRARY) $(LIBRARY)
$(AM_LINK)
db_stress_compression_manager: $(OBJ_DIR)/db_stress_tool/db_stress_compression_manager.o $(LIBRARY)
$(AM_LINK)
write_stress: $(OBJ_DIR)/tools/write_stress.o $(LIBRARY)
$(AM_LINK)
@@ -1422,13 +1352,13 @@ agg_merge_test: $(OBJ_DIR)/utilities/agg_merge/agg_merge_test.o $(TEST_LIBRARY)
stringappend_test: $(OBJ_DIR)/utilities/merge_operators/string_append/stringappend_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
cassandra_format_test: $(OBJ_DIR)/utilities/cassandra/cassandra_format_test.o $(OBJ_DIR)/utilities/cassandra/test_utils.o $(TEST_LIBRARY) $(LIBRARY)
cassandra_format_test: $(OBJ_DIR)/utilities/cassandra/cassandra_format_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
cassandra_functional_test: $(OBJ_DIR)/utilities/cassandra/cassandra_functional_test.o $(OBJ_DIR)/utilities/cassandra/test_utils.o $(TEST_LIBRARY) $(LIBRARY)
cassandra_functional_test: $(OBJ_DIR)/utilities/cassandra/cassandra_functional_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
cassandra_row_merge_test: $(OBJ_DIR)/utilities/cassandra/cassandra_row_merge_test.o $(OBJ_DIR)/utilities/cassandra/test_utils.o $(TEST_LIBRARY) $(LIBRARY)
cassandra_row_merge_test: $(OBJ_DIR)/utilities/cassandra/cassandra_row_merge_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
cassandra_serialize_test: $(OBJ_DIR)/utilities/cassandra/cassandra_serialize_test.o $(TEST_LIBRARY) $(LIBRARY)
@@ -1491,6 +1421,9 @@ db_test: $(OBJ_DIR)/db/db_test.o $(TEST_LIBRARY) $(LIBRARY)
db_test2: $(OBJ_DIR)/db/db_test2.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
db_etc3_test: $(OBJ_DIR)/db/db_etc3_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
compression_test: $(OBJ_DIR)/util/compression_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
@@ -1878,6 +1811,9 @@ heap_test: $(OBJ_DIR)/util/heap_test.o $(TEST_LIBRARY) $(LIBRARY)
point_lock_manager_test: utilities/transactions/lock/point/point_lock_manager_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
point_lock_manager_stress_test: utilities/transactions/lock/point/point_lock_manager_stress_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
transaction_test: $(OBJ_DIR)/utilities/transactions/transaction_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
@@ -2037,6 +1973,9 @@ wide_column_serialization_test: $(OBJ_DIR)/db/wide/wide_column_serialization_tes
wide_columns_helper_test: $(OBJ_DIR)/db/wide/wide_columns_helper_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
interval_test: $(OBJ_DIR)/util/interval_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
#-------------------------------------------------
# make install related stuff
PREFIX ?= /usr/local
@@ -2245,7 +2184,7 @@ libsnappy.a: snappy-$(SNAPPY_VER).tar.gz
-rm -rf snappy-$(SNAPPY_VER)
tar xvzf snappy-$(SNAPPY_VER).tar.gz
mkdir snappy-$(SNAPPY_VER)/build
cd snappy-$(SNAPPY_VER)/build && CFLAGS='$(ARCHFLAG) ${JAVA_STATIC_DEPS_CCFLAGS} ${EXTRA_CFLAGS}' CXXFLAGS='$(ARCHFLAG) ${JAVA_STATIC_DEPS_CXXFLAGS} ${EXTRA_CXXFLAGS}' LDFLAGS='${JAVA_STATIC_DEPS_LDFLAGS} ${EXTRA_LDFLAGS}' cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DSNAPPY_BUILD_BENCHMARKS=OFF -DSNAPPY_BUILD_TESTS=OFF --compile-no-warning-as-error ${PLATFORM_CMAKE_FLAGS} .. && $(MAKE) ${SNAPPY_MAKE_TARGET}
cd snappy-$(SNAPPY_VER)/build && CFLAGS='$(ARCHFLAG) ${JAVA_STATIC_DEPS_CCFLAGS} ${EXTRA_CFLAGS}' CXXFLAGS='$(ARCHFLAG) ${JAVA_STATIC_DEPS_CXXFLAGS} ${EXTRA_CXXFLAGS}' LDFLAGS='${JAVA_STATIC_DEPS_LDFLAGS} ${EXTRA_LDFLAGS}' cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DSNAPPY_BUILD_BENCHMARKS=OFF -DSNAPPY_BUILD_TESTS=OFF ${PLATFORM_CMAKE_FLAGS} .. && $(MAKE) ${SNAPPY_MAKE_TARGET}
cp snappy-$(SNAPPY_VER)/build/libsnappy.a .
lz4-$(LZ4_VER).tar.gz:
@@ -2481,40 +2420,6 @@ commit_prereq:
false # J=$(J) build_tools/precommit_checker.py unit clang_unit release clang_release tsan asan ubsan lite unit_non_shm
# $(MAKE) clean && $(MAKE) jclean && $(MAKE) rocksdbjava;
# For public CI runs, checkout folly in a way that can build with RocksDB.
# This is mostly intended as a test-only simulation of Meta-internal folly
# integration.
checkout_folly:
if [ -e third-party/folly ]; then \
cd third-party/folly && ${GIT_COMMAND} fetch origin; \
else \
cd third-party && ${GIT_COMMAND} clone https://github.com/facebook/folly.git; \
fi
@# Pin to a particular version for public CI, so that PR authors don't
@# need to worry about folly breaking our integration. Update periodically
cd third-party/folly && git reset --hard e95383b7c8b5b1e46cf47acf2f317d54f93c8268
@# Apparently missing include
perl -pi -e 's/(#include <atomic>)/$$1\n#include <cstring>/' third-party/folly/folly/lang/Exception.h
@# Warning-as-error on memcpy
perl -pi -e 's/memcpy.&ptr/memcpy((void*)&ptr/' third-party/folly/folly/lang/Exception.cpp
@# const mismatch
perl -pi -e 's/: environ/: (const char**)(environ)/' third-party/folly/folly/Subprocess.cpp
@# NOTE: boost source will be needed for any build including `USE_FOLLY_LITE` builds as those depend on boost headers
cd third-party/folly && $(PYTHON) build/fbcode_builder/getdeps.py fetch boost
CXX_M_FLAGS = $(filter -m%, $(CXXFLAGS))
build_folly:
FOLLY_INST_PATH=`cd third-party/folly; $(PYTHON) build/fbcode_builder/getdeps.py show-inst-dir`; \
if [ "$$FOLLY_INST_PATH" ]; then \
rm -rf $${FOLLY_INST_PATH}/../../*; \
else \
echo "Please run checkout_folly first"; \
false; \
fi
cd third-party/folly && \
CXXFLAGS=" $(CXX_M_FLAGS) -DHAVE_CXX11_ATOMIC " $(PYTHON) build/fbcode_builder/getdeps.py build --no-tests
# ---------------------------------------------------------------------------
# Build size testing
# ---------------------------------------------------------------------------
+15
View File
@@ -135,6 +135,9 @@ def generate_buck(repo_path, deps_map):
BUCK = TARGETSBuilder("%s/BUCK" % repo_path, extra_argv)
# Add oncall("rocksdb_point_of_contact") at the top
BUCK.add_oncall("rocksdb_point_of_contact")
# rocksdb_lib
BUCK.add_library(
"rocksdb_lib",
@@ -206,6 +209,12 @@ def generate_buck(repo_path, deps_map):
src_mk.get("CACHE_BENCH_LIB_SOURCES", []),
[":rocksdb_lib"],
)
# rocksdb_point_lock_bench_tools_lib
BUCK.add_library(
"rocksdb_point_lock_bench_tools_lib",
src_mk.get("POINT_LOCK_BENCH_LIB_SOURCES", []),
[":rocksdb_lib"],
)
# rocksdb_stress_lib
BUCK.add_rocksdb_library(
"rocksdb_stress_lib",
@@ -229,6 +238,12 @@ def generate_buck(repo_path, deps_map):
BUCK.add_binary(
"cache_bench", ["cache/cache_bench.cc"], [":rocksdb_cache_bench_tools_lib"]
)
# point_lock_bench binary
BUCK.add_binary(
"point_lock_bench",
["utilities/transactions/lock/point/point_lock_bench.cc"],
[":rocksdb_point_lock_bench_tools_lib"]
)
# bench binaries
for src in src_mk.get("MICROBENCH_SOURCES", []):
name = src.rsplit("/", 1)[1].split(".")[0] if "/" in src else src.split(".")[0]
+5
View File
@@ -45,6 +45,11 @@ class TARGETSBuilder:
self.total_bin = 0
self.total_test = 0
self.tests_cfg = ""
def add_oncall(self, oncall):
with open(self.path, "ab") as targets_file:
targets_file.write(targets_cfg.oncall_template.format(name=oncall).encode("utf-8"))
def add_library(
self,
+10 -3
View File
@@ -1,10 +1,12 @@
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
# Copyright (c) Meta Platforms, Inc. and affiliates.
# This source code is licensed under both the GPLv2 (found in the COPYING file in the root directory)
# and the Apache 2.0 License (found in the LICENSE.Apache file in the root directory).
rocksdb_target_header_template = """# This file \100generated by:
#$ python3 buckifier/buckify_rocksdb.py{extra_argv}
# --> DO NOT EDIT MANUALLY <--
# This file is a Facebook-specific integration for buck builds, so can
# only be validated by Facebook employees.
# This file is a Meta-specific integration for buck builds, so can
# only be validated by Meta employees.
load("//rocks/buckifier:defs.bzl", "cpp_library_wrapper","rocks_cpp_library_wrapper","cpp_binary_wrapper","cpp_unittest_wrapper","fancy_bench_wrapper","add_c_test_wrapper")
load("@fbcode_macros//build_defs:export_files.bzl", "export_file")
@@ -41,3 +43,8 @@ fancy_bench_wrapper(suite_name="{name}", binary_to_bench_to_metric_list_map={ben
export_file_template = """
export_file(name = "{name}")
"""
oncall_template = """
oncall("{name}")
"""
+27 -9
View File
@@ -45,18 +45,21 @@ if test -z "$OUTPUT"; then
exit 1
fi
# we depend on C++17, but should be compatible with newer standards
# we depend on C++20, but should be compatible with newer standards
if [ "$ROCKSDB_CXX_STANDARD" ]; then
PLATFORM_CXXFLAGS="-std=$ROCKSDB_CXX_STANDARD"
else
PLATFORM_CXXFLAGS="-std=c++17"
PLATFORM_CXXFLAGS="-std=c++20"
fi
# we currently depend on POSIX platform
COMMON_FLAGS="-DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX"
# Default to fbcode gcc on internal fb machines
if [ -z "$ROCKSDB_NO_FBCODE" -a -d /mnt/gvfs/third-party ]; then
# Default to fbcode gcc on Meta internal machines
IS_META_HOST="$(hostname | grep -E '(facebook|meta).com|fbinfra.net')"
if [ -z "$ROCKSDB_NO_FBCODE" -a "$IS_META_HOST" ]; then
if [ -d /mnt/gvfs/third-party ]; then
echo "NOTE: Using fbcode build" >&2
FBCODE_BUILD="true"
# If we're compiling with TSAN or shared lib, we need pic build
PIC_BUILD=$COMPILE_WITH_TSAN
@@ -64,6 +67,11 @@ if [ -z "$ROCKSDB_NO_FBCODE" -a -d /mnt/gvfs/third-party ]; then
PIC_BUILD=1
fi
source "$PWD/build_tools/fbcode_config_platform010.sh"
else
echo "************************************************************************" >&2
echo "WARNING: -d /mnt/gvfs/third-party failed; no fbcode build" >&2
echo "************************************************************************" >&2
fi
fi
# Delete existing output, if it exists
@@ -71,7 +79,9 @@ rm -f "$OUTPUT"
touch "$OUTPUT"
if test -z "$CC"; then
if [ -x "$(command -v cc)" ]; then
if [ "$USE_CLANG" -a -x "$(command -v clang)" ]; then
CC=clang
elif [ -x "$(command -v cc)" ]; then
CC=cc
elif [ -x "$(command -v clang)" ]; then
CC=clang
@@ -81,7 +91,9 @@ if test -z "$CC"; then
fi
if test -z "$CXX"; then
if [ -x "$(command -v g++)" ]; then
if [ "$USE_CLANG" -a -x "$(command -v clang++)" ]; then
CXX=clang++
elif [ -x "$(command -v g++)" ]; then
CXX=g++
elif [ -x "$(command -v clang++)" ]; then
CXX=clang++
@@ -91,7 +103,9 @@ if test -z "$CXX"; then
fi
if test -z "$AR"; then
if [ -x "$(command -v gcc-ar)" ]; then
if [ "$USE_CLANG" -a -x "$(command -v llvm-ar)" ]; then
AR=llvm-ar
elif [ -x "$(command -v gcc-ar)" ]; then
AR=gcc-ar
elif [ -x "$(command -v llvm-ar)" ]; then
AR=llvm-ar
@@ -297,7 +311,8 @@ EOF
EOF
then
COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=1"
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
# Hack: don't link extra gflags assuming it comes with folly
[ "$USE_FOLLY" ] || PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
# check if namespace is gflags
elif $CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o 2>/dev/null << EOF
#include <gflags/gflags.h>
@@ -306,7 +321,8 @@ EOF
EOF
then
COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=1 -DGFLAGS_NAMESPACE=gflags"
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
# Hack: don't link extra gflags assuming it comes with folly
[ "$USE_FOLLY" ] || PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
# check if namespace is google
elif $CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o 2>/dev/null << EOF
#include <gflags/gflags.h>
@@ -758,6 +774,7 @@ fi
if [ "$USE_FOLLY_LITE" ]; then
if [ "$FOLLY_DIR" ]; then
BOOST_SOURCE_PATH=`cd $FOLLY_DIR && $PYTHON build/fbcode_builder/getdeps.py show-source-dir boost`
FMT_SOURCE_PATH=`cd $FOLLY_DIR && $PYTHON build/fbcode_builder/getdeps.py show-source-dir fmt`
fi
fi
@@ -802,6 +819,7 @@ echo "FIND=$FIND" >> "$OUTPUT"
echo "WATCH=$WATCH" >> "$OUTPUT"
echo "FOLLY_PATH=$FOLLY_PATH" >> "$OUTPUT"
echo "BOOST_SOURCE_PATH=$BOOST_SOURCE_PATH" >> "$OUTPUT"
echo "FMT_SOURCE_PATH=$FMT_SOURCE_PATH" >> "$OUTPUT"
# This will enable some related identifiers for the preprocessor
if test -n "$JEMALLOC"; then
+31
View File
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# Copyright (c) Meta Platforms, Inc. and affiliates. All Rights Reserved.
#
# Check for some simple mistakes in public headers (on the command line)
# that should prevent commit or push
BAD=""
# Look for potential for ODR violations caused by public headers depending on
# build parameters that could vary between RocksDB build and application build.
# * Cases like LUA, ROCKSDB_NAMESPACE, and ROCKSDB_ASSERT_STATUS_CHECKED are
# intentional, hard to avoid. (We expect definitions to change and the user
# should also.)
# * Cases like _WIN32, OS_WIN, and __cplusplus are essentially ODR-safe.
# * Cases like
# #ifdef BLAH // ODR-SAFE
# #undef BLAH
# #endif
# that should not cause ODR violations can be exempted with the ODR-SAFE
# marker recognized here.
grep -nHE '^#if' -- "$@" | grep -vE 'ROCKSDB_NAMESPACE|ROCKSDB_ASSERT_STATUS_CHECKED|LUA|_WIN32|OS_WIN|ODR-SAFE|__cplusplus|ROCKSDB_DLL|ROCKSDB_LIBRARY_EXPORTS'
if [ "$?" != "1" ]; then
echo "^^^^^ #if in public API could cause an ODR violation."
echo " Add // ODR-SAFE if verified safe."
BAD=1
fi
if [ "$BAD" ]; then
exit 1
fi
+64
View File
@@ -148,6 +148,70 @@ else
echo "Checking format of uncommitted changes..."
fi
# Check for missing copyright in new files
echo "Checking for copyright headers in new files..."
# Get list of new files (added, not just modified)
if [ -z "$uncommitted_code" ]; then
# Post-commit: check files added since merge base
new_files=$(git diff --name-only --diff-filter=A "$FORMAT_UPSTREAM_MERGE_BASE" -- '*.h' '*.cc' '*.py' $EXCLUDE)
else
# Pre-commit: check staged new files
new_files=$(git diff --name-only --diff-filter=A --cached HEAD -- '*.h' '*.cc' '*.py' $EXCLUDE)
fi
if [ -n "$new_files" ]; then
files_missing_copyright=""
for file in $new_files; do
if [ -f "$file" ]; then
# Check if file is missing copyright
# For .py files, check for Python-style comment
# For .h and .cc files, check for C++-style comment
if [[ "$file" == *.py ]]; then
if ! grep -q "Copyright (c) Meta Platforms, Inc. and affiliates" "$file"; then
files_missing_copyright="$files_missing_copyright $file"
# Add copyright header to Python file
temp_file=$(mktemp)
{
echo "# Copyright (c) Meta Platforms, Inc. and affiliates."
echo "# This source code is licensed under both the GPLv2 (found in the COPYING file in the root directory)"
echo "# and the Apache 2.0 License (found in the LICENSE.Apache file in the root directory)."
echo
cat "$file"
} > "$temp_file"
mv "$temp_file" "$file"
echo "Added copyright header to $file"
fi
elif [[ "$file" == *.h ]] || [[ "$file" == *.cc ]]; then
if ! grep -q "Copyright (c) Meta Platforms, Inc. and affiliates" "$file"; then
files_missing_copyright="$files_missing_copyright $file"
# Add copyright header to C++ file
temp_file=$(mktemp)
{
echo "// Copyright (c) Meta Platforms, Inc. and affiliates. "
echo "// This source code is licensed under both the GPLv2 (found in the "
echo "// COPYING file in the root directory) and Apache 2.0 License "
echo "// (found in the LICENSE.Apache file in the root directory)."
echo
cat "$file"
} > "$temp_file"
mv "$temp_file" "$file"
echo "Added copyright header to $file"
fi
fi
fi
done
if [ -n "$files_missing_copyright" ]; then
echo "Copyright headers were added to new files."
else
echo "All new files have copyright headers."
fi
else
echo "No new files to check for copyright headers."
fi
if [ -z "$diffs" ]
then
echo "Nothing needs to be reformatted!"
+88
View File
@@ -0,0 +1,88 @@
# INSTRUCTIONS:
# I was not able to build docker images on an isolated devserver because of
# issues with proxy internet access. Use a public cloud or other Linux system.
# (I used a Debian system after installing docker features, adding my user to
# the docker and docker-registry groups, and logging out and back in to pick
# those up.)
#
# Follow https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-with-a-personal-access-token-classic
# to login with your GitHub credentials, as in
#
# $ docker login ghcr.io -u pdillinger
#
# and paste the limited-purpose GitHub token into the terminal.
#
# Then in the build_tools/ubuntu22_image directory, (bump minor version for
# random docker file updates, major version tracks Ubuntu release)
#
# $ docker build -t ghcr.io/facebook/rocksdb_ubuntu:22.0
# $ docker push ghcr.io/facebook/rocksdb_ubuntu:22.0
#
# Might need to change visibility to public through
# https://github.com/orgs/facebook/packages/container/rocksdb_ubuntu/settings
# or similar.
# from official ubuntu 22.04
FROM ubuntu:22.04
# update system
RUN apt-get update
RUN apt-get upgrade -y
# install basic tools
RUN apt-get install -y vim wget curl
# install tzdata noninteractive
RUN DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install tzdata
# install git and default compilers
RUN apt-get install -y git gcc g++ clang clang-tools
# install basic package
RUN apt-get install -y lsb-release software-properties-common gnupg
# install gflags, tbb
RUN apt-get install -y libgflags-dev libtbb-dev
# install compression libs
RUN apt-get install -y libsnappy-dev zlib1g-dev libbz2-dev liblz4-dev libzstd-dev
# install cmake
RUN apt-get install -y cmake
RUN apt-get install -y libssl-dev
# install clang-13
WORKDIR /root
RUN wget https://apt.llvm.org/llvm.sh
RUN chmod +x llvm.sh
RUN ./llvm.sh 13 all
# There are incompatibilities between clang with -std=c++20 and libstdc++
# provided by gcc, so we have to compile with clang-13 using -stdlib=libc++
# and only one version of libc++ can be installed on the system at one time.
# So to avoid confusion we remove unusable clang-14 also.
RUN apt-get install libc++-13-dev libc++abi-13-dev
RUN apt-get purge -y clang-14 && apt-get autoremove -y
# install gcc-10 and more, default is 11
RUN apt-get install -y gcc-10 g++-10
RUN add-apt-repository -y ppa:ubuntu-toolchain-r/test
RUN apt-get install -y gcc-13 g++-13
# install apt-get install -y valgrind
RUN apt-get install -y valgrind
# install folly depencencies
# Missing compatible libunwind: RUN apt-get install -y libgoogle-glog-dev
# So instead install from source. This currently requires compiling with
# -DGLOG_USE_GLOG_EXPORT
RUN wget https://github.com/google/glog/archive/refs/tags/v0.7.1.tar.gz && tar xzf v0.7.1.tar.gz && cd glog-0.7.1/ && cmake -S . -B build -G "Unix Makefiles" && cmake --build build && cmake --build build --target install && cd .. && rm -rf v0.7.1.tar.gz glog-0.7.1
# install openjdk 8
RUN apt-get install -y openjdk-8-jdk
ENV JAVA_HOME /usr/lib/jvm/java-1.8.0-openjdk-amd64
# install mingw
RUN apt-get install -y mingw-w64
# install gtest-parallel package
RUN git clone --single-branch --branch master --depth 1 https://github.com/google/gtest-parallel.git ~/gtest-parallel
ENV PATH $PATH:/root/gtest-parallel
# install libprotobuf for fuzzers test
RUN apt-get install -y ninja-build binutils liblzma-dev libz-dev pkg-config autoconf libtool
RUN git clone --branch v1.0 https://github.com/google/libprotobuf-mutator.git ~/libprotobuf-mutator && cd ~/libprotobuf-mutator && git checkout ffd86a32874e5c08a143019aad1aaf0907294c9f && mkdir build && cd build && cmake .. -GNinja -DCMAKE_C_COMPILER=clang-13 -DCMAKE_CXX_COMPILER=clang++-13 -DCMAKE_BUILD_TYPE=Release -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON && ninja && ninja install
ENV PKG_CONFIG_PATH /usr/local/OFF/:/root/libprotobuf-mutator/build/external.protobuf/lib/pkgconfig/
ENV PROTOC_BIN /root/libprotobuf-mutator/build/external.protobuf/bin/protoc
# install the latest google benchmark
RUN git clone --depth 1 --branch v1.7.0 https://github.com/google/benchmark.git ~/benchmark && cd ~/benchmark && mkdir build && cd build && cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release -DBENCHMARK_ENABLE_GTEST_TESTS=0 && ninja && ninja install && cd ~ && rm -rf /root/benchmark
# clean up
RUN rm -rf /var/lib/apt/lists/*
+72
View File
@@ -0,0 +1,72 @@
# INSTRUCTIONS:
# I was not able to build docker images on an isolated devserver because of
# issues with proxy internet access. Use a public cloud or other Linux system.
# (I used a Debian system after installing docker features, adding my user to
# the docker and docker-registry groups, and logging out and back in to pick
# those up.)
#
# Follow https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-with-a-personal-access-token-classic
# to login with your GitHub credentials, as in
#
# $ docker login ghcr.io -u pdillinger
#
# and paste the limited-purpose GitHub token into the terminal.
#
# Then in the build_tools/ubuntu24_image directory, (bump minor version for
# random docker file updates, major version tracks Ubuntu release)
#
# $ docker build -t ghcr.io/facebook/rocksdb_ubuntu:24.0
# $ docker push ghcr.io/facebook/rocksdb_ubuntu:24.0
#
# Might need to change visibility to public through
# https://github.com/orgs/facebook/packages/container/rocksdb_ubuntu/settings
# or similar.
# from official ubuntu 24.04
FROM ubuntu:24.04
# update system
RUN apt-get update
RUN apt-get upgrade -y
# install basic tools
RUN apt-get install -y vim wget curl
# install tzdata noninteractive
RUN DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install tzdata
# install git and default compilers
RUN apt-get install -y git gcc g++ clang clang-tools
# install basic package
RUN apt-get install -y lsb-release software-properties-common gnupg
# install gflags, tbb
RUN apt-get install -y libgflags-dev libtbb-dev
# install compression libs
RUN apt-get install -y libsnappy-dev zlib1g-dev libbz2-dev liblz4-dev libzstd-dev
# install cmake
RUN apt-get install -y cmake
RUN apt-get install -y libssl-dev
# install gcc-12 and more, default is 13
RUN apt-get install -y gcc-12 g++-12 gcc-14 g++-14
# install apt-get install -y valgrind
RUN apt-get install -y valgrind
# install folly depencencies
RUN apt-get install -y libgoogle-glog-dev
# install openjdk 8
RUN apt-get install -y openjdk-8-jdk
ENV JAVA_HOME /usr/lib/jvm/java-1.8.0-openjdk-amd64
# install mingw
RUN apt-get install -y mingw-w64
# install gtest-parallel package
RUN git clone --single-branch --branch master --depth 1 https://github.com/google/gtest-parallel.git ~/gtest-parallel
ENV PATH $PATH:/root/gtest-parallel
# install libprotobuf for fuzzers test
RUN apt-get install -y ninja-build binutils liblzma-dev libz-dev pkg-config autoconf libtool
RUN git clone --branch v1.0 https://github.com/google/libprotobuf-mutator.git ~/libprotobuf-mutator && cd ~/libprotobuf-mutator && git checkout ffd86a32874e5c08a143019aad1aaf0907294c9f && mkdir build && cd build && cmake .. -GNinja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Release -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON && ninja && ninja install
ENV PKG_CONFIG_PATH /usr/local/OFF/:/root/libprotobuf-mutator/build/external.protobuf/lib/pkgconfig/
ENV PROTOC_BIN /root/libprotobuf-mutator/build/external.protobuf/bin/protoc
# install the latest google benchmark
RUN git clone --depth 1 --branch v1.7.0 https://github.com/google/benchmark.git ~/benchmark && cd ~/benchmark && mkdir build && cd build && cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release -DBENCHMARK_ENABLE_GTEST_TESTS=0 && ninja && ninja install && cd ~ && rm -rf /root/benchmark
# clean up
RUN rm -rf /var/lib/apt/lists/*
+91 -14
View File
@@ -60,6 +60,8 @@ DEFINE_uint32(value_bytes, 8 * KiB, "Size of each value added.");
DEFINE_uint32(value_bytes_estimate, 0,
"If > 0, overrides estimated_entry_charge or "
"min_avg_entry_charge depending on cache_type.");
DEFINE_double(compressible_to_ratio, 0.5,
"Approximate size ratio that values can be compressed to.");
DEFINE_int32(
degenerate_hash_bits, 0,
@@ -117,7 +119,7 @@ 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");
DEFINE_string(cache_type, "lru_cache", "Type of block cache.");
DEFINE_string(cache_type, "hyper_clock_cache", "Type of block cache.");
DEFINE_bool(use_jemalloc_no_dump_allocator, false,
"Whether to use JemallocNoDumpAllocator");
@@ -182,6 +184,11 @@ DEFINE_bool(sck_randomize, false,
DEFINE_bool(sck_footer_unique_id, false,
"(-stress_cache_key) Simulate using proposed footer unique id");
// ## END stress_cache_key sub-tool options ##
// ## BEGIN stress_cache_instances sub-tool options ##
DEFINE_uint32(stress_cache_instances, 0,
"If > 0, run cache instance stress test instead");
// Uses cache_size and cache_type, maybe more
// ## END stress_cache_instance sub-tool options ##
namespace ROCKSDB_NAMESPACE {
@@ -291,10 +298,19 @@ struct KeyGen {
Cache::ObjectPtr createValue(Random64& rnd, MemoryAllocator* alloc) {
char* rv = AllocateBlock(FLAGS_value_bytes, alloc).release();
// Fill with some filler data, and take some CPU time
for (uint32_t i = 0; i < FLAGS_value_bytes; i += 8) {
// Fill with some filler data, and take some CPU time, but add redundancy
// as requested for compressibility.
uint32_t random_fill_size = std::max(
uint32_t{1}, std::min(FLAGS_value_bytes,
static_cast<uint32_t>(FLAGS_compressible_to_ratio *
FLAGS_value_bytes)));
uint32_t i = 0;
for (; i < random_fill_size; i += 8) {
EncodeFixed64(rv + i, rnd.Next());
}
for (; i < FLAGS_value_bytes; i++) {
rv[i] = rv[i % random_fill_size];
}
return rv;
}
@@ -309,16 +325,16 @@ Status SaveToFn(Cache::ObjectPtr from_obj, size_t /*from_offset*/,
Status CreateFn(const Slice& data, CompressionType /*type*/,
CacheTier /*source*/, Cache::CreateContext* /*context*/,
MemoryAllocator* /*allocator*/, Cache::ObjectPtr* out_obj,
MemoryAllocator* alloc, Cache::ObjectPtr* out_obj,
size_t* out_charge) {
*out_obj = new char[data.size()];
*out_obj = AllocateBlock(data.size(), alloc).release();
memcpy(*out_obj, data.data(), data.size());
*out_charge = data.size();
return Status::OK();
};
void DeleteFn(Cache::ObjectPtr value, MemoryAllocator* alloc) {
CustomDeleter{alloc}(static_cast<char*>(value));
CacheAllocationDeleter{alloc}(static_cast<char*>(value));
}
Cache::CacheItemHelper helper1_wos(CacheEntryRole::kDataBlock, DeleteFn);
@@ -376,7 +392,12 @@ class CacheBench {
fprintf(stderr, "Percentages must add to 100.\n");
exit(1);
}
cache_ = MakeCache();
}
~CacheBench() = default;
static std::shared_ptr<Cache> MakeCache() {
std::shared_ptr<MemoryAllocator> allocator;
if (FLAGS_use_jemalloc_no_dump_allocator) {
JemallocAllocatorOptions opts;
@@ -395,12 +416,12 @@ class CacheBench {
opts.hash_seed = BitwiseAnd(FLAGS_seed, INT32_MAX);
opts.memory_allocator = allocator;
opts.eviction_effort_cap = FLAGS_eviction_effort_cap;
if (FLAGS_cache_type == "fixed_hyper_clock_cache" ||
FLAGS_cache_type == "hyper_clock_cache") {
if (FLAGS_cache_type == "fixed_hyper_clock_cache") {
opts.estimated_entry_charge = FLAGS_value_bytes_estimate > 0
? FLAGS_value_bytes_estimate
: FLAGS_value_bytes;
} else if (FLAGS_cache_type == "auto_hyper_clock_cache") {
} else if (FLAGS_cache_type == "auto_hyper_clock_cache" ||
FLAGS_cache_type == "hyper_clock_cache") {
if (FLAGS_value_bytes_estimate > 0) {
opts.min_avg_entry_charge = FLAGS_value_bytes_estimate;
}
@@ -409,7 +430,7 @@ class CacheBench {
exit(1);
}
ConfigureSecondaryCache(opts);
cache_ = opts.MakeSharedCache();
return opts.MakeSharedCache();
} else if (FLAGS_cache_type == "lru_cache") {
LRUCacheOptions opts(FLAGS_cache_size, FLAGS_num_shard_bits,
false /* strict_capacity_limit */,
@@ -417,15 +438,13 @@ class CacheBench {
opts.hash_seed = BitwiseAnd(FLAGS_seed, INT32_MAX);
opts.memory_allocator = allocator;
ConfigureSecondaryCache(opts);
cache_ = NewLRUCache(opts);
return NewLRUCache(opts);
} else {
fprintf(stderr, "Cache type not supported.\n");
exit(1);
}
}
~CacheBench() = default;
void PopulateCache() {
Random64 rnd(FLAGS_seed);
KeyGen keygen;
@@ -479,7 +498,7 @@ class CacheBench {
PrintEnv();
SharedState shared(this);
std::vector<std::unique_ptr<ThreadState> > threads(FLAGS_threads);
std::vector<std::unique_ptr<ThreadState>> threads(FLAGS_threads);
for (uint32_t i = 0; i < FLAGS_threads; i++) {
threads[i].reset(new ThreadState(i, &shared));
std::thread(ThreadBody, threads[i].get()).detach();
@@ -1141,6 +1160,59 @@ class StressCacheKey {
double multiplier_ = 0.0;
};
// cache_bench -stress_cache_instances is a partially independent embedded tool
// for evaluating the time and space required to create and destroy many cache
// instances, as this is considered important for a default cache implementation
// which could see many throw-away instances in handling of Options, or created
// in large numbers for many very small DBs with many CFs. Prefix command line
// with /usr/bin/time to see max RSS memory.
class StressCacheInstances {
public:
void Run() {
const int kNumIterations = 10;
const auto clock = SystemClock::Default().get();
caches_.reserve(FLAGS_stress_cache_instances);
uint64_t total_create_time_us = 0;
uint64_t total_destroy_time_us = 0;
for (int iter = 0; iter < kNumIterations; ++iter) {
// Create many cache instances
uint64_t start_create = clock->NowMicros();
for (uint32_t i = 0; i < FLAGS_stress_cache_instances; ++i) {
caches_.emplace_back(CacheBench::MakeCache());
}
uint64_t end_create = clock->NowMicros();
uint64_t create_time = end_create - start_create;
total_create_time_us += create_time;
// Destroy them
uint64_t start_destroy = clock->NowMicros();
caches_.clear();
uint64_t end_destroy = clock->NowMicros();
uint64_t destroy_time = end_destroy - start_destroy;
total_destroy_time_us += destroy_time;
printf(
"Iteration %d: Created %u caches in %.3f ms, destroyed in %.3f ms\n",
iter + 1, FLAGS_stress_cache_instances, create_time / 1000.0,
destroy_time / 1000.0);
}
printf("Average creation time: %.3f ms (%.1f us per cache)\n",
static_cast<double>(total_create_time_us) / kNumIterations / 1000.0,
static_cast<double>(total_create_time_us) / kNumIterations /
FLAGS_stress_cache_instances);
printf("Average destruction time: %.3f ms (%.1f us per cache)\n",
static_cast<double>(total_destroy_time_us) / kNumIterations / 1000.0,
static_cast<double>(total_destroy_time_us) / kNumIterations /
FLAGS_stress_cache_instances);
}
private:
std::vector<std::shared_ptr<Cache>> caches_;
};
int cache_bench_tool(int argc, char** argv) {
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
ParseCommandLineFlags(&argc, &argv, true);
@@ -1151,6 +1223,11 @@ int cache_bench_tool(int argc, char** argv) {
return 0;
}
if (FLAGS_stress_cache_instances > 0) {
StressCacheInstances().Run();
return 0;
}
if (FLAGS_threads <= 0) {
fprintf(stderr, "threads number <= 0\n");
exit(1);
+88 -75
View File
@@ -10,12 +10,12 @@
#include "cache/clock_cache.h"
#include <algorithm>
#include <atomic>
#include <bitset>
#include <cassert>
#include <cinttypes>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <exception>
#include <functional>
#include <numeric>
@@ -26,10 +26,9 @@
#include "cache/cache_key.h"
#include "cache/secondary_cache_adapter.h"
#include "logging/logging.h"
#include "monitoring/perf_context_imp.h"
#include "monitoring/statistics_impl.h"
#include "port/lang.h"
#include "port/likely.h"
#include "rocksdb/env.h"
#include "util/autovector.h"
#include "util/hash.h"
#include "util/math.h"
#include "util/random.h"
@@ -361,16 +360,9 @@ void ConstApplyToEntriesRange(const Func& func, const HandleImpl* begin,
}
}
constexpr uint32_t kStrictCapacityLimitBit = 1u << 31;
uint32_t SanitizeEncodeEecAndScl(int eviction_effort_cap,
bool strict_capacit_limit) {
uint32_t SanitizeEvictionEffortCap(int eviction_effort_cap) {
eviction_effort_cap = std::max(int{1}, eviction_effort_cap);
eviction_effort_cap =
std::min(static_cast<int>(~kStrictCapacityLimitBit), eviction_effort_cap);
uint32_t eec_and_scl = static_cast<uint32_t>(eviction_effort_cap);
eec_and_scl |= strict_capacit_limit ? kStrictCapacityLimitBit : 0;
return eec_and_scl;
return static_cast<uint32_t>(eviction_effort_cap);
}
} // namespace
@@ -381,6 +373,22 @@ void ClockHandleBasicData::FreeData(MemoryAllocator* allocator) const {
}
}
BaseClockTable::BaseClockTable(size_t capacity, bool strict_capacity_limit,
int eviction_effort_cap,
CacheMetadataChargePolicy metadata_charge_policy,
MemoryAllocator* allocator,
const Cache::EvictionCallback* eviction_callback,
const uint32_t* hash_seed)
: capacity_(capacity),
eec_and_scl_(EecAndScl{}
.With<EvictionEffortCap>(
SanitizeEvictionEffortCap(eviction_effort_cap))
.With<StrictCapacityLimit>(strict_capacity_limit)),
metadata_charge_policy_(metadata_charge_policy),
allocator_(allocator),
eviction_callback_(*eviction_callback),
hash_seed_(*hash_seed) {}
template <class HandleImpl>
HandleImpl* BaseClockTable::StandaloneInsert(
const ClockHandleBasicData& proto) {
@@ -402,8 +410,7 @@ HandleImpl* BaseClockTable::StandaloneInsert(
template <class Table>
typename Table::HandleImpl* BaseClockTable::CreateStandalone(
ClockHandleBasicData& proto, size_t capacity, uint32_t eec_and_scl,
bool allow_uncharged) {
ClockHandleBasicData& proto, bool allow_uncharged) {
Table& derived = static_cast<Table&>(*this);
typename Table::InsertState state;
derived.StartInsert(state);
@@ -412,10 +419,10 @@ typename Table::HandleImpl* BaseClockTable::CreateStandalone(
// NOTE: we can use eec_and_scl as eviction_effort_cap below because
// strict_capacity_limit=true is supposed to disable the limit on eviction
// effort, and a large value effectively does that.
if (eec_and_scl & kStrictCapacityLimitBit) {
if (eec_and_scl_.LoadRelaxed().Get<StrictCapacityLimit>()) {
Status s = ChargeUsageMaybeEvictStrict<Table>(
total_charge, capacity,
/*need_evict_for_occupancy=*/false, eec_and_scl, state);
total_charge,
/*need_evict_for_occupancy=*/false, state);
if (!s.ok()) {
if (allow_uncharged) {
proto.total_charge = 0;
@@ -426,8 +433,8 @@ typename Table::HandleImpl* BaseClockTable::CreateStandalone(
} else {
// Case strict_capacity_limit == false
bool success = ChargeUsageMaybeEvictNonStrict<Table>(
total_charge, capacity,
/*need_evict_for_occupancy=*/false, eec_and_scl, state);
total_charge,
/*need_evict_for_occupancy=*/false, state);
if (!success) {
// Force the issue
usage_.FetchAddRelaxed(total_charge);
@@ -439,8 +446,9 @@ typename Table::HandleImpl* BaseClockTable::CreateStandalone(
template <class Table>
Status BaseClockTable::ChargeUsageMaybeEvictStrict(
size_t total_charge, size_t capacity, bool need_evict_for_occupancy,
uint32_t eviction_effort_cap, typename Table::InsertState& state) {
size_t total_charge, bool need_evict_for_occupancy,
typename Table::InsertState& state) {
const size_t capacity = capacity_.LoadRelaxed();
if (total_charge > capacity) {
return Status::MemoryLimit(
"Cache entry too large for a single cache shard: " +
@@ -465,8 +473,7 @@ Status BaseClockTable::ChargeUsageMaybeEvictStrict(
}
if (request_evict_charge > 0) {
EvictionData data;
static_cast<Table*>(this)->Evict(request_evict_charge, state, &data,
eviction_effort_cap);
static_cast<Table*>(this)->Evict(request_evict_charge, state, &data);
occupancy_.FetchSub(data.freed_count);
if (LIKELY(data.freed_charge > need_evict_charge)) {
assert(data.freed_count > 0);
@@ -495,8 +502,8 @@ Status BaseClockTable::ChargeUsageMaybeEvictStrict(
template <class Table>
inline bool BaseClockTable::ChargeUsageMaybeEvictNonStrict(
size_t total_charge, size_t capacity, bool need_evict_for_occupancy,
uint32_t eviction_effort_cap, typename Table::InsertState& state) {
size_t total_charge, bool need_evict_for_occupancy,
typename Table::InsertState& state) {
// For simplicity, we consider that either the cache can accept the insert
// with no evictions, or we must evict enough to make (at least) enough
// space. It could lead to unnecessary failures or excessive evictions in
@@ -506,7 +513,8 @@ inline bool BaseClockTable::ChargeUsageMaybeEvictNonStrict(
// charge. Thus, we should evict some extra if it's not a signifcant
// portion of the shard capacity. This can have the side benefit of
// involving fewer threads in eviction.
size_t old_usage = usage_.LoadRelaxed();
const size_t old_usage = usage_.LoadRelaxed();
const size_t capacity = capacity_.LoadRelaxed();
size_t need_evict_charge;
// NOTE: if total_charge > old_usage, there isn't yet enough to evict
// `total_charge` amount. Even if we only try to evict `old_usage` amount,
@@ -532,8 +540,7 @@ inline bool BaseClockTable::ChargeUsageMaybeEvictNonStrict(
}
EvictionData data;
if (need_evict_charge > 0) {
static_cast<Table*>(this)->Evict(need_evict_charge, state, &data,
eviction_effort_cap);
static_cast<Table*>(this)->Evict(need_evict_charge, state, &data);
// Deal with potential occupancy deficit
if (UNLIKELY(need_evict_for_occupancy) && data.freed_count == 0) {
assert(data.freed_charge == 0);
@@ -569,8 +576,10 @@ void BaseClockTable::TrackAndReleaseEvictedEntry(ClockHandle* h) {
MarkEmpty(*h);
}
bool IsEvictionEffortExceeded(const BaseClockTable::EvictionData& data,
uint32_t eviction_effort_cap) {
bool BaseClockTable::IsEvictionEffortExceeded(
const BaseClockTable::EvictionData& data) const {
auto eviction_effort_cap =
eec_and_scl_.LoadRelaxed().GetEffectiveEvictionEffortCap();
// Basically checks whether the ratio of useful effort to wasted effort is
// too low, with a start-up allowance for wasted effort before any useful
// effort.
@@ -581,8 +590,7 @@ bool IsEvictionEffortExceeded(const BaseClockTable::EvictionData& data,
template <class Table>
Status BaseClockTable::Insert(const ClockHandleBasicData& proto,
typename Table::HandleImpl** handle,
Cache::Priority priority, size_t capacity,
uint32_t eec_and_scl) {
Cache::Priority priority) {
using HandleImpl = typename Table::HandleImpl;
Table& derived = static_cast<Table&>(*this);
@@ -603,9 +611,9 @@ Status BaseClockTable::Insert(const ClockHandleBasicData& proto,
// NOTE: we can use eec_and_scl as eviction_effort_cap below because
// strict_capacity_limit=true is supposed to disable the limit on eviction
// effort, and a large value effectively does that.
if (eec_and_scl & kStrictCapacityLimitBit) {
if (eec_and_scl_.LoadRelaxed().Get<StrictCapacityLimit>()) {
Status s = ChargeUsageMaybeEvictStrict<Table>(
total_charge, capacity, need_evict_for_occupancy, eec_and_scl, state);
total_charge, need_evict_for_occupancy, state);
if (!s.ok()) {
// Revert occupancy
occupancy_.FetchSubRelaxed(1);
@@ -614,7 +622,7 @@ Status BaseClockTable::Insert(const ClockHandleBasicData& proto,
} else {
// Case strict_capacity_limit == false
bool success = ChargeUsageMaybeEvictNonStrict<Table>(
total_charge, capacity, need_evict_for_occupancy, eec_and_scl, state);
total_charge, need_evict_for_occupancy, state);
if (!success) {
// Revert occupancy
occupancy_.FetchSubRelaxed(1);
@@ -718,11 +726,13 @@ void BaseClockTable::TEST_ReleaseNMinus1(ClockHandle* h, size_t n) {
#endif
FixedHyperClockTable::FixedHyperClockTable(
size_t capacity, CacheMetadataChargePolicy metadata_charge_policy,
size_t capacity, bool strict_capacity_limit,
CacheMetadataChargePolicy metadata_charge_policy,
MemoryAllocator* allocator,
const Cache::EvictionCallback* eviction_callback, const uint32_t* hash_seed,
const Opts& opts)
: BaseClockTable(metadata_charge_policy, allocator, eviction_callback,
: BaseClockTable(capacity, strict_capacity_limit, opts.eviction_effort_cap,
metadata_charge_policy, allocator, eviction_callback,
hash_seed),
length_bits_(CalcHashBits(capacity, opts.estimated_value_size,
metadata_charge_policy)),
@@ -1113,8 +1123,7 @@ inline void FixedHyperClockTable::ReclaimEntryUsage(size_t total_charge) {
}
inline void FixedHyperClockTable::Evict(size_t requested_charge, InsertState&,
EvictionData* data,
uint32_t eviction_effort_cap) {
EvictionData* data) {
// precondition
assert(requested_charge > 0);
@@ -1149,7 +1158,7 @@ inline void FixedHyperClockTable::Evict(size_t requested_charge, InsertState&,
if (old_clock_pointer >= max_clock_pointer) {
return;
}
if (IsEvictionEffortExceeded(*data, eviction_effort_cap)) {
if (IsEvictionEffortExceeded(*data)) {
eviction_effort_exceeded_count_.FetchAddRelaxed(1);
return;
}
@@ -1167,14 +1176,11 @@ ClockCacheShard<Table>::ClockCacheShard(
const Cache::EvictionCallback* eviction_callback, const uint32_t* hash_seed,
const typename Table::Opts& opts)
: CacheShardBase(metadata_charge_policy),
table_(capacity, metadata_charge_policy, allocator, eviction_callback,
hash_seed, opts),
capacity_(capacity),
eec_and_scl_(SanitizeEncodeEecAndScl(opts.eviction_effort_cap,
strict_capacity_limit)) {
table_(capacity, strict_capacity_limit, metadata_charge_policy, allocator,
eviction_callback, hash_seed, opts) {
// Initial charge metadata should not exceed capacity
assert(table_.GetUsage() <= capacity_.LoadRelaxed() ||
capacity_.LoadRelaxed() < sizeof(HandleImpl));
assert(table_.GetUsage() <= table_.GetCapacity() ||
table_.GetCapacity() < sizeof(HandleImpl));
}
template <class Table>
@@ -1240,18 +1246,14 @@ int FixedHyperClockTable::CalcHashBits(
template <class Table>
void ClockCacheShard<Table>::SetCapacity(size_t capacity) {
capacity_.StoreRelaxed(capacity);
table_.SetCapacity(capacity);
// next Insert will take care of any necessary evictions
}
template <class Table>
void ClockCacheShard<Table>::SetStrictCapacityLimit(
bool strict_capacity_limit) {
if (strict_capacity_limit) {
eec_and_scl_.FetchOrRelaxed(kStrictCapacityLimitBit);
} else {
eec_and_scl_.FetchAndRelaxed(~kStrictCapacityLimitBit);
}
table_.SetStrictCapacityLimit(strict_capacity_limit);
// next Insert will take care of any necessary evictions
}
@@ -1271,9 +1273,7 @@ Status ClockCacheShard<Table>::Insert(const Slice& key,
proto.value = value;
proto.helper = helper;
proto.total_charge = charge;
return table_.template Insert<Table>(proto, handle, priority,
capacity_.LoadRelaxed(),
eec_and_scl_.LoadRelaxed());
return table_.template Insert<Table>(proto, handle, priority);
}
template <class Table>
@@ -1288,9 +1288,7 @@ typename Table::HandleImpl* ClockCacheShard<Table>::CreateStandalone(
proto.value = obj;
proto.helper = helper;
proto.total_charge = charge;
return table_.template CreateStandalone<Table>(proto, capacity_.LoadRelaxed(),
eec_and_scl_.LoadRelaxed(),
allow_uncharged);
return table_.template CreateStandalone<Table>(proto, allow_uncharged);
}
template <class Table>
@@ -1359,7 +1357,7 @@ size_t ClockCacheShard<Table>::GetStandaloneUsage() const {
template <class Table>
size_t ClockCacheShard<Table>::GetCapacity() const {
return capacity_.LoadRelaxed();
return table_.GetCapacity();
}
template <class Table>
@@ -1727,10 +1725,13 @@ inline uint64_t UsedLengthToLengthInfo(size_t used_length) {
return length_info;
}
// Avoid potential initialization order race with port::kPageSize
constexpr size_t kPresumedPageSize = 4096;
inline size_t GetStartingLength(size_t capacity) {
if (capacity > port::kPageSize) {
if (capacity > kPresumedPageSize) {
// Start with one memory page
return port::kPageSize / sizeof(AutoHyperClockTable::HandleImpl);
return kPresumedPageSize / sizeof(AutoHyperClockTable::HandleImpl);
} else {
// Mostly to make unit tests happy
return 4;
@@ -1969,11 +1970,13 @@ class AutoHyperClockTable::ChainRewriteLock {
};
AutoHyperClockTable::AutoHyperClockTable(
size_t capacity, CacheMetadataChargePolicy metadata_charge_policy,
size_t capacity, bool strict_capacity_limit,
CacheMetadataChargePolicy metadata_charge_policy,
MemoryAllocator* allocator,
const Cache::EvictionCallback* eviction_callback, const uint32_t* hash_seed,
const Opts& opts)
: BaseClockTable(metadata_charge_policy, allocator, eviction_callback,
: BaseClockTable(capacity, strict_capacity_limit, opts.eviction_effort_cap,
metadata_charge_policy, allocator, eviction_callback,
hash_seed),
array_(MemMapping::AllocateLazyZeroed(
sizeof(HandleImpl) * CalcMaxUsableLength(capacity,
@@ -1985,6 +1988,11 @@ AutoHyperClockTable::AutoHyperClockTable(
grow_frontier_(GetTableSize()),
clock_pointer_mask_(
BottomNBits(UINT64_MAX, LengthInfoToMinShift(length_info_.Load()))) {
if (array_.Get() == nullptr) {
fprintf(stderr,
"Anonymous mmap for RocksDB HyperClockCache failed. Aborting.\n");
std::terminate();
}
if (metadata_charge_policy ==
CacheMetadataChargePolicy::kFullChargeCacheMetadata) {
// NOTE: ignoring page boundaries for simplicity
@@ -2052,15 +2060,21 @@ AutoHyperClockTable::~AutoHyperClockTable() {
HandleImpl::kUnusedMarker) {
used_end++;
}
#ifndef NDEBUG
for (size_t i = used_end; i < array_.Count(); i++) {
// This check can be extra expensive for a cache that is just created,
// maybe used for a small number of entries, as in a unit test, and then
// destroyed. Only do this in rare modes. REVISED: Don't scan the whole mmap,
// just a reasonable frontier past what we expect to have written.
#ifdef MUST_FREE_HEAP_ALLOCATIONS
for (size_t i = used_end; i < array_.Count() && i < used_end + 64U; i++) {
assert(array_[i].head_next_with_shift.LoadRelaxed() == 0);
assert(array_[i].chain_next_with_shift.LoadRelaxed() == 0);
assert(array_[i].meta.LoadRelaxed() == 0);
}
#endif // MUST_FREE_HEAP_ALLOCATIONS
#ifndef NDEBUG // Extra invariant checking
std::vector<bool> was_populated(used_end);
std::vector<bool> was_pointed_to(used_end);
#endif
#endif // !NDEBUG
for (size_t i = 0; i < used_end; i++) {
HandleImpl& h = array_[i];
switch (h.meta.LoadRelaxed() >> ClockHandle::kStateShift) {
@@ -2083,7 +2097,7 @@ AutoHyperClockTable::~AutoHyperClockTable() {
assert(!was_pointed_to[next]);
was_pointed_to[next] = true;
}
#endif
#endif // !NDEBUG
break;
// otherwise
default:
@@ -2097,7 +2111,7 @@ AutoHyperClockTable::~AutoHyperClockTable() {
assert(!was_pointed_to[next]);
was_pointed_to[next] = true;
}
#endif
#endif // !NDEBUG
}
#ifndef NDEBUG // Extra invariant checking
// This check is not perfect, but should detect most reasonable cases
@@ -2110,7 +2124,7 @@ AutoHyperClockTable::~AutoHyperClockTable() {
assert(!was_pointed_to[i]);
}
}
#endif
#endif // !NDEBUG
// Metadata charging only follows the published table size
assert(usage_.LoadRelaxed() == 0 ||
@@ -3468,8 +3482,7 @@ void AutoHyperClockTable::EraseUnRefEntries() {
}
void AutoHyperClockTable::Evict(size_t requested_charge, InsertState& state,
EvictionData* data,
uint32_t eviction_effort_cap) {
EvictionData* data) {
// precondition
assert(requested_charge > 0);
@@ -3561,7 +3574,7 @@ void AutoHyperClockTable::Evict(size_t requested_charge, InsertState& state,
return;
}
if (IsEvictionEffortExceeded(*data, eviction_effort_cap)) {
if (IsEvictionEffortExceeded(*data)) {
eviction_effort_exceeded_count_.FetchAddRelaxed(1);
return;
}
@@ -3579,7 +3592,7 @@ size_t AutoHyperClockTable::CalcMaxUsableLength(
size_t num_slots =
static_cast<size_t>(capacity / min_avg_slot_charge + 0.999999);
const size_t slots_per_page = port::kPageSize / sizeof(HandleImpl);
const size_t slots_per_page = kPresumedPageSize / sizeof(HandleImpl);
// Round up to page size
return ((num_slots + slots_per_page - 1) / slots_per_page) * slots_per_page;
+44 -36
View File
@@ -9,8 +9,6 @@
#pragma once
#include <array>
#include <atomic>
#include <climits>
#include <cstddef>
#include <cstdint>
@@ -19,14 +17,10 @@
#include "cache/cache_key.h"
#include "cache/sharded_cache.h"
#include "port/lang.h"
#include "port/malloc.h"
#include "port/mmap.h"
#include "port/port.h"
#include "rocksdb/cache.h"
#include "rocksdb/secondary_cache.h"
#include "util/atomic.h"
#include "util/autovector.h"
#include "util/bit_fields.h"
#include "util/math.h"
namespace ROCKSDB_NAMESPACE {
@@ -383,25 +377,20 @@ class BaseClockTable {
int eviction_effort_cap;
};
BaseClockTable(CacheMetadataChargePolicy metadata_charge_policy,
BaseClockTable(size_t capacity, bool strict_capacity_limit,
int eviction_effort_cap,
CacheMetadataChargePolicy metadata_charge_policy,
MemoryAllocator* allocator,
const Cache::EvictionCallback* eviction_callback,
const uint32_t* hash_seed)
: metadata_charge_policy_(metadata_charge_policy),
allocator_(allocator),
eviction_callback_(*eviction_callback),
hash_seed_(*hash_seed) {}
const uint32_t* hash_seed);
template <class Table>
typename Table::HandleImpl* CreateStandalone(ClockHandleBasicData& proto,
size_t capacity,
uint32_t eec_and_scl,
bool allow_uncharged);
template <class Table>
Status Insert(const ClockHandleBasicData& proto,
typename Table::HandleImpl** handle, Cache::Priority priority,
size_t capacity, uint32_t eec_and_scl);
typename Table::HandleImpl** handle, Cache::Priority priority);
void Ref(ClockHandle& handle);
@@ -411,6 +400,18 @@ class BaseClockTable {
size_t GetStandaloneUsage() const { return standalone_usage_.LoadRelaxed(); }
size_t GetCapacity() const { return capacity_.LoadRelaxed(); }
void SetCapacity(size_t capacity) { capacity_.StoreRelaxed(capacity); }
void SetStrictCapacityLimit(bool strict_capacity_limit) {
if (strict_capacity_limit) {
eec_and_scl_.ApplyRelaxed(StrictCapacityLimit::SetTransform());
} else {
eec_and_scl_.ApplyRelaxed(StrictCapacityLimit::ClearTransform());
}
}
uint32_t GetHashSeed() const { return hash_seed_; }
uint64_t GetYieldCount() const { return yield_count_.LoadRelaxed(); }
@@ -427,6 +428,7 @@ class BaseClockTable {
void TrackAndReleaseEvictedEntry(ClockHandle* h);
bool IsEvictionEffortExceeded(const BaseClockTable::EvictionData& data) const;
#ifndef NDEBUG
// Acquire N references
void TEST_RefN(ClockHandle& handle, size_t n);
@@ -448,9 +450,8 @@ class BaseClockTable {
// required, and the operation should fail if not possible.
// NOTE: Otherwise, occupancy_ is not managed in this function
template <class Table>
Status ChargeUsageMaybeEvictStrict(size_t total_charge, size_t capacity,
Status ChargeUsageMaybeEvictStrict(size_t total_charge,
bool need_evict_for_occupancy,
uint32_t eviction_effort_cap,
typename Table::InsertState& state);
// Helper for updating `usage_` for new entry with given `total_charge`
@@ -462,9 +463,8 @@ class BaseClockTable {
// true, indicating success.
// NOTE: occupancy_ is not managed in this function
template <class Table>
bool ChargeUsageMaybeEvictNonStrict(size_t total_charge, size_t capacity,
bool ChargeUsageMaybeEvictNonStrict(size_t total_charge,
bool need_evict_for_occupancy,
uint32_t eviction_effort_cap,
typename Table::InsertState& state);
protected: // data
@@ -497,6 +497,25 @@ class BaseClockTable {
// Part of usage by standalone entries (not in table)
AcqRelAtomic<size_t> standalone_usage_{};
// Maximum total charge of all elements stored in the table.
// (Relaxed: eventual consistency/update is OK)
RelaxedAtomic<size_t> capacity_;
// Encodes eviction_effort_cap (bottom 31 bits) and strict_capacity_limit
// (top bit). See HyperClockCacheOptions::eviction_effort_cap etc.
struct EecAndScl : public BitFields<uint32_t, EecAndScl> {
uint32_t GetEffectiveEvictionEffortCap() const {
// Because setting strict_capacity_limit is supposed to imply infinite
// cap on eviction effort, we can let the bit for strict_capacity_limit
// in the upper-most bit position to used as part of the effective cap.
return underlying;
}
};
using EvictionEffortCap = UnsignedBitField<EecAndScl, 31, NoPrevBitField>;
using StrictCapacityLimit = BoolBitField<EecAndScl, EvictionEffortCap>;
// (Relaxed: eventual consistency/update is OK)
RelaxedBitFieldsAtomic<EecAndScl> eec_and_scl_;
ALIGN_AS(CACHE_LINE_SIZE)
const CacheMetadataChargePolicy metadata_charge_policy_;
@@ -551,7 +570,7 @@ class FixedHyperClockTable : public BaseClockTable {
size_t estimated_value_size;
};
FixedHyperClockTable(size_t capacity,
FixedHyperClockTable(size_t capacity, bool strict_capacity_limit,
CacheMetadataChargePolicy metadata_charge_policy,
MemoryAllocator* allocator,
const Cache::EvictionCallback* eviction_callback,
@@ -573,8 +592,7 @@ class FixedHyperClockTable : public BaseClockTable {
// Runs the clock eviction algorithm trying to reclaim at least
// requested_charge. Returns how much is evicted, which could be less
// if it appears impossible to evict the requested amount without blocking.
void Evict(size_t requested_charge, InsertState& state, EvictionData* data,
uint32_t eviction_effort_cap);
void Evict(size_t requested_charge, InsertState& state, EvictionData* data);
HandleImpl* Lookup(const UniqueId64x2& hashed_key);
@@ -841,7 +859,7 @@ class AutoHyperClockTable : public BaseClockTable {
size_t min_avg_value_size;
};
AutoHyperClockTable(size_t capacity,
AutoHyperClockTable(size_t capacity, bool strict_capacity_limit,
CacheMetadataChargePolicy metadata_charge_policy,
MemoryAllocator* allocator,
const Cache::EvictionCallback* eviction_callback,
@@ -868,8 +886,7 @@ class AutoHyperClockTable : public BaseClockTable {
// Runs the clock eviction algorithm trying to reclaim at least
// requested_charge. Returns how much is evicted, which could be less
// if it appears impossible to evict the requested amount without blocking.
void Evict(size_t requested_charge, InsertState& state, EvictionData* data,
uint32_t eviction_effort_cap);
void Evict(size_t requested_charge, InsertState& state, EvictionData* data);
HandleImpl* Lookup(const UniqueId64x2& hashed_key);
@@ -1102,15 +1119,6 @@ class ALIGN_AS(CACHE_LINE_SIZE) ClockCacheShard final : public CacheShardBase {
private: // data
Table table_;
// Maximum total charge of all elements stored in the table.
// (Relaxed: eventual consistency/update is OK)
RelaxedAtomic<size_t> capacity_;
// Encodes eviction_effort_cap (bottom 31 bits) and strict_capacity_limit
// (top bit). See HyperClockCacheOptions::eviction_effort_cap etc.
// (Relaxed: eventual consistency/update is OK)
RelaxedAtomic<uint32_t> eec_and_scl_;
}; // class ClockCacheShard
template <class Table>
+180 -144
View File
@@ -16,6 +16,31 @@
#include "util/string_util.h"
namespace ROCKSDB_NAMESPACE {
namespace {
// Format of values in CompressedSecondaryCache:
// If enable_custom_split_merge:
// * A chain of CacheValueChunk representing the sequence of bytes for a tagged
// value. The overall length of the tagged value is determined by the chain
// of CacheValueChunks.
// If !enable_custom_split_merge:
// * A LengthPrefixedSlice (starts with varint64 size) of a tagged value.
//
// A tagged value has a 2-byte header before the "saved" or compressed block
// data:
// * 1 byte for "source" CacheTier indicating which tier is responsible for
// compression/decompression.
// * 1 byte for compression type which is generated/used by
// CompressedSecondaryCache iff source == CacheTier::kVolatileCompressedTier
// (original entry passed in was uncompressed). Otherwise, the compression
// type is preserved from the entry passed in.
constexpr uint32_t kTagSize = 2;
// Size of tag + varint size prefix when applicable
uint32_t GetHeaderSize(size_t data_size, bool enable_split_merge) {
return (enable_split_merge ? 0 : VarintLength(kTagSize + data_size)) +
kTagSize;
}
} // namespace
CompressedSecondaryCache::CompressedSecondaryCache(
const CompressedSecondaryCacheOptions& opts)
@@ -40,13 +65,9 @@ std::unique_ptr<SecondaryCacheResultHandle> CompressedSecondaryCache::Lookup(
Cache::CreateContext* create_context, bool /*wait*/, bool advise_erase,
Statistics* stats, bool& kept_in_sec_cache) {
assert(helper);
// This is a minor optimization. Its ok to skip it in TSAN in order to
// avoid a false positive.
#ifndef __SANITIZE_THREAD__
if (disable_cache_) {
if (disable_cache_.LoadRelaxed()) {
return nullptr;
}
#endif
std::unique_ptr<SecondaryCacheResultHandle> handle;
kept_in_sec_cache = false;
@@ -62,75 +83,58 @@ std::unique_ptr<SecondaryCacheResultHandle> CompressedSecondaryCache::Lookup(
return nullptr;
}
CacheAllocationPtr* ptr{nullptr};
CacheAllocationPtr merged_value;
size_t handle_value_charge{0};
const char* data_ptr = nullptr;
CacheTier source = CacheTier::kVolatileCompressedTier;
CompressionType type = cache_options_.compression_type;
std::string merged_value;
Slice tagged_data;
if (cache_options_.enable_custom_split_merge) {
CacheValueChunk* value_chunk_ptr =
reinterpret_cast<CacheValueChunk*>(handle_value);
merged_value = MergeChunksIntoValue(value_chunk_ptr, handle_value_charge);
ptr = &merged_value;
data_ptr = ptr->get();
static_cast<CacheValueChunk*>(handle_value);
merged_value = MergeChunksIntoValue(value_chunk_ptr);
tagged_data = Slice(merged_value);
} else {
uint32_t type_32 = static_cast<uint32_t>(type);
uint32_t source_32 = static_cast<uint32_t>(source);
ptr = reinterpret_cast<CacheAllocationPtr*>(handle_value);
handle_value_charge = cache_->GetCharge(lru_handle);
data_ptr = ptr->get();
const char* limit = ptr->get() + handle_value_charge;
data_ptr =
GetVarint32Ptr(data_ptr, limit, static_cast<uint32_t*>(&type_32));
type = static_cast<CompressionType>(type_32);
data_ptr =
GetVarint32Ptr(data_ptr, limit, static_cast<uint32_t*>(&source_32));
source = static_cast<CacheTier>(source_32);
uint64_t data_size = 0;
data_ptr =
GetVarint64Ptr(data_ptr, limit, static_cast<uint64_t*>(&data_size));
assert(handle_value_charge > data_size);
handle_value_charge = data_size;
tagged_data = GetLengthPrefixedSlice(static_cast<char*>(handle_value));
}
MemoryAllocator* allocator = cache_options_.memory_allocator.get();
Status s;
Cache::ObjectPtr value{nullptr};
size_t charge{0};
auto source = lossless_cast<CacheTier>(tagged_data[0]);
auto type = lossless_cast<CompressionType>(tagged_data[1]);
std::unique_ptr<char[]> uncompressed;
Slice saved(tagged_data.data() + kTagSize, tagged_data.size() - kTagSize);
if (source == CacheTier::kVolatileCompressedTier) {
if (cache_options_.compression_type == kNoCompression ||
cache_options_.do_not_compress_roles.Contains(helper->role)) {
s = helper->create_cb(Slice(data_ptr, handle_value_charge),
kNoCompression, CacheTier::kVolatileTier,
create_context, allocator, &value, &charge);
} else {
// TODO: can we work some magic with create_cb, which might be based on
// custom compression, to decompress without an extra copy in create_cb?
if (type != kNoCompression) {
// TODO: can we do something to avoid yet another allocation?
Decompressor::Args args;
args.compressed_data = Slice(data_ptr, handle_value_charge);
args.compression_type = cache_options_.compression_type;
s = decompressor_->ExtractUncompressedSize(args);
assert(s.ok());
args.compressed_data = saved;
args.compression_type = type;
Status s = decompressor_->ExtractUncompressedSize(args);
assert(s.ok()); // in-memory data
if (s.ok()) {
auto uncompressed = std::make_unique<char[]>(args.uncompressed_size);
uncompressed = std::make_unique<char[]>(args.uncompressed_size);
s = decompressor_->DecompressBlock(args, uncompressed.get());
assert(s.ok());
if (s.ok()) {
s = helper->create_cb(
Slice(uncompressed.get(), args.uncompressed_size), kNoCompression,
CacheTier::kVolatileTier, create_context, allocator, &value,
&charge);
}
assert(s.ok()); // in-memory data
}
if (!s.ok()) {
cache_->Release(lru_handle, /*erase_if_last_ref=*/true);
return nullptr;
}
saved = Slice(uncompressed.get(), args.uncompressed_size);
type = kNoCompression;
// Free temporary compressed data as early as we can. This could matter
// for unusually large blocks because we also have
// * Another compressed copy above (from lru_cache).
// * The uncompressed copy in `uncompressed`.
// * Another uncompressed copy in `result_value` below.
// Let's try to max out at 3 copies instead of 4.
merged_value = std::string();
}
} else {
// The item was not compressed by us. Let the helper create_cb
// uncompress it
s = helper->create_cb(Slice(data_ptr, handle_value_charge), type, source,
create_context, allocator, &value, &charge);
// Reduced as if it came from primary cache
source = CacheTier::kVolatileTier;
}
Cache::ObjectPtr result_value = nullptr;
size_t result_charge = 0;
Status s = helper->create_cb(saved, type, source, create_context,
cache_options_.memory_allocator.get(),
&result_value, &result_charge);
if (!s.ok()) {
cache_->Release(lru_handle, /*erase_if_last_ref=*/true);
return nullptr;
@@ -148,7 +152,8 @@ std::unique_ptr<SecondaryCacheResultHandle> CompressedSecondaryCache::Lookup(
kept_in_sec_cache = true;
cache_->Release(lru_handle, /*erase_if_last_ref=*/false);
}
handle.reset(new CompressedSecondaryCacheResultHandle(value, charge));
handle.reset(
new CompressedSecondaryCacheResultHandle(result_value, result_charge));
RecordTick(stats, COMPRESSED_SECONDARY_CACHE_HITS);
return handle;
}
@@ -171,85 +176,111 @@ bool CompressedSecondaryCache::MaybeInsertDummy(const Slice& key) {
Status CompressedSecondaryCache::InsertInternal(
const Slice& key, Cache::ObjectPtr value,
const Cache::CacheItemHelper* helper, CompressionType type,
const Cache::CacheItemHelper* helper, CompressionType from_type,
CacheTier source) {
if (source != CacheTier::kVolatileCompressedTier &&
cache_options_.enable_custom_split_merge) {
// We don't support custom split/merge for the tiered case
return Status::OK();
}
bool enable_split_merge = cache_options_.enable_custom_split_merge;
const Cache::CacheItemHelper* internal_helper = GetHelper(enable_split_merge);
auto internal_helper = GetHelper(cache_options_.enable_custom_split_merge);
char header[20];
char* payload = header;
payload = EncodeVarint32(payload, static_cast<uint32_t>(type));
payload = EncodeVarint32(payload, static_cast<uint32_t>(source));
size_t data_size = (*helper->size_cb)(value);
char* data_size_ptr = payload;
payload = EncodeVarint64(payload, data_size);
// TODO: variant of size_cb that also returns a pointer to the data if
// already available. Saves an allocation if we keep the compressed version.
const size_t data_size_original = (*helper->size_cb)(value);
size_t header_size = payload - header;
size_t total_size = data_size + header_size;
CacheAllocationPtr ptr =
AllocateBlock(total_size, cache_options_.memory_allocator.get());
char* data_ptr = ptr.get() + header_size;
// Allocate enough memory for header/tag + original data because (a) we might
// not be attempting compression at all, and (b) we might keep the original if
// compression is insufficient. But we don't need the length prefix with
// enable_split_merge. TODO: be smarter with CacheValueChunk to save an
// allocation in the enable_split_merge case.
size_t header_size = GetHeaderSize(data_size_original, enable_split_merge);
CacheAllocationPtr allocation = AllocateBlock(
header_size + data_size_original, cache_options_.memory_allocator.get());
char* data_ptr = allocation.get() + header_size;
Slice tagged_data(data_ptr - kTagSize, data_size_original + kTagSize);
assert(tagged_data.data() >= allocation.get());
Status s = (*helper->saveto_cb)(value, 0, data_size, data_ptr);
Status s = (*helper->saveto_cb)(value, 0, data_size_original, data_ptr);
if (!s.ok()) {
return s;
}
Slice val(data_ptr, data_size);
std::string compressed_val;
if (cache_options_.compression_type != kNoCompression &&
type == kNoCompression &&
std::unique_ptr<char[]> tagged_compressed_data;
CompressionType to_type = kNoCompression;
if (compressor_ && from_type == kNoCompression &&
!cache_options_.do_not_compress_roles.Contains(helper->role)) {
PERF_COUNTER_ADD(compressed_sec_cache_uncompressed_bytes, data_size);
assert(source == CacheTier::kVolatileCompressedTier);
CompressionType to_type = kNoCompression;
s = compressor_->CompressBlock(val, &compressed_val, &to_type,
// TODO: consider malloc sizes for max acceptable compressed size
// Or maybe max_compressed_bytes_per_kb
size_t data_size_compressed = data_size_original - 1;
tagged_compressed_data =
std::make_unique<char[]>(data_size_compressed + kTagSize);
s = compressor_->CompressBlock(Slice(data_ptr, data_size_original),
tagged_compressed_data.get() + kTagSize,
&data_size_compressed, &to_type,
nullptr /*working_area*/);
if (!s.ok()) {
return s;
}
// TODO: allow values not compressed when there's no size savings?
assert(to_type == cache_options_.compression_type);
if (to_type != cache_options_.compression_type) {
return Status::Corruption("Failed to compress value.");
}
val = Slice(compressed_val);
data_size = compressed_val.size();
payload = EncodeVarint64(data_size_ptr, data_size);
header_size = payload - header;
total_size = header_size + data_size;
PERF_COUNTER_ADD(compressed_sec_cache_compressed_bytes, data_size);
if (!cache_options_.enable_custom_split_merge) {
ptr = AllocateBlock(total_size, cache_options_.memory_allocator.get());
data_ptr = ptr.get() + header_size;
memcpy(data_ptr, compressed_val.data(), data_size);
PERF_COUNTER_ADD(compressed_sec_cache_uncompressed_bytes,
data_size_original);
if (to_type == kNoCompression) {
// Compression rejected or otherwise aborted/failed
to_type = kNoCompression;
tagged_compressed_data.reset();
// TODO: consider separate counters for rejected compressions
PERF_COUNTER_ADD(compressed_sec_cache_compressed_bytes,
data_size_original);
} else {
PERF_COUNTER_ADD(compressed_sec_cache_compressed_bytes,
data_size_compressed);
if (enable_split_merge) {
// Only need tagged_data for copying into CacheValueChunks.
tagged_data = Slice(tagged_compressed_data.get(),
data_size_compressed + kTagSize);
allocation.reset();
} else {
// Replace allocation with compressed version, copied from string
header_size = GetHeaderSize(data_size_compressed, enable_split_merge);
allocation = AllocateBlock(header_size + data_size_compressed,
cache_options_.memory_allocator.get());
data_ptr = allocation.get() + header_size;
// Ignore unpopulated tag on tagged_compressed_data; will only be
// populated on the new allocation.
std::memcpy(data_ptr, tagged_compressed_data.get() + kTagSize,
data_size_compressed);
tagged_data =
Slice(data_ptr - kTagSize, data_size_compressed + kTagSize);
assert(tagged_data.data() >= allocation.get());
}
}
}
PERF_COUNTER_ADD(compressed_sec_cache_insert_real_count, 1);
if (cache_options_.enable_custom_split_merge) {
// Save the tag fields
const_cast<char*>(tagged_data.data())[0] = lossless_cast<char>(source);
const_cast<char*>(tagged_data.data())[1] = lossless_cast<char>(
source == CacheTier::kVolatileCompressedTier ? to_type : from_type);
if (enable_split_merge) {
size_t split_charge{0};
CacheValueChunk* value_chunks_head = SplitValueIntoChunks(
val, cache_options_.compression_type, split_charge);
return cache_->Insert(key, value_chunks_head, internal_helper,
split_charge);
CacheValueChunk* value_chunks_head =
SplitValueIntoChunks(tagged_data, split_charge);
s = cache_->Insert(key, value_chunks_head, internal_helper, split_charge);
assert(s.ok()); // LRUCache::Insert() with handle==nullptr always OK
} else {
// Save the size prefix
char* ptr = allocation.get();
ptr = EncodeVarint64(ptr, tagged_data.size());
assert(ptr == tagged_data.data());
#ifdef ROCKSDB_MALLOC_USABLE_SIZE
size_t charge = malloc_usable_size(ptr.get());
size_t charge = malloc_usable_size(allocation.get());
#else
size_t charge = total_size;
size_t charge = tagged_data.size();
#endif
std::memcpy(ptr.get(), header, header_size);
CacheAllocationPtr* buf = new CacheAllocationPtr(std::move(ptr));
charge += sizeof(CacheAllocationPtr);
return cache_->Insert(key, buf, internal_helper, charge);
s = cache_->Insert(key, allocation.release(), internal_helper, charge);
assert(s.ok()); // LRUCache::Insert() with handle==nullptr always OK
}
return Status::OK();
}
Status CompressedSecondaryCache::Insert(const Slice& key,
@@ -271,7 +302,17 @@ Status CompressedSecondaryCache::Insert(const Slice& key,
Status CompressedSecondaryCache::InsertSaved(
const Slice& key, const Slice& saved, CompressionType type = kNoCompression,
CacheTier source = CacheTier::kVolatileTier) {
if (source == CacheTier::kVolatileCompressedTier) {
// Unexpected, would violate InsertInternal preconditions
assert(source != CacheTier::kVolatileCompressedTier);
return Status::OK();
}
if (type == kNoCompression) {
// Not currently supported (why?)
return Status::OK();
}
if (cache_options_.enable_custom_split_merge) {
// We don't support custom split/merge for the tiered case (why?)
return Status::OK();
}
@@ -291,7 +332,7 @@ Status CompressedSecondaryCache::SetCapacity(size_t capacity) {
MutexLock l(&capacity_mutex_);
cache_options_.capacity = capacity;
cache_->SetCapacity(capacity);
disable_cache_ = capacity == 0;
disable_cache_.StoreRelaxed(capacity == 0);
return Status::OK();
}
@@ -321,9 +362,14 @@ std::string CompressedSecondaryCache::GetPrintableOptions() const {
return ret;
}
// FIXME: this could use a lot of attention, including:
// * Use allocator
// * We shouldn't be worse than non-split; be more pro-actively aware of
// internal fragmentation
// * Consider a unified object/chunk structure that may or may not split
// * Optimize size overhead of chunks
CompressedSecondaryCache::CacheValueChunk*
CompressedSecondaryCache::SplitValueIntoChunks(const Slice& value,
CompressionType compression_type,
size_t& charge) {
assert(!value.empty());
const char* src_ptr = value.data();
@@ -344,15 +390,14 @@ CompressedSecondaryCache::SplitValueIntoChunks(const Slice& value,
// size, or there is no compression.
if (upper == malloc_bin_sizes_.begin() ||
upper == malloc_bin_sizes_.end() ||
*upper - predicted_chunk_size < malloc_bin_sizes_.front() ||
compression_type == kNoCompression) {
*upper - predicted_chunk_size < malloc_bin_sizes_.front()) {
tmp_size = predicted_chunk_size;
} else {
tmp_size = *(--upper);
}
CacheValueChunk* new_chunk =
reinterpret_cast<CacheValueChunk*>(new char[tmp_size]);
static_cast<CacheValueChunk*>(static_cast<void*>(new char[tmp_size]));
current_chunk->next = new_chunk;
current_chunk = current_chunk->next;
actual_chunk_size = tmp_size - sizeof(CacheValueChunk) + 1;
@@ -367,28 +412,24 @@ CompressedSecondaryCache::SplitValueIntoChunks(const Slice& value,
return dummy_head.next;
}
CacheAllocationPtr CompressedSecondaryCache::MergeChunksIntoValue(
const void* chunks_head, size_t& charge) {
const CacheValueChunk* head =
reinterpret_cast<const CacheValueChunk*>(chunks_head);
std::string CompressedSecondaryCache::MergeChunksIntoValue(
const CacheValueChunk* head) {
const CacheValueChunk* current_chunk = head;
charge = 0;
size_t total_size = 0;
while (current_chunk != nullptr) {
charge += current_chunk->size;
total_size += current_chunk->size;
current_chunk = current_chunk->next;
}
CacheAllocationPtr ptr =
AllocateBlock(charge, cache_options_.memory_allocator.get());
std::string result;
result.reserve(total_size);
current_chunk = head;
size_t pos{0};
while (current_chunk != nullptr) {
memcpy(ptr.get() + pos, current_chunk->data, current_chunk->size);
pos += current_chunk->size;
result.append(current_chunk->data, current_chunk->size);
current_chunk = current_chunk->next;
}
return ptr;
assert(result.size() == total_size);
return result;
}
const Cache::CacheItemHelper* CompressedSecondaryCache::GetHelper(
@@ -402,16 +443,16 @@ const Cache::CacheItemHelper* CompressedSecondaryCache::GetHelper(
CacheValueChunk* tmp_chunk = chunks_head;
chunks_head = chunks_head->next;
tmp_chunk->Free();
obj = nullptr;
}
}};
return &kHelper;
} else {
static const Cache::CacheItemHelper kHelper{
CacheEntryRole::kMisc,
[](Cache::ObjectPtr obj, MemoryAllocator* /*alloc*/) {
delete static_cast<CacheAllocationPtr*>(obj);
obj = nullptr;
[](Cache::ObjectPtr obj, MemoryAllocator* alloc) {
if (obj != nullptr) {
CacheAllocationDeleter{alloc}(static_cast<char*>(obj));
}
}};
return &kHelper;
}
@@ -422,12 +463,7 @@ size_t CompressedSecondaryCache::TEST_GetCharge(const Slice& key) {
if (lru_handle == nullptr) {
return 0;
}
size_t charge = cache_->GetCharge(lru_handle);
if (cache_->Value(lru_handle) != nullptr &&
!cache_options_.enable_custom_split_merge) {
charge -= 10;
}
cache_->Release(lru_handle, /*erase_if_last_ref=*/false);
return charge;
}
+5 -11
View File
@@ -10,13 +10,12 @@
#include <memory>
#include "cache/cache_reservation_manager.h"
#include "cache/lru_cache.h"
#include "memory/memory_allocator_impl.h"
#include "rocksdb/advanced_compression.h"
#include "rocksdb/secondary_cache.h"
#include "rocksdb/slice.h"
#include "rocksdb/status.h"
#include "util/compression.h"
#include "util/mutexlock.h"
#include "util/atomic.h"
namespace ROCKSDB_NAMESPACE {
@@ -124,14 +123,9 @@ class CompressedSecondaryCache : public SecondaryCache {
// Split value into chunks to better fit into jemalloc bins. The chunks
// are stored in CacheValueChunk and extra charge is needed for each chunk,
// so the cache charge is recalculated here.
CacheValueChunk* SplitValueIntoChunks(const Slice& value,
CompressionType compression_type,
size_t& charge);
CacheValueChunk* SplitValueIntoChunks(const Slice& value, size_t& charge);
// After merging chunks, the extra charge for each chunk is removed, so
// the charge is recalculated.
CacheAllocationPtr MergeChunksIntoValue(const void* chunks_head,
size_t& charge);
std::string MergeChunksIntoValue(const CacheValueChunk* head);
bool MaybeInsertDummy(const Slice& key);
@@ -149,7 +143,7 @@ class CompressedSecondaryCache : public SecondaryCache {
std::shared_ptr<Decompressor> decompressor_;
mutable port::Mutex capacity_mutex_;
std::shared_ptr<ConcurrentCacheReservationManager> cache_res_mgr_;
bool disable_cache_;
RelaxedAtomic<bool> disable_cache_;
};
} // namespace ROCKSDB_NAMESPACE
+116 -47
View File
@@ -24,6 +24,14 @@ namespace ROCKSDB_NAMESPACE {
using secondary_cache_test_util::GetTestingCacheTypes;
using secondary_cache_test_util::WithCacheType;
// Read and reset a statistic
template <typename T>
T Pop(T& var) {
T ret = var;
var = T();
return ret;
}
// 16 bytes for HCC compatibility
const std::string key0 = "____ ____key0";
const std::string key1 = "____ ____key1";
@@ -51,7 +59,7 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
Random rnd(301);
// Insert and Lookup the item k1 for the first time.
std::string str1(rnd.RandomString(1000));
std::string str1 = test::CompressibleString(&rnd, 0.5, 1000);
TestItem item1(str1.data(), str1.length());
// A dummy handle is inserted if the item is inserted for the first time.
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper(), false));
@@ -68,7 +76,14 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper(), false));
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 1);
ASSERT_GT(comp_sec_cache->TEST_GetCharge(key1), 1000);
if (sec_cache_is_compressed) {
ASSERT_GT(comp_sec_cache->TEST_GetCharge(key1), str1.length() / 4);
ASSERT_LT(comp_sec_cache->TEST_GetCharge(key1), str1.length() * 3 / 4);
} else {
ASSERT_GE(comp_sec_cache->TEST_GetCharge(key1), str1.length());
// NOTE: split-merge is worse (1048 vs. 1024)
ASSERT_LE(comp_sec_cache->TEST_GetCharge(key1), 1048U);
}
std::unique_ptr<SecondaryCacheResultHandle> handle1_2 =
sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/true,
@@ -76,10 +91,13 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
ASSERT_NE(handle1_2, nullptr);
ASSERT_FALSE(kept_in_sec_cache);
if (sec_cache_is_compressed) {
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes,
1000);
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes,
1007);
ASSERT_EQ(
Pop(get_perf_context()->compressed_sec_cache_uncompressed_bytes),
str1.length());
ASSERT_LT(get_perf_context()->compressed_sec_cache_compressed_bytes,
str1.length() * 3 / 4);
ASSERT_GT(Pop(get_perf_context()->compressed_sec_cache_compressed_bytes),
str1.length() / 4);
} else {
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
@@ -97,7 +115,7 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
ASSERT_EQ(handle1_3, nullptr);
// Insert and Lookup the item k2.
std::string str2(rnd.RandomString(1000));
std::string str2 = test::CompressibleString(&rnd, 0.5, 1017);
TestItem item2(str2.data(), str2.length());
ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper(), false));
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 2);
@@ -109,10 +127,13 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper(), false));
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 2);
if (sec_cache_is_compressed) {
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes,
2000);
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes,
2014);
ASSERT_EQ(
Pop(get_perf_context()->compressed_sec_cache_uncompressed_bytes),
str2.length());
ASSERT_LT(get_perf_context()->compressed_sec_cache_compressed_bytes,
str2.length() * 3 / 4);
ASSERT_GT(Pop(get_perf_context()->compressed_sec_cache_compressed_bytes),
str2.length() / 4);
} else {
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
@@ -126,9 +147,48 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
ASSERT_NE(val2, nullptr);
ASSERT_EQ(memcmp(val2->Buf(), item2.Buf(), item2.Size()), 0);
// Release handles
std::vector<SecondaryCacheResultHandle*> handles = {handle1_2.get(),
handle2_2.get()};
sec_cache->WaitAll(handles);
handle1_2.reset();
handle2_2.reset();
// Insert and Lookup a non-compressible item k3.
std::string str3 = rnd.RandomBinaryString(480);
TestItem item3(str3.data(), str3.length());
ASSERT_OK(sec_cache->Insert(key3, &item3, GetHelper(), false));
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 3);
std::unique_ptr<SecondaryCacheResultHandle> handle3_1 =
sec_cache->Lookup(key3, GetHelper(), this, true, /*advise_erase=*/false,
/*stats=*/nullptr, kept_in_sec_cache);
ASSERT_EQ(handle3_1, nullptr);
ASSERT_OK(sec_cache->Insert(key3, &item3, GetHelper(), false));
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 3);
if (sec_cache_is_compressed) {
// TODO: consider a compression rejected stat?
ASSERT_EQ(
Pop(get_perf_context()->compressed_sec_cache_uncompressed_bytes),
str3.length());
ASSERT_EQ(Pop(get_perf_context()->compressed_sec_cache_compressed_bytes),
str3.length());
} else {
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
}
std::unique_ptr<SecondaryCacheResultHandle> handle3_2 =
sec_cache->Lookup(key3, GetHelper(), this, true, /*advise_erase=*/false,
/*stats=*/nullptr, kept_in_sec_cache);
ASSERT_NE(handle3_2, nullptr);
std::unique_ptr<TestItem> val3 =
std::unique_ptr<TestItem>(static_cast<TestItem*>(handle3_2->Value()));
ASSERT_NE(val3, nullptr);
ASSERT_EQ(memcmp(val3->Buf(), item3.Buf(), item3.Size()), 0);
EXPECT_GE(comp_sec_cache->TEST_GetCharge(key3), str3.length());
EXPECT_LE(comp_sec_cache->TEST_GetCharge(key3), 512);
sec_cache.reset();
}
@@ -178,8 +238,9 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
secondary_cache_opts.compression_type = CompressionType::kNoCompression;
}
secondary_cache_opts.capacity = 1100;
secondary_cache_opts.capacity = 1400;
secondary_cache_opts.num_shard_bits = 0;
secondary_cache_opts.strict_capacity_limit = true;
std::shared_ptr<SecondaryCache> sec_cache =
NewCompressedSecondaryCache(secondary_cache_opts);
@@ -193,7 +254,7 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper(), false));
// Insert and Lookup the second item.
std::string str2(rnd.RandomString(200));
std::string str2(rnd.RandomString(500));
TestItem item2(str2.data(), str2.length());
// Insert a dummy handle, k1 is not evicted.
ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper(), false));
@@ -201,16 +262,23 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
std::unique_ptr<SecondaryCacheResultHandle> handle1 =
sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/false,
/*stats=*/nullptr, kept_in_sec_cache);
ASSERT_EQ(handle1, nullptr);
ASSERT_NE(handle1, nullptr);
std::unique_ptr<TestItem> val1{static_cast<TestItem*>(handle1->Value())};
ASSERT_NE(val1, nullptr);
ASSERT_EQ(val1->ToString(), str1);
handle1.reset();
// Insert k2 and k1 is evicted.
ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper(), false));
handle1 =
sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/false,
/*stats=*/nullptr, kept_in_sec_cache);
ASSERT_EQ(handle1, nullptr);
std::unique_ptr<SecondaryCacheResultHandle> handle2 =
sec_cache->Lookup(key2, GetHelper(), this, true, /*advise_erase=*/false,
/*stats=*/nullptr, kept_in_sec_cache);
ASSERT_NE(handle2, nullptr);
std::unique_ptr<TestItem> val2 =
std::unique_ptr<TestItem>(static_cast<TestItem*>(handle2->Value()));
std::unique_ptr<TestItem> val2{static_cast<TestItem*>(handle2->Value())};
ASSERT_NE(val2, nullptr);
ASSERT_EQ(memcmp(val2->Buf(), item2.Buf(), item2.Size()), 0);
@@ -232,7 +300,7 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
// Save Fails.
std::string str3 = rnd.RandomString(10);
TestItem item3(str3.data(), str3.length());
// The Status is OK because a dummy handle is inserted.
// The first Status is OK because a dummy handle is inserted.
ASSERT_OK(sec_cache->Insert(key3, &item3, GetHelperFail(), false));
ASSERT_NOK(sec_cache->Insert(key3, &item3, GetHelperFail(), false));
@@ -265,11 +333,11 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
get_perf_context()->Reset();
Random rnd(301);
std::string str1 = rnd.RandomString(1001);
std::string str1 = test::CompressibleString(&rnd, 0.5, 1001);
auto item1_1 = new TestItem(str1.data(), str1.length());
ASSERT_OK(cache->Insert(key1, item1_1, GetHelper(), str1.length()));
std::string str2 = rnd.RandomString(1012);
std::string str2 = test::CompressibleString(&rnd, 0.5, 1012);
auto item2_1 = new TestItem(str2.data(), str2.length());
// After this Insert, primary cache contains k2 and secondary cache contains
// k1's dummy item.
@@ -278,7 +346,7 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
std::string str3 = rnd.RandomString(1024);
std::string str3 = test::CompressibleString(&rnd, 0.5, 1024);
auto item3_1 = new TestItem(str3.data(), str3.length());
// After this Insert, primary cache contains k3 and secondary cache contains
// k1's dummy item and k2's dummy item.
@@ -297,10 +365,13 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
ASSERT_OK(cache->Insert(key2, item2_2, GetHelper(), str2.length()));
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 1);
if (sec_cache_is_compressed) {
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes,
ASSERT_EQ(
Pop(get_perf_context()->compressed_sec_cache_uncompressed_bytes),
str1.length());
ASSERT_LT(get_perf_context()->compressed_sec_cache_compressed_bytes,
str1.length());
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes,
1008);
ASSERT_GT(Pop(get_perf_context()->compressed_sec_cache_compressed_bytes),
str1.length() / 10);
} else {
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
@@ -312,10 +383,13 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
ASSERT_OK(cache->Insert(key3, item3_2, GetHelper(), str3.length()));
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 2);
if (sec_cache_is_compressed) {
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes,
str1.length() + str2.length());
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes,
2027);
ASSERT_EQ(
Pop(get_perf_context()->compressed_sec_cache_uncompressed_bytes),
str2.length());
ASSERT_LT(get_perf_context()->compressed_sec_cache_compressed_bytes,
str2.length());
ASSERT_GT(Pop(get_perf_context()->compressed_sec_cache_compressed_bytes),
str2.length() / 10);
} else {
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
@@ -641,8 +715,7 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
size_t str_size{8500};
std::string str = rnd.RandomString(static_cast<int>(str_size));
size_t charge{0};
CacheValueChunk* chunks_head =
sec_cache->SplitValueIntoChunks(str, kLZ4Compression, charge);
CacheValueChunk* chunks_head = sec_cache->SplitValueIntoChunks(str, charge);
ASSERT_EQ(charge, str_size + 3 * (sizeof(CacheValueChunk) - 1));
CacheValueChunk* current_chunk = chunks_head;
@@ -688,12 +761,9 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
std::unique_ptr<CompressedSecondaryCache> sec_cache =
std::make_unique<CompressedSecondaryCache>(
CompressedSecondaryCacheOptions(1000, 0, true, 0.5, 0.0));
size_t charge{0};
CacheAllocationPtr value =
sec_cache->MergeChunksIntoValue(chunks_head, charge);
ASSERT_EQ(charge, size1 + size2 + size3);
std::string value_str{value.get(), charge};
ASSERT_EQ(strcmp(value_str.data(), str.data()), 0);
std::string value_str = sec_cache->MergeChunksIntoValue(chunks_head);
ASSERT_EQ(value_str.size(), size1 + size2 + size3);
ASSERT_EQ(value_str, str);
while (chunks_head != nullptr) {
CacheValueChunk* tmp_chunk = chunks_head;
@@ -725,15 +795,12 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
size_t str_size{8500};
std::string str = rnd.RandomString(static_cast<int>(str_size));
size_t charge{0};
CacheValueChunk* chunks_head =
sec_cache->SplitValueIntoChunks(str, kLZ4Compression, charge);
CacheValueChunk* chunks_head = sec_cache->SplitValueIntoChunks(str, charge);
ASSERT_EQ(charge, str_size + 3 * (sizeof(CacheValueChunk) - 1));
CacheAllocationPtr value =
sec_cache->MergeChunksIntoValue(chunks_head, charge);
ASSERT_EQ(charge, str_size);
std::string value_str{value.get(), charge};
ASSERT_EQ(strcmp(value_str.data(), str.data()), 0);
std::string value_str = sec_cache->MergeChunksIntoValue(chunks_head);
ASSERT_EQ(value_str.size(), str_size);
ASSERT_EQ(value_str, str);
sec_cache->GetHelper(true)->del_cb(chunks_head, /*alloc*/ nullptr);
}
@@ -896,8 +963,8 @@ TEST_P(CompressedSecondaryCacheTestWithCompressionParam, EntryRoles) {
std::shared_ptr<SecondaryCache> sec_cache = NewCompressedSecondaryCache(opts);
// Fixed seed to ensure consistent compressibility (doesn't compress)
std::string junk(Random(301).RandomString(1000));
Random rnd(301);
std::string junk = test::CompressibleString(&rnd, 0.5, 1000);
for (uint32_t i = 0; i < kNumCacheEntryRoles; ++i) {
CacheEntryRole role = static_cast<CacheEntryRole>(i);
@@ -930,9 +997,11 @@ TEST_P(CompressedSecondaryCacheTestWithCompressionParam, EntryRoles) {
sec_cache_is_compressed_ && !do_not_compress.Contains(role);
if (compressed) {
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes,
1000);
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes,
1007);
junk.length());
ASSERT_LT(get_perf_context()->compressed_sec_cache_compressed_bytes,
junk.length() * 3 / 4);
ASSERT_GT(get_perf_context()->compressed_sec_cache_compressed_bytes,
junk.length() / 4);
} else {
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
+1 -1
View File
@@ -1405,9 +1405,9 @@ TEST_P(BasicSecondaryCacheTest, SaveFailTest) {
TestItem* item1 = new TestItem(str1.data(), str1.length());
ASSERT_OK(cache->Insert(k1.AsSlice(), item1, GetHelperFail(), str1.length()));
std::string str2 = rnd.RandomString(1020);
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
TestItem* item2 = new TestItem(str2.data(), str2.length());
// k1 should be demoted to NVM
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_OK(cache->Insert(k2.AsSlice(), item2, GetHelperFail(), str2.length()));
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
+7 -10
View File
@@ -33,7 +33,7 @@ const char* kTieredCacheName = "TieredCache";
// proportionally across the primary/secondary caches.
//
// The primary block cache is initially sized to the sum of the primary cache
// budget + teh secondary cache budget, as follows -
// budget + the secondary cache budget, as follows -
// |--------- Primary Cache Configured Capacity -----------|
// |---Secondary Cache Budget----|----Primary Cache Budget-----|
//
@@ -51,7 +51,7 @@ const char* kTieredCacheName = "TieredCache";
// placeholder is counted against the primary cache. To compensate and count
// a portion of it against the secondary cache, the secondary cache Deflate()
// method is called to shrink it. Since the Deflate() causes the secondary
// actual usage to shrink, it is refelcted here by releasing an equal amount
// actual usage to shrink, it is reflected here by releasing an equal amount
// from the pri_cache_res_ reservation. The Deflate() in the secondary cache
// can be, but is not required to be, implemented using its own cache
// reservation manager.
@@ -72,7 +72,7 @@ const char* kTieredCacheName = "TieredCache";
// reservation is increased by an equal amount.
//
// Another way of implementing this would have been to simply split the user
// reservation into primary and seconary components. However, this would
// reservation into primary and secondary components. However, this would
// require allocating a structure to track the associated secondary cache
// reservation, which adds some complexity and overhead.
//
@@ -121,16 +121,13 @@ CacheWithSecondaryAdapter::~CacheWithSecondaryAdapter() {
assert(s.ok());
assert(placeholder_usage_ == 0);
assert(reserved_usage_ == 0);
bool pri_cache_res_mismatch =
pri_cache_res_->GetTotalMemoryUsed() != sec_capacity;
if (pri_cache_res_mismatch) {
fprintf(stderr,
if (pri_cache_res_->GetTotalMemoryUsed() != sec_capacity) {
fprintf(stdout,
"~CacheWithSecondaryAdapter: Primary cache reservation: "
"%zu, Secondary cache capacity: %zu, "
"Secondary cache reserved: %zu\n",
pri_cache_res_->GetTotalMemoryUsed(), sec_capacity,
sec_reserved_);
assert(!pri_cache_res_mismatch);
}
}
#endif // NDEBUG
@@ -587,7 +584,7 @@ Status CacheWithSecondaryAdapter::UpdateCacheReservationRatio(
size_t pri_capacity = target_->GetCapacity();
size_t sec_capacity =
static_cast<size_t>(pri_capacity * compressed_secondary_ratio);
size_t old_sec_capacity;
size_t old_sec_capacity = 0;
Status s = secondary_cache_->GetCapacity(old_sec_capacity);
if (!s.ok()) {
return s;
@@ -624,7 +621,7 @@ Status CacheWithSecondaryAdapter::UpdateCacheReservationRatio(
} else {
// We're shrinking the ratio. Try to avoid unnecessary evictions -
// 1. Lower the secondary cache capacity
// 2. Decrease pri_cache_res_ reservation to relect lower secondary
// 2. Decrease pri_cache_res_ reservation to reflect lower secondary
// cache utilization (decrease in capacity - decrease in share of cache
// reservations)
// 3. Inflate the secondary cache to give it back the reduction in its
+1
View File
@@ -0,0 +1 @@
ccache.exe cl.exe %*
+1 -1
View File
@@ -98,7 +98,7 @@ class ArenaWrappedDBIter : public Iterator {
bool PrepareValue() override { return db_iter_->PrepareValue(); }
void Prepare(const std::vector<ScanOptions>& scan_opts) override {
void Prepare(const MultiScanArgs& scan_opts) override {
db_iter_->Prepare(scan_opts);
}
+4 -2
View File
@@ -201,8 +201,7 @@ Status BuildTable(
CompactionIterator c_iter(
iter, ucmp, &merge, kMaxSequenceNumber, &snapshots, earliest_snapshot,
earliest_write_conflict_snapshot, job_snapshot, snapshot_checker, env,
ShouldReportDetailedTime(env, ioptions.stats),
true /* internal key corruption is not ok */, range_del_agg.get(),
ShouldReportDetailedTime(env, ioptions.stats), range_del_agg.get(),
blob_file_builder.get(), ioptions.allow_data_in_errors,
ioptions.enforce_single_del_contracts,
/*manual_compaction_canceled=*/kManualCompactionCanceledFalse,
@@ -343,6 +342,9 @@ Status BuildTable(
if (s.ok() && !empty) {
if (flush_stats) {
flush_stats->bytes_written_pre_comp = builder->PreCompressionSize();
// Add worker CPU micros here. Caller needs to add CPU micros from
// calling thread.
flush_stats->cpu_micros += builder->GetWorkerCPUMicros();
}
uint64_t file_size = builder->FileSize();
meta->fd.file_size = file_size;
+573 -130
View File
File diff suppressed because it is too large Load Diff
+348
View File
@@ -1036,6 +1036,78 @@ int main(int argc, char** argv) {
rocksdb_options_set_error_if_exists(options, 1);
}
StartPhase("checkpoint_export_column_family");
{
static char cf_export_path[200];
static char db_import_path[200];
snprintf(cf_export_path, sizeof(cf_export_path),
"%s/rocksdb_c_test-%d-cf_export", GetTempDir(), ((int)geteuid()));
snprintf(db_import_path, sizeof(db_import_path),
"%s/rocksdb_c_test-%d-db_import", GetTempDir(), ((int)geteuid()));
rocksdb_options_t* db_options = rocksdb_options_create();
rocksdb_column_family_handle_t* cf_export =
rocksdb_create_column_family(db, db_options, "cf_export", &err);
CheckNoError(err);
rocksdb_put_cf(db, woptions, cf_export, "k1", 2, "v1", 2, &err);
CheckNoError(err);
rocksdb_put_cf(db, woptions, cf_export, "k2", 2, "v2", 2, &err);
CheckNoError(err);
rocksdb_checkpoint_t* checkpoint =
rocksdb_checkpoint_object_create(db, &err);
CheckNoError(err);
rocksdb_export_import_files_metadata_t* export_metadata =
rocksdb_checkpoint_export_column_family(checkpoint, cf_export,
cf_export_path, &err);
CheckNoError(err);
const char* comparator_name =
rocksdb_export_import_files_metadata_get_db_comparator_name(
export_metadata);
CheckEqual("leveldb.BytewiseComparator", comparator_name, 26);
rocksdb_free((void*)comparator_name);
rocksdb_checkpoint_object_destroy(checkpoint);
checkpoint = NULL;
rocksdb_drop_column_family(db, cf_export, &err);
CheckNoError(err);
rocksdb_column_family_handle_destroy(cf_export);
rocksdb_options_set_create_if_missing(db_options, 1);
rocksdb_options_set_error_if_exists(db_options, 1);
rocksdb_t* db_import = rocksdb_open(db_options, db_import_path, &err);
CheckNoError(err);
rocksdb_import_column_family_options_t* import_options =
rocksdb_import_column_family_options_create();
rocksdb_column_family_handle_t* cf_import =
rocksdb_create_column_family_with_import(db_import, db_options,
"cf_import", import_options,
export_metadata, &err);
CheckNoError(err);
rocksdb_import_column_family_options_destroy(import_options);
rocksdb_export_import_files_metadata_destroy(export_metadata);
size_t val_len;
char* val =
rocksdb_get_cf(db_import, roptions, cf_import, "k1", 2, &val_len, &err);
CheckNoError(err);
CheckEqual("v1", val, val_len);
free(val);
val =
rocksdb_get_cf(db_import, roptions, cf_import, "k2", 2, &val_len, &err);
CheckNoError(err);
CheckEqual("v2", val, val_len);
free(val);
rocksdb_column_family_handle_destroy(cf_import);
cf_import = NULL;
rocksdb_close(db_import);
rocksdb_destroy_db(db_options, db_import_path, &err);
CheckNoError(err);
rocksdb_options_destroy(db_options);
db_options = NULL;
}
StartPhase("compactall");
rocksdb_compact_range(db, NULL, 0, NULL, 0);
CheckGet(db, roptions, "foo", "hello");
@@ -1183,6 +1255,70 @@ int main(int argc, char** argv) {
rocksdb_writebatch_destroy(wb);
}
StartPhase("writebatch_vectors_cf");
{
const char* cf_name = "wb_vectors_cf";
rocksdb_column_family_handle_t* wb_cf =
rocksdb_create_column_family(db, options, cf_name, &err);
CheckNoError(err);
rocksdb_writebatch_t* wb = rocksdb_writebatch_create();
// Test putv_cf: concatenates multiple slices into a single key/value
const char* put_keys[2] = {"k", "ey"};
const size_t put_key_sizes[2] = {1, 2};
const char* put_vals[3] = {"v", "a", "l"};
const size_t put_val_sizes[3] = {1, 1, 1};
rocksdb_writebatch_putv_cf(wb, wb_cf, 2, put_keys, put_key_sizes, 3,
put_vals, put_val_sizes);
rocksdb_write(db, woptions, wb, &err);
CheckNoError(err);
// putv_cf concatenates: key="k"+"ey"="key", value="v"+"a"+"l"="val"
CheckGetCF(db, roptions, wb_cf, "key", "val");
CheckGetCF(db, roptions, wb_cf, "k", NULL);
CheckGetCF(db, roptions, wb_cf, "ey", NULL);
// Test deletev_cf: concatenates multiple slices for key
rocksdb_writebatch_clear(wb);
const char* del_keys[2] = {"k", "ey"};
const size_t del_key_sizes[2] = {1, 2};
rocksdb_writebatch_deletev_cf(wb, wb_cf, 2, del_keys, del_key_sizes);
rocksdb_write(db, woptions, wb, &err);
CheckNoError(err);
CheckGetCF(db, roptions, wb_cf, "key", NULL);
// Test delete_rangev_cf: concatenates slices for range deletion
rocksdb_writebatch_clear(wb);
rocksdb_writebatch_put_cf(wb, wb_cf, "a", 1, "1", 1);
rocksdb_writebatch_put_cf(wb, wb_cf, "b", 1, "2", 1);
rocksdb_writebatch_put_cf(wb, wb_cf, "c", 1, "3", 1);
rocksdb_write(db, woptions, wb, &err);
CheckNoError(err);
CheckGetCF(db, roptions, wb_cf, "a", "1");
CheckGetCF(db, roptions, wb_cf, "b", "2");
CheckGetCF(db, roptions, wb_cf, "c", "3");
rocksdb_writebatch_clear(wb);
const char* range_start[2] = {"a", ""}; // "a" + "" = "a"
const size_t range_start_sizes[2] = {1, 0};
const char* range_end[2] = {"c", ""};
const size_t range_end_sizes[2] = {1, 0};
rocksdb_writebatch_delete_rangev_cf(wb, wb_cf, 2, range_start,
range_start_sizes, range_end,
range_end_sizes);
rocksdb_write(db, woptions, wb, &err);
CheckNoError(err);
// Range [a, c) should delete "a" and "b", but not "c"
CheckGetCF(db, roptions, wb_cf, "a", NULL);
CheckGetCF(db, roptions, wb_cf, "b", NULL);
CheckGetCF(db, roptions, wb_cf, "c", "3");
rocksdb_writebatch_destroy(wb);
rocksdb_drop_column_family(db, wb_cf, &err);
CheckNoError(err);
rocksdb_column_family_handle_destroy(wb_cf);
}
StartPhase("writebatch_vectors");
{
rocksdb_writebatch_t* wb = rocksdb_writebatch_create();
@@ -1348,6 +1484,43 @@ int main(int argc, char** argv) {
rocksdb_iter_destroy(iter);
}
StartPhase("iter_slice");
{
// Test the new slice-based iterator API for better performance
rocksdb_iterator_t* iter = rocksdb_create_iterator(db, roptions);
CheckCondition(!rocksdb_iter_valid(iter));
rocksdb_iter_seek_to_first(iter);
CheckCondition(rocksdb_iter_valid(iter));
// Test rocksdb_iter_key_slice
rocksdb_slice_t key_slice = rocksdb_iter_key_slice(iter);
CheckEqual("box", key_slice.data, key_slice.size);
// Test rocksdb_iter_value_slice
rocksdb_slice_t value_slice = rocksdb_iter_value_slice(iter);
CheckEqual("c", value_slice.data, value_slice.size);
// Move to next entry and test again
rocksdb_iter_next(iter);
CheckCondition(rocksdb_iter_valid(iter));
key_slice = rocksdb_iter_key_slice(iter);
value_slice = rocksdb_iter_value_slice(iter);
CheckEqual("foo", key_slice.data, key_slice.size);
CheckEqual("hello", value_slice.data, value_slice.size);
// Test seeking with slice API
rocksdb_iter_seek(iter, "b", 1);
CheckCondition(rocksdb_iter_valid(iter));
key_slice = rocksdb_iter_key_slice(iter);
value_slice = rocksdb_iter_value_slice(iter);
CheckEqual("box", key_slice.data, key_slice.size);
CheckEqual("c", value_slice.data, value_slice.size);
rocksdb_iter_get_error(iter, &err);
CheckNoError(err);
rocksdb_iter_destroy(iter);
}
StartPhase("wbwi_iter");
{
rocksdb_iterator_t* base_iter = rocksdb_create_iterator(db, roptions);
@@ -1433,6 +1606,53 @@ int main(int argc, char** argv) {
CheckMultiGetValues(3, vals, vals_sizes, errs, expected);
}
StartPhase("zero_copy_get_pinned_v2");
{
// Test new zero-copy get functions
// Test rocksdb_get_pinned_v2
rocksdb_pinnable_handle_t* handle =
rocksdb_get_pinned_v2(db, roptions, "foo", 3, &err);
CheckNoError(err);
CheckCondition(handle != NULL);
size_t val_len;
const char* val = rocksdb_pinnable_handle_get_value(handle, &val_len);
CheckEqual("hello", val, val_len);
rocksdb_pinnable_handle_destroy(handle);
// Test with non-existent key
handle = rocksdb_get_pinned_v2(db, roptions, "notfound", 8, &err);
CheckNoError(err);
CheckCondition(handle == NULL);
// Test rocksdb_get_into_buffer
char buffer[100];
unsigned char found;
unsigned char success = rocksdb_get_into_buffer(
db, roptions, "foo", 3, buffer, sizeof(buffer), &val_len, &found, &err);
CheckNoError(err);
CheckCondition(success == 1);
CheckCondition(found == 1);
CheckCondition(val_len == 5);
CheckCondition(memcmp(buffer, "hello", 5) == 0);
// Test with buffer too small
success = rocksdb_get_into_buffer(db, roptions, "foo", 3, buffer,
2, // Buffer too small
&val_len, &found, &err);
CheckNoError(err);
CheckCondition(success == 0); // Should fail due to small buffer
CheckCondition(found == 1);
CheckCondition(val_len == 5); // Should still report actual size
// Test with non-existent key
success = rocksdb_get_into_buffer(db, roptions, "notfound", 8, buffer,
sizeof(buffer), &val_len, &found, &err);
CheckNoError(err);
CheckCondition(success == 0);
CheckCondition(found == 0);
}
StartPhase("pin_get");
{
CheckPinGet(db, roptions, "box", "c");
@@ -1850,6 +2070,55 @@ int main(int argc, char** argv) {
rocksdb_flush_wal(db, 1, &err);
CheckNoError(err);
// Test column family handle get name
{
size_t name_len;
char* cf_name =
rocksdb_column_family_handle_get_name(handles[1], &name_len);
CheckCondition(name_len == 3);
CheckCondition(memcmp(cf_name, "cf1", 3) == 0);
rocksdb_free(cf_name);
}
// Test zero-copy get with column families
{
rocksdb_pinnable_handle_t* handle =
rocksdb_get_pinned_cf_v2(db, roptions, handles[1], "box", 3, &err);
CheckNoError(err);
CheckCondition(handle != NULL);
size_t val_len;
const char* val = rocksdb_pinnable_handle_get_value(handle, &val_len);
CheckEqual("c", val, val_len);
rocksdb_pinnable_handle_destroy(handle);
// Test with non-existent key
handle = rocksdb_get_pinned_cf_v2(db, roptions, handles[1], "notfound", 8,
&err);
CheckNoError(err);
CheckCondition(handle == NULL);
// Test rocksdb_get_into_buffer_cf
char buffer[100];
unsigned char found;
unsigned char success = rocksdb_get_into_buffer_cf(
db, roptions, handles[1], "buff", 4, buffer, sizeof(buffer), &val_len,
&found, &err);
CheckNoError(err);
CheckCondition(success == 1);
CheckCondition(found == 1);
CheckCondition(val_len == 7);
CheckCondition(memcmp(buffer, "rocksdb", 7) == 0);
// Test with buffer too small
success = rocksdb_get_into_buffer_cf(db, roptions, handles[1], "buff", 4,
buffer, 3, // Buffer too small
&val_len, &found, &err);
CheckNoError(err);
CheckCondition(success == 0); // Should fail due to small buffer
CheckCondition(found == 1);
CheckCondition(val_len == 7); // Should still report actual size
}
// Test WriteBatchWithIndex iteration with Column Family
rocksdb_writebatch_wi_t* wbwi = rocksdb_writebatch_wi_create(0, true);
rocksdb_writebatch_wi_put_cf(wbwi, handles[1], "boat", 4, "row",
@@ -1926,6 +2195,74 @@ int main(int argc, char** argv) {
}
}
{
// Test rocksdb_batched_multi_get_cf_slice for better performance
// Build rocksdb_slice_t array directly to avoid conversion overhead
rocksdb_slice_t batched_key_slices[4];
batched_key_slices[0].data = "box";
batched_key_slices[0].size = 3;
batched_key_slices[1].data = "buff";
batched_key_slices[1].size = 4;
batched_key_slices[2].data = "barfooxx";
batched_key_slices[2].size = 8;
batched_key_slices[3].data = "box";
batched_key_slices[3].size = 3;
const char* expected_value[4] = {"c", "rocksdb", NULL, "c"};
char* batched_errs[4];
rocksdb_pinnableslice_t* pvals[4];
rocksdb_batched_multi_get_cf_slice(db, roptions, handles[1], 4,
batched_key_slices, pvals,
batched_errs, false);
const char* val;
size_t val_len;
for (i = 0; i < 4; ++i) {
CheckNoError(batched_errs[i]);
if (pvals[i] != NULL) {
val = rocksdb_pinnableslice_value(pvals[i], &val_len);
CheckEqual(expected_value[i], val, val_len);
rocksdb_pinnableslice_destroy(pvals[i]);
} else {
CheckEqual(expected_value[i], NULL, 0);
}
}
}
{
// Test rocksdb_batched_multi_get_cf_slice with sorted_input=true
// Keys must be in sorted order for this optimization
rocksdb_slice_t sorted_key_slices[3];
sorted_key_slices[0].data = "box";
sorted_key_slices[0].size = 3;
sorted_key_slices[1].data = "buff";
sorted_key_slices[1].size = 4;
sorted_key_slices[2].data = "notfound";
sorted_key_slices[2].size = 8;
const char* expected_value[3] = {"c", "rocksdb", NULL};
char* batched_errs[3];
rocksdb_pinnableslice_t* pvals[3];
rocksdb_batched_multi_get_cf_slice(db, roptions, handles[1], 3,
sorted_key_slices, pvals, batched_errs,
true);
const char* val;
size_t val_len;
for (i = 0; i < 3; ++i) {
CheckNoError(batched_errs[i]);
if (pvals[i] != NULL) {
val = rocksdb_pinnableslice_value(pvals[i], &val_len);
CheckEqual(expected_value[i], val, val_len);
rocksdb_pinnableslice_destroy(pvals[i]);
} else {
CheckEqual(expected_value[i], NULL, 0);
}
}
}
{
unsigned char value_found = 0;
@@ -3397,6 +3734,17 @@ int main(int argc, char** argv) {
rocksdb_transaction_put(txn, "foo", 3, "hello", 5, &err);
CheckNoError(err);
// test transaction get/set name (before commit)
{
rocksdb_transaction_set_name(txn, "test_txn", 8, &err);
CheckNoError(err);
size_t name_len;
char* txn_name = rocksdb_transaction_get_name(txn, &name_len);
CheckCondition(name_len == 8);
CheckCondition(memcmp(txn_name, "test_txn", 8) == 0);
rocksdb_free(txn_name);
}
// read from outside transaction, before commit
CheckTxnDBGet(txn_db, roptions, "foo", NULL);
CheckTxnDBPinGet(txn_db, roptions, "foo", NULL);
+3 -2
View File
@@ -280,7 +280,8 @@ ColumnFamilyOptions SanitizeCfOptions(const ImmutableDBOptions& db_options,
}
if (result.compaction_style == kCompactionStyleUniversal &&
db_options.allow_ingest_behind && result.num_levels < 3) {
(db_options.allow_ingest_behind || result.cf_allow_ingest_behind) &&
result.num_levels < 3) {
result.num_levels = 3;
}
@@ -1331,7 +1332,7 @@ Compaction* ColumnFamilyData::CompactRange(
const InternalKey* begin, const InternalKey* end,
InternalKey** compaction_end, bool* conflict,
uint64_t max_file_num_to_ignore, const std::string& trim_ts) {
auto* result = compaction_picker_->CompactRange(
auto* result = compaction_picker_->PickCompactionForCompactRange(
GetName(), mutable_cf_options, mutable_db_options,
current_->storage_info(), input_level, output_level,
compact_range_options, begin, end, compaction_end, conflict,
+5
View File
@@ -600,6 +600,11 @@ class ColumnFamilyData {
return (mem_->IsEmpty() ? 0 : 1) + imm_.NumNotFlushed();
}
// thread-safe, DB mutex not needed.
bool AllowIngestBehind() const {
return ioptions_.cf_allow_ingest_behind || ioptions_.allow_ingest_behind;
}
private:
friend class ColumnFamilySet;
ColumnFamilyData(
+230
View File
@@ -441,6 +441,10 @@ TEST_F(CompactFilesTest, SentinelCompressionType) {
}
TEST_F(CompactFilesTest, CompressionWithBlockAlign) {
if (!Snappy_Supported()) {
ROCKSDB_GTEST_SKIP("Test requires Snappy support");
return;
}
Options options;
options.compression = CompressionType::kNoCompression;
options.create_if_missing = true;
@@ -530,6 +534,232 @@ TEST_F(CompactFilesTest, GetCompactionJobInfo) {
delete db;
}
// Helper function to generate zero-padded keys
// e.g., MakeKey("a", 5) -> "a05", MakeKey("b", 42) -> "b42"
static std::string MakeKey(const std::string& prefix, int index) {
return prefix + (index < 10 ? "0" : "") + std::to_string(index);
}
TEST_F(CompactFilesTest, TrivialMoveNonOverlappingFiles) {
Options options;
options.create_if_missing = true;
options.disable_auto_compactions = true;
options.compression = kNoCompression;
options.level_compaction_dynamic_level_bytes = false;
DB* db = nullptr;
ASSERT_OK(DestroyDB(db_name_, options));
Status s = DB::Open(options, db_name_, &db);
ASSERT_OK(s);
ASSERT_NE(db, nullptr);
// Create 3 non-overlapping files in L0
// File 1: keys [a00-a99]
for (int i = 0; i < 100; i++) {
std::string key = MakeKey("a", i);
ASSERT_OK(db->Put(WriteOptions(), key, "value_" + key));
}
ASSERT_OK(db->Flush(FlushOptions()));
// File 2: keys [b00-b99]
for (int i = 0; i < 100; i++) {
std::string key = MakeKey("b", i);
ASSERT_OK(db->Put(WriteOptions(), key, "value_" + key));
}
ASSERT_OK(db->Flush(FlushOptions()));
// File 3: keys [c00-c99]
for (int i = 0; i < 100; i++) {
std::string key = MakeKey("c", i);
ASSERT_OK(db->Put(WriteOptions(), key, "value_" + key));
}
ASSERT_OK(db->Flush(FlushOptions()));
// Verify files are in L0
ColumnFamilyMetaData meta;
db->GetColumnFamilyMetaData(&meta);
ASSERT_EQ(meta.levels[0].files.size(), 3);
ASSERT_EQ(meta.levels[1].files.size(), 0);
// Get L0 files
std::vector<std::string> l0_files;
for (const auto& file : meta.levels[0].files) {
l0_files.push_back(file.db_path + "/" + file.name);
}
CompactionOptions compact_option;
compact_option.allow_trivial_move = true;
// Compact all L0 files to L1 (non-overlapping in L1)
ASSERT_OK(db->CompactFiles(compact_option, l0_files, 1));
// Verify files are now in L1
db->GetColumnFamilyMetaData(&meta);
ASSERT_EQ(meta.levels[0].files.size(), 0);
ASSERT_EQ(meta.levels[1].files.size(), 3);
// Get the first file from L1 (should be the one with keys a00-a99)
std::string l1_file_to_move;
std::vector<std::string> l1_files_to_move_later;
uint64_t l1_file_number = 0;
for (const auto& file : meta.levels[1].files) {
if (file.smallestkey[0] == 'a') {
l1_file_to_move = file.db_path + "/" + file.name;
l1_file_number = file.file_number;
} else {
l1_files_to_move_later.push_back(file.db_path + "/" + file.name);
}
}
ASSERT_FALSE(l1_file_to_move.empty());
// Set up sync point to verify trivial move path is taken
bool trivial_move_executed = false;
SyncPoint::GetInstance()->SetCallBack(
"DBImpl::CompactFilesImpl:TrivialMove",
[&](void* /*arg*/) { trivial_move_executed = true; });
SyncPoint::GetInstance()->EnableProcessing();
// Move the file from L1 to L6 - this should be a trivial move
// because the file doesn't overlap with anything in L6
std::vector<std::string> files_to_move = {l1_file_to_move};
ASSERT_OK(db->CompactFiles(compact_option, files_to_move, 6));
// Verify trivial move was executed
ASSERT_TRUE(trivial_move_executed);
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
// Verify the file is now in L6
db->GetColumnFamilyMetaData(&meta);
ASSERT_EQ(meta.levels[1].files.size(), 2); // Two files remain in L1
ASSERT_EQ(meta.levels[6].files.size(), 1); // One file in L6
// Verify it's the correct file in L6
bool found_file_in_l6 = false;
for (const auto& file : meta.levels[6].files) {
if (file.file_number == l1_file_number) {
found_file_in_l6 = true;
// Verify key range hasn't changed
ASSERT_EQ(file.smallestkey[0], 'a');
ASSERT_EQ(file.largestkey[0], 'a');
break;
}
}
ASSERT_TRUE(found_file_in_l6);
// Move the other 2 files from L1 to L6, with allow_trivial_move set to false.
// This will trigger a normal compaction, so the 2 files will be compacted
// into a single file in L6.
ASSERT_OK(db->CompactFiles(CompactionOptions(), l1_files_to_move_later, 6));
// Verify files in L6
db->GetColumnFamilyMetaData(&meta);
ASSERT_EQ(meta.levels[1].files.size(), 0); // Zero files remain in L1
ASSERT_EQ(meta.levels[6].files.size(), 2); // Two file in L6
// Verify data integrity - all keys should still be readable
for (int i = 0; i < 100; i++) {
std::string key = MakeKey("a", i);
std::string value;
ASSERT_OK(db->Get(ReadOptions(), key, &value));
ASSERT_EQ(value, "value_" + key);
}
delete db;
}
TEST_F(CompactFilesTest, TrivialMoveBlockedByOverlap) {
Options options;
options.create_if_missing = true;
options.disable_auto_compactions = true;
options.compression = kNoCompression;
options.level_compaction_dynamic_level_bytes = false;
options.num_levels = 7;
DB* db = nullptr;
ASSERT_OK(DestroyDB(db_name_, options));
Status s = DB::Open(options, db_name_, &db);
ASSERT_OK(s);
ASSERT_NE(db, nullptr);
// Create a file in L6 with keys [m00-m99] (wide range)
for (int i = 0; i < 100; i++) {
std::string key = MakeKey("m", i);
ASSERT_OK(db->Put(WriteOptions(), key, "value_" + key));
}
ASSERT_OK(db->Flush(FlushOptions()));
// Get L0 file
ColumnFamilyMetaData meta;
db->GetColumnFamilyMetaData(&meta);
std::vector<std::string> l0_files;
for (const auto& file : meta.levels[0].files) {
l0_files.push_back(file.db_path + "/" + file.name);
}
CompactionOptions compact_option;
compact_option.allow_trivial_move = true;
// Move to L6
ASSERT_OK(db->CompactFiles(compact_option, l0_files, 6));
// Now create a file in L1 with overlapping keys [m50-m60]
for (int i = 50; i <= 60; i++) {
std::string key = "m" + std::to_string(i);
ASSERT_OK(db->Put(WriteOptions(), key, "updated_value_" + key));
}
ASSERT_OK(db->Flush(FlushOptions()));
// Get the L0 file
db->GetColumnFamilyMetaData(&meta);
std::vector<std::string> l0_files_2;
for (const auto& file : meta.levels[0].files) {
l0_files_2.push_back(file.db_path + "/" + file.name);
}
// Move to L1
ASSERT_OK(db->CompactFiles(compact_option, l0_files_2, 1));
// Get the L1 file
db->GetColumnFamilyMetaData(&meta);
ASSERT_EQ(meta.levels[1].files.size(), 1);
std::string l1_file =
meta.levels[1].files[0].db_path + "/" + meta.levels[1].files[0].name;
// Set up sync point to verify full compaction path is taken
bool trivial_move_executed = false;
SyncPoint::GetInstance()->SetCallBack(
"DBImpl::CompactFilesImpl:TrivialMove",
[&](void* /*arg*/) { trivial_move_executed = true; });
SyncPoint::GetInstance()->EnableProcessing();
// Try to move from L1 to L6 - this should NOT be a trivial move
// because the file overlaps with the existing file in L6
ASSERT_OK(db->CompactFiles(compact_option, {l1_file}, 6));
// Verify trivial move was NOT executed (full compaction happened)
ASSERT_FALSE(trivial_move_executed);
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
// Verify the result - should have merged data in L6
db->GetColumnFamilyMetaData(&meta);
ASSERT_EQ(meta.levels[1].files.size(), 0); // L1 should be empty
// L6 should have the merged file (may be 1 file if merged, or 2 if not)
ASSERT_GE(meta.levels[6].files.size(), 1);
// Verify updated values are present
for (int i = 50; i <= 60; i++) {
std::string key = "m" + std::to_string(i);
std::string value;
ASSERT_OK(db->Get(ReadOptions(), key, &value));
ASSERT_EQ(value, "updated_value_" + key);
}
delete db;
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+19 -3
View File
@@ -281,8 +281,9 @@ Compaction::Compaction(
std::vector<CompactionInputFiles> _inputs, int _output_level,
uint64_t _target_file_size, uint64_t _max_compaction_bytes,
uint32_t _output_path_id, CompressionType _compression,
CompressionOptions _compression_opts, Temperature _output_temperature,
uint32_t _max_subcompactions, std::vector<FileMetaData*> _grandparents,
CompressionOptions _compression_opts,
Temperature _output_temperature_override, uint32_t _max_subcompactions,
std::vector<FileMetaData*> _grandparents,
std::optional<SequenceNumber> _earliest_snapshot,
const SnapshotChecker* _snapshot_checker,
CompactionReason _compaction_reason, const std::string& _trim_ts,
@@ -303,7 +304,7 @@ Compaction::Compaction(
output_path_id_(_output_path_id),
output_compression_(_compression),
output_compression_opts_(_compression_opts),
output_temperature_(_output_temperature),
output_temperature_override_(_output_temperature_override),
deletion_compaction_(_compaction_reason == CompactionReason::kFIFOTtl ||
_compaction_reason ==
CompactionReason::kFIFOMaxSize),
@@ -647,6 +648,8 @@ bool Compaction::KeyNotExistsBeyondOutputLevel(
return true;
} else if (output_level_ != 0 &&
cfd_->ioptions().compaction_style == kCompactionStyleLevel) {
// TODO: apply the optimization here to other compaction styles and
// compaction/flush to L0.
// Maybe use binary search to find right entry instead of linear search?
const Comparator* user_cmp = cfd_->user_comparator();
for (int lvl = output_level_ + 1; lvl < number_levels_; lvl++) {
@@ -1126,4 +1129,17 @@ void Compaction::FilterInputsForCompactionIterator() {
}
}
Temperature Compaction::GetOutputTemperature(bool is_proximal_level) const {
if (output_temperature_override_ != Temperature::kUnknown) {
return output_temperature_override_;
}
if (is_last_level() && !is_proximal_level &&
mutable_cf_options_.last_level_temperature != Temperature::kUnknown) {
return mutable_cf_options_.last_level_temperature;
}
return mutable_cf_options_.default_write_temperature;
}
} // namespace ROCKSDB_NAMESPACE
+12 -3
View File
@@ -90,7 +90,8 @@ class Compaction {
uint64_t target_file_size, uint64_t max_compaction_bytes,
uint32_t output_path_id, CompressionType compression,
CompressionOptions compression_opts,
Temperature output_temperature, uint32_t max_subcompactions,
Temperature output_temperature_override,
uint32_t max_subcompactions,
std::vector<FileMetaData*> grandparents,
std::optional<SequenceNumber> earliest_snapshot,
const SnapshotChecker* snapshot_checker,
@@ -179,6 +180,10 @@ class Compaction {
const std::vector<CompactionInputFiles>* inputs() { return &inputs_; }
// Returns the LevelFilesBrief of the specified compaction input level.
// Note that if the compaction includes standalone range deletion file,
// this function returns the result after filtering out input files covered
// by the range deletion file.
// Use inputs() if you want to get the original input files.
const LevelFilesBrief* input_levels(size_t compaction_input_level) const {
return &input_levels_[compaction_input_level];
}
@@ -409,7 +414,11 @@ class Compaction {
uint64_t max_compaction_bytes() const { return max_compaction_bytes_; }
Temperature output_temperature() const { return output_temperature_; }
// Order of precedence for temperature:
// 1. Override temp if not kUnknown
// 2. Temperature of the last level files if applicable
// 3. Default write temperature
Temperature GetOutputTemperature(bool is_proximal_level = false) const;
uint32_t max_subcompactions() const { return max_subcompactions_; }
@@ -541,7 +550,7 @@ class Compaction {
const uint32_t output_path_id_;
CompressionType output_compression_;
CompressionOptions output_compression_opts_;
Temperature output_temperature_;
Temperature output_temperature_override_;
// If true, then the compaction can be done by simply deleting input files.
const bool deletion_compaction_;
// should it split the output file using the compact cursor?
+51 -38
View File
@@ -28,7 +28,7 @@ CompactionIterator::CompactionIterator(
SequenceNumber earliest_snapshot,
SequenceNumber earliest_write_conflict_snapshot,
SequenceNumber job_snapshot, const SnapshotChecker* snapshot_checker,
Env* env, bool report_detailed_time, bool expect_valid_internal_key,
Env* env, bool report_detailed_time,
CompactionRangeDelAggregator* range_del_agg,
BlobFileBuilder* blob_file_builder, bool allow_data_in_errors,
bool enforce_single_del_contracts,
@@ -42,8 +42,8 @@ CompactionIterator::CompactionIterator(
: CompactionIterator(
input, cmp, merge_helper, last_sequence, snapshots, earliest_snapshot,
earliest_write_conflict_snapshot, job_snapshot, snapshot_checker, env,
report_detailed_time, expect_valid_internal_key, range_del_agg,
blob_file_builder, allow_data_in_errors, enforce_single_del_contracts,
report_detailed_time, range_del_agg, blob_file_builder,
allow_data_in_errors, enforce_single_del_contracts,
manual_compaction_canceled,
compaction ? std::make_unique<RealCompaction>(compaction) : nullptr,
must_count_input_entries, compaction_filter, shutting_down, info_log,
@@ -55,7 +55,7 @@ CompactionIterator::CompactionIterator(
SequenceNumber earliest_snapshot,
SequenceNumber earliest_write_conflict_snapshot,
SequenceNumber job_snapshot, const SnapshotChecker* snapshot_checker,
Env* env, bool report_detailed_time, bool expect_valid_internal_key,
Env* env, bool report_detailed_time,
CompactionRangeDelAggregator* range_del_agg,
BlobFileBuilder* blob_file_builder, bool allow_data_in_errors,
bool enforce_single_del_contracts,
@@ -76,16 +76,14 @@ CompactionIterator::CompactionIterator(
env_(env),
clock_(env_->GetSystemClock().get()),
report_detailed_time_(report_detailed_time),
expect_valid_internal_key_(expect_valid_internal_key),
range_del_agg_(range_del_agg),
blob_file_builder_(blob_file_builder),
compaction_(std::move(compaction)),
compaction_filter_(compaction_filter),
shutting_down_(shutting_down),
manual_compaction_canceled_(manual_compaction_canceled),
bottommost_level_(!compaction_ ? false
: compaction_->bottommost_level() &&
!compaction_->allow_ingest_behind()),
bottommost_level_(compaction_ && compaction_->bottommost_level() &&
!compaction_->allow_ingest_behind()),
// snapshots_ cannot be nullptr, but we will assert later in the body of
// the constructor.
visible_at_tip_(snapshots_ ? snapshots_->empty() : false),
@@ -161,6 +159,7 @@ void CompactionIterator::Next() {
// MergeUntil stops when it encounters a corrupt key and does not
// include them in the result, so we expect the keys here to be valid.
if (!s.ok()) {
// FIXME: should fail compaction after this fatal logging.
ROCKS_LOG_FATAL(
info_log_, "Invalid ikey %s in compaction. %s",
allow_data_in_errors_ ? key_.ToString(true).c_str() : "hidden",
@@ -464,18 +463,9 @@ void CompactionIterator::NextFromInput() {
if (!pik_status.ok()) {
iter_stats_.num_input_corrupt_records++;
// If `expect_valid_internal_key_` is false, return the corrupted key
// and let the caller decide what to do with it.
if (expect_valid_internal_key_) {
status_ = pik_status;
return;
}
key_ = current_key_.SetInternalKey(key_);
has_current_user_key_ = false;
current_user_key_sequence_ = kMaxSequenceNumber;
current_user_key_snapshot_ = 0;
validity_info_.SetValid(ValidContext::kParseKeyError);
break;
// Always fail compaction when encountering corrupted internal keys
status_ = pik_status;
return;
}
TEST_SYNC_POINT_CALLBACK("CompactionIterator:ProcessKV", &ikey_);
if (is_range_del_) {
@@ -642,7 +632,8 @@ void CompactionIterator::NextFromInput() {
} else if (ikey_.type == kTypeSingleDeletion) {
// We can compact out a SingleDelete if:
// 1) We encounter the corresponding PUT -OR- we know that this key
// doesn't appear past this output level
// doesn't appear past this output level and we are not in
// ingest_behind mode.
// =AND=
// 2) We've already returned a record in this snapshot -OR-
// there are no earlier earliest_write_conflict_snapshot.
@@ -731,6 +722,8 @@ void CompactionIterator::NextFromInput() {
"CompactionIterator::NextFromInput:SingleDelete:1",
const_cast<Compaction*>(c));
if (last_key_seq_zeroed_) {
// Drop SD and the next key since they are both in the last
// snapshot (since last key has seqno zeroed).
++iter_stats_.num_record_drop_hidden;
++iter_stats_.num_record_drop_obsolete;
assert(bottommost_level_);
@@ -841,7 +834,7 @@ void CompactionIterator::NextFromInput() {
// iteration. If the next key is corrupt, we return before the
// comparison, so the value of has_current_user_key does not matter.
has_current_user_key_ = false;
if (compaction_ != nullptr &&
if (compaction_ != nullptr && !compaction_->allow_ingest_behind() &&
DefinitelyInSnapshot(ikey_.sequence, earliest_snapshot_) &&
compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key,
&level_ptrs_) &&
@@ -854,6 +847,9 @@ void CompactionIterator::NextFromInput() {
++iter_stats_.num_optimized_del_drop_obsolete;
}
} else if (last_key_seq_zeroed_) {
// Sequence number zeroing requires bottommost_level_, which is
// false with ingest_behind.
assert(!compaction_->allow_ingest_behind());
// Skip.
++iter_stats_.num_record_drop_hidden;
++iter_stats_.num_record_drop_obsolete;
@@ -870,6 +866,7 @@ void CompactionIterator::NextFromInput() {
} else if (last_sequence != kMaxSequenceNumber &&
(last_snapshot == current_user_key_snapshot_ ||
last_snapshot < current_user_key_snapshot_)) {
// rule (A):
// If the earliest snapshot is which this key is visible in
// is the same as the visibility of a previous instance of the
// same key, then this kv is not visible in any snapshot.
@@ -878,6 +875,15 @@ void CompactionIterator::NextFromInput() {
// Note: Dropping this key will not affect TransactionDB write-conflict
// checking since there has already been a record returned for this key
// in this snapshot.
// When ingest_behind is enabled, it's ok that we drop an overwritten
// Delete here. The overwritting key still covers whatever that will be
// ingested. Note that we will not drop SingleDelete here as SingleDelte
// is handled entirely in its own if clause. This is important, see
// example: from new to old: SingleDelete_1, PUT_1, SingleDelete_2, PUT_2,
// where all operations are on the same key and PUT_2 is ingested with
// ingest_behind=true. If SingleDelete_2 is dropped due to being compacted
// together with PUT_1, and then PUT_1 is compacted away together with
// SingleDelete_1, PUT_2 can incorrectly becomes visible.
if (last_sequence < current_user_key_sequence_) {
ROCKS_LOG_FATAL(info_log_,
"key %s, last_sequence (%" PRIu64
@@ -887,12 +893,13 @@ void CompactionIterator::NextFromInput() {
assert(false);
}
++iter_stats_.num_record_drop_hidden; // rule (A)
++iter_stats_.num_record_drop_hidden;
AdvanceInputIter();
} else if (compaction_ != nullptr &&
(ikey_.type == kTypeDeletion ||
(ikey_.type == kTypeDeletionWithTimestamp &&
cmp_with_history_ts_low_ < 0)) &&
!compaction_->allow_ingest_behind() &&
DefinitelyInSnapshot(ikey_.sequence, earliest_snapshot_) &&
compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key,
&level_ptrs_)) {
@@ -928,11 +935,13 @@ void CompactionIterator::NextFromInput() {
(ikey_.type == kTypeDeletionWithTimestamp &&
cmp_with_history_ts_low_ < 0)) &&
bottommost_level_) {
assert(compaction_);
assert(!compaction_->allow_ingest_behind()); // bottommost_level_ is true
// Handle the case where we have a delete key at the bottom most level
// We can skip outputting the key iff there are no subsequent puts for
// this key
assert(!compaction_ || compaction_->KeyNotExistsBeyondOutputLevel(
ikey_.user_key, &level_ptrs_));
assert(compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key,
&level_ptrs_));
ParsedInternalKey next_ikey;
AdvanceInputIter();
#ifndef NDEBUG
@@ -974,6 +983,12 @@ void CompactionIterator::NextFromInput() {
(compaction_ != nullptr &&
compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key,
&level_ptrs_)))) {
// FIXME: it's possible that we are setting sequence number to 0 as
// preferred sequence number here. If cf_ingest_behind is enabled, this
// may fail ingestions since they expect all keys above the last level
// to have non-zero sequence number. We should probably not allow seqno
// zeroing here.
//
// This section that attempts to swap preferred sequence number will not
// be invoked if this is a CompactionIterator created for flush, since
// `compaction_` will be nullptr and it's not bottommost either.
@@ -1105,17 +1120,15 @@ void CompactionIterator::NextFromInput() {
}
}
if (!Valid() && IsShuttingDown()) {
status_ = Status::ShutdownInProgress();
}
if (IsPausingManualCompaction()) {
status_ = Status::Incomplete(Status::SubCode::kManualCompactionPaused);
}
// Propagate corruption status from memtable itereator
if (!input_.Valid() && input_.status().IsCorruption()) {
status_ = input_.status();
if (status_.ok()) {
if (!Valid() && IsShuttingDown()) {
status_ = Status::ShutdownInProgress();
} else if (IsPausingManualCompaction()) {
status_ = Status::Incomplete(Status::SubCode::kManualCompactionPaused);
} else if (!input_.Valid() && input_.status().IsCorruption()) {
// Propagate corruption status from memtable iterator
status_ = input_.status();
}
}
}
@@ -1274,11 +1287,11 @@ void CompactionIterator::PrepareOutput() {
//
// Can we do the same for levels above bottom level as long as
// KeyNotExistsBeyondOutputLevel() return true?
if (Valid() && compaction_ != nullptr &&
!compaction_->allow_ingest_behind() && bottommost_level_ &&
if (Valid() && bottommost_level_ &&
DefinitelyInSnapshot(ikey_.sequence, earliest_snapshot_) &&
ikey_.type != kTypeMerge && current_key_committed_ &&
ikey_.sequence <= preserve_seqno_after_ && !is_range_del_) {
assert(compaction_ != nullptr && !compaction_->allow_ingest_behind());
if (ikey_.type == kTypeDeletion ||
(ikey_.type == kTypeSingleDeletion && timestamp_size_ == 0)) {
ROCKS_LOG_FATAL(
+39 -12
View File
@@ -145,7 +145,8 @@ class CompactionIterator {
}
bool allow_ingest_behind() const override {
return compaction_->immutable_options().allow_ingest_behind;
return compaction_->immutable_options().cf_allow_ingest_behind ||
compaction_->immutable_options().allow_ingest_behind;
}
bool allow_mmap_reads() const override {
@@ -182,17 +183,27 @@ class CompactionIterator {
const Compaction* compaction_;
};
// @param must_count_input_entries if true, `NumInputEntryScanned()` will
// return the number of input keys scanned. If false, `NumInputEntryScanned()`
// will return this number if no Seek was called on `input`. User should call
// `HasNumInputEntryScanned()` first in this case.
// @param must_count_input_entries Controls input entry counting accuracy vs
// performance:
// - If true: `NumInputEntryScanned()` always returns the exact count of
// input keys
// scanned. The iterator will use sequential `Next()` calls instead of
// `Seek()` to maintain count accuracy as `Seek()` will not count the
// skipped input entries, which is slower but guarantees correctness.
// - If false: `NumInputEntryScanned()` returns the count only if no
// `Seek()` operations
// were performed on the input iterator. When compaction filters request
// skipping ranges of keys or other optimizations trigger seek operations,
// the count becomes unreliable. Always call `HasNumInputEntryScanned()`
// first to verify if the count is accurate before using
// `NumInputEntryScanned()`.
CompactionIterator(
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
SequenceNumber last_sequence, std::vector<SequenceNumber>* snapshots,
SequenceNumber earliest_snapshot,
SequenceNumber earliest_write_conflict_snapshot,
SequenceNumber job_snapshot, const SnapshotChecker* snapshot_checker,
Env* env, bool report_detailed_time, bool expect_valid_internal_key,
Env* env, bool report_detailed_time,
CompactionRangeDelAggregator* range_del_agg,
BlobFileBuilder* blob_file_builder, bool allow_data_in_errors,
bool enforce_single_del_contracts,
@@ -212,7 +223,7 @@ class CompactionIterator {
SequenceNumber earliest_write_conflict_snapshot,
SequenceNumber job_snapshot,
const SnapshotChecker* snapshot_checker, Env* env,
bool report_detailed_time, bool expect_valid_internal_key,
bool report_detailed_time,
CompactionRangeDelAggregator* range_del_agg,
BlobFileBuilder* blob_file_builder,
bool allow_data_in_errors,
@@ -254,7 +265,21 @@ class CompactionIterator {
}
const CompactionIterationStats& iter_stats() const { return iter_stats_; }
bool HasNumInputEntryScanned() const { return input_.HasNumItered(); }
// This method should only be used when `HasNumInputEntryScanned()` returns
// true, unless `must_count_input_entries=true` was specified during iterator
// creation (which ensures the count is always accurate).
uint64_t NumInputEntryScanned() const { return input_.NumItered(); }
// Returns true if the current valid key was already scanned/counted during
// a lookahead operation in a previous iteration.
//
// REQUIRED: Valid() must be true
bool IsCurrentKeyAlreadyScanned() const {
assert(Valid());
return at_next_ || merge_out_iter_.Valid();
}
Status InputStatus() const { return input_.status(); }
bool IsDeleteRangeSentinelKey() const { return is_range_del_; }
@@ -347,7 +372,6 @@ class CompactionIterator {
Env* env_;
SystemClock* clock_;
const bool report_detailed_time_;
const bool expect_valid_internal_key_;
CompactionRangeDelAggregator* range_del_agg_;
BlobFileBuilder* blob_file_builder_;
std::unique_ptr<CompactionProxy> compaction_;
@@ -417,13 +441,15 @@ class CompactionIterator {
// NextFromInput()).
ParsedInternalKey ikey_;
// Stores whether ikey_.user_key is valid. If set to false, the user key is
// not compared against the current key in the underlying iterator.
// Stores whether current_user_key_ is valid. If so, current_user_key_
// stores the user key of the last key seen by the iterator.
// If false, treat the next key to read as a new user key.
bool has_current_user_key_ = false;
// If false, the iterator holds a copy of the current compaction iterator
// output (or current key in the underlying iterator during NextFromInput()).
bool at_next_ = false;
// A copy of the current internal key.
IterKey current_key_;
Slice current_user_key_;
std::string curr_ts_;
@@ -433,8 +459,9 @@ class CompactionIterator {
// True if the iterator has already returned a record for the current key.
bool has_outputted_key_ = false;
// truncated the value of the next key and output it without applying any
// compaction rules. This is used for outputting a put after a single delete.
// Truncate the value of the next key and output it without applying any
// compaction rules. This is an optimization for outputting a put after
// a single delete. See more in `NextFromInput()` under Optimization 3.
bool clear_and_output_next_key_ = false;
MergeOutputIterator merge_out_iter_;
+5 -10
View File
@@ -294,7 +294,7 @@ class CompactionIteratorTest : public testing::TestWithParam<bool> {
snapshots_.empty() ? kMaxSequenceNumber : snapshots_.at(0),
earliest_write_conflict_snapshot, kMaxSequenceNumber,
snapshot_checker_.get(), Env::Default(),
false /* report_detailed_time */, false, range_del_agg_.get(),
false /* report_detailed_time */, range_del_agg_.get(),
nullptr /* blob_file_builder */, true /*allow_data_in_errors*/,
true /*enforce_single_del_contracts*/,
/*manual_compaction_canceled=*/kManualCompactionCanceledFalse_,
@@ -374,8 +374,7 @@ TEST_P(CompactionIteratorTest, EmptyResult) {
ASSERT_FALSE(c_iter_->Valid());
}
// If there is a corruption after a single deletion, the corrupted key should
// be preserved.
// If there is a corruption after a single deletion, the compaction should fail.
TEST_P(CompactionIteratorTest, CorruptionAfterSingleDeletion) {
InitIterators({test::KeyStr("a", 5, kTypeSingleDeletion),
test::KeyStr("a", 3, kTypeValue, true),
@@ -386,14 +385,10 @@ TEST_P(CompactionIteratorTest, CorruptionAfterSingleDeletion) {
ASSERT_EQ(test::KeyStr("a", 5, kTypeSingleDeletion),
c_iter_->key().ToString());
c_iter_->Next();
ASSERT_TRUE(c_iter_->Valid());
ASSERT_EQ(test::KeyStr("a", 3, kTypeValue, true), c_iter_->key().ToString());
c_iter_->Next();
ASSERT_TRUE(c_iter_->Valid());
ASSERT_EQ(test::KeyStr("b", 10, kTypeValue), c_iter_->key().ToString());
c_iter_->Next();
ASSERT_OK(c_iter_->status());
// The iterator should now fail when encountering the corrupted key
ASSERT_FALSE(c_iter_->Valid());
ASSERT_FALSE(c_iter_->status().ok());
ASSERT_TRUE(c_iter_->status().IsCorruption());
}
// Tests compatibility of TimedPut and SingleDelete. TimedPut should act as if
File diff suppressed because it is too large Load Diff
+198 -12
View File
@@ -176,9 +176,20 @@ class CompactionJob {
// and organizing seqno <-> time info. `known_single_subcompact` is non-null
// if we already have a known single subcompaction, with optional key bounds
// (currently for executing a remote compaction).
//
// @param compaction_progress Previously saved compaction progress
// to resume from. If empty, compaction starts fresh from the
// beginning.
//
// @param compaction_progress_writer Writer for persisting
// subcompaction progress periodically during compaction
// execution. If nullptr, progress tracking is disabled and compaction
// cannot be resumed later.
void Prepare(
std::optional<std::pair<std::optional<Slice>, std::optional<Slice>>>
known_single_subcompact);
known_single_subcompact,
const CompactionProgress& compaction_progress = CompactionProgress{},
log::Writer* compaction_progress_writer = nullptr);
// REQUIRED mutex not held
// Launch threads for each subcompaction and wait for them to finish. After
@@ -196,7 +207,8 @@ class CompactionJob {
IOStatus io_status() const { return io_status_; }
protected:
void UpdateCompactionJobOutputStats(
void UpdateCompactionJobOutputStatsFromInternalStats(
const Status& status,
const InternalStats::CompactionStatsFull& internal_stats) const;
void LogCompaction();
@@ -225,7 +237,7 @@ class CompactionJob {
private:
friend class CompactionJobTestBase;
// Collect the following stats from Input Table Properties
// Collect the following stats from input files and table properties
// - num_input_files_in_non_output_levels
// - num_input_files_in_output_level
// - bytes_read_non_output_levels
@@ -242,14 +254,15 @@ class CompactionJob {
// num_input_range_del are calculated successfully.
//
// This should be called only once for compactions (not per subcompaction)
bool BuildStatsFromInputTableProperties(
bool UpdateInternalStatsFromInputFiles(
uint64_t* num_input_range_del = nullptr);
void UpdateCompactionJobInputStats(
void UpdateCompactionJobInputStatsFromInternalStats(
const InternalStats::CompactionStatsFull& internal_stats,
uint64_t num_input_range_del) const;
Status VerifyInputRecordCount(uint64_t num_input_range_del) const;
Status VerifyOutputRecordCount() const;
// Generates a histogram representing potential divisions of key ranges from
// the input. It adds the starting and/or ending keys of certain input files
@@ -258,6 +271,10 @@ class CompactionJob {
// consecutive groups such that each group has a similar size.
void GenSubcompactionBoundaries();
void MaybeAssignCompactionProgressAndWriter(
const CompactionProgress& compaction_progress,
log::Writer* compaction_progress_writer);
// Get the number of planned subcompactions based on max_subcompactions and
// extra reserved resources
uint64_t GetSubcompactionsLimit();
@@ -278,18 +295,145 @@ class CompactionJob {
// Release all reserved threads and update the compaction limits.
void ReleaseSubcompactionResources();
void InitializeCompactionRun();
void RunSubcompactions();
void UpdateTimingStats(uint64_t start_micros);
void RemoveEmptyOutputs();
bool HasNewBlobFiles() const;
Status CollectSubcompactionErrors();
Status SyncOutputDirectories();
Status VerifyOutputFiles();
void SetOutputTableProperties();
// Aggregates subcompaction output stats to internal stat, and aggregates
// subcompaction's compaction job stats to the whole entire surrounding
// compaction job stats.
void AggregateSubcompactionOutputAndJobStats();
Status VerifyCompactionRecordCounts(bool stats_built_from_input_table_prop,
uint64_t num_input_range_del);
void FinalizeCompactionRun(const Status& status,
bool stats_built_from_input_table_prop,
uint64_t num_input_range_del);
CompactionServiceJobStatus ProcessKeyValueCompactionWithCompactionService(
SubcompactionState* sub_compact);
struct CompactionIOStatsSnapshot {
PerfLevel prev_perf_level = PerfLevel::kEnableTime;
uint64_t prev_write_nanos = 0;
uint64_t prev_fsync_nanos = 0;
uint64_t prev_range_sync_nanos = 0;
uint64_t prev_prepare_write_nanos = 0;
uint64_t prev_cpu_write_nanos = 0;
uint64_t prev_cpu_read_nanos = 0;
};
struct SubcompactionKeyBoundaries {
const std::optional<const Slice> start;
const std::optional<const Slice> end;
// Boundaries without timestamps for read options
std::optional<Slice> start_without_ts;
std::optional<Slice> end_without_ts;
// Timestamp management
static constexpr char kMaxTs[] =
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff";
std::string max_ts;
Slice ts_slice;
// Internal key boundaries
IterKey start_ikey;
IterKey end_ikey;
Slice start_internal_key;
Slice end_internal_key;
// User key boundaries
Slice start_user_key;
Slice end_user_key;
SubcompactionKeyBoundaries(std::optional<const Slice> start_boundary,
std::optional<const Slice> end_boundary)
: start(start_boundary), end(end_boundary) {}
};
struct SubcompactionInternalIterators {
std::unique_ptr<InternalIterator> raw_input;
std::unique_ptr<InternalIterator> clip;
std::unique_ptr<InternalIterator> blob_counter;
std::unique_ptr<InternalIterator> trim_history_iter;
};
struct BlobFileResources {
std::vector<std::string> blob_file_paths;
std::unique_ptr<BlobFileBuilder> blob_file_builder;
};
bool ShouldUseLocalCompaction(SubcompactionState* sub_compact);
CompactionIOStatsSnapshot InitializeIOStats();
Status SetupAndValidateCompactionFilter(
SubcompactionState* sub_compact,
const CompactionFilter* configured_compaction_filter,
const CompactionFilter*& compaction_filter,
std::unique_ptr<CompactionFilter>& compaction_filter_from_factory);
void InitializeReadOptionsAndBoundaries(
size_t ts_sz, ReadOptions& read_options,
SubcompactionKeyBoundaries& boundaries);
InternalIterator* CreateInputIterator(
SubcompactionState* sub_compact, ColumnFamilyData* cfd,
SubcompactionInternalIterators& iterators,
SubcompactionKeyBoundaries& boundaries, ReadOptions& read_options);
void CreateBlobFileBuilder(SubcompactionState* sub_compact,
ColumnFamilyData* cfd,
BlobFileResources& blob_resources,
const WriteOptions& write_options);
std::unique_ptr<CompactionIterator> CreateCompactionIterator(
SubcompactionState* sub_compact, ColumnFamilyData* cfd,
InternalIterator* input_iter, const CompactionFilter* compaction_filter,
MergeHelper& merge, BlobFileResources& blob_resources,
const WriteOptions& write_options);
std::pair<CompactionFileOpenFunc, CompactionFileCloseFunc> CreateFileHandlers(
SubcompactionState* sub_compact, SubcompactionKeyBoundaries& boundaries);
Status ProcessKeyValue(SubcompactionState* sub_compact, ColumnFamilyData* cfd,
CompactionIterator* c_iter,
const CompactionFileOpenFunc& open_file_func,
const CompactionFileCloseFunc& close_file_func,
uint64_t& prev_cpu_micros);
void UpdateSubcompactionJobStatsIncrementally(
CompactionIterator* c_iter, CompactionJobStats* compaction_job_stats,
uint64_t cur_cpu_micros, uint64_t& prev_cpu_micros);
void FinalizeSubcompactionJobStats(SubcompactionState* sub_compact,
CompactionIterator* c_iter,
uint64_t start_cpu_micros,
uint64_t prev_cpu_micros,
const CompactionIOStatsSnapshot& io_stats);
Status FinalizeProcessKeyValueStatus(ColumnFamilyData* cfd,
InternalIterator* input_iter,
CompactionIterator* c_iter,
Status status);
Status CleanupCompactionFiles(SubcompactionState* sub_compact, Status status,
const CompactionFileOpenFunc& open_file_func,
const CompactionFileCloseFunc& close_file_func);
Status FinalizeBlobFiles(SubcompactionState* sub_compact,
BlobFileBuilder* blob_file_builder, Status status);
void FinalizeSubcompaction(SubcompactionState* sub_compact, Status status,
const CompactionFileOpenFunc& open_file_func,
const CompactionFileCloseFunc& close_file_func,
BlobFileBuilder* blob_file_builder,
CompactionIterator* c_iter,
InternalIterator* input_iter,
uint64_t start_cpu_micros,
uint64_t prev_cpu_micros,
const CompactionIOStatsSnapshot& io_stats);
// update the thread status for starting a compaction.
void ReportStartedCompaction(Compaction* compaction);
Status FinishCompactionOutputFile(const Status& input_status,
SubcompactionState* sub_compact,
CompactionOutputs& outputs,
const Slice& next_table_min_key,
const Slice* comp_start_user_key,
const Slice* comp_end_user_key);
Status FinishCompactionOutputFile(
const Status& input_status,
const ParsedInternalKey& prev_iter_output_internal_key,
const Slice& next_table_min_key, const Slice* comp_start_user_key,
const Slice* comp_end_user_key, const CompactionIterator* c_iter,
SubcompactionState* sub_compact, CompactionOutputs& outputs);
Status InstallCompactionResults(bool* compaction_released);
Status OpenCompactionOutputFile(SubcompactionState* sub_compact,
CompactionOutputs& outputs);
@@ -366,6 +510,9 @@ class CompactionJob {
// Setting this requires DBMutex.
uint64_t options_file_number_ = 0;
// Writer for persisting compaction progress during compaction
log::Writer* compaction_progress_writer_ = nullptr;
// Get table file name in where it's outputting to, which should also be in
// `output_directory_`.
virtual std::string GetTableFileName(uint64_t file_number);
@@ -373,6 +520,43 @@ class CompactionJob {
// The Compaction Read and Write priorities are the same for different
// scenarios, such as write stalled.
Env::IOPriority GetRateLimiterPriority();
Status MaybeResumeSubcompactionProgressOnInputIterator(
SubcompactionState* sub_compact, InternalIterator* input_iter);
Status ReadOutputFilesTableProperties(
const autovector<FileMetaData>& temporary_output_file_allocation,
const ReadOptions& read_options,
std::vector<std::shared_ptr<const TableProperties>>&
output_files_table_properties,
bool is_proximal_level = false);
Status ReadTablePropertiesDirectly(
const ImmutableOptions& ioptions, const MutableCFOptions& moptions,
const FileMetaData* file_meta, const ReadOptions& read_options,
std::shared_ptr<const TableProperties>* tp);
void RestoreCompactionOutputs(
const ColumnFamilyData* cfd,
const std::vector<std::shared_ptr<const TableProperties>>&
output_files_table_properties,
SubcompactionProgressPerLevel& subcompaction_progress_per_level,
CompactionOutputs* outputs_to_restore);
bool ShouldUpdateSubcompactionProgress(
const SubcompactionState* sub_compact, const CompactionIterator* c_iter,
const ParsedInternalKey& prev_iter_output_internal_key,
const Slice& next_table_min_internal_key, const FileMetaData* meta) const;
void UpdateSubcompactionProgress(const CompactionIterator* c_iter,
const Slice next_table_min_key,
SubcompactionState* sub_compact);
Status PersistSubcompactionProgress(SubcompactionState* sub_compact);
void UpdateSubcompactionProgressPerLevel(
SubcompactionState* sub_compact, bool is_proximal_level,
SubcompactionProgress& subcompaction_progress);
};
// CompactionServiceInput is used the pass compaction information between two
@@ -522,7 +706,9 @@ class CompactionServiceCompactionJob : private CompactionJob {
// REQUIRED: mutex held
// Like CompactionJob::Prepare()
void Prepare();
void Prepare(
const CompactionProgress& compaction_progress = CompactionProgress{},
log::Writer* compaction_progress_writer = nullptr);
// Run the compaction in current thread and return the result
Status Run();
+463 -10
View File
@@ -17,6 +17,7 @@
#include "db/db_impl/db_impl.h"
#include "db/error_handler.h"
#include "db/version_set.h"
#include "file/filename.h"
#include "file/random_access_file_reader.h"
#include "file/writable_file_writer.h"
#include "options/options_helper.h"
@@ -210,12 +211,12 @@ class CompactionJobTestBase : public testing::Test {
table_cache_(NewLRUCache(50000, 16)),
write_buffer_manager_(db_options_.db_write_buffer_size),
versions_(new VersionSet(
dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
dbname_, &db_options_, mutable_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, /*read_only=*/false)),
/*error_handler=*/nullptr, /*unchanging=*/false)),
shutting_down_(false),
mock_table_factory_(new mock::MockTableFactory()),
error_handler_(nullptr, db_options_, &mutex_),
@@ -545,13 +546,13 @@ 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,
test::kUnitTestDbId, /*db_session_id=*/"",
/*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr, /*read_only=*/false));
versions_.reset(new VersionSet(
dbname_, &db_options_, mutable_db_options_, env_options_,
table_cache_.get(), &write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
test::kUnitTestDbId, /*db_session_id=*/"",
/*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr, /*unchanging=*/false));
compaction_job_stats_.Reset();
VersionEdit new_db;
@@ -2409,6 +2410,458 @@ TEST_F(CompactionJobIOPriorityTest, GetRateLimiterPriority) {
Env::IO_LOW, Env::IO_LOW);
}
class ResumableCompactionJobTest : public CompactionJobTestBase {
public:
ResumableCompactionJobTest()
: CompactionJobTestBase(
test::PerThreadDBPath("allow_resumption_job_test"),
BytewiseComparator(), [](uint64_t /*ts*/) { return ""; },
/*test_io_priority=*/false, TableTypeForTest::kBlockBasedTable) {}
protected:
static constexpr const char* kCancelBeforeThisKey = "cancel_before_this_key";
std::string progress_dir_;
bool enable_cancel_ = false;
std::atomic<int> stop_count_{0};
std::atomic<bool> cancel_{false};
SequenceNumber cancel_before_seqno = kMaxSequenceNumber;
void SetUp() override {
CompactionJobTestBase::SetUp();
SyncPoint::GetInstance()->SetCallBack(
"CompactionOutputs::ShouldStopBefore::manual_decision",
[this](void* p) {
auto* pair = static_cast<std::pair<bool*, const Slice>*>(p);
*(pair->first) = true;
// Cancel after outputting a specific key
if (enable_cancel_) {
ParsedInternalKey parsed_key;
if (ParseInternalKey(pair->second, &parsed_key, true).ok()) {
if (parsed_key.user_key == kCancelBeforeThisKey &&
(cancel_before_seqno == kMaxSequenceNumber ||
parsed_key.sequence == cancel_before_seqno)) {
cancel_.store(true);
}
}
}
});
SyncPoint::GetInstance()->EnableProcessing();
}
void TearDown() override {
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
if (env_->FileExists(progress_dir_).ok()) {
std::vector<std::string> files;
EXPECT_OK(env_->GetChildren(progress_dir_, &files));
for (const auto& file : files) {
if (file != "." && file != "..") {
EXPECT_OK(env_->DeleteFile(progress_dir_ + "/" + file));
}
}
EXPECT_OK(env_->DeleteDir(progress_dir_));
}
CompactionJobTestBase::TearDown();
}
void NewDB() {
if (env_->FileExists(progress_dir_).ok()) {
std::vector<std::string> files;
EXPECT_OK(env_->GetChildren(progress_dir_, &files));
for (const auto& file : files) {
if (file != "." && file != "..") {
EXPECT_OK(env_->DeleteFile(progress_dir_ + "/" + file));
}
}
EXPECT_OK(env_->DeleteDir(progress_dir_));
}
CompactionJobTestBase::NewDB();
progress_dir_ = test::PerThreadDBPath("compaction_progress");
ASSERT_OK(env_->CreateDirIfMissing(progress_dir_));
}
void EnableCompactionCancel() { enable_cancel_ = true; }
void DisableCompactionCancel() {
enable_cancel_ = false;
cancel_.store(false);
}
std::unique_ptr<log::Writer> CreateCompactionProgressWriter(
const std::string& compaction_progress_file) {
std::unique_ptr<FSWritableFile> file;
EXPECT_OK(fs_->NewWritableFile(compaction_progress_file, FileOptions(),
&file, nullptr));
auto file_writer = std::make_unique<WritableFileWriter>(
std::move(file), compaction_progress_file, FileOptions());
auto compaction_progress_writer =
std::make_unique<log::Writer>(std::move(file_writer), 0, false);
return compaction_progress_writer;
}
Status RunCompactionWithProgressTracking(
const CompactionProgress& compaction_progress,
log::Writer* compaction_progress_writer,
std::vector<SequenceNumber> snapshots = {},
std::shared_ptr<Statistics> stats = nullptr) {
mutex_.Lock();
auto cfd = versions_->GetColumnFamilySet()->GetDefault();
auto files = cfd->current()->storage_info()->LevelFiles(0);
db_options_.statistics = stats;
db_options_.stats = db_options_.statistics.get();
std::vector<CompactionInputFiles> compaction_input_files;
CompactionInputFiles level;
level.level = 0;
level.files = files;
compaction_input_files.push_back(level);
Compaction compaction(
cfd->current()->storage_info(), cfd->ioptions(),
cfd->GetLatestMutableCFOptions(), mutable_db_options_,
compaction_input_files, 1, mutable_cf_options_.target_file_size_base,
mutable_cf_options_.max_compaction_bytes, 0, kNoCompression,
cfd->GetLatestMutableCFOptions().compression_opts,
Temperature::kUnknown, 0, {}, std::nullopt, nullptr,
CompactionReason::kManualCompaction);
compaction.FinalizeInputInfo(cfd->current());
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL, db_options_.info_log.get());
EventLogger event_logger(db_options_.info_log.get());
JobContext job_context(1, false);
job_context.InitSnapshotContext(nullptr, nullptr, kMaxSequenceNumber,
std::move(snapshots));
CompactionJobStats job_stats;
CompactionJob compaction_job(
0, &compaction, db_options_, mutable_db_options_, env_options_,
versions_.get(), &shutting_down_, &log_buffer, nullptr, nullptr,
nullptr, stats.get(), &mutex_, &error_handler_, &job_context,
table_cache_, &event_logger, false, false, dbname_, &job_stats,
Env::Priority::USER, nullptr, cancel_, env_->GenerateUniqueId(),
DBImpl::GenerateDbSessionId(nullptr), "");
compaction_job.Prepare(std::nullopt, compaction_progress,
compaction_progress_writer);
mutex_.Unlock();
compaction_job.Run().PermitUncheckedError();
EXPECT_OK(compaction_job.io_status());
mutex_.Lock();
bool compaction_released = false;
Status s = compaction_job.Install(&compaction_released);
mutex_.Unlock();
if (!compaction_released) {
compaction.ReleaseCompactionFiles(s);
}
return s;
}
SubcompactionProgress ReadAndParseProgress(
const std::string& compaction_progress_file) {
std::unique_ptr<FSSequentialFile> seq_file;
EXPECT_OK(fs_->NewSequentialFile(compaction_progress_file, FileOptions(),
&seq_file, nullptr));
auto file_reader = std::make_unique<SequentialFileReader>(
std::move(seq_file), compaction_progress_file, 0, nullptr);
log::Reader reader(nullptr, std::move(file_reader), nullptr, true, 0);
SubcompactionProgressBuilder builder;
std::string record;
Slice slice;
while (reader.ReadRecord(&slice, &record)) {
VersionEdit edit;
if (!edit.DecodeFrom(slice).ok()) {
continue;
}
builder.ProcessVersionEdit(edit);
}
EXPECT_TRUE(builder.HasAccumulatedSubcompactionProgress());
return builder.GetAccumulatedSubcompactionProgress();
}
// Test utility function to verify that compaction progress was correctly
// persisted to the progress file after compaction interruption.
//
// VERIFIES:
// - Progress file exists and has expected size (empty if no progress
// expected)
// - Next internal key to compact matches expected user key with proper format
// - Number of processed input records matches position in ordered input keys
// - Number of processed output records equals number of processed input
// records (by test design to simplify verification)
// - Each output file contains exactly one user key (by test design to
// simplify verification)
void VerifyCompactionProgressPersisted(
const std::string& compaction_progress_file,
const std::string& next_user_key_to_compact,
const std::vector<std::string>& ordered_intput_keys) {
ASSERT_OK(env_->FileExists(compaction_progress_file));
uint64_t file_size;
ASSERT_OK(env_->GetFileSize(compaction_progress_file, &file_size));
if (next_user_key_to_compact.empty()) {
ASSERT_EQ(file_size, 0);
return;
}
const auto& subcompaction_progress =
ReadAndParseProgress(compaction_progress_file);
ASSERT_FALSE(subcompaction_progress.next_internal_key_to_compact.empty());
ParsedInternalKey parsed_next_key;
ASSERT_OK(
ParseInternalKey(subcompaction_progress.next_internal_key_to_compact,
&parsed_next_key, true /* log_err_key */));
ASSERT_EQ(parsed_next_key.user_key, next_user_key_to_compact);
ASSERT_EQ(parsed_next_key.sequence, kMaxSequenceNumber);
ASSERT_EQ(parsed_next_key.type, kValueTypeForSeek);
auto it = std::find(ordered_intput_keys.begin(), ordered_intput_keys.end(),
next_user_key_to_compact);
ASSERT_TRUE(it != ordered_intput_keys.end());
auto next_key_index = std::distance(ordered_intput_keys.begin(), it);
ASSERT_EQ(subcompaction_progress.num_processed_input_records,
next_key_index);
ASSERT_EQ(subcompaction_progress.output_level_progress
.GetNumProcessedOutputRecords(),
next_key_index);
ASSERT_EQ(
subcompaction_progress.output_level_progress.GetOutputFiles().size(),
next_key_index);
for (size_t i = 0;
i <
subcompaction_progress.output_level_progress.GetOutputFiles().size();
++i) {
const auto& output_file =
subcompaction_progress.output_level_progress.GetOutputFiles()[i];
ASSERT_EQ(output_file.smallest.user_key().ToString(),
output_file.largest.user_key().ToString());
ASSERT_EQ(output_file.largest.user_key().ToString(),
ordered_intput_keys[i]);
}
}
void RunCancelAndResumeTest(
const std::initializer_list<mock::KVPair>& input_file_1,
const std::initializer_list<mock::KVPair>& input_file_2,
uint64_t last_sequence, const std::vector<uint64_t>& snapshots,
const std::string& expected_next_key_to_compact,
const std::vector<std::string>& expected_input_keys,
bool cancelled_past_mid_point = false) {
std::shared_ptr<Statistics> stats = ROCKSDB_NAMESPACE::CreateDBStatistics();
auto file1 = mock::MakeMockFile(input_file_1);
AddMockFile(file1);
auto file2 = mock::MakeMockFile(input_file_2);
AddMockFile(file2);
SetLastSequence(last_sequence);
// First compaction (will be cancelled)
std::string compaction_progress_file =
CompactionProgressFileName(progress_dir_, 123);
std::unique_ptr<log::Writer> compaction_progress_writer =
CreateCompactionProgressWriter(compaction_progress_file);
ASSERT_OK(stats->Reset());
EnableCompactionCancel();
Status status = RunCompactionWithProgressTracking(
CompactionProgress{}, compaction_progress_writer.get(), snapshots,
stats);
ASSERT_TRUE(status.IsManualCompactionPaused());
DisableCompactionCancel();
HistogramData cancelled_compaction_stats;
stats->histogramData(FILE_WRITE_COMPACTION_MICROS,
&cancelled_compaction_stats);
VerifyCompactionProgressPersisted(compaction_progress_file,
expected_next_key_to_compact,
expected_input_keys);
// Resume compaction
CompactionProgress compaction_progress;
if (expected_next_key_to_compact != "") {
compaction_progress.push_back(
ReadAndParseProgress(compaction_progress_file));
}
std::string compaction_progress_file_2 =
CompactionProgressFileName(progress_dir_, 234);
std::unique_ptr<log::Writer> compaction_progress_writer_2 =
CreateCompactionProgressWriter(compaction_progress_file_2);
ASSERT_OK(stats->Reset());
status = RunCompactionWithProgressTracking(
compaction_progress, compaction_progress_writer_2.get(),
{} /* snapshots */, stats);
ASSERT_OK(status);
if (cancelled_past_mid_point) {
HistogramData resumed_compaction_stats;
stats->histogramData(FILE_WRITE_COMPACTION_MICROS,
&resumed_compaction_stats);
ASSERT_GT(cancelled_compaction_stats.count,
resumed_compaction_stats.count);
}
}
};
TEST_F(ResumableCompactionJobTest, BasicProgressPersistence) {
NewDB();
auto file1 = mock::MakeMockFile({
{KeyStr("a", 1U, kTypeValue), "val1"},
{KeyStr("b", 2U, kTypeValue), "val2"},
});
AddMockFile(file1);
auto file2 = mock::MakeMockFile({
{KeyStr("c", 3U, kTypeValue), "val3"},
{KeyStr("d", 4U, kTypeValue), "val4"},
});
AddMockFile(file2);
SetLastSequence(4U);
std::string compaction_progress_file =
CompactionProgressFileName(progress_dir_, 123);
std::unique_ptr<log::Writer> compaction_progress_writer =
CreateCompactionProgressWriter(compaction_progress_file);
Status status = RunCompactionWithProgressTracking(
CompactionProgress(), compaction_progress_writer.get());
ASSERT_OK(status);
VerifyCompactionProgressPersisted(
compaction_progress_file, "d" /* next_user_key_to_compact */,
{"a", "b", "c", "d"} /* ordered_intput_keys */);
}
TEST_F(ResumableCompactionJobTest, BasicProgressResume) {
NewDB();
RunCancelAndResumeTest(
{{KeyStr("a", 1U, kTypeValue), "val1"},
{KeyStr("b", 2U, kTypeValue), "val2"}} /* input_file_1 */,
{{KeyStr("bb", 3U, kTypeValue), "val3"},
{KeyStr(kCancelBeforeThisKey, 4U, kTypeValue),
"val4"}} /* input_file_2 */,
4U /* last_sequence */, {} /* snapshots */,
kCancelBeforeThisKey /* expected_next_key_to_compact */,
{"a", "b", "bb", kCancelBeforeThisKey} /* expected_input_keys */,
true /* cancelled_past_mid_point */);
}
TEST_F(ResumableCompactionJobTest, NoProgressResumeOnSameKey) {
NewDB();
// `cancel_before_seqno` is set to 0U to force cancellation after
// `kCancelBeforeThisKey@1` instead of `kCancelBeforeThisKey@2`.
// The seqno is 0 because `kCancelBeforeThisKey@1` will have its sequence
// number zeroed during compaction while `kCancelBeforeThisKey@2` won't be
cancel_before_seqno = 0U;
RunCancelAndResumeTest(
{{KeyStr(kCancelBeforeThisKey, 1U, kTypeValue),
"val1"}} /* input_file_1 */,
{{KeyStr(kCancelBeforeThisKey, 2U, kTypeValue), "val11"},
{KeyStr("d", 3U, kTypeValue), "val2"}} /* input_file_2 */,
3U /* last_sequence */, {1U} /* snapshots */,
"" /* expected_next_key_to_compact */,
{kCancelBeforeThisKey, kCancelBeforeThisKey,
"d"} /* expected_input_keys */);
}
TEST_F(ResumableCompactionJobTest, NoProgressResumeOnDeleteRange) {
NewDB();
RunCancelAndResumeTest(
{{KeyStr("a", 1U, kTypeValue), "val1"},
{KeyStr("b", 2U, kTypeValue), "val2"},
{KeyStr(kCancelBeforeThisKey, 3U, kTypeValue),
"val3"}} /* input_file_1 */,
{{KeyStr(kCancelBeforeThisKey, 4U, kTypeRangeDeletion),
"range_deletion_end_key"},
{KeyStr("d", 5U, kTypeValue), "val4"}} /* input_file_2 */,
5U /* last_sequence */, {3U} /* snapshots */,
"b" /* expected_next_key_to_compact */,
{"a", "b", kCancelBeforeThisKey, kCancelBeforeThisKey,
"d"} /* expected_input_keys */);
}
TEST_F(ResumableCompactionJobTest, NoProgressResumeOnMerge) {
merge_op_ = MergeOperators::CreateStringAppendOperator();
NewDB();
RunCancelAndResumeTest(
{{KeyStr("a", 1U, kTypeValue), "val1"},
{KeyStr("b", 2U, kTypeValue), "val2"}} /* input_file_1 */,
{{KeyStr("bb", 3U, kTypeValue), "val3"},
{KeyStr(kCancelBeforeThisKey, 4U, kTypeMerge),
"val4"}} /* input_file_2 */,
4U /* last_sequence */, {} /* snapshots */,
"bb" /* expected_next_key_to_compact */,
{"a", "b", "bb", kCancelBeforeThisKey} /* expected_input_keys */);
}
TEST_F(ResumableCompactionJobTest, NoProgressResumeOnSingleDelete) {
NewDB();
RunCancelAndResumeTest(
{{KeyStr("a", 1U, kTypeValue), "val1"},
{KeyStr("b", 2U, kTypeValue), "val2"},
{KeyStr(kCancelBeforeThisKey, 3U, kTypeValue),
"val3"}} /* input_file_1 */,
{{KeyStr(kCancelBeforeThisKey, 4U, kTypeSingleDeletion), ""},
{KeyStr("d", 5U, kTypeValue), "val4"}} /* input_file_2 */,
5U /* last_sequence */, {3U} /* snapshots */,
"b" /* expected_next_key_to_compact */,
{"a", "b", kCancelBeforeThisKey, kCancelBeforeThisKey,
"d"} /* expected_input_keys */);
}
TEST_F(ResumableCompactionJobTest, NoProgressResumeOnDeletionAtBottom) {
NewDB();
RunCancelAndResumeTest(
{{KeyStr("a", 1U, kTypeValue), "val1"},
{KeyStr("b", 2U, kTypeValue), "val2"},
{KeyStr(kCancelBeforeThisKey, 3U, kTypeValue),
"val3"}} /* input_file_1 */,
{{KeyStr(kCancelBeforeThisKey, 4U, kTypeDeletion), ""},
{KeyStr("d", 5U, kTypeValue), "val4"}} /* input_file_2 */,
5U /* last_sequence */, {3U} /* snapshots */,
"b" /* expected_next_key_to_compact */,
{"a", "b", kCancelBeforeThisKey, kCancelBeforeThisKey,
"d"} /* expected_input_keys */);
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+10 -3
View File
@@ -56,6 +56,7 @@ Status CompactionOutputs::Finish(
stats_.bytes_written += current_bytes;
stats_.bytes_written_pre_comp += builder_->PreCompressionSize();
stats_.num_output_files = static_cast<int>(outputs_.size());
worker_cpu_micros_ += builder_->GetWorkerCPUMicros();
return s;
}
@@ -277,7 +278,11 @@ bool CompactionOutputs::ShouldStopBefore(const CompactionIterator& c_iter) {
}
// reach the max file size
if (current_output_file_size_ >= compaction_->max_output_file_size()) {
uint64_t estimated_file_size = current_output_file_size_;
if (compaction_->mutable_cf_options().target_file_size_is_upper_bound) {
estimated_file_size += builder_->EstimatedTailSize();
}
if (estimated_file_size >= compaction_->max_output_file_size()) {
return true;
}
@@ -358,7 +363,8 @@ bool CompactionOutputs::ShouldStopBefore(const CompactionIterator& c_iter) {
Status CompactionOutputs::AddToOutput(
const CompactionIterator& c_iter,
const CompactionFileOpenFunc& open_file_func,
const CompactionFileCloseFunc& close_file_func) {
const CompactionFileCloseFunc& close_file_func,
const ParsedInternalKey& prev_iter_output_internal_key) {
Status s;
bool is_range_del = c_iter.IsDeleteRangeSentinelKey();
if (is_range_del && compaction_->bottommost_level()) {
@@ -369,7 +375,8 @@ Status CompactionOutputs::AddToOutput(
}
const Slice& key = c_iter.key();
if (ShouldStopBefore(c_iter) && HasBuilder()) {
s = close_file_func(*this, c_iter.InputStatus(), key);
s = close_file_func(c_iter.InputStatus(), prev_iter_output_internal_key,
key, &c_iter, *this);
if (!s.ok()) {
return s;
}
+21 -3
View File
@@ -21,7 +21,8 @@ namespace ROCKSDB_NAMESPACE {
class CompactionOutputs;
using CompactionFileOpenFunc = std::function<Status(CompactionOutputs&)>;
using CompactionFileCloseFunc =
std::function<Status(CompactionOutputs&, const Status&, const Slice&)>;
std::function<Status(const Status&, const ParsedInternalKey&, const Slice&,
const CompactionIterator*, CompactionOutputs&)>;
// Files produced by subcompaction, most of the functions are used by
// compaction_job Open/Close compaction file functions.
@@ -58,6 +59,8 @@ class CompactionOutputs {
precalculated_hash, is_proximal_level_);
}
const std::vector<Output>& GetOutputs() const { return outputs_; }
// Set new table builder for the current output
void NewBuilder(const TableBuilderOptions& tboptions);
@@ -168,6 +171,10 @@ class CompactionOutputs {
uint64_t NumEntries() const { return builder_->NumEntries(); }
uint64_t GetWorkerCPUMicros() const {
return worker_cpu_micros_ + (builder_ ? builder_->GetWorkerCPUMicros() : 0);
}
void ResetBuilder() {
builder_.reset();
current_output_file_size_ = 0;
@@ -191,6 +198,10 @@ class CompactionOutputs {
std::pair<SequenceNumber, SequenceNumber> keep_seqno_range,
const Slice& next_table_min_key, const std::string& full_history_ts_low);
void SetNumOutputRecords(uint64_t num_output_records) {
stats_.num_output_records = num_output_records;
}
private:
friend class SubcompactionState;
@@ -250,7 +261,8 @@ class CompactionOutputs {
// close and open new compaction output with the functions provided.
Status AddToOutput(const CompactionIterator& c_iter,
const CompactionFileOpenFunc& open_file_func,
const CompactionFileCloseFunc& close_file_func);
const CompactionFileCloseFunc& close_file_func,
const ParsedInternalKey& prev_iter_output_internal_key);
// Close the current output. `open_file_func` is needed for creating new file
// for range-dels only output file.
@@ -266,9 +278,12 @@ class CompactionOutputs {
!range_del_agg->IsEmpty()) {
status = open_file_func(*this);
}
if (HasBuilder()) {
const ParsedInternalKey empty_internal_key{};
const Slice empty_key{};
Status s = close_file_func(*this, status, empty_key);
Status s = close_file_func(status, empty_internal_key, empty_key,
nullptr /* c_iter */, *this);
if (!s.ok() && status.ok()) {
status = s;
}
@@ -296,6 +311,9 @@ class CompactionOutputs {
uint64_t current_output_file_size_ = 0;
SequenceNumber smallest_preferred_seqno_ = kMaxSequenceNumber;
// Sum of all the GetWorkerCPUMicros() for all the closed builders so far.
uint64_t worker_cpu_micros_ = 0;
// all the compaction outputs so far
std::vector<Output> outputs_;
+25 -18
View File
@@ -242,7 +242,7 @@ bool CompactionPicker::ExpandInputsToCleanCut(const std::string& /*cf_name*/,
GetRange(*inputs, &smallest, &largest);
inputs->clear();
vstorage->GetOverlappingInputs(level, &smallest, &largest, &inputs->files,
hint_index, &hint_index, true,
hint_index, &hint_index, true, nullptr,
next_smallest);
} while (inputs->size() > old_size);
@@ -333,11 +333,13 @@ bool CompactionPicker::AreFilesInCompaction(
return false;
}
Compaction* CompactionPicker::CompactFiles(
Compaction* CompactionPicker::PickCompactionForCompactFiles(
const CompactionOptions& compact_options,
const std::vector<CompactionInputFiles>& input_files, int output_level,
VersionStorageInfo* vstorage, const MutableCFOptions& mutable_cf_options,
const MutableDBOptions& mutable_db_options, uint32_t output_path_id) {
const MutableDBOptions& mutable_db_options, uint32_t output_path_id,
std::optional<SequenceNumber> earliest_snapshot,
const SnapshotChecker* snapshot_checker) {
#ifndef NDEBUG
assert(input_files.size());
// This compaction output should not overlap with a running compaction as
@@ -373,15 +375,16 @@ Compaction* CompactionPicker::CompactFiles(
// without configurable `CompressionOptions`, which is inconsistent.
compression_type = compact_options.compression;
}
auto c = new Compaction(
vstorage, ioptions_, mutable_cf_options, mutable_db_options, input_files,
output_level, compact_options.output_file_size_limit,
mutable_cf_options.max_compaction_bytes, output_path_id, compression_type,
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
mutable_cf_options.default_write_temperature,
compact_options.output_temperature_override,
compact_options.max_subcompactions,
/* grandparents */ {}, /* earliest_snapshot */ std::nullopt,
/* snapshot_checker */ nullptr, CompactionReason::kManualCompaction);
/* grandparents */ {}, earliest_snapshot, snapshot_checker,
CompactionReason::kManualCompaction);
RegisterCompaction(c);
return c;
}
@@ -462,7 +465,8 @@ bool CompactionPicker::SetupOtherInputs(
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
VersionStorageInfo* vstorage, CompactionInputFiles* inputs,
CompactionInputFiles* output_level_inputs, int* parent_index,
int base_index, bool only_expand_towards_right) {
int base_index, bool only_expand_towards_right,
const FileMetaData* starting_l0_file) {
assert(!inputs->empty());
assert(output_level_inputs->empty());
const int input_level = inputs->level;
@@ -518,11 +522,11 @@ bool CompactionPicker::SetupOtherInputs(
// Round-robin compaction only allows expansion towards the larger side.
vstorage->GetOverlappingInputs(input_level, &smallest, &all_limit,
&expanded_inputs.files, base_index,
nullptr);
nullptr, true, starting_l0_file);
} else {
vstorage->GetOverlappingInputs(input_level, &all_start, &all_limit,
&expanded_inputs.files, base_index,
nullptr);
nullptr, true, starting_l0_file);
}
uint64_t expanded_inputs_size = TotalFileSize(expanded_inputs.files);
if (!ExpandInputsToCleanCut(cf_name, vstorage, &expanded_inputs)) {
@@ -601,7 +605,7 @@ void CompactionPicker::GetGrandparents(
}
}
Compaction* CompactionPicker::CompactRange(
Compaction* CompactionPicker::PickCompactionForCompactRange(
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
int input_level, int output_level,
@@ -617,8 +621,8 @@ Compaction* CompactionPicker::CompactRange(
// Universal compaction with more than one level always compacts all the
// files together to the last level.
assert(vstorage->num_levels() > 1);
int max_output_level =
vstorage->MaxOutputLevel(ioptions_.allow_ingest_behind);
int max_output_level = vstorage->MaxOutputLevel(
ioptions_.cf_allow_ingest_behind || ioptions_.allow_ingest_behind);
// DBImpl::CompactRange() set output level to be the last level
assert(output_level == max_output_level);
// DBImpl::RunManualCompaction will make full range for universal compaction
@@ -677,8 +681,7 @@ Compaction* CompactionPicker::CompactRange(
compact_range_options.target_path_id,
GetCompressionType(vstorage, mutable_cf_options, output_level, 1),
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
mutable_cf_options.default_write_temperature,
compact_range_options.max_subcompactions,
Temperature::kUnknown, compact_range_options.max_subcompactions,
/* grandparents */ {}, /* earliest_snapshot */ std::nullopt,
/* snapshot_checker */ nullptr, CompactionReason::kManualCompaction,
trim_ts, /* score */ -1,
@@ -869,8 +872,8 @@ Compaction* CompactionPicker::CompactRange(
GetCompressionType(vstorage, mutable_cf_options, output_level,
vstorage->base_level()),
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
mutable_cf_options.default_write_temperature,
compact_range_options.max_subcompactions, std::move(grandparents),
Temperature::kUnknown, compact_range_options.max_subcompactions,
std::move(grandparents),
/* earliest_snapshot */ std::nullopt, /* snapshot_checker */ nullptr,
CompactionReason::kManualCompaction, trim_ts, /* score */ -1,
/* l0_files_might_overlap */ true,
@@ -1229,7 +1232,7 @@ void CompactionPicker::PickFilesMarkedForCompaction(
bool CompactionPicker::GetOverlappingL0Files(
VersionStorageInfo* vstorage, CompactionInputFiles* start_level_inputs,
int output_level, int* parent_index) {
int output_level, int* parent_index, const FileMetaData* starting_l0_file) {
// Two level 0 compaction won't run at the same time, so don't need to worry
// about files on level 0 being compacted.
assert(level0_compactions_in_progress()->empty());
@@ -1240,7 +1243,11 @@ bool CompactionPicker::GetOverlappingL0Files(
// which will include the picked file.
start_level_inputs->files.clear();
vstorage->GetOverlappingInputs(0, &smallest, &largest,
&(start_level_inputs->files));
&(start_level_inputs->files),
/*hint_index=*/-1,
/*file_index=*/nullptr,
/*expand_range=*/true,
/*starting_l0_file=*/starting_l0_file);
// If we include more L0 files in the same compaction run it can
// cause the 'smallest' and 'largest' key to get extended to a
+31 -21
View File
@@ -75,7 +75,7 @@ class CompactionPicker {
// *compaction_end should point to valid InternalKey!
// REQUIRES: If not compacting all levels (input_level == kCompactAllLevels),
// then levels between input_level and output_level should be empty.
virtual Compaction* CompactRange(
virtual Compaction* PickCompactionForCompactRange(
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
int input_level, int output_level,
@@ -117,12 +117,17 @@ class CompactionPicker {
// Caller must provide a set of input files that has been passed through
// `SanitizeAndConvertCompactionInputFiles` earlier. The lock should not be
// released between that call and this one.
Compaction* CompactFiles(const CompactionOptions& compact_options,
const std::vector<CompactionInputFiles>& input_files,
int output_level, VersionStorageInfo* vstorage,
const MutableCFOptions& mutable_cf_options,
const MutableDBOptions& mutable_db_options,
uint32_t output_path_id);
//
// TODO - Remove default values for earliest_snapshot and snapshot_checker
// and require all callers to pass them in so that DB::CompactFiles() can
// also benefit from Standalone Range Tombstone Optimization
Compaction* PickCompactionForCompactFiles(
const CompactionOptions& compact_options,
const std::vector<CompactionInputFiles>& input_files, int output_level,
VersionStorageInfo* vstorage, const MutableCFOptions& mutable_cf_options,
const MutableDBOptions& mutable_db_options, uint32_t output_path_id,
std::optional<SequenceNumber> earliest_snapshot = std::nullopt,
const SnapshotChecker* snapshot_checker = nullptr);
// Converts a set of compaction input file numbers into
// a list of CompactionInputFiles.
@@ -198,13 +203,16 @@ class CompactionPicker {
const std::vector<CompactionInputFiles>& inputs, int level,
int proximal_level) const;
// @param starting_l0_file If not null, restricts L0 file selection to only
// include files at or older than starting_l0_file.
bool SetupOtherInputs(const std::string& cf_name,
const MutableCFOptions& mutable_cf_options,
VersionStorageInfo* vstorage,
CompactionInputFiles* inputs,
CompactionInputFiles* output_level_inputs,
int* parent_index, int base_index,
bool only_expand_towards_right = false);
bool only_expand_towards_right = false,
const FileMetaData* starting_l0_file = nullptr);
void GetGrandparents(VersionStorageInfo* vstorage,
const CompactionInputFiles& inputs,
@@ -217,9 +225,12 @@ class CompactionPicker {
CompactionInputFiles* start_level_inputs,
std::function<bool(const FileMetaData*)> skip_marked_file);
// @param starting_l0_file If not null, restricts L0 file selection to only
// include files at or older than starting_l0_file.
bool GetOverlappingL0Files(VersionStorageInfo* vstorage,
CompactionInputFiles* start_level_inputs,
int output_level, int* parent_index);
int output_level, int* parent_index,
const FileMetaData* starting_l0_file = nullptr);
// Register this compaction in the set of running compactions
void RegisterCompaction(Compaction* c);
@@ -278,18 +289,17 @@ class NullCompactionPicker : public CompactionPicker {
}
// Always return "nullptr"
Compaction* CompactRange(const std::string& /*cf_name*/,
const MutableCFOptions& /*mutable_cf_options*/,
const MutableDBOptions& /*mutable_db_options*/,
VersionStorageInfo* /*vstorage*/,
int /*input_level*/, int /*output_level*/,
const CompactRangeOptions& /*compact_range_options*/,
const InternalKey* /*begin*/,
const InternalKey* /*end*/,
InternalKey** /*compaction_end*/,
bool* /*manual_conflict*/,
uint64_t /*max_file_num_to_ignore*/,
const std::string& /*trim_ts*/) override {
Compaction* PickCompactionForCompactRange(
const std::string& /*cf_name*/,
const MutableCFOptions& /*mutable_cf_options*/,
const MutableDBOptions& /*mutable_db_options*/,
VersionStorageInfo* /*vstorage*/, int /*input_level*/,
int /*output_level*/,
const CompactRangeOptions& /*compact_range_options*/,
const InternalKey* /*begin*/, const InternalKey* /*end*/,
InternalKey** /*compaction_end*/, bool* /*manual_conflict*/,
uint64_t /*max_file_num_to_ignore*/,
const std::string& /*trim_ts*/) override {
return nullptr;
}
+15 -13
View File
@@ -124,8 +124,7 @@ Compaction* FIFOCompactionPicker::PickTTLCompaction(
Compaction* c = new Compaction(
vstorage, ioptions_, mutable_cf_options, mutable_db_options,
std::move(inputs), 0, 0, 0, 0, kNoCompression,
mutable_cf_options.compression_opts,
mutable_cf_options.default_write_temperature,
mutable_cf_options.compression_opts, Temperature::kUnknown,
/* max_subcompactions */ 0, {}, /* earliest_snapshot */ std::nullopt,
/* snapshot_checker */ nullptr, CompactionReason::kFIFOTtl,
/* trim_ts */ "", vstorage->CompactionScore(0),
@@ -194,8 +193,7 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
{comp_inputs}, 0, 16 * 1024 * 1024 /* output file size limit */,
0 /* max compaction bytes, not applicable */,
0 /* output path ID */, mutable_cf_options.compression,
mutable_cf_options.compression_opts,
mutable_cf_options.default_write_temperature,
mutable_cf_options.compression_opts, Temperature::kUnknown,
0 /* max_subcompactions */, {},
/* earliest_snapshot */ std::nullopt,
/* snapshot_checker */ nullptr,
@@ -258,6 +256,9 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
// better serves a major type of FIFO use cases where smaller keys are
// associated with older data.
for (const auto& f : last_level_files) {
if (f->being_compacted) {
continue;
}
total_size -= f->fd.file_size;
inputs[0].files.push_back(f);
char tmp_fsize[16];
@@ -291,8 +292,7 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
/* target_file_size */ 0,
/* max_compaction_bytes */ 0,
/* output_path_id */ 0, kNoCompression,
mutable_cf_options.compression_opts,
mutable_cf_options.default_write_temperature,
mutable_cf_options.compression_opts, Temperature::kUnknown,
/* max_subcompactions */ 0, {}, /* earliest_snapshot */ std::nullopt,
/* snapshot_checker */ nullptr, CompactionReason::kFIFOMaxSize,
/* trim_ts */ "", vstorage->CompactionScore(0),
@@ -387,12 +387,14 @@ Compaction* FIFOCompactionPicker::PickTemperatureChangeCompaction(
assert(compaction_target_temp == Temperature::kLastTemperature);
compaction_target_temp = cur_target_temp;
inputs[0].files.push_back(cur_file);
ROCKS_LOG_BUFFER(
log_buffer,
"[%s] FIFO compaction: picking file %" PRIu64
" with estimated newest key time %" PRIu64 " for temperature %s.",
cf_name.c_str(), cur_file->fd.GetNumber(), est_newest_key_time,
temperature_to_string[cur_target_temp].c_str());
ROCKS_LOG_BUFFER(log_buffer,
"[%s] FIFO compaction: picking file %" PRIu64
" with estimated newest key time %" PRIu64
" and temperature %s for temperature %s.",
cf_name.c_str(), cur_file->fd.GetNumber(),
est_newest_key_time,
temperature_to_string[cur_file->temperature].c_str(),
temperature_to_string[cur_target_temp].c_str());
break;
}
}
@@ -440,7 +442,7 @@ Compaction* FIFOCompactionPicker::PickCompaction(
return c;
}
Compaction* FIFOCompactionPicker::CompactRange(
Compaction* FIFOCompactionPicker::PickCompactionForCompactRange(
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
int input_level, int output_level,
+8 -10
View File
@@ -26,16 +26,14 @@ class FIFOCompactionPicker : public CompactionPicker {
VersionStorageInfo* version, LogBuffer* log_buffer,
bool /* require_max_output_level*/ = false) override;
Compaction* CompactRange(const std::string& cf_name,
const MutableCFOptions& mutable_cf_options,
const MutableDBOptions& mutable_db_options,
VersionStorageInfo* vstorage, int input_level,
int output_level,
const CompactRangeOptions& compact_range_options,
const InternalKey* begin, const InternalKey* end,
InternalKey** compaction_end, bool* manual_conflict,
uint64_t max_file_num_to_ignore,
const std::string& trim_ts) override;
Compaction* PickCompactionForCompactRange(
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
int input_level, int output_level,
const CompactRangeOptions& compact_range_options,
const InternalKey* begin, const InternalKey* end,
InternalKey** compaction_end, bool* manual_conflict,
uint64_t max_file_num_to_ignore, const std::string& trim_ts) override;
// The maximum allowed output level. Always returns 0.
int MaxOutputLevel() const override { return 0; }
+1 -1
View File
@@ -557,7 +557,7 @@ Compaction* LevelCompactionBuilder::GetCompaction() {
GetCompressionType(vstorage_, mutable_cf_options_, output_level_,
vstorage_->base_level()),
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level_),
mutable_cf_options_.default_write_temperature,
Temperature::kUnknown,
/* max_subcompactions */ 0, std::move(grandparents_),
/* earliest_snapshot */ std::nullopt, /* snapshot_checker */ nullptr,
compaction_reason_,
+172 -81
View File
@@ -544,41 +544,48 @@ TEST_F(CompactionPickerTest, NeedsCompactionUniversal) {
}
TEST_F(CompactionPickerTest, CompactionUniversalIngestBehindReservedLevel) {
const uint64_t kFileSize = 100000;
NewVersionStorage(3 /* num_levels */, kCompactionStyleUniversal);
ioptions_.allow_ingest_behind = true;
ioptions_.num_levels = 3;
UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
UpdateVersionStorageInfo();
// must return false when there's no files.
ASSERT_EQ(universal_compaction_picker.NeedsCompaction(vstorage_.get()),
false);
for (bool cf_option : {false, true}) {
SCOPED_TRACE("cf_option = " + std::to_string(cf_option));
const uint64_t kFileSize = 100000;
NewVersionStorage(3 /* num_levels */, kCompactionStyleUniversal);
if (cf_option) {
ioptions_.cf_allow_ingest_behind = true;
} else {
ioptions_.allow_ingest_behind = true;
}
ioptions_.num_levels = 3;
UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
UpdateVersionStorageInfo();
// must return false when there's no files.
ASSERT_EQ(universal_compaction_picker.NeedsCompaction(vstorage_.get()),
false);
NewVersionStorage(3, kCompactionStyleUniversal);
NewVersionStorage(3, kCompactionStyleUniversal);
Add(0, 1U, "150", "200", kFileSize, 0, 500, 550);
Add(0, 2U, "201", "250", kFileSize, 0, 401, 450);
Add(0, 4U, "260", "300", kFileSize, 0, 260, 300);
Add(1, 5U, "100", "151", kFileSize, 0, 200, 251);
Add(1, 3U, "301", "350", kFileSize, 0, 101, 150);
Add(2, 6U, "120", "200", kFileSize, 0, 20, 100);
Add(0, 1U, "150", "200", kFileSize, 0, 500, 550);
Add(0, 2U, "201", "250", kFileSize, 0, 401, 450);
Add(0, 4U, "260", "300", kFileSize, 0, 260, 300);
Add(1, 5U, "100", "151", kFileSize, 0, 200, 251);
Add(1, 3U, "301", "350", kFileSize, 0, 101, 150);
Add(2, 6U, "120", "200", kFileSize, 0, 20, 100);
UpdateVersionStorageInfo();
UpdateVersionStorageInfo();
std::unique_ptr<Compaction> compaction(
universal_compaction_picker.PickCompaction(
cf_name_, mutable_cf_options_, mutable_db_options_,
/*existing_snapshots=*/{}, /* snapshot_checker */ nullptr,
vstorage_.get(), &log_buffer_));
std::unique_ptr<Compaction> compaction(
universal_compaction_picker.PickCompaction(
cf_name_, mutable_cf_options_, mutable_db_options_,
/*existing_snapshots=*/{}, /* snapshot_checker */ nullptr,
vstorage_.get(), &log_buffer_));
// output level should be the one above the bottom-most
ASSERT_EQ(1, compaction->output_level());
// output level should be the one above the bottom-most
ASSERT_EQ(1, compaction->output_level());
// input should not include the reserved level
const std::vector<CompactionInputFiles>* inputs = compaction->inputs();
for (const auto& compaction_input : *inputs) {
if (!compaction_input.empty()) {
ASSERT_LT(compaction_input.level, 2);
// input should not include the reserved level
const std::vector<CompactionInputFiles>* inputs = compaction->inputs();
for (const auto& compaction_input : *inputs) {
if (!compaction_input.empty()) {
ASSERT_LT(compaction_input.level, 2);
}
}
}
}
@@ -1171,7 +1178,7 @@ TEST_F(CompactionPickerTest, FIFOToCold1) {
ASSERT_TRUE(compaction.get() != nullptr);
ASSERT_EQ(compaction->compaction_reason(),
CompactionReason::kChangeTemperature);
ASSERT_EQ(compaction->output_temperature(), Temperature::kCold);
ASSERT_EQ(compaction->GetOutputTemperature(), Temperature::kCold);
ASSERT_EQ(1U, compaction->num_input_files(0));
ASSERT_EQ(3U, compaction->input(0, 0)->fd.GetNumber());
}
@@ -1241,7 +1248,7 @@ TEST_F(CompactionPickerTest, FIFOToColdMaxCompactionSize) {
ASSERT_EQ(compaction->compaction_reason(),
CompactionReason::kChangeTemperature);
// Compaction picker picks older files first and picks one file at a time.
ASSERT_EQ(compaction->output_temperature(), Temperature::kCold);
ASSERT_EQ(compaction->GetOutputTemperature(), Temperature::kCold);
ASSERT_EQ(1U, compaction->num_input_files(0));
ASSERT_EQ(1U, compaction->input(0, 0)->fd.GetNumber());
}
@@ -1309,7 +1316,7 @@ TEST_F(CompactionPickerTest, FIFOToColdWithExistingCold) {
ASSERT_EQ(compaction->compaction_reason(),
CompactionReason::kChangeTemperature);
// Compaction picker picks older files first and picks one file at a time.
ASSERT_EQ(compaction->output_temperature(), Temperature::kCold);
ASSERT_EQ(compaction->GetOutputTemperature(), Temperature::kCold);
ASSERT_EQ(1U, compaction->num_input_files(0));
ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
}
@@ -1376,7 +1383,7 @@ TEST_F(CompactionPickerTest, FIFOToColdWithHotBetweenCold) {
ASSERT_TRUE(compaction.get() != nullptr);
ASSERT_EQ(compaction->compaction_reason(),
CompactionReason::kChangeTemperature);
ASSERT_EQ(compaction->output_temperature(), Temperature::kCold);
ASSERT_EQ(compaction->GetOutputTemperature(), Temperature::kCold);
ASSERT_EQ(1U, compaction->num_input_files(0));
ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
}
@@ -1457,12 +1464,35 @@ TEST_F(CompactionPickerTest, FIFOToHotAndWarm) {
ASSERT_EQ(compaction->compaction_reason(),
CompactionReason::kChangeTemperature);
// Compaction picker picks older files first and picks one file at a time.
ASSERT_EQ(compaction->output_temperature(), Temperature::kWarm);
ASSERT_EQ(compaction->GetOutputTemperature(), Temperature::kWarm);
ASSERT_EQ(1U, compaction->num_input_files(0));
ASSERT_EQ(1U, compaction->input(0, 0)->fd.GetNumber());
}
}
TEST_F(CompactionPickerTest, CompactFilesOutputTemperature) {
NewVersionStorage(6, kCompactionStyleLevel);
auto file_number = 66U;
Add(0, file_number, "150", "200", 1000000000U);
UpdateVersionStorageInfo();
std::unordered_set<uint64_t> input{file_number};
std::vector<CompactionInputFiles> input_files;
ASSERT_OK(level_compaction_picker.GetCompactionInputsFromFileNumbers(
&input_files, &input, vstorage_.get(), CompactionOptions()));
auto compaction_options = CompactionOptions();
compaction_options.output_temperature_override = Temperature::kCold;
std::unique_ptr<Compaction> compaction(
level_compaction_picker.PickCompactionForCompactFiles(
compaction_options, input_files, 1, vstorage_.get(),
mutable_cf_options_, mutable_db_options_, /*output_path_id=*/0));
ASSERT_TRUE(compaction.get() != nullptr);
ASSERT_EQ(compaction->GetOutputTemperature(), Temperature::kCold);
}
TEST_F(CompactionPickerTest, CompactionPriMinOverlapping1) {
NewVersionStorage(6, kCompactionStyleLevel);
ioptions_.compaction_pri = kMinOverlappingRatio;
@@ -2689,13 +2719,14 @@ TEST_F(CompactionPickerTest, CompactRangeMaxCompactionBytes) {
bool manual_conflict = false;
InternalKey manual_end;
InternalKey* manual_end_ptr = &manual_end;
std::unique_ptr<Compaction> compaction(level_compaction_picker.CompactRange(
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
/*input_level=*/1, /*output_level=*/2,
/*compact_range_options*/ {}, /*begin=*/nullptr, /*end=*/nullptr,
&manual_end_ptr, &manual_conflict,
/*max_file_num_to_ignore=*/std::numeric_limits<uint64_t>::max(),
/*trim_ts=*/""));
std::unique_ptr<Compaction> compaction(
level_compaction_picker.PickCompactionForCompactRange(
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
/*input_level=*/1, /*output_level=*/2,
/*compact_range_options*/ {}, /*begin=*/nullptr, /*end=*/nullptr,
&manual_end_ptr, &manual_conflict,
/*max_file_num_to_ignore=*/std::numeric_limits<uint64_t>::max(),
/*trim_ts=*/""));
ASSERT_TRUE(compaction.get() != nullptr);
ASSERT_EQ(1U, compaction->num_input_levels());
ASSERT_EQ(2, compaction->output_level());
@@ -3627,7 +3658,7 @@ TEST_F(CompactionPickerTest, UniversalMarkedManualCompaction) {
bool manual_conflict = false;
InternalKey* manual_end = nullptr;
std::unique_ptr<Compaction> compaction(
universal_compaction_picker.CompactRange(
universal_compaction_picker.PickCompactionForCompactRange(
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
ColumnFamilyData::kCompactAllLevels, 6, CompactRangeOptions(),
nullptr, nullptr, &manual_end, &manual_conflict,
@@ -3831,9 +3862,10 @@ TEST_F(CompactionPickerU64TsTest, Overlap) {
std::vector<CompactionInputFiles> input_files;
ASSERT_OK(level_compaction_picker.GetCompactionInputsFromFileNumbers(
&input_files, &input, vstorage_.get(), CompactionOptions()));
std::unique_ptr<Compaction> comp1(level_compaction_picker.CompactFiles(
CompactionOptions(), input_files, level, vstorage_.get(),
mutable_cf_options_, mutable_db_options_, /*output_path_id=*/0));
std::unique_ptr<Compaction> comp1(
level_compaction_picker.PickCompactionForCompactFiles(
CompactionOptions(), input_files, level, vstorage_.get(),
mutable_cf_options_, mutable_db_options_, /*output_path_id=*/0));
{
// [600, ts=50000] to [600, ts=50000] is the range to check.
@@ -3942,9 +3974,10 @@ TEST_P(PerKeyPlacementCompactionPickerTest, OverlapWithNormalCompaction) {
ASSERT_OK(level_compaction_picker.GetCompactionInputsFromFileNumbers(
&input_files, &input_set, vstorage_.get(), comp_options));
std::unique_ptr<Compaction> comp1(level_compaction_picker.CompactFiles(
comp_options, input_files, 5, vstorage_.get(), mutable_cf_options_,
mutable_db_options_, 0));
std::unique_ptr<Compaction> comp1(
level_compaction_picker.PickCompactionForCompactFiles(
comp_options, input_files, 5, vstorage_.get(), mutable_cf_options_,
mutable_db_options_, 0));
input_set.clear();
input_files.clear();
@@ -3988,9 +4021,10 @@ TEST_P(PerKeyPlacementCompactionPickerTest, NormalCompactionOverlap) {
ASSERT_OK(level_compaction_picker.GetCompactionInputsFromFileNumbers(
&input_files, &input_set, vstorage_.get(), comp_options));
std::unique_ptr<Compaction> comp1(level_compaction_picker.CompactFiles(
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
mutable_db_options_, 0));
std::unique_ptr<Compaction> comp1(
level_compaction_picker.PickCompactionForCompactFiles(
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
mutable_db_options_, 0));
input_set.clear();
input_files.clear();
@@ -4030,9 +4064,10 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
ASSERT_OK(universal_compaction_picker.GetCompactionInputsFromFileNumbers(
&input_files, &input_set, vstorage_.get(), comp_options));
std::unique_ptr<Compaction> comp1(universal_compaction_picker.CompactFiles(
comp_options, input_files, 5, vstorage_.get(), mutable_cf_options_,
mutable_db_options_, 0));
std::unique_ptr<Compaction> comp1(
universal_compaction_picker.PickCompactionForCompactFiles(
comp_options, input_files, 5, vstorage_.get(), mutable_cf_options_,
mutable_db_options_, 0));
input_set.clear();
input_files.clear();
@@ -4077,9 +4112,10 @@ TEST_P(PerKeyPlacementCompactionPickerTest, NormalCompactionOverlapUniversal) {
ASSERT_OK(universal_compaction_picker.GetCompactionInputsFromFileNumbers(
&input_files, &input_set, vstorage_.get(), comp_options));
std::unique_ptr<Compaction> comp1(universal_compaction_picker.CompactFiles(
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
mutable_db_options_, 0));
std::unique_ptr<Compaction> comp1(
universal_compaction_picker.PickCompactionForCompactFiles(
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
mutable_db_options_, 0));
input_set.clear();
input_files.clear();
@@ -4125,9 +4161,10 @@ TEST_P(PerKeyPlacementCompactionPickerTest, ProximalOverlapUniversal) {
ASSERT_OK(universal_compaction_picker.GetCompactionInputsFromFileNumbers(
&input_files, &input_set, vstorage_.get(), comp_options));
std::unique_ptr<Compaction> comp1(universal_compaction_picker.CompactFiles(
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
mutable_db_options_, 0));
std::unique_ptr<Compaction> comp1(
universal_compaction_picker.PickCompactionForCompactFiles(
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
mutable_db_options_, 0));
input_set.clear();
input_files.clear();
@@ -4176,9 +4213,10 @@ TEST_P(PerKeyPlacementCompactionPickerTest, LastLevelOnlyOverlapUniversal) {
ASSERT_OK(universal_compaction_picker.GetCompactionInputsFromFileNumbers(
&input_files, &input_set, vstorage_.get(), comp_options));
std::unique_ptr<Compaction> comp1(universal_compaction_picker.CompactFiles(
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
mutable_db_options_, 0));
std::unique_ptr<Compaction> comp1(
universal_compaction_picker.PickCompactionForCompactFiles(
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
mutable_db_options_, 0));
// cannot compact file 41 if the preclude_last_level feature is on, otherwise
// compact file 41 is okay.
@@ -4234,9 +4272,10 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
ASSERT_OK(universal_compaction_picker.GetCompactionInputsFromFileNumbers(
&input_files, &input_set, vstorage_.get(), comp_options));
std::unique_ptr<Compaction> comp1(universal_compaction_picker.CompactFiles(
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
mutable_db_options_, 0));
std::unique_ptr<Compaction> comp1(
universal_compaction_picker.PickCompactionForCompactFiles(
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
mutable_db_options_, 0));
ASSERT_TRUE(comp1);
ASSERT_EQ(comp1->GetProximalLevel(), Compaction::kInvalidLevel);
@@ -4252,9 +4291,10 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
ASSERT_FALSE(universal_compaction_picker.FilesRangeOverlapWithCompaction(
input_files, 5, Compaction::kInvalidLevel));
std::unique_ptr<Compaction> comp2(universal_compaction_picker.CompactFiles(
comp_options, input_files, 5, vstorage_.get(), mutable_cf_options_,
mutable_db_options_, 0));
std::unique_ptr<Compaction> comp2(
universal_compaction_picker.PickCompactionForCompactFiles(
comp_options, input_files, 5, vstorage_.get(), mutable_cf_options_,
mutable_db_options_, 0));
ASSERT_TRUE(comp2);
ASSERT_EQ(Compaction::kInvalidLevel, comp2->GetProximalLevel());
}
@@ -4290,9 +4330,10 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
ASSERT_OK(universal_compaction_picker.GetCompactionInputsFromFileNumbers(
&input_files, &input_set, vstorage_.get(), comp_options));
std::unique_ptr<Compaction> comp1(universal_compaction_picker.CompactFiles(
comp_options, input_files, 5, vstorage_.get(), mutable_cf_options_,
mutable_db_options_, 0));
std::unique_ptr<Compaction> comp1(
universal_compaction_picker.PickCompactionForCompactFiles(
comp_options, input_files, 5, vstorage_.get(), mutable_cf_options_,
mutable_db_options_, 0));
ASSERT_TRUE(comp1);
ASSERT_EQ(comp1->GetProximalLevel(), Compaction::kInvalidLevel);
@@ -4310,9 +4351,10 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
vstorage_.get(), mutable_cf_options_, ioptions_, 6, 6)));
if (!enable_per_key_placement_) {
std::unique_ptr<Compaction> comp2(universal_compaction_picker.CompactFiles(
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
mutable_db_options_, 0));
std::unique_ptr<Compaction> comp2(
universal_compaction_picker.PickCompactionForCompactFiles(
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
mutable_db_options_, 0));
ASSERT_TRUE(comp2);
ASSERT_EQ(Compaction::kInvalidLevel, comp2->GetProximalLevel());
}
@@ -4350,9 +4392,10 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
ASSERT_OK(universal_compaction_picker.GetCompactionInputsFromFileNumbers(
&input_files, &input_set, vstorage_.get(), comp_options));
std::unique_ptr<Compaction> comp1(universal_compaction_picker.CompactFiles(
comp_options, input_files, 5, vstorage_.get(), mutable_cf_options_,
mutable_db_options_, 0));
std::unique_ptr<Compaction> comp1(
universal_compaction_picker.PickCompactionForCompactFiles(
comp_options, input_files, 5, vstorage_.get(), mutable_cf_options_,
mutable_db_options_, 0));
ASSERT_TRUE(comp1);
ASSERT_EQ(comp1->GetProximalLevel(), Compaction::kInvalidLevel);
@@ -4370,9 +4413,10 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
ioptions_, 6, 6)));
// 2 compactions can be run in parallel
std::unique_ptr<Compaction> comp2(universal_compaction_picker.CompactFiles(
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
mutable_db_options_, 0));
std::unique_ptr<Compaction> comp2(
universal_compaction_picker.PickCompactionForCompactFiles(
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
mutable_db_options_, 0));
ASSERT_TRUE(comp2);
if (enable_per_key_placement_) {
ASSERT_NE(Compaction::kInvalidLevel, comp2->GetProximalLevel());
@@ -4672,6 +4716,53 @@ TEST_F(CompactionPickerTest, UniversalMaxReadAmpSmallDB) {
}
}
TEST_F(CompactionPickerTest, StandaloneRangeDeletionOnlyPicksOlderFiles) {
NewVersionStorage(6, kCompactionStyleUniversal);
// Create L0 files with overlapping ranges
// File 1: newest regular file (epoch 5), keys [100, 200]
Add(0, 1U, "100", "200", 1U, 0, 100, 100, 0, false, Temperature::kUnknown,
kUnknownOldestAncesterTime, kUnknownNewestKeyTime, Slice(), Slice(), 5);
// File 2: standalone range deletion (epoch 4), keys [150, 250]
// This file should be marked as having only range deletions
Add(0, 2U, "150", "250", 1U, 0, 200, 200, 0, true, Temperature::kUnknown,
kUnknownOldestAncesterTime, kUnknownNewestKeyTime, Slice(), Slice(), 4);
// Manually set file 2 as standalone range deletion
FileMetaData* range_del_file = file_map_[2U].first;
range_del_file->num_entries = 1;
range_del_file->num_range_deletions = 1;
ASSERT_TRUE(range_del_file->FileIsStandAloneRangeTombstone());
Add(4, 10U, "000", "400", 1U);
Add(5, 20U, "000", "400", 100);
UpdateVersionStorageInfo();
UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
ASSERT_TRUE(universal_compaction_picker.NeedsCompaction(vstorage_.get()));
std::unique_ptr<Compaction> compaction(
universal_compaction_picker.PickCompaction(
cf_name_, mutable_cf_options_, mutable_db_options_,
/*existing_snapshots=*/{}, /* snapshot_checker */ nullptr,
vstorage_.get(), &log_buffer_));
ASSERT_NE(nullptr, compaction);
ASSERT_EQ(2U, compaction->num_input_levels());
// First input level should be L0 with only the standalone range del file
// (file 2)
ASSERT_EQ(0, compaction->level(0));
ASSERT_EQ(1U, compaction->num_input_files(0));
ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
ASSERT_TRUE(compaction->input(0, 0)->FileIsStandAloneRangeTombstone());
// Second input level should be L4 with file 10
ASSERT_EQ(4, compaction->level(1));
ASSERT_EQ(1U, compaction->num_input_files(1));
ASSERT_EQ(10U, compaction->input(1, 0)->fd.GetNumber());
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+27 -20
View File
@@ -48,7 +48,9 @@ class UniversalCompactionBuilder {
vstorage_(vstorage),
picker_(picker),
log_buffer_(log_buffer),
require_max_output_level_(require_max_output_level) {
require_max_output_level_(require_max_output_level),
allow_ingest_behind_(ioptions.cf_allow_ingest_behind ||
ioptions.allow_ingest_behind) {
assert(icmp_);
const auto* ucmp = icmp_->user_comparator();
assert(ucmp);
@@ -422,8 +424,7 @@ class UniversalCompactionBuilder {
bool MeetsOutputLevelRequirements(int output_level) const {
return !require_max_output_level_ ||
Compaction::OutputToNonZeroMaxOutputLevel(
output_level,
vstorage_->MaxOutputLevel(ioptions_.allow_ingest_behind));
output_level, vstorage_->MaxOutputLevel(allow_ingest_behind_));
}
const ImmutableOptions& ioptions_;
@@ -437,7 +438,6 @@ class UniversalCompactionBuilder {
VersionStorageInfo* vstorage_;
UniversalCompactionPicker* picker_;
LogBuffer* log_buffer_;
bool require_max_output_level_;
// Optional earliest snapshot at time of compaction picking. This is only
// provided if the column family doesn't enable user-defined timestamps.
// And this information is only passed to `Compaction` picked by deletion
@@ -448,6 +448,8 @@ class UniversalCompactionBuilder {
// marked for compaction. This is only populated when snapshot info is
// populated.
std::map<uint64_t, size_t> file_marked_for_compaction_to_sorted_run_index_;
bool require_max_output_level_;
bool allow_ingest_behind_;
std::vector<UniversalCompactionBuilder::SortedRun> CalculateSortedRuns(
const VersionStorageInfo& vstorage, int last_level,
@@ -733,8 +735,7 @@ bool UniversalCompactionBuilder::ShouldSkipMarkedFile(
Compaction* UniversalCompactionBuilder::PickCompaction() {
const int kLevel0 = 0;
score_ = vstorage_->CompactionScore(kLevel0);
const int max_output_level =
vstorage_->MaxOutputLevel(ioptions_.allow_ingest_behind);
const int max_output_level = vstorage_->MaxOutputLevel(allow_ingest_behind_);
const int file_num_compaction_trigger =
mutable_cf_options_.level0_file_num_compaction_trigger;
const unsigned int ratio =
@@ -781,8 +782,7 @@ Compaction* UniversalCompactionBuilder::PickCompaction() {
"UniversalCompactionBuilder::PickCompaction:Return", nullptr);
return nullptr;
}
assert(c->output_level() <=
vstorage_->MaxOutputLevel(ioptions_.allow_ingest_behind));
assert(c->output_level() <= vstorage_->MaxOutputLevel(allow_ingest_behind_));
assert(MeetsOutputLevelRequirements(c->output_level()));
if (mutable_cf_options_.compaction_options_universal.allow_trivial_move ==
@@ -1024,8 +1024,7 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
int start_level = sorted_runs_[start_index].level;
int output_level;
// last level is reserved for the files ingested behind
int max_output_level =
vstorage_->MaxOutputLevel(ioptions_.allow_ingest_behind);
int max_output_level = vstorage_->MaxOutputLevel(allow_ingest_behind_);
if (first_index_after == sorted_runs_.size()) {
output_level = max_output_level;
} else if (sorted_runs_[first_index_after].level == 0) {
@@ -1094,7 +1093,7 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
output_level, 1, enable_compression),
GetCompressionOptions(mutable_cf_options_, vstorage_,
output_level, enable_compression),
mutable_cf_options_.default_write_temperature,
Temperature::kUnknown,
/* max_subcompactions */ 0, grandparents,
/* earliest_snapshot */ std::nullopt,
/* snapshot_checker */ nullptr, compaction_reason,
@@ -1442,7 +1441,7 @@ Compaction* UniversalCompactionBuilder::PickIncrementalForReduceSizeAmp(
true /* enable_compression */),
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level,
true /* enable_compression */),
mutable_cf_options_.default_write_temperature,
Temperature::kUnknown,
/* max_subcompactions */ 0, /* grandparents */ {},
/* earliest_snapshot */ std::nullopt,
/* snapshot_checker */ nullptr,
@@ -1517,8 +1516,7 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
return nullptr;
}
int max_output_level =
vstorage_->MaxOutputLevel(ioptions_.allow_ingest_behind);
int max_output_level = vstorage_->MaxOutputLevel(allow_ingest_behind_);
// Pick the first non-empty level after the start_level
for (output_level = start_level + 1; output_level <= max_output_level;
output_level++) {
@@ -1546,9 +1544,18 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
}
if (output_level != 0) {
// For standalone range deletion, we don't want to compact it with newer
// L0 files that it doesn't cover.
const FileMetaData* starting_l0_file =
(start_level == 0 && start_level_inputs.size() == 1 &&
start_level_inputs.files[0]->FileIsStandAloneRangeTombstone())
? start_level_inputs.files[0]
: nullptr;
if (start_level == 0) {
if (!picker_->GetOverlappingL0Files(vstorage_, &start_level_inputs,
output_level, nullptr)) {
output_level, nullptr,
starting_l0_file)) {
return nullptr;
}
}
@@ -1559,7 +1566,8 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
output_level_inputs.level = output_level;
if (!picker_->SetupOtherInputs(cf_name_, mutable_cf_options_, vstorage_,
&start_level_inputs, &output_level_inputs,
&parent_index, -1)) {
&parent_index, -1, false,
starting_l0_file)) {
return nullptr;
}
inputs.push_back(start_level_inputs);
@@ -1596,7 +1604,7 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
/* max_grandparent_overlap_bytes */ GetMaxOverlappingBytes(), path_id,
GetCompressionType(vstorage_, mutable_cf_options_, output_level, 1),
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level),
mutable_cf_options_.default_write_temperature,
Temperature::kUnknown,
/* max_subcompactions */ 0, grandparents, earliest_snapshot_,
snapshot_checker_, CompactionReason::kFilesMarkedForCompaction,
/* trim_ts */ "", score_,
@@ -1621,8 +1629,7 @@ Compaction* UniversalCompactionBuilder::PickCompactionWithSortedRunRange(
uint32_t path_id =
GetPathId(ioptions_, mutable_cf_options_, estimated_total_size);
int start_level = sorted_runs_[start_index].level;
int max_output_level =
vstorage_->MaxOutputLevel(ioptions_.allow_ingest_behind);
int max_output_level = vstorage_->MaxOutputLevel(allow_ingest_behind_);
std::vector<CompactionInputFiles> inputs(max_output_level + 1);
for (size_t i = 0; i < inputs.size(); ++i) {
inputs[i].level = start_level + static_cast<int>(i);
@@ -1693,7 +1700,7 @@ Compaction* UniversalCompactionBuilder::PickCompactionWithSortedRunRange(
true /* enable_compression */),
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level,
true /* enable_compression */),
mutable_cf_options_.default_write_temperature,
Temperature::kUnknown,
/* max_subcompactions */ 0, /* grandparents */ {},
/* earliest_snapshot */ std::nullopt,
/* snapshot_checker */ nullptr, compaction_reason,
+6 -3
View File
@@ -326,7 +326,9 @@ CompactionServiceCompactionJob::CompactionServiceCompactionJob(
compaction_input_(compaction_service_input),
compaction_result_(compaction_service_result) {}
void CompactionServiceCompactionJob::Prepare() {
void CompactionServiceCompactionJob::Prepare(
const CompactionProgress& compaction_progress,
log::Writer* compaction_progress_writer) {
std::optional<Slice> begin;
if (compaction_input_.has_begin) {
begin = compaction_input_.begin;
@@ -335,7 +337,8 @@ void CompactionServiceCompactionJob::Prepare() {
if (compaction_input_.has_end) {
end = compaction_input_.end;
}
CompactionJob::Prepare(std::make_pair(begin, end));
CompactionJob::Prepare(std::make_pair(begin, end), compaction_progress,
compaction_progress_writer);
}
Status CompactionServiceCompactionJob::Run() {
@@ -408,7 +411,7 @@ Status CompactionServiceCompactionJob::Run() {
// 2. Update job-level output stats with the aggregated internal_stats_
// Please note that input stats will be updated by primary host when all
// subcompactions are finished
UpdateCompactionJobOutputStats(internal_stats_);
UpdateCompactionJobOutputStatsFromInternalStats(status, internal_stats_);
// and set fields that are not propagated as part of the update
compaction_result_->stats.is_manual_compaction = c->is_manual_compaction();
compaction_result_->stats.is_full_compaction = c->is_full_compaction();
+631 -28
View File
@@ -4,6 +4,7 @@
// (found in the LICENSE.Apache file in the root directory).
#include "db/db_test_util.h"
#include "file/file_util.h"
#include "port/stack_trace.h"
#include "rocksdb/utilities/options_util.h"
#include "table/unique_id_impl.h"
@@ -16,17 +17,17 @@ class MyTestCompactionService : public CompactionService {
MyTestCompactionService(
std::string db_path, Options& options,
std::shared_ptr<Statistics>& statistics,
std::vector<std::shared_ptr<EventListener>>& listeners,
std::vector<std::shared_ptr<EventListener>> listeners,
std::vector<std::shared_ptr<TablePropertiesCollectorFactory>>
table_properties_collector_factories)
: db_path_(std::move(db_path)),
options_(options),
statistics_(statistics),
options_(options),
start_info_("na", "na", "na", 0, "na", 0, Env::TOTAL,
CompactionReason::kUnknown, false, false, false, -1, -1),
wait_info_("na", "na", "na", 0, "na", 0, Env::TOTAL,
CompactionReason::kUnknown, false, false, false, -1, -1),
listeners_(listeners),
listeners_(std::move(listeners)),
table_properties_collector_factories_(
std::move(table_properties_collector_factories)) {}
@@ -72,6 +73,31 @@ class MyTestCompactionService : public CompactionService {
if (is_override_wait_status_) {
return override_wait_status_;
}
CompactionServiceOptionsOverride options_override = GetOptionsOverride();
OpenAndCompactOptions options;
options.canceled = &canceled_;
Status s =
DB::OpenAndCompact(options, db_path_, GetOutputPath(scheduled_job_id),
compaction_input, result, options_override);
{
InstrumentedMutexLock l(&mutex_);
if (is_override_wait_result_) {
*result = override_wait_result_;
}
result_ = *result;
}
compaction_num_.fetch_add(1);
if (s.ok()) {
return CompactionServiceJobStatus::kSuccess;
} else {
return CompactionServiceJobStatus::kFailure;
}
}
CompactionServiceOptionsOverride GetOptionsOverride() {
CompactionServiceOptionsOverride options_override;
options_override.env = options_.env;
options_override.file_checksum_gen_factory =
@@ -94,26 +120,7 @@ class MyTestCompactionService : public CompactionService {
options_override.table_properties_collector_factories =
table_properties_collector_factories_;
}
OpenAndCompactOptions options;
options.canceled = &canceled_;
Status s =
DB::OpenAndCompact(options, db_path_, db_path_ + "/" + scheduled_job_id,
compaction_input, result, options_override);
{
InstrumentedMutexLock l(&mutex_);
if (is_override_wait_result_) {
*result = override_wait_result_;
}
result_ = *result;
}
compaction_num_.fetch_add(1);
if (s.ok()) {
return CompactionServiceJobStatus::kSuccess;
} else {
return CompactionServiceJobStatus::kFailure;
}
return options_override;
}
void CancelAwaitingJobs() override { canceled_ = true; }
@@ -160,14 +167,21 @@ class MyTestCompactionService : public CompactionService {
return final_updated_status_.load();
}
private:
protected:
InstrumentedMutex mutex_;
std::atomic_int compaction_num_{0};
const std::string db_path_;
std::shared_ptr<Statistics> statistics_;
std::map<std::string, std::string> jobs_;
std::map<std::string, CompactionServiceJobInfo> infos_;
const std::string db_path_;
std::string result_;
std::string GetOutputPath(const std::string& scheduled_job_id) {
return db_path_ + "/" + scheduled_job_id;
}
private:
std::atomic_int compaction_num_{0};
Options options_;
std::shared_ptr<Statistics> statistics_;
CompactionServiceJobInfo start_info_;
CompactionServiceJobInfo wait_info_;
bool is_override_start_status_ = false;
@@ -177,7 +191,6 @@ class MyTestCompactionService : public CompactionService {
CompactionServiceJobStatus override_wait_status_ =
CompactionServiceJobStatus::kFailure;
bool is_override_wait_result_ = false;
std::string result_;
std::string override_wait_result_;
std::vector<std::shared_ptr<EventListener>> listeners_;
std::vector<std::shared_ptr<TablePropertiesCollectorFactory>>
@@ -461,6 +474,109 @@ TEST_F(CompactionServiceTest, ManualCompaction) {
ASSERT_EQ(handles_[1]->GetName(), info.cf_name);
}
TEST_F(CompactionServiceTest, StandaloneDeleteRangeTombstoneOptimization) {
Options options = CurrentOptions();
size_t num_files_after_filtered = 0;
SyncPoint::GetInstance()->SetCallBack(
"VersionSet::MakeInputIterator:NewCompactionMergingIterator",
[&](void* arg) {
num_files_after_filtered = *static_cast<size_t*>(arg);
});
SyncPoint::GetInstance()->EnableProcessing();
for (auto compaction_style : {CompactionStyle::kCompactionStyleLevel,
CompactionStyle::kCompactionStyleUniversal}) {
SCOPED_TRACE("Style: " + std::to_string(compaction_style));
options.compaction_style = compaction_style;
ReopenWithCompactionService(&options);
num_files_after_filtered = 0;
std::vector<std::string> files;
{
// Writes first version of data in range partitioned files.
SstFileWriter sst_file_writer(EnvOptions(), options);
std::string file1 = dbname_ + "file1.sst";
ASSERT_OK(sst_file_writer.Open(file1));
ASSERT_OK(sst_file_writer.Put("a", "a1"));
ASSERT_OK(sst_file_writer.Put("b", "b1"));
ExternalSstFileInfo file1_info;
ASSERT_OK(sst_file_writer.Finish(&file1_info));
files.push_back(std::move(file1));
std::string file2 = dbname_ + "file2.sst";
ASSERT_OK(sst_file_writer.Open(file2));
ASSERT_OK(sst_file_writer.Put("x", "x1"));
ASSERT_OK(sst_file_writer.Put("y", "y1"));
ExternalSstFileInfo file2_info;
ASSERT_OK(sst_file_writer.Finish(&file2_info));
files.push_back(std::move(file2));
}
IngestExternalFileOptions ifo;
ASSERT_OK(db_->IngestExternalFile(files, ifo));
ASSERT_EQ(Get("a"), "a1");
ASSERT_EQ(Get("b"), "b1");
ASSERT_EQ(Get("x"), "x1");
ASSERT_EQ(Get("y"), "y1");
ASSERT_EQ(2, NumTableFilesAtLevel(6));
auto my_cs = GetCompactionService();
uint64_t comp_num = my_cs->GetCompactionNum();
{
// Atomically delete old version of data with one range delete file.
// And a new batch of range partitioned files with new version of data.
files.clear();
SstFileWriter sst_file_writer(EnvOptions(), options);
std::string file2 = dbname_ + "file2.sst";
ASSERT_OK(sst_file_writer.Open(file2));
ASSERT_OK(sst_file_writer.DeleteRange("a", "z"));
ExternalSstFileInfo file2_info;
ASSERT_OK(sst_file_writer.Finish(&file2_info));
files.push_back(std::move(file2));
std::string file3 = dbname_ + "file3.sst";
ASSERT_OK(sst_file_writer.Open(file3));
ASSERT_OK(sst_file_writer.Put("a", "a2"));
ASSERT_OK(sst_file_writer.Put("b", "b2"));
ExternalSstFileInfo file3_info;
ASSERT_OK(sst_file_writer.Finish(&file3_info));
files.push_back(std::move(file3));
std::string file4 = dbname_ + "file4.sst";
ASSERT_OK(sst_file_writer.Open(file4));
ASSERT_OK(sst_file_writer.Put("x", "x2"));
ASSERT_OK(sst_file_writer.Put("y", "y2"));
ExternalSstFileInfo file4_info;
ASSERT_OK(sst_file_writer.Finish(&file4_info));
files.push_back(std::move(file4));
}
ASSERT_OK(db_->IngestExternalFile(files, ifo));
ASSERT_OK(db_->WaitForCompact(WaitForCompactOptions()));
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
CompactionServiceResult result;
my_cs->GetResult(&result);
ASSERT_OK(result.status);
ASSERT_TRUE(result.stats.is_manual_compaction);
ASSERT_TRUE(result.stats.is_remote_compaction);
if (compaction_style == kCompactionStyleUniversal) {
ASSERT_EQ(num_files_after_filtered, 1);
} else {
// Not filtered
ASSERT_EQ(num_files_after_filtered, 3);
}
Close();
}
SyncPoint::GetInstance()->DisableProcessing();
}
TEST_F(CompactionServiceTest, CompactionOutputFileIOError) {
Options options = CurrentOptions();
options.disable_auto_compactions = true;
@@ -793,6 +909,79 @@ TEST_F(CompactionServiceTest, VerifyInputRecordCount) {
SyncPoint::GetInstance()->ClearAllCallBacks();
}
TEST_F(CompactionServiceTest, EmptyResult) {
Options options = CurrentOptions();
options.disable_auto_compactions = true;
ReopenWithCompactionService(&options);
GenerateTestData();
auto my_cs = GetCompactionService();
uint64_t comp_num = my_cs->GetCompactionNum();
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
// Delete range to cover entire range
ASSERT_OK(db_->DeleteRange(WriteOptions(), "key", "keyz"));
ASSERT_OK(Flush());
// In this unit test, both remote compaction and primary db instance are
// running in the same process, so NewFileNumber will never have a collision.
// In the real-world remote compactions, when the compaction is indeed running
// in another process, this is not going to be the case.
// To simulate the SST file with the same name created in the tmp directory,
// override the file number in remote compaction to re-use old SST file
// number.
bool need_to_override_file_number = false;
SyncPoint::GetInstance()->SetCallBack(
"DBImplSecondary::OpenAndCompact::BeforeLoadingOptions:0",
[&](void*) { need_to_override_file_number = true; });
SyncPoint::GetInstance()->SetCallBack(
"CompactionJob::OpenCompactionOutputFile::NewFileNumber",
[&](void* file_number) {
if (need_to_override_file_number) {
auto n = static_cast<uint64_t*>(file_number);
ColumnFamilyMetaData cf_meta;
db_->GetColumnFamilyMetaData(&cf_meta);
for (const auto& level : cf_meta.levels) {
for (const auto& file : level.files) {
// Use one of the existing file name
*n = test::GetFileNumber(file.name);
need_to_override_file_number = false;
return;
}
}
}
});
// Inject failure, so that the remote compaction fails after
// ProcessKeyValueCompaction()
SyncPoint::GetInstance()->SetCallBack(
"DBImplSecondary::CompactWithoutInstallation::End", [&](void* status) {
// override job status
auto s = static_cast<Status*>(status);
*s = Status::Aborted("MyTestCompactionService failed to compact!");
});
SyncPoint::GetInstance()->EnableProcessing();
// Compaction should fail and SST files in the primary db should exist
{
ASSERT_NOK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ColumnFamilyMetaData meta;
db_->GetColumnFamilyMetaData(&meta);
for (const auto& level : meta.levels) {
for (const auto& file : level.files) {
std::string fname = file.db_path + "/" + file.name;
ASSERT_OK(db_->GetEnv()->FileExists(fname));
}
}
}
Close();
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
}
TEST_F(CompactionServiceTest, CorruptedOutput) {
Options options = CurrentOptions();
options.disable_auto_compactions = true;
@@ -858,6 +1047,7 @@ TEST_F(CompactionServiceTest, CorruptedOutputParanoidFileCheck) {
Destroy(options);
options.disable_auto_compactions = true;
options.paranoid_file_checks = paranoid_file_check_enabled;
options.verify_output_flags = VerifyOutputFlags::kVerifyNone;
ReopenWithCompactionService(&options);
GenerateTestData();
@@ -912,6 +1102,87 @@ TEST_F(CompactionServiceTest, CorruptedOutputParanoidFileCheck) {
}
}
TEST_F(CompactionServiceTest, CorruptedOutputVerifyOutputFlags) {
for (VerifyOutputFlags verify_output_flags :
{VerifyOutputFlags::kVerifyNone,
VerifyOutputFlags::kEnableForLocalCompaction |
VerifyOutputFlags::kVerifyBlockChecksum,
VerifyOutputFlags::kEnableForRemoteCompaction |
VerifyOutputFlags::kVerifyBlockChecksum,
VerifyOutputFlags::kEnableForRemoteCompaction |
VerifyOutputFlags::kVerifyIteration,
VerifyOutputFlags::kVerifyAll}) {
SCOPED_TRACE(
"verify_output_flags=" +
std::to_string(static_cast<std::underlying_type_t<VerifyOutputFlags>>(
verify_output_flags)));
Options options = CurrentOptions();
Destroy(options);
options.disable_auto_compactions = true;
options.paranoid_file_checks = false;
options.verify_output_flags = verify_output_flags;
ReopenWithCompactionService(&options);
GenerateTestData();
auto my_cs = GetCompactionService();
std::string start_str = Key(15);
std::string end_str = Key(45);
Slice start(start_str);
Slice end(end_str);
uint64_t comp_num = my_cs->GetCompactionNum();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"CompactionServiceCompactionJob::Run:0", [&](void* arg) {
CompactionServiceResult* compaction_result =
*(static_cast<CompactionServiceResult**>(arg));
ASSERT_TRUE(compaction_result != nullptr &&
!compaction_result->output_files.empty());
// Corrupt files here
for (const auto& output_file : compaction_result->output_files) {
std::string file_name =
compaction_result->output_path + "/" + output_file.file_name;
// Corrupt very small range of bytes. This corruption is so small
// that this isn't caught by default light-weight check
ASSERT_OK(test::CorruptFile(env_, file_name, 0, 1,
false /* verifyChecksum */));
}
});
SyncPoint::GetInstance()->EnableProcessing();
const bool is_enabled_for_remote_compaction =
!!(verify_output_flags & VerifyOutputFlags::kEnableForRemoteCompaction);
const bool should_verify_block_checksum =
!!(verify_output_flags & VerifyOutputFlags::kVerifyBlockChecksum);
const bool should_verify_iteration =
!!(verify_output_flags & VerifyOutputFlags::kVerifyIteration);
Status s = db_->CompactRange(CompactRangeOptions(), &start, &end);
if (is_enabled_for_remote_compaction &&
(should_verify_block_checksum || should_verify_iteration)) {
ASSERT_NOK(s);
ASSERT_TRUE(s.IsCorruption());
} else {
// CompactRange() goes through if block checksum wasn't verified
ASSERT_OK(s);
}
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
// On the worker side, the compaction is considered success
// Verification is done on the primary side
CompactionServiceResult result;
my_cs->GetResult(&result);
ASSERT_OK(result.status);
ASSERT_TRUE(result.stats.is_manual_compaction);
ASSERT_TRUE(result.stats.is_remote_compaction);
}
}
TEST_F(CompactionServiceTest, TruncatedOutput) {
Options options = CurrentOptions();
options.disable_auto_compactions = true;
@@ -1829,6 +2100,338 @@ TEST_F(CompactionServiceTest, TablePropertiesCollector) {
ASSERT_TRUE(has_user_property);
}
class ResumableCompactionService : public MyTestCompactionService {
public:
enum class TestScenario {
// Test scenario 1: Two-phase compaction with resumption
// - Phase 1: Cancel the compaction running with resumption enabled (saves
// progress)
// - Phase 2: Resume from saved progress and complete
// Validates: Resumption reduces redundant work
kCancelThenResume,
// Test scenario 2: Two-phase compaction without resumption
// - Phase 1: Cancel the compaction running with resumption enabled (saves
// progress)
// - Phase 2: Start fresh without resumption (ignores saved progress) and
// complete
// Validates: Disabling resumption causes full reprocessing
kCancelThenFreshStart,
// Test scenario 3: Three-phase compaction toggling resumption on/off/on
// - Phase 1: Cancel the compaction running with resumption enabled (saves
// progress)
// - Phase 2: Start fresh wtihout resumption (ignores saved progress) and
// cancel agains
// - Phase 3: Resume with resumption support (loads Phase 1's progress) and
// complete
// Validates: Resumption state can be toggled;
kMultipleCancelToggleResumption
};
ResumableCompactionService(const std::string& db_path, Options& options,
std::shared_ptr<Statistics> statistics,
TestScenario scenario)
: MyTestCompactionService(db_path, options, statistics,
{} /* listeners */,
{} /* table_properties_collector_factories */),
scenario_(scenario) {}
CompactionServiceJobStatus Wait(const std::string& scheduled_job_id,
std::string* result) override {
std::string compaction_input = ExtractCompactionInput(scheduled_job_id);
EXPECT_FALSE(compaction_input.empty());
OpenAndCompactOptions open_and_compaction_options;
auto override_options = GetOptionsOverride();
// Force creation of one key per output file for test simplicity.
// ASSUMPTION: This makes stats.count directly proportional to keys
// processed.
SyncPoint::GetInstance()->SetCallBack(
"CompactionOutputs::ShouldStopBefore::manual_decision", [](void* p) {
auto* pair = static_cast<std::pair<bool*, const Slice>*>(p);
*(pair->first) = true;
});
// Simulate cancelled compaction by overriding status at completion. So
// compaction processes all keys before this point to make stats.count
// comparison straightforward.
SyncPoint::GetInstance()->SetCallBack(
"DBImplSecondary::CompactWithoutInstallation::End", [&](void* status) {
auto s = static_cast<Status*>(status);
*s = Status::Incomplete(Status::SubCode::kManualCompactionPaused);
});
SyncPoint::GetInstance()->EnableProcessing();
// Phase 1: Run compaction with resumption enabled and cancel it
// - Processes all input keys
// - Creates output files and saves progress
// - Status overridden to "paused"
open_and_compaction_options.allow_resumption = true;
auto phase1_stats =
RunCancelledCompaction(open_and_compaction_options, scheduled_job_id,
compaction_input, override_options);
HistogramData phase2_stats;
if (scenario_ == TestScenario::kMultipleCancelToggleResumption) {
// Phase 2: Run compaction WITHOUT resumption (fresh start) and cancel it
// - Delete all files left behind Phase 1 before calling OpenAndCompact()
// - Processes all input keys again from scratch
// - Creates output files but does NOT save progress
// - Status overridden to "paused"
open_and_compaction_options.allow_resumption = false;
// Clean up output folder for fresh start
std::string output_dir = GetOutputPath(scheduled_job_id);
Status cleanup_status = DestroyDir(override_options.env, output_dir);
EXPECT_TRUE(cleanup_status.ok());
EXPECT_OK(override_options.env->CreateDir(output_dir));
phase2_stats =
RunCancelledCompaction(open_and_compaction_options, scheduled_job_id,
compaction_input, override_options);
// Validation: Phase 2 starts from scratch, so it processes the same
// input keys as Phase 1.
// ASSUMPTION: With fixed input (10 keys) and deterministic cancellation
// (after processing), both phases create the same number of output files.
EXPECT_EQ(phase2_stats.count, phase1_stats.count);
}
SyncPoint::GetInstance()->ClearCallBack(
"DBImplSecondary::CompactWithoutInstallation::End");
// Final phase: Run compaction to completion (no cancellation)
if (scenario_ == TestScenario::kMultipleCancelToggleResumption) {
// Attempt to resume but it ends up starting fresh
open_and_compaction_options.allow_resumption = true;
} else if (scenario_ == TestScenario::kCancelThenResume) {
// Resume from Phase 1's saved progress
open_and_compaction_options.allow_resumption = true;
} else { // kCancelThenFreshStart
// Start fresh without resumption
open_and_compaction_options.allow_resumption = false;
// Clean up output folder for fresh start
std::string output_dir = GetOutputPath(scheduled_job_id);
Status cleanup_status = DestroyDir(override_options.env, output_dir);
EXPECT_TRUE(cleanup_status.ok());
EXPECT_OK(override_options.env->CreateDir(output_dir));
}
auto final_phase_stats =
RunCompaction(open_and_compaction_options, scheduled_job_id,
compaction_input, override_options, result);
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
// Validate statistics based on scenario
if (scenario_ == TestScenario::kMultipleCancelToggleResumption) {
// ASSUMPTION: Phase 1 processes all keys before cancellation
EXPECT_GT(phase1_stats.count, 0);
// ASSUMPTION: Phase 2 runs with allow_resumption=false and an empty
// folder. Phase 2 then creates its own output files (but doesn't save
// progress). When Phase 3 starts with allow_resumption=true, it finds no
// progress file exists, so it cannot resume and must start from scratch,
// processing all input keys again.
// Result: Phase 3 does the same amount of work as Phase 1.
EXPECT_EQ(final_phase_stats.count, phase1_stats.count);
} else if (scenario_ == TestScenario::kCancelThenResume) {
// ASSUMPTION: Phase 1 processes all keys before cancellation
EXPECT_GT(phase1_stats.count, 0);
// ASSUMPTION: Phase 1 processes all keys and saves progress before
// cancellation. Final phase resumes from Phase 1's saved progress.
// Since Phase 1 completed all processing before being cancelled, the
// final phase should do less work than Phase 1.
EXPECT_LT(final_phase_stats.count, phase1_stats.count);
} else { // kCancelThenFreshStart
// ASSUMPTION: Phase 1 processes all keys before cancellation
EXPECT_GT(phase1_stats.count, 0);
// ASSUMPTION: Final phase starts fresh without resumption, so it
// processes all input keys again and creates the same number of files
EXPECT_EQ(final_phase_stats.count, phase1_stats.count);
}
StoreResult(*result);
return CompactionServiceJobStatus::kSuccess;
}
private:
std::string ExtractCompactionInput(const std::string& scheduled_job_id) {
InstrumentedMutexLock l(&mutex_);
auto job_index = jobs_.find(scheduled_job_id);
if (job_index == jobs_.end()) {
return "";
}
std::string compaction_input = std::move(job_index->second);
jobs_.erase(job_index);
auto info_index = infos_.find(scheduled_job_id);
if (info_index == infos_.end()) {
return "";
}
infos_.erase(info_index);
return compaction_input;
}
HistogramData RunCancelledCompaction(
const OpenAndCompactOptions& options, const std::string& scheduled_job_id,
const std::string& compaction_input,
const CompactionServiceOptionsOverride& override_options) {
std::string temp_result;
EXPECT_OK(statistics_->Reset());
Status s =
DB::OpenAndCompact(options, db_path_, GetOutputPath(scheduled_job_id),
compaction_input, &temp_result, override_options);
EXPECT_TRUE(s.IsManualCompactionPaused());
HistogramData stats;
statistics_->histogramData(FILE_WRITE_COMPACTION_MICROS, &stats);
return stats;
}
HistogramData RunCompaction(
const OpenAndCompactOptions& options, const std::string& scheduled_job_id,
const std::string& compaction_input,
const CompactionServiceOptionsOverride& override_options,
std::string* result) {
EXPECT_OK(statistics_->Reset());
Status s =
DB::OpenAndCompact(options, db_path_, GetOutputPath(scheduled_job_id),
compaction_input, result, override_options);
EXPECT_TRUE(s.ok());
HistogramData stats;
statistics_->histogramData(FILE_WRITE_COMPACTION_MICROS, &stats);
return stats;
}
void StoreResult(const std::string& result) {
InstrumentedMutexLock l(&mutex_);
result_ = result;
}
TestScenario scenario_;
};
class ResumableCompactionServiceTest : public CompactionServiceTest {
public:
explicit ResumableCompactionServiceTest() : CompactionServiceTest() {}
void RunCompactionCancelTest(
ResumableCompactionService::TestScenario scenario) {
Options options = CurrentOptions();
options.disable_auto_compactions = true;
std::shared_ptr<Statistics> statistics = CreateDBStatistics();
options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
BlockBasedTableOptions table_options;
table_options.verify_compression = true;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
auto resume_cs = std::make_shared<ResumableCompactionService>(
dbname_, options, statistics, scenario);
options.compaction_service = resume_cs;
DestroyAndReopen(options);
GenerateTestData();
ASSERT_OK(statistics->Reset());
CompactRangeOptions cro;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
Status s = db_->CompactRange(cro, nullptr, nullptr);
ASSERT_OK(s);
VerifyTestData();
s = db_->VerifyChecksum();
ASSERT_OK(s);
s = db_->VerifyFileChecksums(ReadOptions());
ASSERT_OK(s);
CompactionServiceResult result;
resume_cs->GetResult(&result);
ASSERT_OK(result.status);
ASSERT_TRUE(result.stats.is_manual_compaction);
ASSERT_TRUE(result.stats.is_remote_compaction);
ASSERT_GT(result.output_files.size(), 0);
uint64_t resumed_bytes =
statistics->getTickerCount(REMOTE_COMPACT_RESUMED_BYTES);
if (scenario ==
ResumableCompactionService::TestScenario::kCancelThenResume) {
// When resuming compaction, some bytes should be resumed from previous
// progress
ASSERT_GT(resumed_bytes, 0);
} else if (scenario == ResumableCompactionService::TestScenario::
kCancelThenFreshStart) {
// When starting fresh (ignoring existing progress), no bytes should be
// resumed
ASSERT_EQ(resumed_bytes, 0);
} else { // kMultipleCancelToggleResumption
// Phase 2 ran without resumption (fresh start), so Phase 3 has no
// progress to resume from. It processes all keys again from scratch.
ASSERT_EQ(resumed_bytes, 0);
}
}
void GenerateTestData() {
for (int i = 0; i < kNumKeys; ++i) {
ASSERT_OK(Put(Key(i), "value"));
ASSERT_OK(Flush());
if (i % 2 == 0) {
ASSERT_OK(Delete(Key(i)));
ASSERT_OK(Flush());
}
}
}
void VerifyTestData() {
for (int i = 0; i < kNumKeys; ++i) {
if (i % 2 == 0) {
ASSERT_EQ("NOT_FOUND", Get((Key(i))));
} else {
ASSERT_EQ("value", Get((Key(i))));
}
}
}
private:
static constexpr int kNumKeys = 10;
};
TEST_F(ResumableCompactionServiceTest, CompactionCancelThenResume) {
RunCompactionCancelTest(
ResumableCompactionService::TestScenario::kCancelThenResume);
}
TEST_F(ResumableCompactionServiceTest, CompactionCancelThenFreshStart) {
RunCompactionCancelTest(
ResumableCompactionService::TestScenario::kCancelThenFreshStart);
}
TEST_F(ResumableCompactionServiceTest,
CompactionMultipleCancelToggleResumption) {
RunCompactionCancelTest(ResumableCompactionService::TestScenario::
kMultipleCancelToggleResumption);
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+4 -2
View File
@@ -108,11 +108,13 @@ Slice SubcompactionState::LargestUserKey() const {
Status SubcompactionState::AddToOutput(
const CompactionIterator& iter, bool use_proximal_output,
const CompactionFileOpenFunc& open_file_func,
const CompactionFileCloseFunc& close_file_func) {
const CompactionFileCloseFunc& close_file_func,
const ParsedInternalKey& prev_iter_output_internal_key) {
// update target output
current_outputs_ =
use_proximal_output ? &proximal_level_outputs_ : &compaction_outputs_;
return current_outputs_->AddToOutput(iter, open_file_func, close_file_func);
return current_outputs_->AddToOutput(iter, open_file_func, close_file_func,
prev_iter_output_internal_key);
}
} // namespace ROCKSDB_NAMESPACE
+21 -1
View File
@@ -191,6 +191,14 @@ class SubcompactionState {
return &compaction_outputs_.stats_;
}
uint64_t GetWorkerCPUMicros() const {
uint64_t rv = compaction_outputs_.GetWorkerCPUMicros();
if (compaction->SupportsPerKeyPlacement()) {
rv += proximal_level_outputs_.GetWorkerCPUMicros();
}
return rv;
}
CompactionRangeDelAggregator* RangeDelAgg() const {
return range_del_agg_.get();
}
@@ -200,10 +208,20 @@ class SubcompactionState {
return range_del_agg_ && !range_del_agg_->IsEmpty();
}
void SetSubcompactionProgress(
const SubcompactionProgress& subcompaction_progress) {
subcompaction_progress_ = subcompaction_progress;
}
SubcompactionProgress& GetSubcompactionProgressRef() {
return subcompaction_progress_;
}
// Add compaction_iterator key/value to the `Current` output group.
Status AddToOutput(const CompactionIterator& iter, bool use_proximal_output,
const CompactionFileOpenFunc& open_file_func,
const CompactionFileCloseFunc& close_file_func);
const CompactionFileCloseFunc& close_file_func,
const ParsedInternalKey& prev_iter_output_internal_key);
// Close all compaction output files, both output_to_proximal_level outputs
// and normal outputs.
@@ -233,6 +251,8 @@ class SubcompactionState {
CompactionOutputs proximal_level_outputs_;
CompactionOutputs* current_outputs_ = &compaction_outputs_;
std::unique_ptr<CompactionRangeDelAggregator> range_del_agg_;
SubcompactionProgress subcompaction_progress_;
};
} // namespace ROCKSDB_NAMESPACE
+16 -3
View File
@@ -1764,7 +1764,10 @@ TEST_P(PrecludeLastLevelTest, SmallPrecludeTime) {
options.env = mock_env_.get();
options.level0_file_num_compaction_trigger = kNumTrigger;
options.num_levels = kNumLevels;
options.last_level_temperature = Temperature::kCold;
// This existing test selected to also check the case of various temperatures
// for last_level_temperature, which should not be interesting enough to
// exercise across many/all test cases
options.last_level_temperature = RandomKnownTemperature();
DestroyAndReopen(options);
Random rnd(301);
@@ -1791,6 +1794,10 @@ TEST_P(PrecludeLastLevelTest, SmallPrecludeTime) {
ASSERT_FALSE(tp_mapping.Empty());
auto seqs = tp_mapping.TEST_GetInternalMapping();
ASSERT_FALSE(seqs.empty());
ASSERT_GE(GetSstSizeHelper(Temperature::kUnknown), 1);
for (auto t : kKnownTemperatures) {
ASSERT_EQ(GetSstSizeHelper(t), 0);
}
// Wait more than preclude_last_level time, then make sure all the data is
// compacted to the last level even there's no write (no seqno -> time
@@ -1799,8 +1806,14 @@ TEST_P(PrecludeLastLevelTest, SmallPrecludeTime) {
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
ASSERT_EQ(GetSstSizeHelper(Temperature::kUnknown), 0);
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
for (auto t : kKnownTemperatures) {
if (t == options.last_level_temperature) {
ASSERT_GT(GetSstSizeHelper(t), 0);
} else {
ASSERT_EQ(GetSstSizeHelper(t), 0);
}
}
Close();
}
+1 -24
View File
@@ -675,30 +675,6 @@ TEST_F(DBBasicTest, Flush) {
} while (ChangeCompactOptions());
}
TEST_F(DBBasicTest, ManifestRollOver) {
do {
Options options;
options.max_manifest_file_size = 10; // 10 bytes
options = CurrentOptions(options);
CreateAndReopenWithCF({"pikachu"}, options);
{
ASSERT_OK(Put(1, "manifest_key1", std::string(1000, '1')));
ASSERT_OK(Put(1, "manifest_key2", std::string(1000, '2')));
ASSERT_OK(Put(1, "manifest_key3", std::string(1000, '3')));
uint64_t manifest_before_flush = dbfull()->TEST_Current_Manifest_FileNo();
ASSERT_OK(Flush(1)); // This should trigger LogAndApply.
uint64_t manifest_after_flush = dbfull()->TEST_Current_Manifest_FileNo();
ASSERT_GT(manifest_after_flush, manifest_before_flush);
ReopenWithColumnFamilies({"default", "pikachu"}, options);
ASSERT_GT(dbfull()->TEST_Current_Manifest_FileNo(), manifest_after_flush);
// check if a new manifest file got inserted or not.
ASSERT_EQ(std::string(1000, '1'), Get(1, "manifest_key1"));
ASSERT_EQ(std::string(1000, '2'), Get(1, "manifest_key2"));
ASSERT_EQ(std::string(1000, '3'), Get(1, "manifest_key3"));
}
} while (ChangeCompactOptions());
}
TEST_F(DBBasicTest, IdentityAcrossRestarts) {
constexpr size_t kMinIdSize = 10;
do {
@@ -5087,6 +5063,7 @@ TEST_F(DBBasicTest, DisallowMemtableWrite) {
Options options_disallow = options_allow;
options_disallow.disallow_memtable_writes = true;
options_disallow.paranoid_memory_checks = true;
options_disallow.memtable_veirfy_per_key_checksum_on_seek = true;
DestroyAndReopen(options_allow);
// CFs allowing and disallowing memtable write
+9 -1
View File
@@ -710,12 +710,20 @@ class AlwaysTrueBitsBuilder : public FilterBitsBuilder {
count_ = 0;
// Interpreted as "always true" filter (0 probes over 1 byte of
// payload, 5 bytes metadata)
return Slice("\0\0\0\0\0\0", 6);
return Slice("\0\0\0\0\0\0", kAlwaysTrueFilterBytes);
}
using FilterBitsBuilder::Finish;
size_t ApproximateNumEntries(size_t) override { return SIZE_MAX; }
size_t CalculateSpace(size_t /* num_entries */) override {
return kAlwaysTrueFilterBytes;
}
double EstimatedFpRate(size_t /* num_entries */,
size_t /* bytes */) override {
return 1.0;
}
private:
static constexpr size_t kAlwaysTrueFilterBytes = 6;
size_t count_ = 0;
};
+513 -1
View File
@@ -19,6 +19,7 @@
#include "rocksdb/advanced_options.h"
#include "rocksdb/concurrent_task_limiter.h"
#include "rocksdb/experimental.h"
#include "rocksdb/iostats_context.h"
#include "rocksdb/sst_file_writer.h"
#include "test_util/mock_time_env.h"
#include "test_util/sync_point.h"
@@ -74,6 +75,43 @@ class CompactionStatsCollector : public EventListener {
std::vector<std::atomic<int>> compaction_completed_;
};
class DeletionTriggeredCompactionWithMinFileSizeTestListener
: public EventListener {
public:
explicit DeletionTriggeredCompactionWithMinFileSizeTestListener(
uint64_t min_file_size)
: min_file_size_(min_file_size) {}
void OnCompactionBegin(DB* db, const CompactionJobInfo& ci) override {
ASSERT_EQ(ci.compaction_reason,
CompactionReason::kFilesMarkedForCompaction);
auto env = db->GetEnv();
const std::vector<DbPath>& db_paths = db->GetOptions().db_paths;
for (const auto& file : ci.input_file_infos) {
uint64_t file_size = GetSstFileSize(env, db_paths, file.file_number);
// Assert that the file size respects the minimum threshold
ASSERT_GE(file_size, min_file_size_);
}
}
private:
static uint64_t GetSstFileSize(Env* env, const std::vector<DbPath>& db_paths,
uint64_t file_number) {
uint32_t path_id = 0; // since only one path
std::string sst_file_name = TableFileName(db_paths, file_number, path_id);
uint64_t file_size = 0;
Status s = env->GetFileSize(sst_file_name, &file_size);
if (!s.ok()) {
return 0;
}
return file_size;
}
uint64_t min_file_size_;
};
class DBCompactionTest : public DBTestBase {
public:
DBCompactionTest()
@@ -1371,6 +1409,89 @@ TEST_F(DBCompactionTest, RecoverDuringMemtableCompaction) {
} while (ChangeOptions());
}
TEST_F(DBCompactionTest, CompactionWithDeletionsAndMinFileSize) {
const uint64_t kMinFileSize = 32 * 1024; // 32KB
const int kDeletionTriggerCount = 50;
const int kInitialKeyCount = 100;
const int kAdditionalKeyCount = 50;
const int kValueSize = 1024;
const int kSmallValueSize = 512;
const int kSeed = 301;
Options options = CurrentOptions();
options.compaction_style = kCompactionStyleLevel;
options.write_buffer_size = 1024 * 1024; // 1MB
options.level0_file_num_compaction_trigger = 100;
options.table_properties_collector_factories = {
NewCompactOnDeletionCollectorFactory(
kInitialKeyCount /* sliding window size */, kDeletionTriggerCount,
0.5 /* deletion ratio */, kMinFileSize)};
auto listener =
new DeletionTriggeredCompactionWithMinFileSizeTestListener(kMinFileSize);
options.listeners.emplace_back(listener);
DestroyAndReopen(options);
Random rnd(kSeed);
// Create a large file that will be subject to DTC later
for (int i = 0; i < kInitialKeyCount; i++) {
ASSERT_OK(Put(Key(i), rnd.RandomString(kValueSize)));
}
ASSERT_OK(Flush());
std::vector<LiveFileMetaData> initial_metadata;
db_->GetLiveFilesMetaData(&initial_metadata);
ASSERT_EQ(initial_metadata.size(), 1);
// Create small files that should not trigger compaction
ASSERT_OK(Put("small_file_key1", rnd.RandomString(kSmallValueSize)));
ASSERT_OK(Put("small_file_key2", rnd.RandomString(kSmallValueSize)));
ASSERT_OK(Flush());
ASSERT_OK(Delete("small_file_key1"));
ASSERT_OK(Flush());
// Create a file with enough deletions and size to trigger DTC
// Delete keys from the large file to reach deletion threshold
for (int i = 0; i < kDeletionTriggerCount; i++) {
ASSERT_OK(Delete(Key(i)));
}
// Add new keys to ensure the deletion file meets the min_file_size threshold
for (int i = kInitialKeyCount; i < kInitialKeyCount + kAdditionalKeyCount;
i++) {
ASSERT_OK(Put(Key(i), rnd.RandomString(kValueSize)));
}
ASSERT_OK(Flush());
ASSERT_OK(dbfull()->TEST_WaitForCompact());
// Verify file count after compaction
ASSERT_EQ(NumTableFilesAtLevel(0), 2); // Small file and deletion file
ASSERT_EQ(NumTableFilesAtLevel(1), 1); // Compacted large file
// Verify deleted keys are gone
for (int i = 0; i < kDeletionTriggerCount; i++) {
std::string value;
ASSERT_TRUE(db_->Get(ReadOptions(), Key(i), &value).IsNotFound());
}
// Verify non-deleted keys from large file are still accessible
for (int i = kDeletionTriggerCount; i < kInitialKeyCount; i++) {
std::string value;
ASSERT_OK(db_->Get(ReadOptions(), Key(i), &value));
ASSERT_EQ(value.size(), kValueSize);
}
// Verify new keys are accessible
for (int i = kInitialKeyCount; i < kInitialKeyCount + kAdditionalKeyCount;
i++) {
std::string value;
ASSERT_OK(db_->Get(ReadOptions(), Key(i), &value));
ASSERT_EQ(value.size(), kValueSize);
}
}
TEST_P(DBCompactionTestWithParam, TrivialMoveOneFile) {
int32_t trivial_move = 0;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
@@ -6748,7 +6869,11 @@ INSTANTIATE_TEST_CASE_P(RoundRobinSubcompactionsAgainstPressureToken,
RoundRobinSubcompactionsAgainstPressureToken,
testing::Bool());
TEST_P(RoundRobinSubcompactionsAgainstResources, SubcompactionsUsingResources) {
// FIXME: the test is flaky and failing the assertion
// ASSERT_EQ(actual_reserved_threads, expected_reserved_threads);
// It's likely a test set up issue, fix if we are to use RoubdRobin compaction.
TEST_P(RoundRobinSubcompactionsAgainstResources,
DISABLED_SubcompactionsUsingResources) {
const int kKeysPerBuffer = 200;
Options options = CurrentOptions();
options.num_levels = 4;
@@ -7004,6 +7129,70 @@ TEST_F(DBCompactionTest, PartialManualCompaction) {
ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr));
}
TEST_F(DBCompactionTest, ConcurrentFIFOPickingSameFileBug) {
Options opts = CurrentOptions();
opts.compaction_style = CompactionStyle::kCompactionStyleLevel;
opts.num_levels = 3;
opts.disable_auto_compactions = true;
opts.max_background_jobs = 3;
DestroyAndReopen(opts);
ASSERT_OK(Put("k1", "v1"));
ASSERT_OK(Flush());
// Create a non-L0 SST file for multi-level FIFO size-based compaction later
MoveFilesToLevel(2);
Options opts_new(opts);
opts_new.compaction_style = CompactionStyle::kCompactionStyleFIFO;
opts_new.max_open_files = -1;
// Set a low threshold to trigger multi-level size-based compaction
opts_new.compaction_options_fifo.max_table_files_size = 1;
Reopen(opts_new);
const CompactRangeOptions cro;
const Slice begin_key("k1");
const Slice end_key("k2");
std::unique_ptr<port::Thread> concurrent_compaction;
bool within_first_compaction = true;
SyncPoint::GetInstance()->SetCallBack(
"VersionSet::LogAndApply:WriteManifestStart", [&](void* /*arg*/) {
if (!within_first_compaction) {
return;
}
within_first_compaction = false;
// To allow the second/concurrent compaction to still see the non-L0
// SST file and coerce the bug of picking that file
SyncPoint::GetInstance()->LoadDependency({
{"DBImpl::BackgroundCompaction:BeforeCompaction",
"VersionSet::LogAndApply:WriteManifest"},
});
concurrent_compaction.reset(new port::Thread([&]() {
// Before the fix, the second CompactRange() will either fail the
// assertion of double file picking `being_compacted !=
// inputs_[i][j]->being_compacted` in debug mode or cause LSM shape
// corruption "Cannot delete table file XXX from level 2 since it is
// not in the LSM tree" in release mode
Status s = db_->CompactRange(cro, &begin_key, &end_key);
ASSERT_OK(s);
}));
});
SyncPoint::GetInstance()->EnableProcessing();
Status s = db_->CompactRange(cro, &begin_key, &end_key);
SyncPoint::GetInstance()->DisableProcessing();
ASSERT_OK(s);
concurrent_compaction->join();
}
TEST_F(DBCompactionTest, ManualCompactionFailsInReadOnlyMode) {
// Regression test for bug where manual compaction hangs forever when the DB
// is in read-only mode. Verify it now at least returns, despite failing.
@@ -9603,6 +9792,7 @@ TEST_F(DBCompactionTest, FIFOChangeTemperature) {
int total_cold = 0;
int total_warm = 0;
int total_hot = 0;
int total_ice = 0;
int total_unknown = 0;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"NewWritableFile::FileOptions.temperature", [&](void* arg) {
@@ -9613,6 +9803,8 @@ TEST_F(DBCompactionTest, FIFOChangeTemperature) {
total_warm++;
} else if (temperature == Temperature::kHot) {
total_hot++;
} else if (temperature == Temperature::kIce) {
total_ice++;
} else {
assert(temperature == Temperature::kUnknown);
total_unknown++;
@@ -9686,6 +9878,261 @@ TEST_F(DBCompactionTest, FIFOChangeTemperature) {
}
}
using TemperatureSet = SmallEnumSet<Temperature, Temperature::kLastTemperature>;
static void VerifyTemperatureFileReadStats(const Statistics& st,
TemperatureSet temps) {
SCOPED_TRACE("Temp set size = " + std::to_string(temps.count()));
constexpr uint64_t min_bytes = 100;
constexpr uint64_t min_count = 1;
IOStatsContext* iostats = get_iostats_context();
if (temps.Contains(Temperature::kHot)) {
EXPECT_GE(st.getTickerCount(HOT_FILE_READ_BYTES), min_bytes);
EXPECT_GE(st.getTickerCount(HOT_FILE_READ_COUNT), min_count);
EXPECT_GE(iostats->file_io_stats_by_temperature.hot_file_bytes_read,
min_bytes);
EXPECT_GE(iostats->file_io_stats_by_temperature.hot_file_read_count,
min_count);
} else {
EXPECT_EQ(st.getTickerCount(HOT_FILE_READ_BYTES), 0);
EXPECT_EQ(st.getTickerCount(HOT_FILE_READ_COUNT), 0);
EXPECT_EQ(iostats->file_io_stats_by_temperature.hot_file_bytes_read, 0);
EXPECT_EQ(iostats->file_io_stats_by_temperature.hot_file_read_count, 0);
}
if (temps.Contains(Temperature::kWarm)) {
EXPECT_GE(st.getTickerCount(WARM_FILE_READ_BYTES), min_bytes);
EXPECT_GE(st.getTickerCount(WARM_FILE_READ_COUNT), min_count);
EXPECT_GE(iostats->file_io_stats_by_temperature.warm_file_bytes_read,
min_bytes);
EXPECT_GE(iostats->file_io_stats_by_temperature.warm_file_read_count,
min_count);
} else {
EXPECT_EQ(st.getTickerCount(WARM_FILE_READ_BYTES), 0);
EXPECT_EQ(st.getTickerCount(WARM_FILE_READ_COUNT), 0);
EXPECT_EQ(iostats->file_io_stats_by_temperature.warm_file_bytes_read, 0);
EXPECT_EQ(iostats->file_io_stats_by_temperature.warm_file_read_count, 0);
}
if (temps.Contains(Temperature::kCool)) {
EXPECT_GE(st.getTickerCount(COOL_FILE_READ_BYTES), min_bytes);
EXPECT_GE(st.getTickerCount(COOL_FILE_READ_COUNT), min_count);
EXPECT_GE(iostats->file_io_stats_by_temperature.cool_file_bytes_read,
min_bytes);
EXPECT_GE(iostats->file_io_stats_by_temperature.cool_file_read_count,
min_count);
} else {
EXPECT_EQ(st.getTickerCount(COOL_FILE_READ_BYTES), 0);
EXPECT_EQ(st.getTickerCount(COOL_FILE_READ_COUNT), 0);
EXPECT_EQ(iostats->file_io_stats_by_temperature.cool_file_bytes_read, 0);
EXPECT_EQ(iostats->file_io_stats_by_temperature.cool_file_read_count, 0);
}
if (temps.Contains(Temperature::kCold)) {
EXPECT_GE(st.getTickerCount(COLD_FILE_READ_BYTES), min_bytes);
EXPECT_GE(st.getTickerCount(COLD_FILE_READ_COUNT), min_count);
EXPECT_GE(iostats->file_io_stats_by_temperature.cold_file_bytes_read,
min_bytes);
EXPECT_GE(iostats->file_io_stats_by_temperature.cold_file_read_count,
min_count);
} else {
EXPECT_EQ(st.getTickerCount(COLD_FILE_READ_BYTES), 0);
EXPECT_EQ(st.getTickerCount(COLD_FILE_READ_COUNT), 0);
EXPECT_EQ(iostats->file_io_stats_by_temperature.cold_file_bytes_read, 0);
EXPECT_EQ(iostats->file_io_stats_by_temperature.cold_file_read_count, 0);
}
if (temps.Contains(Temperature::kIce)) {
EXPECT_GE(st.getTickerCount(ICE_FILE_READ_BYTES), min_bytes);
EXPECT_GE(st.getTickerCount(ICE_FILE_READ_COUNT), min_count);
EXPECT_GE(iostats->file_io_stats_by_temperature.ice_file_bytes_read,
min_bytes);
EXPECT_GE(iostats->file_io_stats_by_temperature.ice_file_read_count,
min_count);
} else {
EXPECT_EQ(st.getTickerCount(ICE_FILE_READ_BYTES), 0);
EXPECT_EQ(st.getTickerCount(ICE_FILE_READ_COUNT), 0);
EXPECT_EQ(iostats->file_io_stats_by_temperature.ice_file_bytes_read, 0);
EXPECT_EQ(iostats->file_io_stats_by_temperature.ice_file_read_count, 0);
}
}
TEST_F(DBCompactionTest, FIFOMultiTierTemperatureAging) {
// Test multi-tier aging: Hot -> Warm -> Cool -> Cold -> Ice
Options options = CurrentOptions();
options.compaction_style = kCompactionStyleFIFO;
options.num_levels = 1;
options.max_open_files = -1;
options.level0_file_num_compaction_trigger = 2;
options.create_if_missing = true;
options.statistics = CreateDBStatistics();
BlockBasedTableOptions bbto;
bbto.no_block_cache = true; // Simplify statistics
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
CompactionOptionsFIFO fifo_options;
// Multi-tier aging: files age through multiple temperatures
fifo_options.file_temperature_age_thresholds = {
{Temperature::kWarm, 500}, // Hot -> Warm after 500s
{Temperature::kCool, 1000}, // Warm -> Cool
{Temperature::kCold, 1500}, // Cool -> Cold
{Temperature::kIce, 2000} // Cold -> Ice
};
fifo_options.max_table_files_size = 100000000;
fifo_options.allow_trivial_copy_when_change_temperature = true;
options.compaction_options_fifo = fifo_options;
options.default_write_temperature = Temperature::kHot;
Reopen(options);
env_->SetMockSleep();
// Track all temperature file creations
int total_hot = 0, total_warm = 0, total_cool = 0, total_cold = 0,
total_ice = 0, total_unknown = 0;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"NewWritableFile::FileOptions.temperature", [&](void* arg) {
Temperature temperature = *(static_cast<Temperature*>(arg));
switch (temperature) {
case Temperature::kHot:
total_hot++;
break;
case Temperature::kWarm:
total_warm++;
break;
case Temperature::kCool:
total_cool++;
break;
case Temperature::kCold:
total_cold++;
break;
case Temperature::kIce:
total_ice++;
break;
case Temperature::kUnknown:
total_unknown++;
break;
default:
break;
}
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
// Create initial three files (will start as Hot), enough to ensure key
// range filtering will be applied in FilePicker::GetNextFile() with one
// more file
for (int i = 0; i < 3; ++i) {
ASSERT_OK(Put(Key(0), Random::GetTLSInstance()->RandomBinaryString(100)));
ASSERT_OK(Flush());
}
// Test reading from Hot temperature file
ASSERT_OK(options.statistics->Reset());
get_iostats_context()->Reset();
ASSERT_EQ(100U, Get(Key(0)).size());
VerifyTemperatureFileReadStats(*options.statistics, Temperature::kHot);
// Land well into each time interval
env_->MockSleepForSeconds(100);
// Age initial files to warm
env_->MockSleepForSeconds(500);
ASSERT_OK(Put(Key(1), Random::GetTLSInstance()->RandomBinaryString(101)));
ASSERT_OK(Flush());
ASSERT_OK(dbfull()->TEST_WaitForCompact());
// Test reading from Warm temperature file (the aged file)
ASSERT_OK(options.statistics->Reset());
get_iostats_context()->Reset();
ASSERT_EQ(100U, Get(Key(0)).size());
// Verify Warm file statistics
VerifyTemperatureFileReadStats(*options.statistics, Temperature::kWarm);
// Age initial files to cool
env_->MockSleepForSeconds(500);
ASSERT_OK(Put(Key(2), Random::GetTLSInstance()->RandomBinaryString(102)));
ASSERT_OK(Flush());
ASSERT_OK(dbfull()->TEST_WaitForCompact());
// Test reading from Cool temperature file (the aged file)
ASSERT_OK(options.statistics->Reset());
get_iostats_context()->Reset();
ASSERT_EQ(100U, Get(Key(0)).size());
VerifyTemperatureFileReadStats(*options.statistics, Temperature::kCool);
// Age initial files to cold
env_->MockSleepForSeconds(500);
ASSERT_OK(Put(Key(3), Random::GetTLSInstance()->RandomBinaryString(103)));
ASSERT_OK(Flush());
ASSERT_OK(dbfull()->TEST_WaitForCompact());
// Test reading from Cold temperature file (the aged file)
ASSERT_OK(options.statistics->Reset());
get_iostats_context()->Reset();
ASSERT_EQ(100U, Get(Key(0)).size());
VerifyTemperatureFileReadStats(*options.statistics, Temperature::kCold);
// Age initial files to ice
env_->MockSleepForSeconds(500);
ASSERT_OK(Put(Key(4), Random::GetTLSInstance()->RandomBinaryString(104)));
ASSERT_OK(Flush());
ASSERT_OK(dbfull()->TEST_WaitForCompact());
// Test reading from Ice temperature file (the aged file)
ASSERT_OK(options.statistics->Reset());
get_iostats_context()->Reset();
ASSERT_EQ(100U, Get(Key(0)).size());
VerifyTemperatureFileReadStats(*options.statistics, Temperature::kIce);
// Verify temperature progression in metadata
ColumnFamilyMetaData metadata;
db_->GetColumnFamilyMetaData(&metadata);
// Should have files at different temperatures
std::map<Temperature, int> temp_counts;
for (const auto& file : metadata.levels[0].files) {
temp_counts[file.temperature]++;
}
// Verify current files temperatures
EXPECT_EQ(temp_counts[Temperature::kHot], 1);
EXPECT_EQ(temp_counts[Temperature::kWarm], 1);
EXPECT_EQ(temp_counts[Temperature::kCool], 1);
EXPECT_EQ(temp_counts[Temperature::kCold], 1);
EXPECT_EQ(temp_counts[Temperature::kIce], 3);
// Verify historical (and current) file temperatures
EXPECT_EQ(total_hot, 7);
EXPECT_EQ(total_warm, 6);
EXPECT_EQ(total_cool, 5);
EXPECT_EQ(total_cold, 4);
EXPECT_EQ(total_ice, 3);
// Final comprehensive test: read from all temperature files
Reopen(options);
ASSERT_OK(options.statistics->Reset());
get_iostats_context()->Reset();
// Read from all files to verify cumulative statistics
for (int i = 0; i < 5; i++) {
ASSERT_EQ(static_cast<unsigned>(100 + i), Get(Key(i)).size());
}
VerifyTemperatureFileReadStats(*options.statistics, TemperatureSet::All());
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
}
TEST_F(DBCompactionTest, DisableMultiManualCompaction) {
const int kNumL0Files = 10;
@@ -11094,6 +11541,71 @@ TEST_F(DBCompactionTest, RecordNewestKeyTimeForTtlCompaction) {
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
}
// Test verifies compaction file cutting logic when using tail size estimation
// maintains output files at or below the target file size.
TEST_F(DBCompactionTest, CompactionRespectsTargetSizeWithTailEstimation) {
const int kInitialKeyCount = 10000; // 10k keys
const int kValueSize = 100; // 100 bytes per key
const int kSeed = 301;
Options options = CurrentOptions();
options.target_file_size_is_upper_bound = true;
options.target_file_size_base = 256 * 1024;
options.write_buffer_size = 2 * 1024 * 1024;
options.level0_file_num_compaction_trigger = 100; // Never trigger L0->L1
options.compression = kNoCompression;
BlockBasedTableOptions table_options;
table_options.partition_filters = true;
table_options.metadata_block_size = 4 * 1024;
table_options.index_type = BlockBasedTableOptions::kBinarySearch;
table_options.filter_policy.reset(NewBloomFilterPolicy(10));
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
DestroyAndReopen(options);
// Generate 2 L0 files
// Generate first file with 10k keys (each ~100 bytes) approx 1.2MB total
Random rnd(kSeed);
for (int i = 0; i < kInitialKeyCount; i++) {
ASSERT_OK(Put(Key(i), rnd.RandomString(kValueSize)));
}
ASSERT_OK(Flush());
// Generate second file with overlapping keys to force compaction (prevent
// trivial move)
for (int i = kInitialKeyCount / 2; i < kInitialKeyCount * 1.5; i++) {
ASSERT_OK(Put(Key(i), rnd.RandomString(kValueSize)));
}
ASSERT_OK(Flush());
// Capture file metadata and assert two L0 files
std::vector<LiveFileMetaData> file_metadata;
db_->GetLiveFilesMetaData(&file_metadata);
ASSERT_EQ(file_metadata.size(), 2);
for (const auto& file : file_metadata) {
ASSERT_EQ(file.level, 0);
};
// Manually compact LO files to L1
CompactRangeOptions cro;
cro.change_level = true;
cro.target_level = 1;
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
ASSERT_OK(dbfull()->TEST_WaitForCompact());
// Verify that compacted output files are under target file size
for (const auto& file : file_metadata) {
if (file.level > 0) {
EXPECT_LE(file.size, options.target_file_size_base)
<< "Output file size exceeds target size: " << " File: " << file.name
<< " level: " << file.level << " File size: " << file.size
<< " Target size: " << options.target_file_size_base;
}
}
}
class PeriodicCompactionListener : public EventListener {
public:
explicit PeriodicCompactionListener() {}
+161
View File
@@ -0,0 +1,161 @@
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#include "db/db_test_util.h"
namespace ROCKSDB_NAMESPACE {
class DBEtc3Test : public DBTestBase {
public:
DBEtc3Test() : DBTestBase("db_etc3_test", /*env_do_fsync=*/true) {}
};
TEST_F(DBEtc3Test, ManifestRollOver) {
do {
Options options;
// Force new manifest on each manifest write
options.max_manifest_file_size = 0;
options.max_manifest_space_amp_pct = 0;
options = CurrentOptions(options);
CreateAndReopenWithCF({"pikachu"}, options);
{
ASSERT_OK(Put(1, "key1", std::string(1000, '1')));
ASSERT_OK(Put(1, "key2", std::string(1000, '2')));
ASSERT_OK(Put(1, "key3", std::string(1000, '3')));
uint64_t manifest_before_flush = dbfull()->TEST_Current_Manifest_FileNo();
ASSERT_OK(Flush(1)); // This should trigger LogAndApply.
uint64_t manifest_after_flush = dbfull()->TEST_Current_Manifest_FileNo();
ASSERT_GT(manifest_after_flush, manifest_before_flush);
// Re-open should always re-create manifest file
ReopenWithColumnFamilies({"default", "pikachu"}, options);
ASSERT_GT(dbfull()->TEST_Current_Manifest_FileNo(), manifest_after_flush);
ASSERT_EQ(std::string(1000, '1'), Get(1, "key1"));
ASSERT_EQ(std::string(1000, '2'), Get(1, "key2"));
ASSERT_EQ(std::string(1000, '3'), Get(1, "key3"));
}
} while (ChangeCompactOptions());
}
TEST_F(DBEtc3Test, AutoTuneManifestSize) {
// Ensure we have auto-tuning beyond max_manifest_file_size by default
ASSERT_EQ(DBOptions{}.max_manifest_space_amp_pct, 500);
Options options = CurrentOptions();
ASSERT_OK(db_->SetOptions({{"level0_file_num_compaction_trigger", "20"}}));
// Use large column family names to essentially control the amount of payload
// data needed for the manifest file. Drop manifest entries don't include the
// CF name so are small.
uint64_t prev_manifest_num = 0, cur_manifest_num = 0;
std::deque<ColumnFamilyHandle*> handles;
int counter = 5;
auto AddCfFn = [&]() {
std::string name = "cf" + std::to_string(counter++);
name.resize(1000, 'a');
ASSERT_OK(db_->CreateColumnFamily(options, name, &handles.emplace_back()));
prev_manifest_num = cur_manifest_num;
cur_manifest_num = dbfull()->TEST_Current_Manifest_FileNo();
};
auto DropCfFn = [&]() {
ASSERT_OK(db_->DropColumnFamily(handles.front()));
ASSERT_OK(db_->DestroyColumnFamilyHandle(handles.front()));
handles.pop_front();
prev_manifest_num = cur_manifest_num;
cur_manifest_num = dbfull()->TEST_Current_Manifest_FileNo();
};
auto TrivialManifestWriteFn = [&]() {
ASSERT_OK(Put("x", std::to_string(counter++)));
ASSERT_OK(Flush());
prev_manifest_num = cur_manifest_num;
cur_manifest_num = dbfull()->TEST_Current_Manifest_FileNo();
};
options.max_manifest_file_size = 1000000;
options.max_manifest_space_amp_pct = 0; // no auto-tuning yet
DestroyAndReopen(options);
// With the generous (minimum) maximum manifest size, should not be rotated
AddCfFn();
AddCfFn();
AddCfFn();
ASSERT_EQ(prev_manifest_num, cur_manifest_num);
// Change options for small max and (still) no auto-tuning
ASSERT_OK(db_->SetDBOptions({{"max_manifest_file_size", "3000"}}));
// Takes effect on the next manifest write
TrivialManifestWriteFn();
ASSERT_LT(prev_manifest_num, cur_manifest_num);
// Now we have to rewrite the whole manifest on each write because the
// compacted size exceeds the "max" size.
AddCfFn();
ASSERT_LT(prev_manifest_num, cur_manifest_num);
DropCfFn();
ASSERT_LT(prev_manifest_num, cur_manifest_num);
AddCfFn();
ASSERT_LT(prev_manifest_num, cur_manifest_num);
TrivialManifestWriteFn();
ASSERT_LT(prev_manifest_num, cur_manifest_num);
// Enabling auto-tuning should fix this, immediately for next manifest writes.
// This will allow up to double-ish the size of the compacted manifest,
// which last should have been 4000 + some bytes.
ASSERT_EQ(handles.size(), 4U);
ASSERT_OK(db_->SetDBOptions({{"max_manifest_space_amp_pct", "105"}}));
// After 9 CF names should be enough to rotate the manifest
for (int i = 1; i <= 5; ++i) {
if ((i % 2) == 1) {
DropCfFn();
}
AddCfFn();
ASSERT_EQ(prev_manifest_num, cur_manifest_num);
}
TrivialManifestWriteFn();
ASSERT_LT(prev_manifest_num, cur_manifest_num);
// We now have a different last compacted manifest size, should be
// able to go beyond 9 CFs named in manifest this time.
ASSERT_EQ(handles.size(), 6U);
DropCfFn();
DropCfFn();
for (int i = 1; i <= 4; ++i) {
DropCfFn();
AddCfFn();
ASSERT_EQ(prev_manifest_num, cur_manifest_num);
}
// We've written 10 named CFs to the manifest. We should be able to
// dynamically change the auto-tuning still based on the last "compacted"
// manifest size of 7000 + some bytes.
ASSERT_OK(db_->SetDBOptions({{"max_manifest_space_amp_pct", "51"}}));
TrivialManifestWriteFn();
ASSERT_LT(prev_manifest_num, cur_manifest_num);
// And the "compacted" manifest size has reset again, so should be changed
// again sooner.
ASSERT_EQ(handles.size(), 4U);
for (int i = 1; i <= 2; ++i) {
AddCfFn();
ASSERT_EQ(prev_manifest_num, cur_manifest_num);
}
// Enough for manifest change
AddCfFn();
ASSERT_LT(prev_manifest_num, cur_manifest_num);
// Wrap up
while (!handles.empty()) {
DropCfFn();
}
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
::testing::InitGoogleTest(&argc, argv);
RegisterCustomObjects(argc, argv);
return RUN_ALL_TESTS();
}
+1
View File
@@ -109,6 +109,7 @@ Status DBImpl::GetSortedWalFilesImpl(VectorWalPtr& files, bool need_seqnos) {
{
InstrumentedMutexLock l(&mutex_);
while (pending_purge_obsolete_files_ > 0 || bg_purge_scheduled_ > 0) {
TEST_SYNC_POINT("DBImpl::GetSortedWalFilesImpl:WaitPurge");
bg_cv_.Wait();
}
+146
View File
@@ -3561,6 +3561,152 @@ TEST_F(DBFlushTest, VerifyOutputRecordCount) {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
}
}
class DBFlushSuperBlockTest
: public DBFlushTest,
public ::testing::WithParamInterface<std::tuple<bool, size_t, size_t>> {
public:
DBFlushSuperBlockTest() : DBFlushTest() {}
std::string formatKey(int i) {
int desired_length = 10;
char buffer[64];
snprintf(buffer, 64, "%0*d", desired_length, i);
return buffer;
}
void VerifyReadWithGet(int key_count) {
for (int i = 0; i < key_count; ++i) {
PinnableSlice value;
ASSERT_OK(Get(formatKey(i), &value));
ASSERT_EQ(value.ToString(), added_data[formatKey(i)]);
}
}
void VerifyReadWithIterator(int key_count) {
{
std::unique_ptr<Iterator> it(db_->NewIterator(ReadOptions()));
int i = 0;
for (it->SeekToFirst(); it->Valid(); it->Next()) {
ASSERT_OK(it->status());
ASSERT_EQ((it->key()).ToString(), formatKey(i));
ASSERT_EQ((it->value()).ToString(), added_data[formatKey(i)]);
i++;
}
ASSERT_OK(it->status());
ASSERT_EQ(i, key_count);
}
}
protected:
Random rnd{123};
std::unordered_map<std::string, std::string> added_data;
};
constexpr size_t kLowSpaceOverheadRatio = 256;
TEST_P(DBFlushSuperBlockTest, SuperBlock) {
constexpr int key_count = 12345;
Options options;
options.env = env_;
options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
options.paranoid_file_checks = true;
options.write_buffer_size = 1024 * 1024;
BlockBasedTableOptions block_options;
block_options.block_align = get<0>(GetParam());
block_options.index_block_restart_interval = 3;
block_options.super_block_alignment_size = get<1>(GetParam());
block_options.super_block_alignment_space_overhead_ratio = get<2>(GetParam());
options.table_factory.reset(NewBlockBasedTableFactory(block_options));
if (block_options.block_align) {
// When block align is enabled, disable compression
options.compression = kNoCompression;
}
ASSERT_OK(options.table_factory->ValidateOptions(
DBOptions(options), ColumnFamilyOptions(options)));
Reopen(options);
int super_block_pad_count = 0;
int super_block_pad_exceed_limit_count = 0;
SyncPoint::GetInstance()->SetCallBack(
"BlockBasedTableBuilder::WriteMaybeCompressedBlock:"
"SuperBlockAlignment",
[&super_block_pad_count](void* /*arg*/) { super_block_pad_count++; });
SyncPoint::GetInstance()->SetCallBack(
"BlockBasedTableBuilder::WriteMaybeCompressedBlock:"
"SuperBlockAlignmentPaddingBytesExceedLimit",
[&super_block_pad_exceed_limit_count](void* /*arg*/) {
super_block_pad_exceed_limit_count++;
});
SyncPoint::GetInstance()->EnableProcessing();
// Add lots of keys
for (int i = 0; i < key_count; ++i) {
added_data[formatKey(i)] = std::string(rnd.RandomString(rnd.Next() % 1000));
ASSERT_OK(Put(formatKey(i), added_data[formatKey(i)]));
}
// flush the data in memory to disk to verify with super block alignment, the
// data could be read back properly
Reopen(options);
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
// When block_align is enabled, super block is always aligned, so there should
// be 0 padding for super block alignment
if (block_options.super_block_alignment_size != 0 &&
!block_options.block_align) {
ASSERT_GT(super_block_pad_count, 0);
} else {
ASSERT_EQ(super_block_pad_count, 0);
}
if (!block_options.block_align &&
block_options.super_block_alignment_size != 0 &&
block_options.super_block_alignment_space_overhead_ratio ==
kLowSpaceOverheadRatio) {
ASSERT_GT(super_block_pad_exceed_limit_count, 0);
}
// verify the values are correct
VerifyReadWithGet(key_count);
Reopen(options);
VerifyReadWithIterator(key_count);
// verify checksum
ASSERT_OK(db_->VerifyFileChecksums(ReadOptions()));
// Reopen options and flip the option of super block configuration, read still
// works. This verifies the forward/backward compatibility
if (block_options.super_block_alignment_size == 0) {
block_options.super_block_alignment_size = 16 * 1024;
} else {
block_options.super_block_alignment_size = 0;
}
options.table_factory.reset(NewBlockBasedTableFactory(block_options));
Reopen(options);
// verify the values are correct
VerifyReadWithGet(key_count);
Reopen(options);
VerifyReadWithIterator(key_count);
// verify checksum
ASSERT_OK(db_->VerifyFileChecksums(ReadOptions()));
}
INSTANTIATE_TEST_CASE_P(
SuperBlockTests, DBFlushSuperBlockTest,
testing::Combine(testing::Bool(), testing::Values(0, 32 * 1024, 16 * 1024),
// Use very low space overhead ratio to test
// the case where required padded bytes is
// larger than the max allowed padding size
testing::Values(4, kLowSpaceOverheadRatio)));
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+2 -2
View File
@@ -370,10 +370,10 @@ TEST_F(DBFollowerTest, RetryCatchupManifestRollover) {
// This test creates 4 L0 files and compacts them. The follower, during catchup,
// successfully instantiates 4 Versions corresponding to the 4 files (but
// donesn't install them yet), followed by deleting those 4 and adding a new
// doesn't install them yet), followed by deleting those 4 and adding a new
// file from compaction. The test verifies that the 4 L0 files are deleted
// correctly by the follower.
// We use teh Barrier* functions to ensure that the follower first sees the 4
// We use the Barrier* functions to ensure that the follower first sees the 4
// L0 files and is able to link them, and then sees the compaction that
// obsoletes those L0 files (so those L0 files are intermediates that it has
// to explicitly delete). Suppose we don't have any barriers, its possible
+48 -27
View File
@@ -258,10 +258,10 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
[this]() { this->TriggerPeriodicCompaction(); });
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_, read_only));
dbname_, &immutable_db_options_, mutable_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_, read_only));
column_family_memtables_.reset(
new ColumnFamilyMemTablesImpl(versions_->GetColumnFamilySet()));
@@ -1069,7 +1069,7 @@ void DBImpl::DumpStats() {
{
InstrumentedMutexLock l(&mutex_);
for (auto cfd : versions_->GetRefedColumnFamilySet()) {
if (!cfd->initialized()) {
if (!cfd->initialized() || cfd->IsDropped()) {
continue;
}
@@ -1245,13 +1245,11 @@ Status DBImpl::SetOptions(
WriteOptionsFile(write_options, true /*db_mutex_already_held*/);
bg_cv_.SignalAll();
#if __cplusplus >= 202002L
assert(new_options_copy == cfd->GetLatestMutableCFOptions());
assert(cfd->GetLatestMutableCFOptions() ==
cfd->GetCurrentMutableCFOptions());
assert(cfd->GetCurrentMutableCFOptions() ==
cfd->current()->GetMutableCFOptions());
#endif
}
}
sv_context.Clean();
@@ -1414,7 +1412,7 @@ Status DBImpl::SetDBOptions(
file_options_for_compaction_ = FileOptions(new_db_options);
file_options_for_compaction_ = fs_->OptimizeForCompactionTableWrite(
file_options_for_compaction_, immutable_db_options_);
versions_->ChangeFileOptions(mutable_db_options_);
versions_->UpdatedMutableDbOptions(mutable_db_options_, &mutex_);
// TODO(xiez): clarify why apply optimize for read to write options
file_options_for_compaction_ = fs_->OptimizeForCompactionTableRead(
file_options_for_compaction_, immutable_db_options_);
@@ -1482,6 +1480,12 @@ int DBImpl::FindMinimumEmptyLevelFitting(
return minimum_level;
}
Status DBImpl::FlushWAL(const FlushWALOptions& options) {
WriteOptions write_options;
write_options.rate_limiter_priority = options.rate_limiter_priority;
return FlushWAL(write_options, options.sync);
}
Status DBImpl::FlushWAL(const WriteOptions& write_options, bool sync) {
if (manual_wal_flush_) {
IOStatus io_s;
@@ -3835,7 +3839,7 @@ bool DBImpl::KeyMayExist(const ReadOptions& read_options,
std::unique_ptr<MultiScan> DBImpl::NewMultiScan(
const ReadOptions& _read_options, ColumnFamilyHandle* column_family,
const std::vector<ScanOptions>& scan_opts) {
const MultiScanArgs& scan_opts) {
std::unique_ptr<MultiScan> ms_iter = std::make_unique<MultiScan>(
_read_options, scan_opts, this, column_family);
return ms_iter;
@@ -4345,7 +4349,7 @@ void DBImpl::ReleaseSnapshot(const Snapshot* s) {
CfdList cf_scheduled;
if (oldest_snapshot > bottommost_files_mark_threshold_) {
for (auto* cfd : *versions_->GetColumnFamilySet()) {
if (!cfd->ioptions().allow_ingest_behind) {
if (!cfd->AllowIngestBehind()) {
cfd->current()->storage_info()->UpdateOldestSnapshot(
oldest_snapshot, /*allow_ingest_behind=*/false);
if (!cfd->current()
@@ -4365,8 +4369,7 @@ void DBImpl::ReleaseSnapshot(const Snapshot* s) {
// inaccurate.
SequenceNumber new_bottommost_files_mark_threshold = kMaxSequenceNumber;
for (auto* cfd : *versions_->GetColumnFamilySet()) {
if (CfdListContains(cf_scheduled, cfd) ||
cfd->ioptions().allow_ingest_behind) {
if (CfdListContains(cf_scheduled, cfd) || cfd->AllowIngestBehind()) {
continue;
}
new_bottommost_files_mark_threshold = std::min(
@@ -5044,6 +5047,19 @@ void DBImpl::GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
}
}
void DBImpl::GetColumnFamilyMetaData(
ColumnFamilyHandle* column_family,
const GetColumnFamilyMetaDataOptions& options,
ColumnFamilyMetaData* metadata) {
assert(column_family);
auto* cfd =
static_cast_with_check<ColumnFamilyHandleImpl>(column_family)->cfd();
{
InstrumentedMutexLock l(&mutex_);
cfd->current()->GetColumnFamilyMetaData(options, metadata);
}
}
void DBImpl::GetAllColumnFamilyMetaData(
std::vector<ColumnFamilyMetaData>* metadata) {
InstrumentedMutexLock l(&mutex_);
@@ -5503,7 +5519,7 @@ Status DBImpl::RenameTempFileToOptionsFile(const std::string& file_name,
return s;
}
#ifdef ROCKSDB_USING_THREAD_STATUS
#ifndef NROCKSDB_THREAD_STATUS
void DBImpl::NewThreadStatusCfInfo(ColumnFamilyData* cfd) const {
if (immutable_db_options_.enable_thread_tracking) {
@@ -5530,7 +5546,7 @@ void DBImpl::NewThreadStatusCfInfo(ColumnFamilyData* /*cfd*/) const {}
void DBImpl::EraseThreadStatusCfInfo(ColumnFamilyData* /*cfd*/) const {}
void DBImpl::EraseThreadStatusDbInfo() const {}
#endif // ROCKSDB_USING_THREAD_STATUS
#endif // !NROCKSDB_THREAD_STATUS
//
// A global method that can dump out the build version
@@ -5761,10 +5777,6 @@ Status DBImpl::IngestExternalFiles(
for (const auto& arg : args) {
const IngestExternalFileOptions& ingest_opts = arg.options;
if (ingest_opts.ingest_behind) {
if (!immutable_db_options_.allow_ingest_behind) {
return Status::InvalidArgument(
"can't ingest_behind file in DB with allow_ingest_behind=false");
}
auto ucmp = arg.column_family->GetComparator();
assert(ucmp);
if (ucmp->timestamp_size() > 0) {
@@ -5772,6 +5784,14 @@ Status DBImpl::IngestExternalFiles(
"Column family with user-defined "
"timestamps enabled doesn't support ingest behind.");
}
if (!static_cast<ColumnFamilyHandleImpl*>(arg.column_family)
->cfd()
->AllowIngestBehind()) {
return Status::InvalidArgument(
"Can't ingest_behind file in ColumnFamily %s with "
"cf_allow_ingest_behind=false");
}
}
if (arg.atomic_replace_range.has_value()) {
if (ingest_opts.ingest_behind) {
@@ -6006,18 +6026,19 @@ Status DBImpl::IngestExternalFiles(
// mutex when persisting MANIFEST file, and the snapshots taken during
// that period will not be stable if VersionSet last seqno is updated
// before LogAndApply.
int consumed_seqno_count =
ingestion_jobs[0].ConsumedSequenceNumbersCount();
SequenceNumber max_assigned_seqno =
ingestion_jobs[0].MaxAssignedSequenceNumber();
for (size_t i = 1; i != num_cfs; ++i) {
consumed_seqno_count =
std::max(consumed_seqno_count,
ingestion_jobs[i].ConsumedSequenceNumbersCount());
max_assigned_seqno = std::max(
max_assigned_seqno, ingestion_jobs[i].MaxAssignedSequenceNumber());
}
if (consumed_seqno_count > 0) {
if (max_assigned_seqno > 0) {
const SequenceNumber last_seqno = versions_->LastSequence();
versions_->SetLastAllocatedSequence(last_seqno + consumed_seqno_count);
versions_->SetLastPublishedSequence(last_seqno + consumed_seqno_count);
versions_->SetLastSequence(last_seqno + consumed_seqno_count);
if (max_assigned_seqno > last_seqno) {
versions_->SetLastAllocatedSequence(max_assigned_seqno);
versions_->SetLastPublishedSequence(max_assigned_seqno);
versions_->SetLastSequence(max_assigned_seqno);
}
}
}
+22 -3
View File
@@ -386,7 +386,7 @@ class DBImpl : public DB {
using DB::NewMultiScan;
std::unique_ptr<MultiScan> NewMultiScan(
const ReadOptions& _read_options, ColumnFamilyHandle* column_family,
const std::vector<ScanOptions>& scan_opts) override;
const MultiScanArgs& scan_opts) override;
const Snapshot* GetSnapshot() override;
void ReleaseSnapshot(const Snapshot* snapshot) override;
@@ -484,10 +484,13 @@ class DBImpl : public DB {
const FlushOptions& options,
const std::vector<ColumnFamilyHandle*>& column_families) override;
Status FlushWAL(bool sync) override {
// TODO: plumb Env::IOActivity, Env::IOPriority
return FlushWAL(WriteOptions(), sync);
FlushWALOptions options;
options.sync = sync;
return FlushWAL(options);
}
Status FlushWAL(const FlushWALOptions& options) override;
virtual Status FlushWAL(const WriteOptions& write_options, bool sync);
bool WALBufferIsEmpty();
Status SyncWAL() override;
@@ -570,6 +573,11 @@ class DBImpl : public DB {
void GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
ColumnFamilyMetaData* metadata) override;
// Get column family metadata with filtering based on key range and level
void GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
const GetColumnFamilyMetaDataOptions& options,
ColumnFamilyMetaData* metadata) override;
void GetAllColumnFamilyMetaData(
std::vector<ColumnFamilyMetaData>* metadata) override;
@@ -1388,6 +1396,9 @@ class DBImpl : public DB {
// logs_, cur_wal_number_. Refer to the definition of each variable below for
// more description.
//
// Protects access to most ColumnFamilyData methods, see more in comment for
// each method.
//
// `mutex_` can be a hot lock in some workloads, so it deserves dedicated
// cachelines.
mutable CacheAlignedInstrumentedMutex mutex_;
@@ -2390,6 +2401,14 @@ class DBImpl : public DB {
JobContext* job_context, LogBuffer* log_buffer,
CompactionJobInfo* compaction_job_info);
// Helper function to perform trivial move by updating manifest metadata
// without rewriting data files. This is called when IsTrivialMove() is true.
// REQUIRES: mutex held
// Returns: Status of the trivial move operation
Status PerformTrivialMove(Compaction& c, LogBuffer* log_buffer,
bool& compaction_released, size_t& moved_files,
size_t& moved_bytes);
// REQUIRES: mutex unlocked
void TrackOrUntrackFiles(const std::vector<std::string>& existing_data_files,
bool track);
+137 -63
View File
@@ -1111,8 +1111,7 @@ Status DBImpl::CompactRangeInternal(const CompactRangeOptions& options,
cfd->NumberLevels() > 1) {
// Always compact all files together.
final_output_level = cfd->NumberLevels() - 1;
// if bottom most level is reserved
if (immutable_db_options_.allow_ingest_behind) {
if (cfd->AllowIngestBehind()) {
final_output_level--;
}
s = RunManualCompaction(cfd, ColumnFamilyData::kCompactAllLevels,
@@ -1425,6 +1424,56 @@ Status DBImpl::CompactFiles(const CompactionOptions& compact_options,
return s;
}
Status DBImpl::PerformTrivialMove(Compaction& c, LogBuffer* log_buffer,
bool& compaction_released,
size_t& moved_files, size_t& moved_bytes) {
mutex_.AssertHeld();
ROCKS_LOG_BUFFER(log_buffer, "[%s] Moving %d files to level-%d\n",
c.column_family_data()->GetName().c_str(),
static_cast<int>(c.num_input_files(0)), c.output_level());
// Move files to the output level by editing the manifest
for (unsigned int l = 0; l < c.num_input_levels(); l++) {
if (c.level(l) == c.output_level()) {
continue;
}
for (size_t i = 0; i < c.num_input_files(l); i++) {
FileMetaData* f = c.input(l, i);
c.edit()->DeleteFile(c.level(l), f->fd.GetNumber());
c.edit()->AddFile(c.output_level(), f->fd.GetNumber(), f->fd.GetPathId(),
f->fd.GetFileSize(), f->smallest, f->largest,
f->fd.smallest_seqno, f->fd.largest_seqno,
f->marked_for_compaction, f->temperature,
f->oldest_blob_file_number, f->oldest_ancester_time,
f->file_creation_time, f->epoch_number,
f->file_checksum, f->file_checksum_func_name,
f->unique_id, f->compensated_range_deletion_size,
f->tail_size, f->user_defined_timestamps_persisted);
moved_bytes += static_cast<size_t>(c.input(l, i)->fd.GetFileSize());
ROCKS_LOG_BUFFER(
log_buffer, "[%s] Moved #%" PRIu64 " to level-%d %" PRIu64 " bytes\n",
c.column_family_data()->GetName().c_str(), f->fd.GetNumber(),
c.output_level(), f->fd.GetFileSize());
}
moved_files += c.num_input_files(l);
}
// Install the new version
const ReadOptions read_options(Env::IOActivity::kCompaction);
const WriteOptions write_options(Env::IOActivity::kCompaction);
Status status = versions_->LogAndApply(
c.column_family_data(), read_options, write_options, c.edit(), &mutex_,
directories_.GetDbDir(), /*new_descriptor_log=*/false,
/*column_family_options=*/nullptr,
[&c, &compaction_released](const Status& s) {
c.ReleaseCompactionFiles(s);
compaction_released = true;
});
return status;
}
Status DBImpl::CompactFilesImpl(
const CompactionOptions& compact_options, ColumnFamilyData* cfd,
Version* version, const std::vector<std::string>& input_file_names,
@@ -1460,7 +1509,7 @@ Status DBImpl::CompactFilesImpl(
}
}
if (cfd->ioptions().allow_ingest_behind &&
if (cfd->AllowIngestBehind() &&
output_level >= cfd->ioptions().num_levels - 1) {
return Status::InvalidArgument(
"Exceed the maximum output level defined by "
@@ -1500,7 +1549,7 @@ Status DBImpl::CompactFilesImpl(
std::unique_ptr<Compaction> c;
assert(cfd->compaction_picker());
c.reset(cfd->compaction_picker()->CompactFiles(
c.reset(cfd->compaction_picker()->PickCompactionForCompactFiles(
compact_options, input_files, output_level, version->storage_info(),
cfd->GetLatestMutableCFOptions(), mutable_db_options_, output_path_id));
// we already sanitized the set of input files and checked for conflicts
@@ -1512,6 +1561,63 @@ Status DBImpl::CompactFilesImpl(
// deletion compaction currently not allowed in CompactFiles.
assert(!c->deletion_compaction());
// Check if this can be a trivial move (metadata-only update)
// Similar to the logic in DBImpl::BackgroundCompaction
// Note: We disable trivial move when compaction_service is present because
// the service expects all compactions to go through CompactionJob for
// tracking
bool is_trivial_move = compact_options.allow_trivial_move &&
c->IsTrivialMove() &&
immutable_db_options().compaction_service == nullptr;
if (is_trivial_move) {
// Perform trivial move: just update manifest without rewriting data
TEST_SYNC_POINT("DBImpl::CompactFilesImpl:TrivialMove");
bool compaction_released = false;
size_t moved_files = 0;
size_t moved_bytes = 0;
Status status = PerformTrivialMove(
*c.get(), log_buffer, compaction_released, moved_files, moved_bytes);
if (status.ok()) {
InstallSuperVersionAndScheduleWork(
c->column_family_data(), job_context->superversion_contexts.data());
// Populate output file names for trivial move
if (output_file_names != nullptr) {
for (const auto& newf : c->edit()->GetNewFiles()) {
output_file_names->push_back(TableFileName(
c->immutable_options().cf_paths, newf.second.fd.GetNumber(),
newf.second.fd.GetPathId()));
}
}
ROCKS_LOG_BUFFER(
log_buffer,
"[%s] Trivial move succeeded for %zu files, %zu bytes total\n",
c->column_family_data()->GetName().c_str(), moved_files, moved_bytes);
} else {
if (!compaction_released) {
c->ReleaseCompactionFiles(status);
}
ROCKS_LOG_BUFFER(log_buffer, "[%s] Trivial move failed: %s\n",
c->column_family_data()->GetName().c_str(),
status.ToString().c_str());
error_handler_.SetBGError(status, BackgroundErrorReason::kCompaction);
}
c.reset();
bg_compaction_scheduled_--;
if (bg_compaction_scheduled_ == 0) {
bg_cv_.SignalAll();
}
MaybeScheduleFlushOrCompaction();
return status;
}
// Not a trivial move, proceed with full compaction
InitSnapshotContext(job_context);
std::unique_ptr<std::list<uint64_t>::iterator> pending_outputs_inserted_elem(
@@ -1844,8 +1950,7 @@ Status DBImpl::ReFitLevel(ColumnFamilyData* cfd, int level, int target_level) {
,
LLONG_MAX /* max compaction bytes, not applicable */,
0 /* output path ID, not applicable */, mutable_cf_options.compression,
mutable_cf_options.compression_opts,
mutable_cf_options.default_write_temperature,
mutable_cf_options.compression_opts, Temperature::kUnknown,
0 /* max_subcompactions, not applicable */,
{} /* grandparents, not applicable */,
std::nullopt /* earliest_snapshot */, nullptr /* snapshot_checker */,
@@ -3862,7 +3967,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
uint64_t out_file_creation_time = static_cast<uint64_t>(tmp_current_time);
FileOptions copied_file_options = file_options_;
copied_file_options.temperature = c->output_temperature();
copied_file_options.temperature = c->GetOutputTemperature();
std::unique_ptr<WritableFileWriter> dest_writer;
{
std::unique_ptr<FSWritableFile> dest_file;
@@ -3880,7 +3985,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
"NewWritableFile %s\n"
" out_fname=%s, temperature=%s, io_status=%s",
c->column_family_data()->GetName().c_str(), out_fname.c_str(),
temperature_to_string[c->output_temperature()].c_str(),
temperature_to_string[c->GetOutputTemperature()].c_str(),
io_s.ToString().c_str());
break;
}
@@ -3902,7 +4007,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
c->column_family_data()->GetName().c_str(), in_fname.c_str(),
temperature_to_string[in_file->temperature].c_str(),
out_fname.c_str(),
temperature_to_string[c->output_temperature()].c_str(),
temperature_to_string[c->GetOutputTemperature()].c_str(),
c->mutable_cf_options()
.compaction_options_fifo.trivial_copy_buffer_size);
// Add IO_LOW HINT for compaction
@@ -3942,7 +4047,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
c->column_family_data()->GetName().c_str(), in_fname.c_str(),
temperature_to_string[in_file->temperature].c_str(),
out_fname.c_str(),
temperature_to_string[c->output_temperature()].c_str(),
temperature_to_string[c->GetOutputTemperature()].c_str(),
io_s.ToString().c_str());
break;
}
@@ -3951,15 +4056,15 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
io_s = copy_file_io_status;
if (!io_s.ok()) {
ROCKS_LOG_BUFFER(log_buffer,
"[%s] Failed to copy from: %s\n"
" temperature=%s, to=%s, temperature=%s, io_status=%s",
c->column_family_data()->GetName().c_str(),
in_fname.c_str(),
temperature_to_string[in_file->temperature].c_str(),
out_fname.c_str(),
temperature_to_string[c->output_temperature()].c_str(),
io_s.ToString().c_str());
ROCKS_LOG_BUFFER(
log_buffer,
"[%s] Failed to copy from: %s\n"
" temperature=%s, to=%s, temperature=%s, io_status=%s",
c->column_family_data()->GetName().c_str(), in_fname.c_str(),
temperature_to_string[in_file->temperature].c_str(),
out_fname.c_str(),
temperature_to_string[c->GetOutputTemperature()].c_str(),
io_s.ToString().c_str());
break;
}
ROCKS_LOG_BUFFER(log_buffer,
@@ -3969,7 +4074,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
in_fname.c_str(),
temperature_to_string[in_file->temperature].c_str(),
out_fname.c_str(),
temperature_to_string[c->output_temperature()].c_str(),
temperature_to_string[c->GetOutputTemperature()].c_str(),
io_s.ToString().c_str());
FileMetaData out_file_metadata{
@@ -3981,7 +4086,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
in_file->fd.smallest_seqno,
in_file->fd.largest_seqno,
false /* marked_for_compact */,
c->output_temperature() /* temperature */,
c->GetOutputTemperature() /* temperature */,
in_file->oldest_blob_file_number,
in_file->oldest_ancester_time,
out_file_creation_time,
@@ -4050,7 +4155,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
" temperature=%s, to temperature=%s, status=%s, io_status=%s",
c->column_family_data()->GetName().c_str(), in_fname.c_str(),
temperature_to_string[in_file->temperature].c_str(),
temperature_to_string[c->output_temperature()].c_str(),
temperature_to_string[c->GetOutputTemperature()].c_str(),
status.ToString().c_str(), io_s.ToString().c_str());
}
}
@@ -4076,35 +4181,6 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
NotifyOnCompactionBegin(c->column_family_data(), c.get(), status,
compaction_job_stats, job_context->job_id);
// Move files to next level
int32_t moved_files = 0;
int64_t moved_bytes = 0;
for (unsigned int l = 0; l < c->num_input_levels(); l++) {
if (c->level(l) == c->output_level()) {
continue;
}
for (size_t i = 0; i < c->num_input_files(l); i++) {
FileMetaData* f = c->input(l, i);
c->edit()->DeleteFile(c->level(l), f->fd.GetNumber());
c->edit()->AddFile(
c->output_level(), f->fd.GetNumber(), f->fd.GetPathId(),
f->fd.GetFileSize(), f->smallest, f->largest, f->fd.smallest_seqno,
f->fd.largest_seqno, f->marked_for_compaction, f->temperature,
f->oldest_blob_file_number, f->oldest_ancester_time,
f->file_creation_time, f->epoch_number, f->file_checksum,
f->file_checksum_func_name, f->unique_id,
f->compensated_range_deletion_size, f->tail_size,
f->user_defined_timestamps_persisted);
ROCKS_LOG_BUFFER(
log_buffer,
"[%s] Moving #%" PRIu64 " to level-%d %" PRIu64 " bytes\n",
c->column_family_data()->GetName().c_str(), f->fd.GetNumber(),
c->output_level(), f->fd.GetFileSize());
++moved_files;
moved_bytes += f->fd.GetFileSize();
}
}
if (c->compaction_reason() == CompactionReason::kLevelMaxLevelSize &&
c->immutable_options().compaction_pri == kRoundRobin) {
int start_level = c->start_level();
@@ -4115,14 +4191,12 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
vstorage->GetNextCompactCursor(start_level, c->num_input_files(0)));
}
}
status = versions_->LogAndApply(
c->column_family_data(), read_options, write_options, c->edit(),
&mutex_, directories_.GetDbDir(),
/*new_descriptor_log=*/false, /*column_family_options=*/nullptr,
[&c, &compaction_released](const Status& s) {
c->ReleaseCompactionFiles(s);
compaction_released = true;
});
// Perform the trivial move
size_t moved_files = 0;
size_t moved_bytes = 0;
status = PerformTrivialMove(*c.get(), log_buffer, compaction_released,
moved_files, moved_bytes);
io_s = versions_->io_status();
InstallSuperVersionAndScheduleWork(
c->column_family_data(), job_context->superversion_contexts.data());
@@ -4137,8 +4211,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
<< "total_files_size" << moved_bytes;
}
ROCKS_LOG_BUFFER(
log_buffer,
"[%s] Moved #%d files to level-%d %" PRIu64 " bytes %s: %s\n",
log_buffer, "[%s] Moved #%d files to level-%zu %zu bytes %s: %s\n",
c->column_family_data()->GetName().c_str(), moved_files,
c->output_level(), moved_bytes, status.ToString().c_str(),
c->column_family_data()->current()->storage_info()->LevelSummary(&tmp));
@@ -4155,6 +4228,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
->current()
->storage_info()
->MaxOutputLevel(
c->immutable_options().cf_allow_ingest_behind ||
immutable_db_options_.allow_ingest_behind)) &&
env_->GetBackgroundThreads(Env::Priority::BOTTOM) > 0) {
assert(thread_pri == Env::Priority::LOW);
@@ -4428,7 +4502,7 @@ Compaction* DBImpl::CreateIntendedCompactionForwardedToBottomPriorityPool(
c->output_level(), c->target_output_file_size(),
c->max_compaction_bytes(), c->output_path_id(),
c->output_compression(), c->output_compression_opts(),
c->output_temperature(), c->max_subcompactions(),
c->GetOutputTemperature(), c->max_subcompactions(),
c->grandparents(), std::nullopt /* earliest_snapshot */,
nullptr /* snapshot_checker */, c->compaction_reason());
@@ -4660,7 +4734,7 @@ void DBImpl::InstallSuperVersionAndScheduleWork(
bottommost_files_mark_threshold_ = kMaxSequenceNumber;
standalone_range_deletion_files_mark_threshold_ = kMaxSequenceNumber;
for (auto* my_cfd : *versions_->GetColumnFamilySet()) {
if (!my_cfd->ioptions().allow_ingest_behind) {
if (!my_cfd->AllowIngestBehind()) {
bottommost_files_mark_threshold_ = std::min(
bottommost_files_mark_threshold_,
my_cfd->current()->storage_info()->bottommost_files_mark_threshold());
+8
View File
@@ -267,6 +267,9 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
if (!job_context->HaveSomethingToDelete()) {
mutex_.AssertHeld();
--pending_purge_obsolete_files_;
if (pending_purge_obsolete_files_ == 0) {
bg_cv_.SignalAll();
}
}
});
@@ -614,6 +617,11 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
case kOptionsFile:
keep = (number >= optsfile_num2);
break;
case kCompactionProgressFile:
// Keep compaction progress files - they are managed
// separately by DBImplSecondary for now
keep = true;
break;
case kCurrentFile:
case kDBLockFile:
case kIdentityFile:
+3 -3
View File
@@ -293,9 +293,9 @@ Status DB::OpenAsFollower(
DBImplFollower* impl =
new DBImplFollower(tmp_opts, std::move(new_env), dbname, src_path);
impl->versions_.reset(new ReactiveVersionSet(
dbname, &impl->immutable_db_options_, impl->file_options_,
impl->table_cache_.get(), impl->write_buffer_manager_,
&impl->write_controller_, impl->io_tracer_));
dbname, &impl->immutable_db_options_, impl->mutable_db_options_,
impl->file_options_, impl->table_cache_.get(),
impl->write_buffer_manager_, &impl->write_controller_, impl->io_tracer_));
impl->column_family_memtables_.reset(
new ColumnFamilyMemTablesImpl(impl->versions_->GetColumnFamilySet()));
impl->wal_in_db_path_ = impl->immutable_db_options_.IsWalDirSameAsDBPath();
+8 -4
View File
@@ -329,7 +329,7 @@ Status DBImpl::NewDB(std::vector<std::string>* new_filenames) {
}
FileTypeSet tmp_set = immutable_db_options_.checksum_handoff_file_types;
file->SetPreallocationBlockSize(
immutable_db_options_.manifest_preallocation_size);
mutable_db_options_.manifest_preallocation_size);
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
std::move(file), manifest, file_options, immutable_db_options_.clock,
io_tracer_, nullptr /* stats */,
@@ -599,7 +599,7 @@ Status DBImpl::Recover(
// allow_ingest_behind does not support Level Compaction,
// and per_key_placement can have infinite compaction loop for Level
// Compaction. Adjust to_level here just to be safe.
if (cfd->ioptions().allow_ingest_behind ||
if (cfd->AllowIngestBehind() ||
moptions.preclude_last_level_data_seconds > 0) {
to_level -= 1;
}
@@ -1755,8 +1755,12 @@ Status DBImpl::MaybeHandleStopReplayForCorruptionForInconsistency(
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
"Column family inconsistency: SST file contains data"
" beyond the point of corruption.");
status = Status::Corruption("SST file is ahead of WALs in CF " +
cfd->GetName());
status = Status::Corruption(
"Column family inconsistency: SST file contains data"
" beyond the point of corruption in CF " +
cfd->GetName() +
". WAL recovery stopped at corruption point, but SST files"
" contain newer data.");
return status;
}
}
+699 -26
View File
@@ -8,7 +8,12 @@
#include <cinttypes>
#include "db/arena_wrapped_db_iter.h"
#include "db/log_reader.h"
#include "db/log_writer.h"
#include "db/merge_context.h"
#include "db/version_edit.h"
#include "file/filename.h"
#include "file/writable_file_writer.h"
#include "logging/auto_roll_logger.h"
#include "logging/logging.h"
#include "monitoring/perf_context_imp.h"
@@ -778,9 +783,9 @@ Status DB::OpenAsSecondary(
handles->clear();
DBImplSecondary* impl = new DBImplSecondary(tmp_opts, dbname, secondary_path);
impl->versions_.reset(new ReactiveVersionSet(
dbname, &impl->immutable_db_options_, impl->file_options_,
impl->table_cache_.get(), impl->write_buffer_manager_,
&impl->write_controller_, impl->io_tracer_));
dbname, &impl->immutable_db_options_, impl->mutable_db_options_,
impl->file_options_, impl->table_cache_.get(),
impl->write_buffer_manager_, &impl->write_controller_, impl->io_tracer_));
impl->column_family_memtables_.reset(
new ColumnFamilyMemTablesImpl(impl->versions_->GetColumnFamilySet()));
impl->wal_in_db_path_ = impl->immutable_db_options_.IsWalDirSameAsDBPath();
@@ -823,18 +828,516 @@ Status DB::OpenAsSecondary(
return s;
}
Status DBImplSecondary::ScanCompactionProgressFiles(
CompactionProgressFilesScan* scan_result) {
assert(scan_result != nullptr);
scan_result->Clear();
WriteOptions write_options(Env::IOActivity::kCompaction);
IOOptions opts;
Status s = WritableFileWriter::PrepareIOOptions(write_options, opts);
if (!s.ok()) {
return s;
}
std::vector<std::string> all_filenames;
s = fs_->GetChildren(secondary_path_, opts, &all_filenames, nullptr /* dbg*/);
if (!s.ok()) {
return s;
}
for (const auto& filename : all_filenames) {
if (filename == "." || filename == "..") {
continue;
}
uint64_t number;
FileType type;
if (!ParseFileName(filename, &number, &type)) {
continue;
}
// Categorize compaction progress files
if (type == kCompactionProgressFile) {
if (number > scan_result->latest_progress_timestamp) {
// Found a newer progress file
if (scan_result->HasLatestProgressFile()) {
// Previous "latest" becomes "old"
scan_result->old_progress_filenames.push_back(
scan_result->latest_progress_filename.value());
}
scan_result->latest_progress_timestamp = number;
scan_result->latest_progress_filename = filename;
} else {
// This is an older progress file
scan_result->old_progress_filenames.push_back(filename);
}
} else if (type == kTempFile &&
filename.find(kCompactionProgressFileNamePrefix) == 0) {
// Temporary progress files
scan_result->temp_progress_filenames.push_back(filename);
} else if (type == kTableFile) {
// Collect table file numbers for CleanupPhysicalCompactionOutputFiles
scan_result->table_file_numbers.push_back(number);
}
}
return Status::OK();
}
Status DBImplSecondary::DeleteCompactionProgressFiles(
const std::vector<std::string>& filenames) {
WriteOptions write_options(Env::IOActivity::kCompaction);
IOOptions opts;
Status s = WritableFileWriter::PrepareIOOptions(write_options, opts);
if (!s.ok()) {
return s;
}
for (const auto& filename : filenames) {
std::string file_path = secondary_path_ + "/" + filename;
Status delete_status = fs_->DeleteFile(file_path, opts, nullptr /* dbg */);
if (!delete_status.ok()) {
return delete_status;
}
}
return Status::OK();
}
Status DBImplSecondary::CleanupOldAndTemporaryCompactionProgressFiles(
bool preserve_latest, const CompactionProgressFilesScan& scan_result) {
std::vector<std::string> filenames_to_delete;
// Always delete old progress files
filenames_to_delete.insert(filenames_to_delete.end(),
scan_result.old_progress_filenames.begin(),
scan_result.old_progress_filenames.end());
// Always delete temp files
filenames_to_delete.insert(filenames_to_delete.end(),
scan_result.temp_progress_filenames.begin(),
scan_result.temp_progress_filenames.end());
// Conditionally delete latest file
if (!preserve_latest && scan_result.HasLatestProgressFile()) {
filenames_to_delete.push_back(scan_result.latest_progress_filename.value());
}
return DeleteCompactionProgressFiles(filenames_to_delete);
}
// Loads compaction progress from a file and cleans up extra output
// files. After loading the progress, this function identifies and deletes any
// SST files in the output folder that are NOT tracked in the
// progress. This ensures consistency between the progress file and
// actual output files on disk.
Status DBImplSecondary::LoadCompactionProgressAndCleanupExtraOutputFiles(
const std::string& compaction_progress_file_path,
const CompactionProgressFilesScan& scan_result) {
Status s = ParseCompactionProgressFile(compaction_progress_file_path,
&compaction_progress_);
if (s.ok()) {
s = CleanupPhysicalCompactionOutputFiles(true /* preserve_tracked_files */,
scan_result);
}
return s;
}
Status DBImplSecondary::ParseCompactionProgressFile(
const std::string& compaction_progress_file_path,
CompactionProgress* compaction_progress) {
std::unique_ptr<FSSequentialFile> file;
Status s = fs_->NewSequentialFile(compaction_progress_file_path,
FileOptions(), &file, nullptr /* dbg */);
if (!s.ok()) {
return s;
}
std::unique_ptr<SequentialFileReader> file_reader(new SequentialFileReader(
std::move(file), compaction_progress_file_path,
immutable_db_options_.log_readahead_size, io_tracer_, {} /* listeners */,
immutable_db_options_.rate_limiter.get()));
Status reader_status;
struct CompactionProgressReaderReporter : public log::Reader::Reporter {
Status* status;
explicit CompactionProgressReaderReporter(Status* s) : status(s) {}
void Corruption(size_t /*bytes*/, const Status& s,
uint64_t /*log_number*/) override {
if (status->ok()) {
*status = s;
}
}
void OldLogRecord(size_t /*bytes*/) override {
// Ignore old records
}
} progress_reporter(&reader_status);
log::Reader compaction_progress_reader(
immutable_db_options_.info_log, std::move(file_reader),
&progress_reporter, true /* checksum */, 0 /* log_num */);
// LIMITATION: Only supports resuming single subcompaction
SubcompactionProgressBuilder progress_builder;
Slice slice;
std::string record;
while (compaction_progress_reader.ReadRecord(&slice, &record)) {
if (!reader_status.ok()) {
return reader_status;
}
VersionEdit edit;
s = edit.DecodeFrom(slice);
if (!s.ok()) {
break;
}
bool res = progress_builder.ProcessVersionEdit(edit);
if (!res) {
break;
}
}
if (!s.ok()) {
return s;
}
if (progress_builder.HasAccumulatedSubcompactionProgress()) {
compaction_progress->clear();
compaction_progress->push_back(
progress_builder.GetAccumulatedSubcompactionProgress());
} else {
s = Status::NotFound("No compaction progress was persisted yet");
}
return s;
}
Status DBImplSecondary::RenameCompactionProgressFile(
const std::string& temp_file_path, std::string* final_file_path) {
uint64_t current_time = env_->NowMicros();
*final_file_path = CompactionProgressFileName(secondary_path_, current_time);
WriteOptions write_options(Env::IOActivity::kCompaction);
IOOptions opts;
Status s = WritableFileWriter::PrepareIOOptions(write_options, opts);
if (!s.ok()) {
return s;
}
s = fs_->RenameFile(temp_file_path, *final_file_path, opts,
nullptr /* dbg */);
return s;
}
Status DBImplSecondary::CleanupPhysicalCompactionOutputFiles(
bool preserve_tracked_files,
const CompactionProgressFilesScan& scan_result) {
std::unordered_set<uint64_t> files_to_preserve;
if (preserve_tracked_files) {
for (const auto& subcompaction_progress : compaction_progress_) {
for (const auto& file_metadata :
subcompaction_progress.output_level_progress.GetOutputFiles()) {
files_to_preserve.insert(file_metadata.fd.GetNumber());
}
for (const auto& file_metadata :
subcompaction_progress.proximal_output_level_progress
.GetOutputFiles()) {
files_to_preserve.insert(file_metadata.fd.GetNumber());
}
}
}
WriteOptions write_options(Env::IOActivity::kCompaction);
IOOptions opts;
Status s = WritableFileWriter::PrepareIOOptions(write_options, opts);
if (!s.ok()) {
return s;
}
for (uint64_t file_number : scan_result.table_file_numbers) {
bool should_delete =
!preserve_tracked_files ||
(files_to_preserve.find(file_number) == files_to_preserve.end());
if (should_delete) {
std::string file_path = MakeTableFileName(secondary_path_, file_number);
Status delete_status =
fs_->DeleteFile(file_path, opts, nullptr /* dbg */);
if (!delete_status.ok()) {
return delete_status;
}
}
}
return Status::OK();
}
Status DBImplSecondary::InitializeCompactionWorkspace(
bool allow_resumption, std::unique_ptr<FSDirectory>* output_dir,
std::unique_ptr<log::Writer>* compaction_progress_writer) {
// Create output directory if it doest exist yet
Status s = CreateAndNewDirectory(fs_.get(), secondary_path_, output_dir);
if (!s.ok() || !allow_resumption) {
return s;
}
s = PrepareCompactionProgressState();
if (!s.ok()) {
return s;
}
s = FinalizeCompactionProgressWriter(compaction_progress_writer);
if (!s.ok()) {
return s;
}
return Status::OK();
}
// PrepareCompactionProgressState() manages compaction progress files and output
// files to ensure a clean, consistent state for resuming or starting fresh
// compaction.
//
// PRECONDITION:
// - This function is ONLY called when allow_resumption = true
// - The caller wants resumption support for this compaction attempt
//
// FILE SYSTEM STATE (before entering this function):
// - 0 or more compaction progress files may exist in `secondary_path_`:
// * Latest progress file (from the most recent compaction attempt)
// * Older progress files (left by crashing during a previous
// InitializeCompactionWorkspace() call)
// * Temporary progress files (left by crashing during a previous
// InitializeCompactionWorkspace() call)
// - 0 or more compaction output files may exist in `secondary_path_`
//
// POSTCONDITIONS (after this function):
// - IF the latest progress file exists AND it parses successfully AND
// actually contains valid compaction progress:
// * Exactly one latest progress file remains
// * All older and temporary compaction progress files are deleted
// * All corresponding compaction output files are preserved
// * All extra compaction output files are deleted (files left by
// compaction
// crashing before persisting the progress)
// * Result: Ready to resume compaction from the saved progress
// - OTHERWISE (no latest progress file OR it fails to parse OR it's
// invalid):
// * ALL compaction progress files are deleted (latest + older +
// temporary)
// * ALL compaction output files are deleted
// * Result: Ready to start fresh compaction (despite allow_resumption =
// true, we cannot resume because there's no valid progress to resume from)
//
// ERROR HANDLING:
// - ON ERROR (if any of the postconditions cannot be achieved):
// * Function returns error status
// * File system may be left in a partially modified state
// * Caller should manually clean up secondary_path_ before retrying
// * Subsequent OpenAndCompact() calls to this clean secondary_path_ will
// effectively start fresh compaction
Status DBImplSecondary::PrepareCompactionProgressState() {
Status s;
// STEP 1: Scan directory ONCE (includes progress files + table files)
CompactionProgressFilesScan scan_result;
s = ScanCompactionProgressFiles(&scan_result);
if (!s.ok()) {
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
"Encountered error when scanning for compaction "
"progress files: %s",
s.ToString().c_str());
return s;
}
std::optional<std::string> latest_progress_file =
scan_result.latest_progress_filename;
// STEP 2: Determine if we should resume
bool should_resume = false;
if (latest_progress_file.has_value()) {
should_resume = true;
} else {
ROCKS_LOG_WARN(immutable_db_options_.info_log,
"Did not find any latest compaction progress file. "
"Will perform clean up to start fresh compaction");
}
// STEP 3: Cleanup using pre-scanned results
if (should_resume) {
// Keep latest, delete old/temp
s = CleanupOldAndTemporaryCompactionProgressFiles(
true /* preserve_latest */, scan_result);
} else {
// Delete everything including latest
s = CleanupOldAndTemporaryCompactionProgressFiles(
false /* preserve_latest */, scan_result);
latest_progress_file.reset();
}
if (!s.ok()) {
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
"Failed to clean up compaction progress file(s): %s. "
"Will fail the compaction",
s.ToString().c_str());
return s;
}
// STEP 4: Load progress if resuming
if (latest_progress_file.has_value()) {
uint64_t timestamp = scan_result.latest_progress_timestamp;
std::string compaction_progress_file_path =
CompactionProgressFileName(secondary_path_, timestamp);
s = LoadCompactionProgressAndCleanupExtraOutputFiles(
compaction_progress_file_path, scan_result);
if (!s.ok()) {
ROCKS_LOG_WARN(immutable_db_options_.info_log,
"Failed to load the latest compaction "
"progress from %s: %s. Will perform clean up "
"to start fresh compaction",
latest_progress_file.value().c_str(),
s.ToString().c_str());
return HandleInvalidOrNoCompactionProgress(compaction_progress_file_path,
scan_result);
}
ROCKS_LOG_DEBUG(
immutable_db_options_.info_log,
"Loaded compaction progress with %zu subcompaction(s) from %s",
compaction_progress_.size(), compaction_progress_file_path.c_str());
return s;
} else {
return HandleInvalidOrNoCompactionProgress(
std::nullopt /* compaction_progress_file_path */, scan_result);
}
}
uint64_t DBImplSecondary::CalculateResumedCompactionBytes(
const CompactionProgress& compaction_progress) const {
uint64_t total_resumed_bytes = 0;
for (const auto& subcompaction_progress : compaction_progress) {
for (const auto& file_meta :
subcompaction_progress.output_level_progress.GetOutputFiles()) {
total_resumed_bytes += file_meta.fd.file_size;
}
for (const auto& file_meta :
subcompaction_progress.proximal_output_level_progress
.GetOutputFiles()) {
total_resumed_bytes += file_meta.fd.file_size;
}
}
return total_resumed_bytes;
}
Status DBImplSecondary::HandleInvalidOrNoCompactionProgress(
const std::optional<std::string>& compaction_progress_file_path,
const CompactionProgressFilesScan& scan_result) {
compaction_progress_.clear();
Status s;
if (compaction_progress_file_path.has_value()) {
WriteOptions write_options(Env::IOActivity::kCompaction);
IOOptions opts;
s = WritableFileWriter::PrepareIOOptions(write_options, opts);
if (s.ok()) {
s = fs_->DeleteFile(compaction_progress_file_path.value(), opts,
nullptr /* dbg */);
}
if (!s.ok()) {
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
"Failed to remove invalid progress file: %s",
s.ToString().c_str());
return s;
}
}
s = CleanupPhysicalCompactionOutputFiles(false /* preserve_tracked_files */,
scan_result);
if (!s.ok()) {
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
"Failed to cleanup existing compaction output files: %s",
s.ToString().c_str());
return s;
}
return Status::OK();
}
Status DBImplSecondary::CompactWithoutInstallation(
const OpenAndCompactOptions& options, ColumnFamilyHandle* cfh,
const CompactionServiceInput& input, CompactionServiceResult* result) {
if (options.canceled && options.canceled->load(std::memory_order_acquire)) {
return Status::Incomplete(Status::SubCode::kManualCompactionPaused);
}
std::unique_ptr<FSDirectory> output_dir;
std::unique_ptr<log::Writer> compaction_progress_writer;
InstrumentedMutexLock l(&mutex_);
auto cfd = static_cast_with_check<ColumnFamilyHandleImpl>(cfh)->cfd();
if (!cfd) {
return Status::InvalidArgument("Cannot find column family" +
cfh->GetName());
}
Status s;
const auto& mutable_cf_options = cfd->GetLatestMutableCFOptions();
// TODO(hx235): Resuming compaction is currently incompatible with
// output hash verification (enabled via paranoid_file_checks=true or
// verify_output_flags containing kVerifyIteration) because resumed compaction
// will lose the hash computed before interruption.
// Potential solutions:
// 1. Persist the hash state: Before interruption, save the current hash value
// of each output file to disk, allowing validation to continue correctly
// after resumption.
// 2. Immediate verification: Move output verification to happen
// immediately after each output file is created and closed, eliminating
// the need to maintain hash state across resumption boundaries.
bool output_hash_verification_enabled =
mutable_cf_options.paranoid_file_checks ||
!!(mutable_cf_options.verify_output_flags &
VerifyOutputFlags::kVerifyIteration);
bool allow_resumption =
options.allow_resumption && !output_hash_verification_enabled;
if (options.allow_resumption && output_hash_verification_enabled) {
ROCKS_LOG_WARN(immutable_db_options_.info_log,
"Resume compaction configured but disabled due to "
"incompatibility with output hash verification "
"(paranoid_file_checks=true or verify_output_flags "
"containing kVerifyIteration)");
}
mutex_.Unlock();
s = InitializeCompactionWorkspace(allow_resumption, &output_dir,
&compaction_progress_writer);
mutex_.Lock();
if (!s.ok()) {
return s;
}
std::unordered_set<uint64_t> input_set;
for (const auto& file_name : input.input_files) {
@@ -848,16 +1351,15 @@ Status DBImplSecondary::CompactWithoutInstallation(
VersionStorageInfo* vstorage = version->storage_info();
// Use comp_options to reuse some CompactFiles functions
CompactionOptions comp_options;
comp_options.compression = kDisableCompressionOption;
comp_options.output_file_size_limit = MaxFileSizeForLevel(
cfd->GetLatestMutableCFOptions(), input.output_level,
cfd->ioptions().compaction_style, vstorage->base_level(),
mutable_cf_options, input.output_level, cfd->ioptions().compaction_style,
vstorage->base_level(),
cfd->ioptions().level_compaction_dynamic_level_bytes);
std::vector<CompactionInputFiles> input_files;
Status s = cfd->compaction_picker()->GetCompactionInputsFromFileNumbers(
s = cfd->compaction_picker()->GetCompactionInputsFromFileNumbers(
&input_files, &input_set, vstorage, comp_options);
if (!s.ok()) {
ROCKS_LOG_ERROR(
@@ -867,30 +1369,38 @@ Status DBImplSecondary::CompactWithoutInstallation(
return s;
}
const int job_id = next_job_id_.fetch_add(1);
JobContext job_context(job_id, true /*create_superversion*/);
std::vector<SequenceNumber> snapshots = input.snapshots;
// TODO - snapshot_checker support in Remote Compaction
job_context.InitSnapshotContext(/*checker=*/nullptr,
/*managed_snapshot=*/nullptr,
kMaxSequenceNumber, std::move(snapshots));
// TODO - consider serializing the entire Compaction object and using it as
// input instead of recreating it in the remote worker
std::unique_ptr<Compaction> c;
assert(cfd->compaction_picker());
c.reset(cfd->compaction_picker()->CompactFiles(
comp_options, input_files, input.output_level, vstorage,
cfd->GetLatestMutableCFOptions(), mutable_db_options_, 0));
assert(c != nullptr);
c->FinalizeInputInfo(version);
// Create output directory if it's not existed yet
std::unique_ptr<FSDirectory> output_dir;
s = CreateAndNewDirectory(fs_.get(), secondary_path_, &output_dir);
if (!s.ok()) {
return s;
std::optional<SequenceNumber> earliest_snapshot = std::nullopt;
// Standalone Range Deletion Optimization is only supported in Universal
// Compactions - https://github.com/facebook/rocksdb/pull/13078
if (cfd->GetLatestCFOptions().compaction_style ==
CompactionStyle::kCompactionStyleUniversal) {
earliest_snapshot = !job_context.snapshot_seqs.empty()
? job_context.snapshot_seqs.front()
: kMaxSequenceNumber;
}
c.reset(cfd->compaction_picker()->PickCompactionForCompactFiles(
comp_options, input_files, input.output_level, vstorage,
mutable_cf_options, mutable_db_options_, 0, earliest_snapshot,
job_context.snapshot_checker));
assert(c != nullptr);
c->FinalizeInputInfo(version);
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL,
immutable_db_options_.info_log.get());
const int job_id = next_job_id_.fetch_add(1);
JobContext job_context(0, true /*create_superversion*/);
std::vector<SequenceNumber> snapshots = input.snapshots;
job_context.InitSnapshotContext(nullptr, nullptr, kMaxSequenceNumber,
std::move(snapshots));
// use primary host's db_id for running the compaction, but db_session_id is
// using the local one, which is to make sure the unique id is unique from
// the remote compactors. Because the id is generated from db_id,
@@ -905,13 +1415,15 @@ Status DBImplSecondary::CompactWithoutInstallation(
options.canceled ? *options.canceled : kManualCompactionCanceledFalse_,
input.db_id, db_session_id_, secondary_path_, input, result);
compaction_job.Prepare();
compaction_job.Prepare(compaction_progress_,
compaction_progress_writer.get());
mutex_.Unlock();
s = compaction_job.Run();
mutex_.Lock();
// clean up
// These cleanup functions handle metadata and state cleanup only and
// not the physical files
compaction_job.io_status().PermitUncheckedError();
compaction_job.CleanupCompaction();
c->ReleaseCompactionFiles(s);
@@ -919,6 +1431,18 @@ Status DBImplSecondary::CompactWithoutInstallation(
TEST_SYNC_POINT_CALLBACK("DBImplSecondary::CompactWithoutInstallation::End",
&s);
if (!compaction_progress_.empty() && s.ok()) {
uint64_t total_resumed_bytes =
CalculateResumedCompactionBytes(compaction_progress_);
if (total_resumed_bytes > 0 &&
immutable_db_options_.statistics != nullptr) {
RecordTick(immutable_db_options_.statistics.get(),
REMOTE_COMPACT_RESUMED_BYTES, total_resumed_bytes);
}
}
result->status = s;
return s;
}
@@ -1074,4 +1598,153 @@ Status DB::OpenAndCompact(
output, override_options);
}
Status DBImplSecondary::CreateCompactionProgressWriter(
const std::string& file_path,
std::unique_ptr<log::Writer>* compaction_progress_writer) {
std::unique_ptr<FSWritableFile> file;
Status s =
fs_->NewWritableFile(file_path, FileOptions(), &file, nullptr /* dbg */);
if (!s.ok()) {
return s;
}
std::unique_ptr<WritableFileWriter> file_writer(
new WritableFileWriter(std::move(file), file_path, FileOptions()));
compaction_progress_writer->reset(
new log::Writer(std::move(file_writer), 0 /* log_number */,
false /* recycle_log_files */));
return Status::OK();
}
Status DBImplSecondary::PersistInitialCompactionProgress(
log::Writer* compaction_progress_writer,
const CompactionProgress& compaction_progress) {
assert(compaction_progress_writer);
// LIMITATION: Only supports resuming single subcompaction
assert(compaction_progress.size() == 1);
const SubcompactionProgress& subcompaction_progress = compaction_progress[0];
VersionEdit edit;
edit.SetSubcompactionProgress(subcompaction_progress);
std::string record;
if (!edit.EncodeTo(&record)) {
return Status::IOError("Failed to encode the initial compaction progress");
}
WriteOptions write_options(Env::IOActivity::kCompaction);
Status s = compaction_progress_writer->AddRecord(write_options, record);
if (!s.ok()) {
return s;
}
IOOptions opts;
s = WritableFileWriter::PrepareIOOptions(write_options, opts);
if (!s.ok()) {
return s;
}
s = compaction_progress_writer->file()->Sync(opts,
immutable_db_options_.use_fsync);
return s;
}
Status DBImplSecondary::HandleCompactionProgressWriterCreationFailure(
const std::string& temp_file_path, const std::string& final_file_path,
std::unique_ptr<log::Writer>* compaction_progress_writer) {
compaction_progress_writer->reset();
const std::vector<std::string> paths_to_delete = {final_file_path,
temp_file_path};
Status s;
for (const auto& file_path : paths_to_delete) {
WriteOptions write_options(Env::IOActivity::kCompaction);
IOOptions opts;
s = WritableFileWriter::PrepareIOOptions(write_options, opts);
if (s.ok()) {
s = fs_->DeleteFile(file_path, opts, nullptr /* dbg */);
}
if (!s.ok()) {
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
"Failed to cleanup the compaction progress file "
"during writer creation failure: %s",
s.ToString().c_str());
return s;
}
}
return s;
}
Status DBImplSecondary::FinalizeCompactionProgressWriter(
std::unique_ptr<log::Writer>* compaction_progress_writer) {
uint64_t timestamp = env_->NowMicros();
const std::string temp_file_path =
TempCompactionProgressFileName(secondary_path_, timestamp);
Status s = CreateCompactionProgressWriter(temp_file_path,
compaction_progress_writer);
if (!s.ok()) {
ROCKS_LOG_WARN(immutable_db_options_.info_log,
"Failed to create compaction progress writer at "
"temp path %s: %s. Will perform clean up "
"to start compaction without progress persistence",
temp_file_path.c_str(), s.ToString().c_str());
return HandleCompactionProgressWriterCreationFailure(
temp_file_path, "" /* final_file_path */, compaction_progress_writer);
}
if (!compaction_progress_.empty()) {
s = PersistInitialCompactionProgress(compaction_progress_writer->get(),
compaction_progress_);
if (!s.ok()) {
ROCKS_LOG_WARN(immutable_db_options_.info_log,
"Failed to persist the initial copmaction "
"progress: %s. Will perform clean up "
"to start compaction without progress persistence",
s.ToString().c_str());
return HandleCompactionProgressWriterCreationFailure(
temp_file_path, "" /* final_file_path */, compaction_progress_writer);
}
}
compaction_progress_writer->reset();
std::string final_file_path;
s = RenameCompactionProgressFile(temp_file_path, &final_file_path);
if (!s.ok()) {
ROCKS_LOG_WARN(immutable_db_options_.info_log,
"Failed to rename temporary compaction progress "
"file from %s to %s: %s. Will perform clean up "
"to start compaction without progress persistence",
temp_file_path.c_str(), final_file_path.c_str(),
s.ToString().c_str());
return HandleCompactionProgressWriterCreationFailure(
temp_file_path, final_file_path, compaction_progress_writer);
}
s = CreateCompactionProgressWriter(final_file_path,
compaction_progress_writer);
if (!s.ok()) {
ROCKS_LOG_WARN(immutable_db_options_.info_log,
"Failed to create the final compaction progress "
"writer: %s. Will attempt clean to start the compaction "
"without progress persistence",
s.ToString().c_str());
return HandleCompactionProgressWriterCreationFailure(
"" /* temp_file_path */, final_file_path, compaction_progress_writer);
}
ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
"Finalized compaction progress writer onto %s",
final_file_path.c_str());
return Status::OK();
}
} // namespace ROCKSDB_NAMESPACE
+83
View File
@@ -303,6 +303,87 @@ class DBImplSecondary : public DBImpl {
const CompactionServiceInput& input,
CompactionServiceResult* result);
private:
// Holds results of compaction progress files and output files from a single
// directory scan
struct CompactionProgressFilesScan {
// The latest (newest) progress file filename
std::optional<std::string> latest_progress_filename;
uint64_t latest_progress_timestamp = 0;
// Older progress file filenames (to be deleted)
autovector<std::string> old_progress_filenames;
// Temporary progress file filenames (to be deleted)
autovector<std::string> temp_progress_filenames;
// All output file numbers - for cleanup optimization
std::vector<uint64_t> table_file_numbers;
bool HasLatestProgressFile() const {
return latest_progress_filename.has_value();
}
void Clear() {
latest_progress_filename.reset();
latest_progress_timestamp = 0;
old_progress_filenames.clear();
temp_progress_filenames.clear();
table_file_numbers.clear();
}
};
Status InitializeCompactionWorkspace(
bool allow_resumption, std::unique_ptr<FSDirectory>* output_dir,
std::unique_ptr<log::Writer>* compaction_progress_writer);
Status PrepareCompactionProgressState();
Status ScanCompactionProgressFiles(CompactionProgressFilesScan* scan_result);
Status DeleteCompactionProgressFiles(
const std::vector<std::string>& filenames);
Status CleanupOldAndTemporaryCompactionProgressFiles(
bool preserve_latest, const CompactionProgressFilesScan& scan_result);
Status LoadCompactionProgressAndCleanupExtraOutputFiles(
const std::string& compaction_progress_file_path,
const CompactionProgressFilesScan& scan_result);
Status ParseCompactionProgressFile(
const std::string& compaction_progress_file_path,
CompactionProgress* compaction_progress);
Status HandleInvalidOrNoCompactionProgress(
const std::optional<std::string>& compaction_progress_file_path,
const CompactionProgressFilesScan& scan_result);
Status CleanupPhysicalCompactionOutputFiles(
bool preserve_tracked_files,
const CompactionProgressFilesScan& scan_result);
Status FinalizeCompactionProgressWriter(
std::unique_ptr<log::Writer>* compaction_progress_writer);
Status CreateCompactionProgressWriter(
const std::string& file_path,
std::unique_ptr<log::Writer>* compaction_progress_writer);
Status PersistInitialCompactionProgress(
log::Writer* compaction_progress_writer,
const CompactionProgress& compaction_progress);
Status RenameCompactionProgressFile(const std::string& temp_file_path,
std::string* final_file_path);
Status HandleCompactionProgressWriterCreationFailure(
const std::string& temp_file_path, const std::string& final_file_path,
std::unique_ptr<log::Writer>* compaction_progress_writer);
uint64_t CalculateResumedCompactionBytes(
const CompactionProgress& compaction_progress) const;
// Cache log readers for each log number, used for continue WAL replay
// after recovery
std::map<uint64_t, std::unique_ptr<LogReaderContainer>> log_readers_;
@@ -311,6 +392,8 @@ class DBImplSecondary : public DBImpl {
std::unordered_map<ColumnFamilyData*, uint64_t> cfd_to_current_log_;
const std::string secondary_path_;
CompactionProgress compaction_progress_;
};
} // namespace ROCKSDB_NAMESPACE
+104
View File
@@ -1565,11 +1565,115 @@ void DBIter::SetSavedKeyToSeekForPrevTarget(const Slice& target) {
}
}
Status DBIter::ValidateScanOptions(const MultiScanArgs& multiscan_opts) const {
if (multiscan_opts.empty()) {
return Status::InvalidArgument("Empty MultiScanArgs");
}
const std::vector<ScanOptions>& scan_opts = multiscan_opts.GetScanRanges();
const bool has_limit = scan_opts.front().range.limit.has_value();
if (!has_limit && scan_opts.size() > 1) {
return Status::InvalidArgument("Scan has no upper bound");
}
for (size_t i = 0; i < scan_opts.size(); ++i) {
const auto& scan_range = scan_opts[i].range;
if (!scan_range.start.has_value()) {
return Status::InvalidArgument("Scan has no start key at index " +
std::to_string(i));
}
if (scan_range.limit.has_value()) {
if (user_comparator_.CompareWithoutTimestamp(
scan_range.start.value(), /*a_has_ts=*/false,
scan_range.limit.value(), /*b_has_ts=*/false) >= 0) {
return Status::InvalidArgument(
"Scan start key is large or equal than limit at index " +
std::to_string(i));
}
}
if (i > 0) {
if (!scan_range.limit.has_value()) {
// multiple scan without limit scan ranges
return Status::InvalidArgument("Scan has no upper bound at index " +
std::to_string(i));
}
const auto& last_end_key = scan_opts[i - 1].range.limit.value();
if (user_comparator_.CompareWithoutTimestamp(
scan_range.start.value(), /*a_has_ts=*/false, last_end_key,
/*b_has_ts=*/false) < 0) {
return Status::InvalidArgument("Overlapping ranges at index " +
std::to_string(i));
}
}
}
return Status::OK();
}
void DBIter::Prepare(const MultiScanArgs& scan_opts) {
status_ = ValidateScanOptions(scan_opts);
if (!status_.ok()) {
return;
}
std::optional<MultiScanArgs> new_scan_opts;
new_scan_opts.emplace(scan_opts);
scan_opts_.swap(new_scan_opts);
scan_index_ = 0;
if (!scan_opts.empty()) {
iter_.Prepare(&scan_opts_.value());
} else {
iter_.Prepare(nullptr);
}
}
void DBIter::Seek(const Slice& target) {
PERF_COUNTER_ADD(iter_seek_count, 1);
PERF_CPU_TIMER_GUARD(iter_seek_cpu_nanos, clock_);
StopWatch sw(clock_, statistics_, DB_SEEK);
if (scan_opts_.has_value()) {
// Validate the seek target is as expected in the previously prepared range
auto const& scan_ranges = scan_opts_.value().GetScanRanges();
if (scan_index_ >= scan_ranges.size()) {
status_ = Status::InvalidArgument(
"Seek called after exhausting all of the scan ranges");
valid_ = false;
return;
}
// Validate start key of next prepare range matches the seek target
auto const& range = scan_ranges[scan_index_];
auto const& start = range.range.start;
assert(start.has_value());
if (user_comparator_.CompareWithoutTimestamp(target, *start) != 0) {
status_ = Status::InvalidArgument(
"Seek target does not match the start of the next prepared range at "
"index " +
std::to_string(scan_index_));
valid_ = false;
return;
}
// validate the upper bound is set to the same value of limit, if limit
// exists
auto const& limit = range.range.limit;
if (limit.has_value()) {
if (iterate_upper_bound_ == nullptr ||
user_comparator_.CompareWithoutTimestamp(
limit.value(), *iterate_upper_bound_) != 0) {
status_ = Status::InvalidArgument(
"Upper bound is not set to the same limit value of the next "
"prepared range at index " +
std::to_string(scan_index_));
valid_ = false;
return;
}
}
scan_index_++;
}
if (cfh_ != nullptr) {
// TODO: What do we do if this returns an error?
Slice lower_bound, upper_bound;
+4 -11
View File
@@ -240,16 +240,8 @@ class DBIter final : public Iterator {
bool PrepareValue() override;
void Prepare(const std::vector<ScanOptions>& scan_opts) override {
std::optional<std::vector<ScanOptions>> new_scan_opts;
new_scan_opts.emplace(scan_opts);
scan_opts_.swap(new_scan_opts);
if (!scan_opts.empty()) {
iter_.Prepare(&scan_opts_.value());
} else {
iter_.Prepare(nullptr);
}
}
void Prepare(const MultiScanArgs& scan_opts) override;
Status ValidateScanOptions(const MultiScanArgs& multiscan_opts) const;
private:
DBIter(Env* _env, const ReadOptions& read_options,
@@ -505,7 +497,8 @@ class DBIter final : public Iterator {
const Slice* const timestamp_lb_;
const size_t timestamp_size_;
std::string saved_timestamp_;
std::optional<std::vector<ScanOptions>> scan_opts_;
std::optional<MultiScanArgs> scan_opts_;
size_t scan_index_{0};
ReadOnlyMemTable* const active_mem_;
SequenceNumber memtable_seqno_lb_;
uint32_t memtable_op_scan_flush_trigger_;
+785 -83
View File
@@ -4142,13 +4142,21 @@ TEST_P(DBIteratorTest, AverageMemtableOpsScanFlushTriggerByOverwrites) {
ASSERT_EQ(1, NumTableFilesAtLevel(0));
}
class DBMultiScanIteratorTest : public DBTestBase {
class DBMultiScanIteratorTest : public DBTestBase,
public ::testing::WithParamInterface<bool> {
public:
DBMultiScanIteratorTest()
: DBTestBase("db_multi_scan_iterator_test", /*env_do_fsync=*/true) {}
};
TEST_F(DBMultiScanIteratorTest, BasicTest) {
// Param 0: ReadOptions::fill_cache
INSTANTIATE_TEST_CASE_P(DBMultiScanIteratorTest, DBMultiScanIteratorTest,
::testing::Bool());
TEST_P(DBMultiScanIteratorTest, BasicTest) {
auto options = CurrentOptions();
DestroyAndReopen(options);
// Create a file
for (int i = 0; i < 100; ++i) {
std::stringstream ss;
@@ -4159,9 +4167,10 @@ TEST_F(DBMultiScanIteratorTest, BasicTest) {
std::vector<std::string> key_ranges({"k03", "k10", "k25", "k50"});
ReadOptions ro;
std::vector<ScanOptions> scan_options(
{ScanOptions(key_ranges[0], key_ranges[1]),
ScanOptions(key_ranges[2], key_ranges[3])});
ro.fill_cache = GetParam();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.insert(key_ranges[0], key_ranges[1]);
scan_options.insert(key_ranges[2], key_ranges[3]);
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
std::unique_ptr<MultiScan> iter =
dbfull()->NewMultiScan(ro, cfh, scan_options);
@@ -4187,65 +4196,11 @@ TEST_F(DBMultiScanIteratorTest, BasicTest) {
abort();
}
iter.reset();
// Test the overlapping scan case
key_ranges[1] = "k30";
scan_options[0] = ScanOptions(key_ranges[0], key_ranges[1]);
iter = dbfull()->NewMultiScan(ro, cfh, scan_options);
try {
int idx = 0;
int count = 0;
for (auto range : *iter) {
for (auto it : range) {
ASSERT_GE(it.first.ToString().compare(key_ranges[idx]), 0);
ASSERT_LT(it.first.ToString().compare(key_ranges[idx + 1]), 0);
count++;
}
idx += 2;
}
ASSERT_EQ(count, 52);
} catch (MultiScanException& ex) {
// Make sure exception contains the status
ASSERT_NOK(ex.status());
std::cerr << "Iterator returned status " << ex.what();
abort();
} catch (std::logic_error& ex) {
std::cerr << "Iterator returned logic error " << ex.what();
abort();
}
iter.reset();
// Test the no limit scan case
scan_options[0] = ScanOptions(key_ranges[0]);
scan_options[1] = ScanOptions(key_ranges[2]);
iter = dbfull()->NewMultiScan(ro, cfh, scan_options);
try {
int idx = 0;
int count = 0;
for (auto range : *iter) {
for (auto it : range) {
ASSERT_GE(it.first.ToString().compare(key_ranges[idx]), 0);
if (it.first.ToString().compare(key_ranges[idx + 1]) == 0) {
break;
}
count++;
}
idx += 2;
}
ASSERT_EQ(count, 52);
} catch (MultiScanException& ex) {
// Make sure exception contains the status
ASSERT_NOK(ex.status());
std::cerr << "Iterator returned status " << ex.what();
abort();
} catch (std::logic_error& ex) {
std::cerr << "Iterator returned logic error " << ex.what();
abort();
}
iter.reset();
}
TEST_F(DBMultiScanIteratorTest, MixedBoundsTest) {
TEST_P(DBMultiScanIteratorTest, MixedBoundsTest) {
auto options = CurrentOptions();
DestroyAndReopen(options);
// Create a file
for (int i = 0; i < 100; ++i) {
std::stringstream ss;
@@ -4257,9 +4212,11 @@ TEST_F(DBMultiScanIteratorTest, MixedBoundsTest) {
std::vector<std::string> key_ranges(
{"k03", "k10", "k25", "k50", "k75", "k90"});
ReadOptions ro;
std::vector<ScanOptions> scan_options(
{ScanOptions(key_ranges[0], key_ranges[1]), ScanOptions(key_ranges[2]),
ScanOptions(key_ranges[4], key_ranges[5])});
ro.fill_cache = GetParam();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.insert(key_ranges[0], key_ranges[1]);
scan_options.insert(key_ranges[2]);
scan_options.insert(key_ranges[4], key_ranges[5]);
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
std::unique_ptr<MultiScan> iter =
dbfull()->NewMultiScan(ro, cfh, scan_options);
@@ -4268,13 +4225,15 @@ TEST_F(DBMultiScanIteratorTest, MixedBoundsTest) {
int count = 0;
for (auto range : *iter) {
for (auto it : range) {
ASSERT_GE(it.first.ToString().compare(
scan_options[idx].range.start->ToString()),
0);
if (scan_options[idx].range.limit) {
ASSERT_LT(it.first.ToString().compare(
scan_options[idx].range.limit->ToString()),
0);
ASSERT_GE(
it.first.ToString().compare(
scan_options.GetScanRanges()[idx].range.start->ToString()),
0);
if (scan_options.GetScanRanges()[idx].range.limit) {
ASSERT_LT(
it.first.ToString().compare(
scan_options.GetScanRanges()[idx].range.limit->ToString()),
0);
}
count++;
}
@@ -4291,23 +4250,25 @@ TEST_F(DBMultiScanIteratorTest, MixedBoundsTest) {
abort();
}
iter.reset();
scan_options[0] = ScanOptions(key_ranges[0]);
scan_options[1] = ScanOptions(key_ranges[2], key_ranges[3]);
scan_options[2] = ScanOptions(key_ranges[4]);
scan_options = MultiScanArgs(BytewiseComparator());
scan_options.insert(key_ranges[0]);
scan_options.insert(key_ranges[2], key_ranges[3]);
scan_options.insert(key_ranges[4]);
iter = dbfull()->NewMultiScan(ro, cfh, scan_options);
try {
int idx = 0;
int count = 0;
for (auto range : *iter) {
for (auto it : range) {
ASSERT_GE(it.first.ToString().compare(
scan_options[idx].range.start->ToString()),
0);
if (scan_options[idx].range.limit) {
ASSERT_LT(it.first.ToString().compare(
scan_options[idx].range.limit->ToString()),
0);
ASSERT_GE(
it.first.ToString().compare(
scan_options.GetScanRanges()[idx].range.start->ToString()),
0);
if (scan_options.GetScanRanges()[idx].range.limit) {
ASSERT_LT(
it.first.ToString().compare(
scan_options.GetScanRanges()[idx].range.limit->ToString()),
0);
}
count++;
}
@@ -4325,6 +4286,747 @@ TEST_F(DBMultiScanIteratorTest, MixedBoundsTest) {
}
iter.reset();
}
TEST_P(DBMultiScanIteratorTest, RangeAcrossFiles) {
auto options = CurrentOptions();
options.target_file_size_base = 100 << 10; // 20KB
options.compaction_style = kCompactionStyleUniversal;
options.num_levels = 50;
options.compression = kNoCompression;
DestroyAndReopen(options);
auto rnd = Random::GetTLSInstance();
// Write ~200KB data
for (int i = 0; i < 100; ++i) {
ASSERT_OK(Put(Key(i), rnd->RandomString(2 << 10)));
}
ASSERT_OK(Flush());
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
ASSERT_EQ(2, NumTableFilesAtLevel(49));
std::vector<std::string> key_ranges({Key(10), Key(90)});
ReadOptions ro;
ro.fill_cache = GetParam();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.insert(key_ranges[0], key_ranges[1]);
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
std::unique_ptr<MultiScan> iter =
dbfull()->NewMultiScan(ro, cfh, scan_options);
try {
int i = 10;
for (auto range : *iter) {
for (auto it : range) {
ASSERT_EQ(it.first.ToString(), Key(i));
++i;
}
}
ASSERT_EQ(i, 90);
} catch (MultiScanException& ex) {
// Make sure exception contains the status
ASSERT_NOK(ex.status());
std::cerr << "Iterator returned status " << ex.what();
abort();
} catch (std::logic_error& ex) {
std::cerr << "Iterator returned logic error " << ex.what();
abort();
}
iter.reset();
}
TEST_P(DBMultiScanIteratorTest, FailureTest) {
auto options = CurrentOptions();
options.compression = kNoCompression;
DestroyAndReopen(options);
Random rnd(301);
// Create a file
for (int i = 0; i < 100; ++i) {
std::stringstream ss;
ss << std::setw(2) << std::setfill('0') << i;
ASSERT_OK(Put("k" + ss.str(), rnd.RandomString(1024)));
}
ASSERT_OK(Flush());
std::vector<std::string> key_ranges({"k04", "k06", "k12", "k14"});
ReadOptions ro;
Slice ub;
ro.iterate_upper_bound = &ub;
ro.fill_cache = GetParam();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.insert(key_ranges[0], key_ranges[1]);
scan_options.insert(key_ranges[2], key_ranges[3]);
scan_options.max_prefetch_size = 4500;
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
std::unique_ptr<Iterator> iter(dbfull()->NewIterator(ro, cfh));
ASSERT_NE(iter, nullptr);
iter->Prepare(scan_options);
int count = 0;
ub = key_ranges[1];
iter->Seek(key_ranges[0]);
while (iter->status().ok() && iter->Valid()) {
ASSERT_GE(iter->key().compare(key_ranges[0]), 0);
ASSERT_LT(iter->key().compare(key_ranges[1]), 0);
count++;
iter->Next();
}
ASSERT_OK(iter->status()) << iter->status().ToString();
ASSERT_EQ(count, 2);
// Second seek should hit the max_prefetch_size limit
ub = key_ranges[3];
iter->Seek(key_ranges[2]);
ASSERT_NOK(iter->status());
iter.reset();
// Test the case of unexpected Seek key
iter.reset(dbfull()->NewIterator(ro, cfh));
ASSERT_NE(iter, nullptr);
scan_options.max_prefetch_size = 0;
iter->Prepare(scan_options);
ub = key_ranges[3];
iter->Seek(key_ranges[2]);
ASSERT_NOK(iter->status());
iter.reset();
}
TEST_P(DBMultiScanIteratorTest, OutOfL0FileRange) {
// Test that prepare does not fail scan when a scan range
// is outside of a L0 file's key range.
auto options = CurrentOptions();
options.compression = kNoCompression;
DestroyAndReopen(options);
Random rnd(301);
// Create a Lmax file
// key01 ~ key99
for (int i = 0; i < 100; ++i) {
std::stringstream ss;
ss << std::setw(2) << std::setfill('0') << i;
ASSERT_OK(Put("k" + ss.str(), rnd.RandomString(1024)));
}
ASSERT_OK(Flush());
CompactRangeOptions cro;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
// Create a L0 file
// key00 ~ key09
for (int i = 0; i < 10; ++i) {
std::stringstream ss;
ss << std::setw(2) << std::setfill('0') << i;
ASSERT_OK(Put("k" + ss.str(), rnd.RandomString(1024)));
}
ASSERT_OK(Flush());
ASSERT_EQ(NumTableFilesAtLevel(0), 1);
// The second range is outside of L0 file's key range
std::vector<std::string> key_ranges({"k04", "k06", "k12", "k14"});
ReadOptions ro;
Slice ub;
ro.iterate_upper_bound = &ub;
ro.fill_cache = GetParam();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.insert(key_ranges[0], key_ranges[1]);
scan_options.insert(key_ranges[2], key_ranges[3]);
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
std::unique_ptr<Iterator> iter(dbfull()->NewIterator(ro, cfh));
ASSERT_NE(iter, nullptr);
iter->Prepare(scan_options);
int count = 0;
ub = key_ranges[1];
iter->Seek(key_ranges[0]);
while (iter->status().ok() && iter->Valid()) {
ASSERT_GE(iter->key().compare(key_ranges[0]), 0);
ASSERT_LT(iter->key().compare(key_ranges[1]), 0);
count++;
iter->Next();
}
ASSERT_OK(iter->status()) << iter->status().ToString();
ASSERT_EQ(count, 2);
ub = key_ranges[3];
count = 0;
iter->Seek(key_ranges[2]);
while (iter->status().ok() && iter->Valid()) {
ASSERT_GE(iter->key().compare(key_ranges[2]), 0);
ASSERT_LT(iter->key().compare(key_ranges[3]), 0);
count++;
iter->Next();
}
ASSERT_OK(iter->status()) << iter->status().ToString();
ASSERT_EQ(count, 2);
}
TEST_P(DBMultiScanIteratorTest, RangeBetweenFiles) {
auto options = CurrentOptions();
options.target_file_size_base = 100 << 10; // 20KB
options.compaction_style = kCompactionStyleUniversal;
options.num_levels = 50;
options.compression = kNoCompression;
DestroyAndReopen(options);
auto rnd = Random::GetTLSInstance();
// Write ~200KB data
for (int i = 0; i < 100; ++i) {
ASSERT_OK(Put(Key(i), rnd->RandomString(2 << 10)));
}
ASSERT_OK(Flush());
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
ASSERT_EQ(2, NumTableFilesAtLevel(49));
// Test with a scan range that overlaps an entire file, with upper bound
// between 2 files
std::vector<LiveFileMetaData> file_meta;
dbfull()->GetLiveFilesMetaData(&file_meta);
ASSERT_EQ(file_meta.size(), 2);
std::vector<std::string> key_ranges(4);
key_ranges[0] = file_meta[0].smallestkey;
key_ranges[1] = file_meta[0].largestkey + "0";
key_ranges[2] = file_meta[1].smallestkey + "0";
key_ranges[3] = file_meta[1].largestkey;
ReadOptions ro;
ro.fill_cache = GetParam();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.insert(key_ranges[0], key_ranges[1]);
scan_options.insert(key_ranges[2], key_ranges[3]);
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
std::unique_ptr<MultiScan> iter =
dbfull()->NewMultiScan(ro, cfh, scan_options);
try {
for (auto range : *iter) {
for (auto it : range) {
ASSERT_GE(it.first.ToString(), key_ranges[0]);
}
}
} catch (MultiScanException& ex) {
// Make sure exception contains the status
ASSERT_NOK(ex.status());
std::cerr << "Iterator returned status " << ex.what();
abort();
} catch (std::logic_error& ex) {
std::cerr << "Iterator returned logic error " << ex.what();
abort();
}
iter.reset();
// Test multiscan with a range entirely between adjacent files
key_ranges[0] = file_meta[0].largestkey + "0";
key_ranges[1] = file_meta[0].largestkey + "1";
key_ranges[2] = file_meta[1].smallestkey + "0";
key_ranges[3] = file_meta[1].largestkey;
(*scan_options).clear();
scan_options.insert(key_ranges[0], key_ranges[1]);
scan_options.insert(key_ranges[2], key_ranges[3]);
iter = dbfull()->NewMultiScan(ro, cfh, scan_options);
try {
for (auto range : *iter) {
for (auto it : range) {
ASSERT_GE(it.first.ToString(), key_ranges[0]);
}
}
} catch (MultiScanException& ex) {
// Make sure exception contains the status
ASSERT_NOK(ex.status());
std::cerr << "Iterator returned status " << ex.what();
abort();
} catch (std::logic_error& ex) {
std::cerr << "Iterator returned logic error " << ex.what();
abort();
}
iter.reset();
}
// This test case tests multiscan in the presence of fragmented range
// tombstones in the LSM.
TEST_P(DBMultiScanIteratorTest, FragmentedRangeTombstones) {
auto options = CurrentOptions();
// Compaction may create files 2x the target_file_size_base,
// so set this to 50KB so we atleast end up with 2 files of
// 100KB
options.target_file_size_base = 50 << 10; // 50KB
options.compaction_style = kCompactionStyleUniversal;
options.num_levels = 50;
options.compression = kNoCompression;
DestroyAndReopen(options);
// Setup the LSM as follows -
// 1. Ingest a file with 100 keys
// 2. Ingest a file with one overlapping key
// 3. Do a Put and flush a file to L0 with one overlapping key
// 4. Ingest a standalone delete range file that covers the full key space
// and a file with the same 100 keys with new values. This will ingest
// into L0 due to the presence of an existing file in L0
// The final LSM will have an SST in Lmax with 100 keys, and 2 SST files
// in Lmax-1 with half the keys each and completely overlapping delete ranges
std::unordered_map<std::string, std::string> kvs;
auto rnd = Random::GetTLSInstance();
auto create_ingestion_data_file_and_update_key_value =
[&](const std::string& filename, int start_key, int end_key) {
std::unique_ptr<SstFileWriter> writer;
writer.reset(new SstFileWriter(EnvOptions(), options));
ASSERT_OK(writer->Open(filename));
for (int i = start_key; i < end_key; ++i) {
auto kiter = kvs.find(Key(i));
if (kiter != kvs.end()) {
kvs.erase(kiter);
}
auto res =
kvs.emplace(std::make_pair(Key(i), rnd->RandomString(2 << 10)));
ASSERT_OK(writer->Put(res.first->first, res.first->second));
}
ASSERT_OK(writer->Finish());
writer.reset();
};
CreateColumnFamilies({"new_cf"}, options);
std::string ingest_file = dbname_ + "test.sst";
// Write ~200KB data
create_ingestion_data_file_and_update_key_value(ingest_file + "_0", 0, 100);
create_ingestion_data_file_and_update_key_value(ingest_file + "_1", 50, 51);
ColumnFamilyHandle* cfh = handles_[0];
IngestExternalFileOptions ifo;
Status s = dbfull()->IngestExternalFile(
cfh, {ingest_file + "_0", ingest_file + "_1"}, ifo);
ASSERT_OK(s);
ASSERT_OK(Put(0, Key(50), rnd->RandomString(2 << 10)));
ASSERT_OK(Flush());
{
std::unique_ptr<SstFileWriter> writer;
writer.reset(new SstFileWriter(EnvOptions(), options));
ASSERT_OK(writer->Open(ingest_file + "_2"));
ASSERT_OK(writer->DeleteRange("a", "z"));
ASSERT_OK(writer->Finish());
writer.reset();
}
create_ingestion_data_file_and_update_key_value(ingest_file + "_3", 0, 100);
s = dbfull()->IngestExternalFile(
cfh, {ingest_file + "_2", ingest_file + "_3"}, ifo);
ASSERT_OK(s);
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ColumnFamilyMetaData cf_meta;
dbfull()->GetColumnFamilyMetaData(cfh, &cf_meta);
// Only the L0 with range deletion is compacted.
ASSERT_EQ(1, cf_meta.levels[0].files.size());
ASSERT_EQ(0, cf_meta.levels[0].files[0].num_deletions);
// The first scan range overlaps the DB key range, while the second extends
// beyond but overlaps the delete range
std::vector<std::string> key_ranges({"key000085", "key000090", "l", "n"});
ReadOptions ro;
ro.fill_cache = GetParam();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.insert(key_ranges[0], key_ranges[1]);
scan_options.insert(key_ranges[2], key_ranges[3]);
std::unique_ptr<MultiScan> iter =
dbfull()->NewMultiScan(ro, cfh, scan_options);
try {
int i = 0;
int count = 0;
for (auto range : *iter) {
for (auto it : range) {
ASSERT_GE(it.first.ToString(), key_ranges[i]);
ASSERT_LT(it.first.ToString(), key_ranges[i + 1]);
auto kiter = kvs.find(it.first.ToString());
ASSERT_NE(kiter, kvs.end());
ASSERT_EQ(kiter->second, it.second.ToString());
count++;
}
i += 2;
}
ASSERT_EQ(i, 4);
ASSERT_EQ(count, 5);
} catch (MultiScanException& ex) {
ASSERT_OK(ex.status());
}
iter.reset();
// The second scan range start overlaps the delete range in the first file
// in Lmax-1, while the end overlaps the keys in the second file
(*scan_options).clear();
key_ranges[0] = "key000010";
key_ranges[1] = "key000020";
key_ranges[2] = "key0000500";
key_ranges[3] = "key000060";
scan_options.insert(key_ranges[0], key_ranges[1]);
scan_options.insert(key_ranges[2], key_ranges[3]);
iter = dbfull()->NewMultiScan(ro, cfh, scan_options);
try {
int i = 0;
int count = 0;
for (auto range : *iter) {
for (auto it : range) {
ASSERT_GE(it.first.ToString(), key_ranges[i]);
ASSERT_LT(it.first.ToString(), key_ranges[i + 1]);
auto kiter = kvs.find(it.first.ToString());
ASSERT_NE(kiter, kvs.end());
ASSERT_EQ(kiter->second, it.second.ToString());
count++;
}
i += 2;
}
ASSERT_EQ(i, 4);
ASSERT_EQ(count, 19);
} catch (MultiScanException& ex) {
ASSERT_OK(ex.status());
}
iter.reset();
}
TEST_P(DBMultiScanIteratorTest, ReseekAcrossBlocksSameUserKey) {
// This test exposes a bug where multiscan reseeks backwards when
// max_sequential_skip_in_iterations is triggered with the same user key
// spanning multiple data blocks.
auto options = CurrentOptions();
options.max_sequential_skip_in_iterations = 3;
options.compression = kNoCompression;
// Force each internal key into its own block
BlockBasedTableOptions table_options;
table_options.flush_block_policy_factory =
std::make_shared<FlushBlockEveryKeyPolicyFactory>();
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
DestroyAndReopen(options);
// Taking a snapshot after each Put to preserve all versions during flush.
std::vector<const Snapshot*> snapshots;
for (int i = 0; i < 7; ++i) {
ASSERT_OK(Put("key_a", "value_" + std::to_string(i)));
snapshots.push_back(db_->GetSnapshot());
}
ASSERT_OK(Put("key_b", "value_b"));
ASSERT_OK(Flush());
ASSERT_EQ(1, NumTableFilesAtLevel(0));
// Setup multiscan range covering both keys
std::vector<std::string> key_ranges({"key_a", "key_c"});
ReadOptions ro;
Slice ub = key_ranges[1];
ro.iterate_upper_bound = &ub;
ro.fill_cache = GetParam();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.insert(key_ranges[0], key_ranges[1]);
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
std::unique_ptr<Iterator> iter(dbfull()->NewIterator(ro, cfh));
ASSERT_NE(iter, nullptr);
iter->Prepare(scan_options);
std::vector<std::string> seen_keys;
std::vector<std::string> seen_values;
iter->Seek(key_ranges[0]);
while (iter->status().ok() && iter->Valid()) {
seen_keys.push_back(iter->key().ToString());
seen_values.push_back(iter->value().ToString());
iter->Next();
}
ASSERT_OK(iter->status()) << iter->status().ToString();
ASSERT_EQ(seen_keys.size(), 2) << "Should see key_a and key_b";
ASSERT_EQ(seen_keys[0], "key_a");
ASSERT_EQ(seen_keys[1], "key_b");
ASSERT_EQ(seen_values[0], "value_6");
ASSERT_EQ(seen_values[1], "value_b");
for (auto* snapshot : snapshots) {
db_->ReleaseSnapshot(snapshot);
}
}
TEST_P(DBMultiScanIteratorTest, AsyncPrefetchAcrossMultipleFiles) {
// Test async prefetch with multiple ranges within a single file
auto options = CurrentOptions();
options.target_file_size_base = 1 << 15; // 32KiB
options.compaction_style = kCompactionStyleUniversal;
options.num_levels = 50;
options.compression = kNoCompression;
options.statistics = CreateDBStatistics();
DestroyAndReopen(options);
Random rnd(303);
// Create a single large file with many keys
// ~1MiB of data
// Should be lots of files now
for (int i = 0; i < 1000; ++i) {
std::stringstream ss;
ss << "k" << std::setw(5) << std::setfill('0') << i;
// 1KiB values
ASSERT_OK(Put(ss.str(), rnd.RandomString(1 << 10)));
}
ASSERT_OK(Flush());
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
ASSERT_GT(NumTableFilesAtLevel(49), 3);
// Set up multiple non-overlapping ranges in the same file
// Every 32 values should be a file or so
std::vector<std::string> key_ranges(
{"k00000", "k00100", "k00500", "k00600", "k00800", "k00900"});
ReadOptions ro;
ro.fill_cache = GetParam();
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.use_async_io = true;
scan_options.insert(key_ranges[0], key_ranges[1]);
scan_options.insert(key_ranges[2], key_ranges[3]);
scan_options.insert(key_ranges[4], key_ranges[5]);
auto read_count_before =
options.statistics->getTickerCount(NON_LAST_LEVEL_READ_COUNT);
std::unique_ptr<MultiScan> iter =
dbfull()->NewMultiScan(ro, cfh, scan_options);
ASSERT_NE(iter, nullptr);
auto read_count_after =
options.statistics->getTickerCount(NON_LAST_LEVEL_READ_COUNT);
ASSERT_EQ(read_count_after, read_count_before);
// Verify all three ranges can be scanned successfully
try {
for (auto range : *iter) {
for (auto it : range) {
it.first.ToString();
}
}
} catch (MultiScanException& ex) {
// Make sure exception contains the status
ASSERT_NOK(ex.status());
std::cerr << "Iterator returned status " << ex.what();
abort();
} catch (std::logic_error& ex) {
std::cerr << "Iterator returned logic error " << ex.what();
abort();
}
iter.reset();
}
TEST_P(DBMultiScanIteratorTest, AsyncPrefetchMultipleLevels) {
// Test async prefetch with files in L0 and non-L0 levels
// Similar setup to AsyncPrefetchAcrossMultipleFiles but with L0 files
auto options = CurrentOptions();
options.target_file_size_base = 1 << 15; // 32KiB
options.compaction_style = kCompactionStyleUniversal;
options.num_levels = 50;
options.compression = kNoCompression;
options.statistics = CreateDBStatistics();
DestroyAndReopen(options);
Random rnd(304);
// Create base files and compact to bottom level - ~500KiB of data
for (int i = 0; i < 500; ++i) {
std::stringstream ss;
ss << "k" << std::setw(5) << std::setfill('0') << i;
ASSERT_OK(Put(ss.str(), rnd.RandomString(1 << 10)));
}
ASSERT_OK(Flush());
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
// Verify we have files at bottom level
ASSERT_GT(NumTableFilesAtLevel(49), 0);
// Create additional L0 files with overlapping key ranges
for (int i = 100; i < 150; ++i) {
std::stringstream ss;
ss << "k" << std::setw(5) << std::setfill('0') << i;
ASSERT_OK(Put(ss.str(), rnd.RandomString(1 << 10)));
}
ASSERT_OK(Flush());
// Verify we now have files in both L0 and bottom level
ASSERT_GT(NumTableFilesAtLevel(0), 0);
ASSERT_GT(NumTableFilesAtLevel(49), 0);
// Set up multiple non-overlapping ranges
std::vector<std::string> key_ranges(
{"k00000", "k00100", "k00200", "k00300", "k00400", "k00500"});
ReadOptions ro;
ro.fill_cache = GetParam();
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.use_async_io = true;
scan_options.insert(key_ranges[0], key_ranges[1]);
scan_options.insert(key_ranges[2], key_ranges[3]);
scan_options.insert(key_ranges[4], key_ranges[5]);
std::unique_ptr<MultiScan> iter =
dbfull()->NewMultiScan(ro, cfh, scan_options);
ASSERT_NE(iter, nullptr);
// Verify all three ranges can be scanned successfully
int total_keys = 0;
try {
for (auto range : *iter) {
for (auto it : range) {
it.first.ToString();
total_keys++;
}
}
} catch (MultiScanException& ex) {
ASSERT_NOK(ex.status());
std::cerr << "Iterator returned status " << ex.what();
abort();
} catch (std::logic_error& ex) {
std::cerr << "Iterator returned logic error " << ex.what();
abort();
}
// Should have keys from all three ranges
ASSERT_GT(total_keys, 0);
iter.reset();
}
TEST_P(DBMultiScanIteratorTest, AsyncPrefetchWithDeleteRange) {
// Test async prefetch with delete ranges
auto options = CurrentOptions();
options.target_file_size_base = 1 << 15; // 32KiB
options.compaction_style = kCompactionStyleUniversal;
options.num_levels = 50;
options.compression = kNoCompression;
DestroyAndReopen(options);
Random rnd(305);
// Create base data - ~500KiB
for (int i = 0; i < 500; ++i) {
std::stringstream ss;
ss << "k" << std::setw(5) << std::setfill('0') << i;
ASSERT_OK(Put(ss.str(), rnd.RandomString(1 << 10)));
}
ASSERT_OK(Flush());
// Add delete ranges
ASSERT_OK(db_->DeleteRange(WriteOptions(), dbfull()->DefaultColumnFamily(),
"k00100", "k00200"));
ASSERT_OK(Flush());
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
ASSERT_GT(NumTableFilesAtLevel(49), 0);
// Set up scan ranges that interact with delete ranges
std::vector<std::string> key_ranges({"k00000", "k00500"});
ReadOptions ro;
ro.fill_cache = GetParam();
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.use_async_io = true;
scan_options.insert(key_ranges[0], key_ranges[1]);
std::unique_ptr<MultiScan> iter =
dbfull()->NewMultiScan(ro, cfh, scan_options);
ASSERT_NE(iter, nullptr);
// Verify ranges can be scanned successfully
int total_keys = 0;
try {
for (auto range : *iter) {
for (auto it : range) {
std::string key = it.first.ToString();
// Verify deleted keys are not returned
ASSERT_TRUE((key < "k00100" || key >= "k00200"));
total_keys++;
}
}
} catch (MultiScanException& ex) {
ASSERT_NOK(ex.status());
std::cerr << "Iterator returned status " << ex.what();
abort();
} catch (std::logic_error& ex) {
std::cerr << "Iterator returned logic error " << ex.what();
abort();
}
// Should have keys excluding deleted ranges
ASSERT_EQ(total_keys, 400);
iter.reset();
}
TEST_P(DBMultiScanIteratorTest, AsyncPrefetchWithExternalFileIngestion) {
// Test async prefetch with externally ingested files
auto options = CurrentOptions();
options.target_file_size_base = 1 << 15; // 32KiB
options.compaction_style = kCompactionStyleUniversal;
options.num_levels = 50;
options.compression = kNoCompression;
DestroyAndReopen(options);
Random rnd(306);
// Create base data - ~200KiB
for (int i = 0; i < 200; ++i) {
std::stringstream ss;
ss << "k" << std::setw(5) << std::setfill('0') << i;
ASSERT_OK(Put(ss.str(), rnd.RandomString(1 << 10)));
}
ASSERT_OK(Flush());
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
// Create and ingest external SST file with new data
std::string ingest_file = dbname_ + "/test_ingest.sst";
{
std::unique_ptr<SstFileWriter> writer;
writer.reset(new SstFileWriter(EnvOptions(), options));
ASSERT_OK(writer->Open(ingest_file));
for (int i = 300; i < 500; ++i) {
std::stringstream ss;
ss << "k" << std::setw(5) << std::setfill('0') << i;
ASSERT_OK(writer->Put(ss.str(), rnd.RandomString(1 << 10)));
}
ASSERT_OK(writer->Finish());
}
IngestExternalFileOptions ifo;
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
ASSERT_OK(dbfull()->IngestExternalFile(cfh, {ingest_file}, ifo));
// Set up scan ranges that span both regular and ingested files
std::vector<std::string> key_ranges({"k00000", "k00500"});
ReadOptions ro;
ro.fill_cache = GetParam();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.use_async_io = true;
scan_options.insert(key_ranges[0], key_ranges[1]);
std::unique_ptr<MultiScan> iter =
dbfull()->NewMultiScan(ro, cfh, scan_options);
ASSERT_NE(iter, nullptr);
// Verify all ranges can be scanned successfully
int total_keys = 0;
try {
for (auto range : *iter) {
for (auto it : range) {
it.first.ToString();
total_keys++;
}
}
} catch (MultiScanException& ex) {
ASSERT_NOK(ex.status());
std::cerr << "Iterator returned status " << ex.what();
abort();
} catch (std::logic_error& ex) {
std::cerr << "Iterator returned logic error " << ex.what();
abort();
}
ASSERT_EQ(total_keys, 400);
iter.reset();
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+129
View File
@@ -339,6 +339,135 @@ TEST_F(DBMemTableTest, ColumnFamilyId) {
}
}
class DBMemTableTestForSeek : public DBMemTableTest,
virtual public ::testing::WithParamInterface<
std::tuple<bool, bool, bool>> {};
TEST_P(DBMemTableTestForSeek, IntegrityChecks) {
// Validate key corruption could be detected during seek.
// We insert many keys into skiplist. Then we corrupt the each key one at a
// time. With memtable_veirfy_per_key_checksum_on_seek enabled, when the
// corrupted key is searched, the checksum of every key visited during the
// seek is validated. It will report data corruption. Otherwise seek returns
// not found.
auto allow_data_in_error = std::get<0>(GetParam());
Options options = CurrentOptions();
options.allow_data_in_errors = allow_data_in_error;
options.paranoid_memory_checks = std::get<1>(GetParam());
options.memtable_veirfy_per_key_checksum_on_seek = std::get<2>(GetParam());
options.memtable_protection_bytes_per_key = 8;
DestroyAndReopen(options);
// capture the data pointer of all of the keys
std::vector<char*> raw_data_pointer;
// Insert enough keys, so memtable would create multiple levels.
auto key_count = 100;
for (int i = 0; i < key_count; i++) {
// The last digit of the key will be corrupted from value 0 to value 5
ASSERT_OK(Put(Key(i * 10), "val0"));
}
ReadOptions rops;
// Iterate all the keys to get key pointers
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->SetCallBack("InlineSkipList::Iterator::Next::key",
[&raw_data_pointer](void* key) {
auto p = static_cast<char*>(key);
raw_data_pointer.push_back(p);
});
SyncPoint::GetInstance()->EnableProcessing();
{
std::unique_ptr<Iterator> iter{db_->NewIterator(rops)};
iter->Seek(Key(0));
while (iter->Valid()) {
ASSERT_OK(iter->status());
iter->Next();
}
// check status after valid returned false.
auto status = iter->status();
ASSERT_TRUE(status.ok());
}
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
ASSERT_EQ(raw_data_pointer.size(), key_count);
bool enable_key_validation_on_seek =
options.memtable_veirfy_per_key_checksum_on_seek;
// For each key, corrupt it, validate corruption is detected correctly, then
// revert it.
for (int i = 0; i < key_count; i++) {
std::string key_to_corrupt = Key(i * 10);
raw_data_pointer[i][key_to_corrupt.size()] = '5';
auto corrupted_key = key_to_corrupt;
corrupted_key.data()[key_to_corrupt.size() - 1] = '5';
auto corrupted_key_slice =
Slice(corrupted_key.data(), corrupted_key.length());
auto corrupted_key_hex = corrupted_key_slice.ToString(/*hex=*/true);
{
// Test Get API
std::string val;
auto status = db_->Get(rops, key_to_corrupt, &val);
if (enable_key_validation_on_seek) {
ASSERT_TRUE(status.IsCorruption()) << key_to_corrupt;
ASSERT_EQ(
status.ToString().find(corrupted_key_hex) != std::string::npos,
allow_data_in_error)
<< status.ToString() << "\n"
<< corrupted_key_hex;
} else {
ASSERT_TRUE(status.IsNotFound());
}
}
{
// Test MultiGet API
std::vector<std::string> vals;
std::vector<Status> statuses = db_->MultiGet(
rops, {db_->DefaultColumnFamily()}, {key_to_corrupt}, &vals, nullptr);
if (enable_key_validation_on_seek) {
ASSERT_TRUE(statuses[0].IsCorruption());
ASSERT_EQ(
statuses[0].ToString().find(corrupted_key_hex) != std::string::npos,
allow_data_in_error);
} else {
ASSERT_TRUE(statuses[0].IsNotFound());
}
}
{
// Test Iterator Seek API
std::unique_ptr<Iterator> iter{db_->NewIterator(rops)};
ASSERT_OK(iter->status());
iter->Seek(key_to_corrupt);
auto status = iter->status();
if (enable_key_validation_on_seek) {
ASSERT_TRUE(status.IsCorruption());
ASSERT_EQ(
status.ToString().find(corrupted_key_hex) != std::string::npos,
allow_data_in_error);
} else {
ASSERT_FALSE(iter->Valid());
ASSERT_FALSE(status.ok());
}
}
// revert the key corruption.
raw_data_pointer[i][key_to_corrupt.size()] = '0';
}
}
INSTANTIATE_TEST_CASE_P(DBMemTableTestForSeek, DBMemTableTestForSeek,
::testing::Combine(::testing::Bool(), ::testing::Bool(),
::testing::Bool()));
TEST_F(DBMemTableTest, IntegrityChecks) {
// We insert keys key000000, key000001 and key000002 into skiplist at fixed
// height 1 (smallest height). Then we corrupt the second key to aey000001 to
+35
View File
@@ -432,12 +432,47 @@ TEST_F(DBOptionsTest, SetWalBytesPerSync) {
ASSERT_GT(low_bytes_per_sync, counter);
}
TEST_F(DBOptionsTest, MutableManifestOptions) {
// These aren't end-to-end tests, but sufficient to ensure the VersionSet
// receives the updates with SetDBOptions
for (int64_t i : {0, 1, 100, 100000, 10000000}) {
ASSERT_OK(
db_->SetDBOptions({{"max_manifest_file_size", std::to_string(i)}}));
ASSERT_EQ(i,
static_cast<int64_t>(db_->GetDBOptions().max_manifest_file_size));
ASSERT_EQ(i,
static_cast<int64_t>(
dbfull()->GetVersionSet()->TEST_GetMinMaxManifestFileSize()));
if (i > 1) {
++i;
}
ASSERT_OK(
db_->SetDBOptions({{"max_manifest_space_amp_pct", std::to_string(i)}}));
ASSERT_EQ(i, static_cast<int64_t>(
db_->GetDBOptions().max_manifest_space_amp_pct));
ASSERT_EQ(i,
static_cast<int64_t>(
dbfull()->GetVersionSet()->TEST_GetMaxManifestSpaceAmpPct()));
if (i > 1) {
++i;
}
ASSERT_OK(db_->SetDBOptions(
{{"manifest_preallocation_size", std::to_string(i)}}));
ASSERT_EQ(i, static_cast<int64_t>(
db_->GetDBOptions().manifest_preallocation_size));
ASSERT_EQ(
i, static_cast<int64_t>(
dbfull()->GetVersionSet()->TEST_GetManifestPreallocationSize()));
}
}
TEST_F(DBOptionsTest, WritableFileMaxBufferSize) {
Options options;
options.create_if_missing = true;
options.writable_file_max_buffer_size = 1024 * 1024;
options.level0_file_num_compaction_trigger = 3;
options.max_manifest_file_size = 1;
options.max_manifest_space_amp_pct = 0;
options.env = env_;
int buffer_size = 1024 * 1024;
Reopen(options);
+83
View File
@@ -3825,6 +3825,89 @@ TEST_F(DBRangeDelTest, RowCache) {
// and should not turn db into read-only mdoe.
ASSERT_OK(Put(Key(5), "foo"));
}
TEST_F(DBRangeDelTest, SeekForPrevTest) {
// open db
Options options = GetDefaultOptions();
options.create_if_missing = true;
options.compaction_style = kCompactionStyleUniversal;
// add SST partitioner, split sst file with prefix length 2
options.sst_partitioner_factory = NewSstPartitionerFixedPrefixFactory(2);
Reopen(options);
// File uses SST partitioner, so it will be split into 3 files
// SST file 1: ka1, ka2
// SST file 2: kb1
// SST file 3: kc1, kc2
// Delete range covers from ka2 to kc2, which means record ka2 and kb1, kc1
// are covered by the delete range
std::vector<std::pair<std::string, std::string>> kv = {{"ka1", "value_1"},
{"ka2", "value_2"},
{"kb1", "value_3"},
{"kc1", "value_4"},
{"kc2", "value_5"}};
for (auto& p : kv) {
ASSERT_OK(Put(p.first, p.second));
}
ASSERT_OK(Flush());
// Compact to Lmax, it should have seq 0 now.
ASSERT_OK(CompactRange(CompactRangeOptions(), nullptr, nullptr));
// Open an iterator and create a snapshot, so that keys are not deleted
// completely by delete range in SST
ReadOptions read_opts;
read_opts.snapshot = db_->GetSnapshot();
std::unique_ptr<Iterator> iter(db_->NewIterator(read_opts));
iter->SeekToFirst();
// iterate all the keys and validate the value
for (int i = 0; iter->Valid(); iter->Next()) {
ASSERT_EQ(kv[i].first, iter->key().ToString());
ASSERT_EQ(kv[i].second, iter->value().ToString());
i++;
}
// use delete range to delete the record
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "ka2",
"kc2"));
// Flush
ASSERT_OK(Flush());
// Compact to Lmax
ASSERT_OK(CompactRange(CompactRangeOptions(), nullptr, nullptr));
// Close the iterator and release the snapshot.
ASSERT_OK(iter->status());
iter.reset();
db_->ReleaseSnapshot(read_opts.snapshot);
// create second iterator, seek each key and validate result
std::unique_ptr<Iterator> iter2(db_->NewIterator(ReadOptions()));
// Validate keys are deleted
iter2->SeekToFirst();
ASSERT_TRUE(iter2->Valid());
ASSERT_EQ("ka1", iter2->key().ToString());
iter2->Next();
ASSERT_TRUE(iter2->Valid());
ASSERT_EQ("kc2", iter2->key().ToString());
iter2->Next();
ASSERT_FALSE(iter2->Valid());
// Validate seek for prev result
for (auto& p : kv) {
iter2->SeekForPrev(p.first);
ASSERT_TRUE(iter2->Valid());
if (p.first == "kc2") {
ASSERT_EQ("kc2", iter2->key().ToString());
} else {
ASSERT_EQ("ka1", iter2->key().ToString());
}
}
ASSERT_OK(iter2->status());
iter2.reset();
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+101
View File
@@ -442,6 +442,107 @@ TEST_P(DBRateLimiterOnWriteWALTest, AutoWalFlush) {
EXPECT_EQ(actual_auto_wal_flush_request,
options_.rate_limiter->GetTotalRequests(Env::IO_USER));
}
class DBRateLimiterOnManualWALFlushTest
: public DBRateLimiterOnWriteTest,
public ::testing::WithParamInterface<Env::IOPriority> {
public:
static std::string GetTestNameSuffix(
::testing::TestParamInfo<Env::IOPriority> info) {
std::ostringstream oss;
if (info.param == Env::IO_USER) {
oss << "RateLimitManualWALFlush";
} else if (info.param == Env::IO_TOTAL) {
oss << "NoRateLimitManualWALFlush";
} else if (info.param == Env::IO_HIGH) {
oss << "RateLimitManualWALFlushWithHighPriority";
} else {
oss << "RateLimitManualWALFlushWithLowPriority";
}
return oss.str();
}
explicit DBRateLimiterOnManualWALFlushTest()
: rate_limiter_priority_(GetParam()) {}
void Init() {
options_ = GetOptions();
// Enable manual WAL flush mode
options_.manual_wal_flush = true;
Reopen(options_);
}
WriteOptions GetWriteOptions() {
WriteOptions write_options;
// WAL must be enabled for manual WAL flush to work
write_options.disableWAL = false;
// In manual WAL flush mode, WAL write rate limiting should be done through
// FlushWAL(), not WriteOptions::rate_limiter_priority
write_options.rate_limiter_priority = Env::IO_TOTAL;
return write_options;
}
protected:
Env::IOPriority rate_limiter_priority_;
};
INSTANTIATE_TEST_CASE_P(DBRateLimiterOnManualWALFlushTest,
DBRateLimiterOnManualWALFlushTest,
::testing::Values(Env::IO_TOTAL, Env::IO_USER,
Env::IO_HIGH, Env::IO_LOW),
DBRateLimiterOnManualWALFlushTest::GetTestNameSuffix);
TEST_P(DBRateLimiterOnManualWALFlushTest, ManualWALFlush) {
Init();
const bool no_rate_limit = (rate_limiter_priority_ == Env::IO_TOTAL);
ASSERT_EQ(0, options_.rate_limiter->GetTotalRequests(Env::IO_TOTAL));
for (bool sync : {false, true}) {
std::int64_t prev_total_request =
options_.rate_limiter->GetTotalRequests(Env::IO_TOTAL);
Status put_status = Put("key_" + std::to_string(sync),
"value_" + std::to_string(sync), GetWriteOptions());
EXPECT_TRUE(put_status.ok());
// Since manual_wal_flush is enabled and write_options.rate_limiter_priority
// is IO_TOTAL, no rate limiting should have occurred for this user write
EXPECT_EQ(0, options_.rate_limiter->GetTotalRequests(Env::IO_TOTAL) -
prev_total_request);
// Now explicitly flush the WAL with the test's rate_limiter_priority
prev_total_request = options_.rate_limiter->GetTotalRequests(Env::IO_TOTAL);
std::int64_t prev_priority_request =
options_.rate_limiter->GetTotalRequests(rate_limiter_priority_);
FlushWALOptions flush_options;
flush_options.sync = sync;
flush_options.rate_limiter_priority = rate_limiter_priority_;
Status flush_status = db_->FlushWAL(flush_options);
EXPECT_TRUE(flush_status.ok());
std::int64_t manual_wal_flush_requests_total =
options_.rate_limiter->GetTotalRequests(Env::IO_TOTAL) -
prev_total_request;
std::int64_t manual_wal_flush_requests_for_priority =
options_.rate_limiter->GetTotalRequests(rate_limiter_priority_) -
prev_priority_request;
if (no_rate_limit) {
EXPECT_EQ(0, manual_wal_flush_requests_total);
EXPECT_EQ(0, manual_wal_flush_requests_for_priority);
} else {
EXPECT_EQ(manual_wal_flush_requests_total,
manual_wal_flush_requests_for_priority);
EXPECT_GT(manual_wal_flush_requests_for_priority, 0);
}
}
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+64
View File
@@ -1937,6 +1937,70 @@ TEST_F(DBSSTTest, DBWithSFMForBlobFilesAtomicFlush) {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
}
TEST_F(DBSSTTest, SstGetFileSizeFails) {
// Build an SST file
ASSERT_OK(Put("x", "zaphod"));
ASSERT_OK(Flush());
std::vector<LiveFileMetaData> metadata;
db_->GetLiveFilesMetaData(&metadata);
ASSERT_EQ(1U, metadata.size());
std::string filename = dbname_ + metadata[0].name;
// Prepare for fault injection
std::shared_ptr<FaultInjectionTestFS> fault_fs =
std::make_shared<FaultInjectionTestFS>(
CurrentOptions().env->GetFileSystem());
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
Options options = CurrentOptions();
options.env = fault_fs_env.get();
options.paranoid_checks = false; // don't check file sizes on open
for (int i = 0; i < 4; i++) {
SCOPED_TRACE("Iteration = " + std::to_string(i));
fault_fs->SetFailRandomAccessGetFileSizeSst(false);
fault_fs->SetFailFilesystemGetFileSizeSst(false);
Close();
if (i == 1) {
// Just FSRandomAccessFile::GetFileSize fails, which should be worked
// around
fault_fs->SetFailRandomAccessGetFileSizeSst(true);
} else if (i == 2) {
// FileSystem::GetFileSize fails, which should be worked around if
// FSRandomAccessFile::GetFileSize is supported
fault_fs->SetFailFilesystemGetFileSizeSst(true);
} else if (i == 3) {
// Both GetFileSize APIs fail with an IOError
fault_fs->SetFailRandomAccessGetFileSizeSst(true);
fault_fs->SetFailFilesystemGetFileSizeSst(true);
}
ASSERT_OK(TryReopen(options));
std::string value;
Status get_status = db_->Get({}, "x", &value);
if (i < 2) {
ASSERT_OK(get_status);
} else if (i == 2) {
if (encrypted_env_) {
// Can't recover because RandomAccessFile::GetFileSize is not supported
// on EncryptedEnv
// Fail with propagated IOError. (Not Corruption nor NotSupported!)
ASSERT_EQ(get_status.code(), Status::Code::kIOError);
ASSERT_STREQ(get_status.getState(), "FileSystem::GetFileSize failed");
} else {
// Never sees the FileSystem::GetFileSize failure
ASSERT_OK(get_status);
}
} else {
ASSERT_EQ(i, 3);
// Fail with propagated IOError. (Not Corruption nor NotSupported!)
ASSERT_EQ(get_status.code(), Status::Code::kIOError);
ASSERT_STREQ(get_status.getState(), "FileSystem::GetFileSize failed");
}
}
Close();
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+40
View File
@@ -787,6 +787,46 @@ TEST_P(DBTablePropertiesTest, RatioBasedDeletionTriggeredCompactionMarking) {
}
}
TEST_F(DBTablePropertiesTest, KeyLargestSmallestSeqno) {
ASSERT_OK(db_->Put(WriteOptions(), "key1", "value1"));
ASSERT_OK(db_->Put(WriteOptions(), "key2", "value2"));
ASSERT_OK(db_->Put(WriteOptions(), "key3", "value3"));
ASSERT_OK(db_->Flush(FlushOptions()));
{
TablePropertiesCollection props;
ASSERT_OK(db_->GetPropertiesOfAllTables(&props));
ASSERT_EQ(1U, props.size());
auto table_props = props.begin()->second;
ASSERT_TRUE(table_props->HasKeyLargestSeqno());
ASSERT_TRUE(table_props->HasKeySmallestSeqno());
ASSERT_EQ(table_props->key_largest_seqno,
table_props->key_smallest_seqno + 2);
ASSERT_GT(table_props->key_largest_seqno, 0U);
ASSERT_GT(table_props->key_smallest_seqno, 0U);
}
// Becomes zero after compaction
{
CompactRangeOptions cro;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
TablePropertiesCollection props;
ASSERT_OK(db_->GetPropertiesOfAllTables(&props));
ASSERT_EQ(1U, props.size());
auto table_props = props.begin()->second;
ASSERT_TRUE(table_props->HasKeyLargestSeqno());
ASSERT_TRUE(table_props->HasKeySmallestSeqno());
ASSERT_EQ(table_props->key_largest_seqno, table_props->key_smallest_seqno);
ASSERT_EQ(table_props->key_largest_seqno, 0U);
}
}
INSTANTIATE_TEST_CASE_P(DBTablePropertiesTest, DBTablePropertiesTest,
::testing::Values("kCompactionStyleLevel",
"kCompactionStyleUniversal"));
+249 -4
View File
@@ -1492,6 +1492,246 @@ TEST_F(DBTest, MetaDataTest) {
CheckLiveFilesMeta(live_file_meta, files_by_level);
}
TEST_F(DBTest, GetColumnFamilyMetaDataWithKeyRangeAndLevel) {
Options options = CurrentOptions();
options.create_if_missing = true;
options.disable_auto_compactions = true;
int64_t temp_time = 0;
ASSERT_OK(options.env->GetCurrentTime(&temp_time));
DestroyAndReopen(options);
Random rnd(301);
int key_index = 0;
for (int i = 0; i < 100; ++i) {
// Add a single blob reference to each file
std::string blob_index;
BlobIndex::EncodeBlob(&blob_index, /* blob_file_number */ i + 1000,
/* offset */ 1234, /* size */ 5678, kNoCompression);
WriteBatch batch;
ASSERT_OK(WriteBatchInternal::PutBlobIndex(&batch, 0, Key(key_index),
blob_index));
ASSERT_OK(dbfull()->Write(WriteOptions(), &batch));
++key_index;
// Fill up the rest of the file with random values.
GenerateNewFile(&rnd, &key_index, /* nowait */ true);
ASSERT_OK(Flush());
}
std::vector<std::vector<FileMetaData>> files_by_level;
dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(), &files_by_level);
ASSERT_OK(options.env->GetCurrentTime(&temp_time));
ColumnFamilyMetaData cf_meta;
// Keys in the SST files are distributed
// (key000000, key000100) ->File 1
// (key000101, key000201) -> File 2
// (key000202, key000302) -> File 3
// (key009999, key010099) -> File 100
// With keySlice (key000050, key000150) => should only pick 2 files(instead of
// default 100 that is in the level)
auto startKey = Slice("key000050");
auto endKey = Slice("key000150");
GetColumnFamilyMetaDataOptions cf_options(startKey, endKey, 0);
db_->GetColumnFamilyMetaData(cf_options, &cf_meta);
ASSERT_EQ(cf_meta.levels.size(), 1);
const auto& level_meta_from_cf = cf_meta.levels[0];
ASSERT_EQ(level_meta_from_cf.files.size(), 2);
ASSERT_LT(level_meta_from_cf.files[1].smallestkey,
std::string(startKey.data()));
ASSERT_GT(level_meta_from_cf.files[0].largestkey, std::string(endKey.data()));
GetColumnFamilyMetaDataOptions cf_option_default;
db_->GetColumnFamilyMetaData(cf_option_default, &cf_meta);
ASSERT_EQ(cf_meta.levels.size(), 1);
ASSERT_EQ(cf_meta.levels[0].files.size(), 100);
// Test with start key valid and end key unbounded
// This should get all files from key000150 onwards (99 files)
auto startKeyUnbounded = Slice("key000150");
GetColumnFamilyMetaDataOptions cf_options_unbounded_end(startKeyUnbounded,
OptSlice(), 0);
db_->GetColumnFamilyMetaData(cf_options_unbounded_end, &cf_meta);
ASSERT_EQ(cf_meta.levels.size(), 1);
ASSERT_EQ(cf_meta.levels[0].files.size(), 99);
// Test with end key valid and start key unbounded
// This should get all files from beginning to key000250 ( 3 files)
auto endKeyUnbounded = Slice("key000250");
GetColumnFamilyMetaDataOptions cf_options_unbounded_start(OptSlice(),
endKeyUnbounded, 0);
db_->GetColumnFamilyMetaData(cf_options_unbounded_start, &cf_meta);
ASSERT_EQ(cf_meta.levels.size(), 1);
ASSERT_EQ(cf_meta.levels[0].files.size(), 3);
}
TEST_F(DBTest, GetColumnFamilyMetaDataBottommostLevel) {
Options options = CurrentOptions();
options.create_if_missing = true;
options.disable_auto_compactions = true;
options.num_levels = 7;
DestroyAndReopen(options);
Random rnd(301);
int key_index = 0;
for (int i = 0; i < 100; ++i) {
GenerateNewFile(&rnd, &key_index, /* nowait */ true);
ASSERT_OK(Flush());
}
CompactRangeOptions compact_options;
compact_options.bottommost_level_compaction =
BottommostLevelCompaction::kForce;
compact_options.change_level = true;
compact_options.target_level = 6;
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
// Nothing on Level 0 after compaction
ColumnFamilyMetaData cf_meta;
GetColumnFamilyMetaDataOptions cf_options_0(OptSlice(), OptSlice(), 0);
db_->GetColumnFamilyMetaData(cf_options_0, &cf_meta);
ASSERT_EQ(cf_meta.levels.size(), 0);
ASSERT_EQ(cf_meta.file_count, 0);
// Data should be in Level 6
GetColumnFamilyMetaDataOptions cf_options(OptSlice(), OptSlice(), 6);
db_->GetColumnFamilyMetaData(cf_options, &cf_meta);
ASSERT_EQ(cf_meta.levels.size(), 1);
ASSERT_EQ(cf_meta.levels[0].level, 6);
ASSERT_GT(cf_meta.levels[0].files.size(), 0);
size_t all_files = cf_meta.levels[0].files.size();
// Keys in the SST files are distributed across level 6
// Test with key range - should only return files within the range
auto startKey = Slice("key000050");
auto endKey = Slice("key000150");
GetColumnFamilyMetaDataOptions cf_options_range(startKey, endKey, 6);
db_->GetColumnFamilyMetaData(cf_options_range, &cf_meta);
ASSERT_EQ(cf_meta.levels.size(), 1);
ASSERT_EQ(cf_meta.levels[0].level, 6);
ASSERT_GT(cf_meta.levels[0].files.size(), 0);
size_t files_in_range = cf_meta.levels[0].files.size();
// Files in range should be less than or equal to all files
ASSERT_LE(files_in_range, all_files);
}
TEST_F(DBTest, GetColumnFamilyMetaDataMultipleLevels) {
Options options = CurrentOptions();
options.create_if_missing = true;
options.disable_auto_compactions = true;
options.num_levels = 7;
DestroyAndReopen(options);
Random rnd(301);
int key_index = 0;
for (int i = 0; i < 50; ++i) {
GenerateNewFile(&rnd, &key_index, /* nowait */ true);
ASSERT_OK(Flush());
}
CompactRangeOptions compact_options;
compact_options.bottommost_level_compaction =
BottommostLevelCompaction::kForce;
compact_options.change_level = true;
compact_options.target_level = 6;
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
for (int i = 0; i < 30; ++i) {
GenerateNewFile(&rnd, &key_index, /* nowait */ true);
ASSERT_OK(Flush());
}
// First verify both levels have files without key range filter
ColumnFamilyMetaData cf_meta_all_no_range;
GetColumnFamilyMetaDataOptions cf_options_all_no_range;
db_->GetColumnFamilyMetaData(cf_options_all_no_range, &cf_meta_all_no_range);
bool has_level_0 = false;
bool has_level_6 = false;
for (const auto& level : cf_meta_all_no_range.levels) {
if (level.level == 0 && level.files.size() > 0) {
has_level_0 = true;
}
if (level.level == 6 && level.files.size() > 0) {
has_level_6 = true;
}
}
ASSERT_TRUE(has_level_0);
ASSERT_TRUE(has_level_6);
// Test querying bottommost level only with key range
// Use a range that should be in the first set of files (now in level 6)
auto startKey = Slice("key000050");
auto endKey = Slice("key000150");
ColumnFamilyMetaData cf_meta_bottommost;
GetColumnFamilyMetaDataOptions cf_options_bottommost(startKey, endKey, 6);
db_->GetColumnFamilyMetaData(cf_options_bottommost, &cf_meta_bottommost);
ASSERT_EQ(cf_meta_bottommost.levels.size(), 1);
ASSERT_EQ(cf_meta_bottommost.levels[0].level, 6);
ASSERT_GT(cf_meta_bottommost.levels[0].files.size(), 0);
size_t level_6_files_in_range = cf_meta_bottommost.levels[0].files.size();
// Test querying all levels with same key range
ColumnFamilyMetaData cf_meta_all;
GetColumnFamilyMetaDataOptions cf_options_all(startKey, endKey);
db_->GetColumnFamilyMetaData(cf_options_all, &cf_meta_all);
size_t level_6_files_in_range_from_all = 0;
for (const auto& level : cf_meta_all.levels) {
if (level.level == 6) {
level_6_files_in_range_from_all = level.files.size();
}
}
ASSERT_GT(level_6_files_in_range_from_all, 0);
ASSERT_EQ(level_6_files_in_range, level_6_files_in_range_from_all);
}
TEST_F(DBTest, GetColumnFamilyMetaDataEmptyDB) {
Options options = CurrentOptions();
options.create_if_missing = true;
options.num_levels = 7;
DestroyAndReopen(options);
// Test on empty database
ColumnFamilyMetaData cf_meta_empty_db;
GetColumnFamilyMetaDataOptions cf_options_empty_db;
db_->GetColumnFamilyMetaData(cf_options_empty_db, &cf_meta_empty_db);
ASSERT_EQ(cf_meta_empty_db.levels.size(), 0);
ASSERT_EQ(cf_meta_empty_db.file_count, 0);
ASSERT_EQ(cf_meta_empty_db.size, 0);
// Test on empty database with key range
auto startKey = Slice("key000050");
auto endKey = Slice("key000150");
ColumnFamilyMetaData cf_meta_empty_range;
GetColumnFamilyMetaDataOptions cf_options_empty_range(startKey, endKey);
db_->GetColumnFamilyMetaData(cf_options_empty_range, &cf_meta_empty_range);
ASSERT_EQ(cf_meta_empty_range.levels.size(), 0);
ASSERT_EQ(cf_meta_empty_range.file_count, 0);
ASSERT_EQ(cf_meta_empty_range.size, 0);
}
TEST_F(DBTest, AllMetaDataTest) {
Options options = CurrentOptions();
options.create_if_missing = true;
@@ -3535,6 +3775,11 @@ class ModelDB : public DB {
void GetColumnFamilyMetaData(ColumnFamilyHandle* /*column_family*/,
ColumnFamilyMetaData* /*metadata*/) override {}
void GetColumnFamilyMetaData(
ColumnFamilyHandle* /*column_family*/,
const GetColumnFamilyMetaDataOptions& /*options*/,
ColumnFamilyMetaData* /*metadata*/) override {}
Status GetDbIdentity(std::string& /*identity*/) const override {
return Status::OK();
}
@@ -4934,7 +5179,7 @@ TEST_F(DBTest, DynamicMemtableOptions) {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
}
#ifdef ROCKSDB_USING_THREAD_STATUS
#ifndef NROCKSDB_THREAD_STATUS
namespace {
bool VerifyOperationCount(Env* env, ThreadStatus::OperationType op_type,
int expected_count) {
@@ -5392,7 +5637,7 @@ TEST_P(DBTestWithParam, PreShutdownCompactionMiddle) {
ASSERT_EQ(operation_count[ThreadStatus::OP_COMPACTION], 0);
}
#endif // ROCKSDB_USING_THREAD_STATUS
#endif // !NROCKSDB_THREAD_STATUS
TEST_F(DBTest, FlushOnDestroy) {
WriteOptions wo;
@@ -6127,9 +6372,9 @@ TEST_F(DBTest, MergeTestTime) {
ASSERT_EQ(1, count);
ASSERT_EQ(4000000, TestGetTickerCount(options, MERGE_OPERATION_TOTAL_TIME));
#ifdef ROCKSDB_USING_THREAD_STATUS
#ifndef NROCKSDB_THREAD_STATUS
ASSERT_GT(TestGetTickerCount(options, FLUSH_WRITE_BYTES), 0);
#endif // ROCKSDB_USING_THREAD_STATUS
#endif // !NROCKSDB_THREAD_STATUS
}
TEST_P(DBTestWithParam, MergeCompactionTimeTest) {
+28 -15
View File
@@ -5205,6 +5205,7 @@ TEST_F(DBTest2, SwitchMemtableRaceWithNewManifest) {
Options options = CurrentOptions();
DestroyAndReopen(options);
options.max_manifest_file_size = 10;
options.max_manifest_space_amp_pct = 0;
options.create_if_missing = true;
CreateAndReopenWithCF({"pikachu"}, options);
ASSERT_EQ(2, handles_.size());
@@ -5896,6 +5897,7 @@ TEST_P(RenameCurrentTest, Flush) {
Destroy(last_options_);
Options options = GetDefaultOptions();
options.max_manifest_file_size = 1;
options.max_manifest_space_amp_pct = 0;
options.create_if_missing = true;
Reopen(options);
ASSERT_OK(Put("key", "value"));
@@ -5915,6 +5917,7 @@ TEST_P(RenameCurrentTest, Compaction) {
Destroy(last_options_);
Options options = GetDefaultOptions();
options.max_manifest_file_size = 1;
options.max_manifest_space_amp_pct = 0;
options.create_if_missing = true;
Reopen(options);
ASSERT_OK(Put("a", "a_value"));
@@ -6063,15 +6066,9 @@ TEST_F(DBTest2, VariousFileTemperatures) {
};
// We don't have enough non-unknown temps to confidently distinguish that
// a specific setting caused a specific outcome, in a single run. This is a
// reasonable work-around without blowing up test time. Only returns
// non-unknown temperatures.
auto RandomTemp = [] {
static std::vector<Temperature> temps = {
Temperature::kHot, Temperature::kWarm, Temperature::kCold};
return temps[Random::GetTLSInstance()->Uniform(
static_cast<int>(temps.size()))];
};
// a specific setting caused a specific outcome, in a single run. Using
// RandomKnownTemperature() is a reasonable work-around without blowing up
// test time.
auto test_fs = std::make_shared<MyTestFS>(env_->GetFileSystem());
std::unique_ptr<Env> env(new CompositeEnvWrapper(env_, test_fs));
@@ -6087,22 +6084,22 @@ TEST_F(DBTest2, VariousFileTemperatures) {
options.env = env.get();
test_fs->Reset();
if (use_optimize) {
test_fs->optimize_manifest_temperature = RandomTemp();
test_fs->optimize_manifest_temperature = RandomKnownTemperature();
test_fs->expected_manifest_temperature =
test_fs->optimize_manifest_temperature;
test_fs->optimize_wal_temperature = RandomTemp();
test_fs->optimize_wal_temperature = RandomKnownTemperature();
test_fs->expected_wal_temperature = test_fs->optimize_wal_temperature;
}
if (use_temp_options) {
options.metadata_write_temperature = RandomTemp();
options.metadata_write_temperature = RandomKnownTemperature();
test_fs->expected_manifest_temperature =
options.metadata_write_temperature;
test_fs->expected_other_metadata_temperature =
options.metadata_write_temperature;
options.wal_write_temperature = RandomTemp();
options.wal_write_temperature = RandomKnownTemperature();
test_fs->expected_wal_temperature = options.wal_write_temperature;
options.last_level_temperature = RandomTemp();
options.default_write_temperature = RandomTemp();
options.last_level_temperature = RandomKnownTemperature();
options.default_write_temperature = RandomKnownTemperature();
}
DestroyAndReopen(options);
@@ -7466,11 +7463,27 @@ TEST_F(DBTest2, GetFileChecksumsFromCurrentManifest_CRC32) {
FlushOptions fopts;
fopts.wait = true;
Random rnd(test::RandomSeed());
// Write 4 files into the default column family.
for (int i = 0; i < 4; i++) {
ASSERT_OK(db->Put(wopts, Key(i), rnd.RandomString(100)));
ASSERT_OK(db->Flush(fopts));
}
// Create a new column family, write 1 file into it and drop it.
ColumnFamilyHandle* cf;
ASSERT_OK(
db->CreateColumnFamily(ColumnFamilyOptions(), "soon_to_be_deleted", &cf));
ASSERT_OK(db->Put(wopts, cf, "some_key", "some_value"));
ASSERT_OK(db->Flush(fopts, cf));
// Drop column family should generate corresponding version edit
// in manifest, which we expect to be correctly interpreted by
// GetFileChecksumsFromCurrentManifest API after db close.
ASSERT_OK(db->DropColumnFamily(cf));
delete cf;
cf = nullptr;
// Obtain rich files metadata for source of truth.
std::vector<LiveFileMetaData> live_files;
db->GetLiveFilesMetaData(&live_files);
+44 -25
View File
@@ -71,9 +71,9 @@ DBTestBase::DBTestBase(const std::string path, bool env_do_fsync)
if (getenv("MEM_ENV")) {
mem_env_ = MockEnv::Create(base_env, base_env->GetSystemClock());
}
if (getenv("ENCRYPTED_ENV")) {
if (auto ee = getenv("ENCRYPTED_ENV")) {
std::shared_ptr<EncryptionProvider> provider;
std::string provider_id = getenv("ENCRYPTED_ENV");
std::string provider_id = ee;
if (provider_id.find('=') == std::string::npos &&
!EndsWith(provider_id, "://test")) {
provider_id = provider_id + "://test";
@@ -366,11 +366,6 @@ Options DBTestBase::GetOptions(
table_options.block_cache = NewLRUCache(/* too small */ 1);
}
// Test anticipated new default as much as reasonably possible (and remove
// this code when obsolete)
assert(!table_options.decouple_partitioned_filters);
table_options.decouple_partitioned_filters = true;
bool can_allow_mmap = IsMemoryMappedAccessSupported();
switch (option_config) {
case kHashSkipList:
@@ -459,7 +454,8 @@ Options DBTestBase::GetOptions(
options.allow_mmap_reads = can_allow_mmap;
break;
case kManifestFileSize:
options.max_manifest_file_size = 50; // 50 bytes
options.max_manifest_file_size = 50; // 50 bytes
options.max_manifest_space_amp_pct = 0; // old behavior
break;
case kPerfOptions:
options.delayed_write_rate = 8 * 1024 * 1024;
@@ -1159,16 +1155,18 @@ size_t DBTestBase::CountLiveFiles() {
}
int DBTestBase::NumTableFilesAtLevel(int level, int cf) {
std::string property;
if (cf == 0) {
// default cfd
EXPECT_TRUE(db_->GetProperty(
"rocksdb.num-files-at-level" + std::to_string(level), &property));
} else {
EXPECT_TRUE(db_->GetProperty(
handles_[cf], "rocksdb.num-files-at-level" + std::to_string(level),
&property));
return NumTableFilesAtLevel(level,
cf ? handles_[cf] : db_->DefaultColumnFamily());
}
int DBTestBase::NumTableFilesAtLevel(int level, ColumnFamilyHandle* cfh,
DB* db) {
if (!db) {
db = db_;
}
std::string property;
EXPECT_TRUE(db->GetProperty(
cfh, "rocksdb.num-files-at-level" + std::to_string(level), &property));
return atoi(property.c_str());
}
@@ -1201,12 +1199,22 @@ int DBTestBase::TotalTableFiles(int cf, int levels) {
// Return spread of files per level
std::string DBTestBase::FilesPerLevel(int cf) {
int num_levels =
(cf == 0) ? db_->NumberLevels() : db_->NumberLevels(handles_[cf]);
if (cf == 0) {
return FilesPerLevel(db_->DefaultColumnFamily());
} else {
return FilesPerLevel(handles_[cf]);
}
}
std::string DBTestBase::FilesPerLevel(ColumnFamilyHandle* cfh, DB* db) {
if (!db) {
db = db_;
}
int num_levels = db->NumberLevels(cfh);
std::string result;
size_t last_non_zero_offset = 0;
for (int level = 0; level < num_levels; level++) {
int f = NumTableFilesAtLevel(level, cf);
int f = NumTableFilesAtLevel(level, cfh, db);
char buf[100];
snprintf(buf, sizeof(buf), "%s%d", (level ? "," : ""), f);
result += buf;
@@ -1339,12 +1347,14 @@ void DBTestBase::FillLevels(const std::string& smallest,
}
void DBTestBase::MoveFilesToLevel(int level, int cf) {
MoveFilesToLevel(level, cf ? handles_[cf] : db_->DefaultColumnFamily());
}
void DBTestBase::MoveFilesToLevel(int level, ColumnFamilyHandle* column_family,
DB* db) {
DBImpl* db_impl = db ? static_cast<DBImpl*>(db) : dbfull();
for (int l = 0; l < level; ++l) {
if (cf > 0) {
EXPECT_OK(dbfull()->TEST_CompactRange(l, nullptr, nullptr, handles_[cf]));
} else {
EXPECT_OK(dbfull()->TEST_CompactRange(l, nullptr, nullptr));
}
EXPECT_OK(db_impl->TEST_CompactRange(l, nullptr, nullptr, column_family));
}
}
@@ -1863,4 +1873,13 @@ template class TargetCacheChargeTrackingCache<
CacheEntryRole::kBlockBasedTableReader>;
template class TargetCacheChargeTrackingCache<CacheEntryRole::kFileMetadata>;
const std::vector<Temperature> kKnownTemperatures = {
Temperature::kHot, Temperature::kWarm, Temperature::kCool,
Temperature::kCold, Temperature::kIce};
Temperature RandomKnownTemperature() {
return kKnownTemperatures[Random::GetTLSInstance()->Uniform(
static_cast<int>(kKnownTemperatures.size()))];
}
} // namespace ROCKSDB_NAMESPACE
+29 -14
View File
@@ -1280,6 +1280,9 @@ class DBTestBase : public testing::Test {
int NumTableFilesAtLevel(int level, int cf = 0);
int NumTableFilesAtLevel(int level, ColumnFamilyHandle* column_family,
DB* db = nullptr);
double CompressionRatioAtLevel(int level, int cf = 0);
int TotalTableFiles(int cf = 0, int levels = -1);
@@ -1289,6 +1292,8 @@ class DBTestBase : public testing::Test {
// Return spread of files per level
std::string FilesPerLevel(int cf = 0);
std::string FilesPerLevel(ColumnFamilyHandle* cfh, DB* db = nullptr);
size_t CountFiles();
Status CountFiles(size_t* count);
@@ -1320,6 +1325,9 @@ class DBTestBase : public testing::Test {
void MoveFilesToLevel(int level, int cf = 0);
void MoveFilesToLevel(int level, ColumnFamilyHandle* column_family,
DB* db = nullptr);
void DumpFileCounts(const char* label);
std::string DumpSSTableList();
@@ -1430,20 +1438,23 @@ class DBTestBase : public testing::Test {
std::replace(tp_string.begin(), tp_string.end(), ';', ' ');
std::replace(tp_string.begin(), tp_string.end(), '=', ' ');
ResetTableProperties(tp);
sscanf(tp_string.c_str(),
"# data blocks %" SCNu64 " # entries %" SCNu64
" # deletions %" SCNu64 " # merge operands %" SCNu64
" # range deletions %" SCNu64 " raw key size %" SCNu64
" raw average key size %lf "
" raw value size %" SCNu64
" raw average value size %lf "
" data block size %" SCNu64 " index block size (user-key? %" SCNu64
", delta-value? %" SCNu64 ") %" SCNu64 " filter block size %" SCNu64,
&tp->num_data_blocks, &tp->num_entries, &tp->num_deletions,
&tp->num_merge_operands, &tp->num_range_deletions, &tp->raw_key_size,
&dummy_double, &tp->raw_value_size, &dummy_double, &tp->data_size,
&tp->index_key_is_user_key, &tp->index_value_is_delta_encoded,
&tp->index_size, &tp->filter_size);
int count = sscanf(
tp_string.c_str(),
"# data blocks %" SCNu64 " # entries %" SCNu64 " # deletions %" SCNu64
" # merge operands %" SCNu64 " # range deletions %" SCNu64
" raw key size %" SCNu64
" raw average key size %lf "
" raw value size %" SCNu64
" raw average value size %lf "
" data block size %" SCNu64 " data uncompressed size %" SCNu64
" index block size (user-key? %" SCNu64 ", delta-value? %" SCNu64
") %" SCNu64 " filter block size %" SCNu64,
&tp->num_data_blocks, &tp->num_entries, &tp->num_deletions,
&tp->num_merge_operands, &tp->num_range_deletions, &tp->raw_key_size,
&dummy_double, &tp->raw_value_size, &dummy_double, &tp->data_size,
&tp->uncompressed_data_size, &tp->index_key_is_user_key,
&tp->index_value_is_delta_encoded, &tp->index_size, &tp->filter_size);
ASSERT_EQ(count, 15);
}
private: // Prone to error on direct use
@@ -1456,4 +1467,8 @@ class DBTestBase : public testing::Test {
// unique ids.
void VerifySstUniqueIds(const TablePropertiesCollection& props);
// Excludes kUnknown
extern const std::vector<Temperature> kKnownTemperatures;
Temperature RandomKnownTemperature();
} // namespace ROCKSDB_NAMESPACE
+68 -35
View File
@@ -2106,46 +2106,79 @@ TEST_F(DBTestUniversalCompaction2, OverlappingL0) {
}
TEST_F(DBTestUniversalCompaction2, IngestBehind) {
const int kNumKeys = 3000;
const int kWindowSize = 100;
const int kNumDelsTrigger = 90;
for (bool cf_option : {false, true}) {
SCOPED_TRACE("cf_option = " + std::to_string(cf_option));
const int kNumKeys = 3000;
const int kWindowSize = 100;
const int kNumDelsTrigger = 90;
Options opts = CurrentOptions();
opts.table_properties_collector_factories.emplace_back(
NewCompactOnDeletionCollectorFactory(kWindowSize, kNumDelsTrigger));
opts.compaction_style = kCompactionStyleUniversal;
opts.level0_file_num_compaction_trigger = 2;
opts.compression = kNoCompression;
opts.allow_ingest_behind = true;
opts.compaction_options_universal.size_ratio = 10;
opts.compaction_options_universal.min_merge_width = 2;
opts.compaction_options_universal.max_size_amplification_percent = 200;
Reopen(opts);
// add an L1 file to prevent tombstones from dropping due to obsolescence
// during flush
int i;
for (i = 0; i < 2000; ++i) {
ASSERT_OK(Put(Key(i), "val"));
}
ASSERT_OK(Flush());
// MoveFilesToLevel(6);
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
for (i = 1999; i < kNumKeys; ++i) {
if (i >= kNumKeys - kWindowSize &&
i < kNumKeys - kWindowSize + kNumDelsTrigger) {
ASSERT_OK(Delete(Key(i)));
Options opts = CurrentOptions();
opts.table_properties_collector_factories.emplace_back(
NewCompactOnDeletionCollectorFactory(kWindowSize, kNumDelsTrigger));
opts.compaction_style = kCompactionStyleUniversal;
opts.level0_file_num_compaction_trigger = 2;
opts.compression = kNoCompression;
if (cf_option) {
opts.cf_allow_ingest_behind = true;
} else {
opts.allow_ingest_behind = true;
}
opts.compaction_options_universal.size_ratio = 10;
opts.compaction_options_universal.min_merge_width = 2;
opts.compaction_options_universal.max_size_amplification_percent = 200;
Reopen(opts);
// add an L1 file to prevent tombstones from dropping due to obsolescence
// during flush
int i;
for (i = 0; i < 2000; ++i) {
ASSERT_OK(Put(Key(i), "val"));
}
}
ASSERT_OK(Flush());
ASSERT_OK(Flush());
// MoveFilesToLevel(6);
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ASSERT_EQ(0, NumTableFilesAtLevel(0));
ASSERT_EQ(0, NumTableFilesAtLevel(6));
ASSERT_GT(NumTableFilesAtLevel(5), 0);
for (i = 1999; i < kNumKeys; ++i) {
if (i >= kNumKeys - kWindowSize &&
i < kNumKeys - kWindowSize + kNumDelsTrigger) {
ASSERT_OK(Delete(Key(i)));
} else {
ASSERT_OK(Put(Key(i), "val"));
}
}
ASSERT_OK(Flush());
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ASSERT_EQ(0, NumTableFilesAtLevel(0));
ASSERT_EQ(0, NumTableFilesAtLevel(6));
ASSERT_GT(NumTableFilesAtLevel(5), 0);
if (cf_option) {
// Test that another CF does not allow ingest behind
ColumnFamilyHandle* new_cfh;
Options new_cf_option;
new_cf_option.compaction_style = kCompactionStyleUniversal;
new_cf_option.num_levels = 7;
// CreateColumnFamilies({"new_cf"}, new_cf_option);
ASSERT_OK(db_->CreateColumnFamily(new_cf_option, "new_cf", &new_cfh));
// handles_.push_back(new_cfh);
for (i = 0; i < 10; ++i) {
// ASSERT_OK(Put(1, Key(i), "val"));
ASSERT_OK(db_->Put(WriteOptions(), new_cfh, Key(i), "val"));
}
ASSERT_OK(
db_->CompactRange(CompactRangeOptions(), new_cfh, nullptr, nullptr));
// This CF can use the last leve.
std::string property;
EXPECT_TRUE(db_->GetProperty(
new_cfh, "rocksdb.num-files-at-level" + std::to_string(6),
&property));
ASSERT_EQ(1, atoi(property.c_str()));
ASSERT_OK(db_->DropColumnFamily(new_cfh));
ASSERT_OK(db_->DestroyColumnFamilyHandle(new_cfh));
}
}
}
TEST_F(DBTestUniversalCompaction2, PeriodicCompactionDefault) {
+3 -2
View File
@@ -1746,8 +1746,8 @@ class RecoveryTestHelper {
WriteController write_controller;
versions.reset(new VersionSet(
test->dbname_, &db_options, file_options, table_cache.get(),
&write_buffer_manager, &write_controller,
test->dbname_, &db_options, MutableDBOptions{options}, file_options,
table_cache.get(), &write_buffer_manager, &write_controller,
/*block_cache_tracer=*/nullptr,
/*io_tracer=*/nullptr, /*db_id=*/"", /*db_session_id=*/"",
options.daily_offpeak_time_utc,
@@ -2277,6 +2277,7 @@ TEST_F(DBWALTest, FixSyncWalOnObseletedWalWithNewManifestCausingMissingWAL) {
Options options = CurrentOptions();
// Small size to force manifest creation
options.max_manifest_file_size = 1;
options.max_manifest_space_amp_pct = 0;
options.track_and_verify_wals_in_manifest = true;
DestroyAndReopen(options);
+44
View File
@@ -333,6 +333,50 @@ TEST_F(FormatTest, ReplaceInternalKeyWithMinTimestamp) {
ASSERT_EQ(kTypeValue, new_key.type);
}
TEST(RocksdbVersionTest, Version) {
// Test preprocessor macros for versioning
ASSERT_GT(ROCKSDB_MAJOR, 0);
ASSERT_GE(ROCKSDB_MINOR, 0);
ASSERT_GE(ROCKSDB_PATCH, 0);
ASSERT_LT(ROCKSDB_MAJOR, 1000);
ASSERT_LT(ROCKSDB_MINOR, 1000);
ASSERT_LT(ROCKSDB_PATCH, 1000);
ASSERT_EQ(ROCKSDB_MAKE_VERSION_INT(123, 456, 789), 123456789);
ASSERT_GT(ROCKSDB_VERSION_INT, 9999999);
ASSERT_LT(ROCKSDB_VERSION_INT, 99999999);
static_assert(ROCKSDB_VERSION_GE(9, 8, 7));
static_assert(
ROCKSDB_VERSION_GE(ROCKSDB_MAJOR, ROCKSDB_MINOR, ROCKSDB_PATCH));
static_assert(
ROCKSDB_VERSION_GE(ROCKSDB_MAJOR, ROCKSDB_MINOR, ROCKSDB_PATCH - 1));
static_assert(
ROCKSDB_VERSION_GE(ROCKSDB_MAJOR, ROCKSDB_MINOR, ROCKSDB_PATCH - 100));
static_assert(
ROCKSDB_VERSION_GE(ROCKSDB_MAJOR, ROCKSDB_MINOR - 1, ROCKSDB_PATCH + 1));
static_assert(ROCKSDB_VERSION_GE(ROCKSDB_MAJOR - 1, ROCKSDB_MINOR + 1,
ROCKSDB_PATCH + 1));
static_assert(
!ROCKSDB_VERSION_GE(ROCKSDB_MAJOR, ROCKSDB_MINOR, ROCKSDB_PATCH + 1));
static_assert(
!ROCKSDB_VERSION_GE(ROCKSDB_MAJOR, ROCKSDB_MINOR, ROCKSDB_PATCH + 100));
static_assert(
!ROCKSDB_VERSION_GE(ROCKSDB_MAJOR, ROCKSDB_MINOR + 1, ROCKSDB_PATCH - 1));
static_assert(!ROCKSDB_VERSION_GE(ROCKSDB_MAJOR + 1, ROCKSDB_MINOR - 1,
ROCKSDB_PATCH - 1));
// More typical usage (but with literal numbers based on relevant API
// features)
#if ROCKSDB_VERSION_GE(ROCKSDB_MAJOR, ROCKSDB_MINOR, ROCKSDB_PATCH)
static_assert(true);
#else
static_assert(false);
#endif
#if !ROCKSDB_VERSION_GE(ROCKSDB_MAJOR, ROCKSDB_MINOR, ROCKSDB_PATCH + 1)
static_assert(true);
#else
static_assert(false);
#endif
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+1
View File
@@ -129,6 +129,7 @@ void EventHelpers::LogAndNotifyTableFileCreationFinished(
<< "user_defined_timestamps_persisted"
<< table_properties.user_defined_timestamps_persisted
<< "key_largest_seqno" << table_properties.key_largest_seqno
<< "key_smallest_seqno" << table_properties.key_smallest_seqno
<< "merge_operator" << table_properties.merge_operator_name
<< "prefix_extractor_name"
<< table_properties.prefix_extractor_name << "property_collectors"
+5 -4
View File
@@ -88,11 +88,12 @@ Status GetFileChecksumsFromCurrentManifest(FileSystem* fs,
// Read all records from the manifest file...
uint64_t manifest_file_size = std::numeric_limits<uint64_t>::max();
FileChecksumRetriever retriever(read_options, manifest_file_size,
*checksum_list);
FileChecksumRetriever retriever(read_options, manifest_file_size);
retriever.Iterate(reader, &s);
return retriever.status();
if (!retriever.status().ok()) {
return retriever.status();
}
return retriever.FetchFileChecksumList(*checksum_list);
}
Status UpdateManifestForFilesState(
+8 -1
View File
@@ -2567,7 +2567,14 @@ TEST_F(ExternalSSTFileBasicTest, IngestWithTemperature) {
options.default_write_temperature = Temperature::kHot;
SstFileWriter sst_file_writer(EnvOptions(), options);
options.level0_file_num_compaction_trigger = 2;
options.allow_ingest_behind = (mode == "ingest_behind");
bool cf_option = Random::GetTLSInstance()->OneIn(2);
SCOPED_TRACE(std::string("Use ") + (cf_option ? "CF" : "DB") +
" option for ingest behind");
if (cf_option) {
options.cf_allow_ingest_behind = (mode == "ingest_behind");
} else {
options.allow_ingest_behind = (mode == "ingest_behind");
}
Reopen(options);
Defer destroyer([&]() { Destroy(options); });
+276 -81
View File
@@ -42,6 +42,9 @@ Status ExternalSstFileIngestionJob::Prepare(
status =
GetIngestedFileInfo(file_path, next_file_number++, &file_to_ingest, sv);
if (!status.ok()) {
ROCKS_LOG_WARN(db_options_.info_log,
"Failed to get ingested file info: %s: %s",
file_path.c_str(), status.ToString().c_str());
return status;
}
@@ -122,24 +125,28 @@ Status ExternalSstFileIngestionJob::Prepare(
}
}
if (ingestion_options_.ingest_behind && files_overlap_) {
return Status::NotSupported(
"Files with overlapping ranges cannot be ingested with ingestion "
"behind mode.");
}
if (files_overlap_) {
if (ingestion_options_.ingest_behind) {
return Status::NotSupported(
"Files with overlapping ranges cannot be ingested with ingestion "
"behind mode.");
}
// Overlapping files need at least two different sequence numbers. If settings
// disables global seqno, ingestion will fail anyway, so fail fast in prepare.
if (!ingestion_options_.allow_global_seqno && files_overlap_) {
return Status::InvalidArgument(
"Global seqno is required, but disabled (because external files key "
"range overlaps).");
}
// Overlapping files need at least two different sequence numbers. If
// settings disables global seqno, ingestion will fail anyway, so fail
// fast in prepare.
if (!ingestion_options_.allow_global_seqno &&
!ingestion_options_.allow_db_generated_files) {
return Status::InvalidArgument(
"Global seqno is required, but disabled (because external files key "
"range overlaps).");
}
if (ucmp_->timestamp_size() > 0 && files_overlap_) {
return Status::NotSupported(
"Files with overlapping ranges cannot be ingested to column "
"family with user-defined timestamp enabled.");
if (ucmp_->timestamp_size() > 0) {
return Status::NotSupported(
"Files with overlapping ranges cannot be ingested to column "
"family with user-defined timestamp enabled.");
}
}
// Copy/Move external files into DB
@@ -194,6 +201,10 @@ Status ExternalSstFileIngestionJob::Prepare(
ROCKS_LOG_INFO(db_options_.info_log,
"Tried to link file %s but it's not supported : %s",
path_outside_db.c_str(), status.ToString().c_str());
} else {
ROCKS_LOG_WARN(db_options_.info_log, "Failed to link file %s to %s: %s",
path_outside_db.c_str(), path_inside_db.c_str(),
status.ToString().c_str());
}
} else {
f.copy_file = true;
@@ -218,6 +229,12 @@ Status ExternalSstFileIngestionJob::Prepare(
io_tracer_);
// The destination of the copy will be ingested
f.file_temperature = dst_temp;
if (!status.ok()) {
ROCKS_LOG_WARN(db_options_.info_log, "Failed to copy file %s to %s: %s",
path_outside_db.c_str(), path_inside_db.c_str(),
status.ToString().c_str());
}
} else {
// Note: we currently assume that linking files does not cross
// temperatures, so no need to change f.file_temperature
@@ -443,6 +460,11 @@ Status ExternalSstFileIngestionJob::NeedsFlush(bool* flush_needed,
}
status = cfd_->RangesOverlapWithMemtables(
ranges, super_version, db_options_.allow_data_in_errors, flush_needed);
if (!status.ok()) {
ROCKS_LOG_WARN(db_options_.info_log,
"Failed to check ranges overlap with memtables: %s",
status.ToString().c_str());
}
}
if (status.ok() && *flush_needed) {
if (!ingestion_options_.allow_blocking_flush) {
@@ -477,6 +499,9 @@ Status ExternalSstFileIngestionJob::Run() {
bool need_flush = false;
status = NeedsFlush(&need_flush, super_version);
if (!status.ok()) {
ROCKS_LOG_WARN(db_options_.info_log,
"Failed to check if flush is needed: %s",
status.ToString().c_str());
return status;
}
if (need_flush) {
@@ -540,12 +565,17 @@ Status ExternalSstFileIngestionJob::Run() {
// Find levels to ingest into
std::optional<int> prev_batch_uppermost_level;
// batches at the front of file_batches_to_ingest_ contains older updates and
// are placed in smaller levels.
for (auto& batch : file_batches_to_ingest_) {
int batch_uppermost_level = 0;
status = AssignLevelsForOneBatch(batch, super_version, force_global_seqno,
&last_seqno, &batch_uppermost_level,
prev_batch_uppermost_level);
if (!status.ok()) {
ROCKS_LOG_WARN(db_options_.info_log,
"Failed to assign levels for one batch: %s",
status.ToString().c_str());
return status;
}
@@ -588,8 +618,19 @@ Status ExternalSstFileIngestionJob::AssignLevelsForOneBatch(
&largest_parsed, false /* log_err_key */);
}
if (!status.ok()) {
ROCKS_LOG_WARN(db_options_.info_log, "Failed to parse internal key: %s",
status.ToString().c_str());
return status;
}
// If any ingested file overlaps with the DB, it will fail here.
if (ingestion_options_.allow_db_generated_files && assigned_seqno != 0) {
return Status::InvalidArgument(
"An ingested file overlaps with existing data in the DB and has been "
"assigned a non-zero sequence number, which is not allowed when "
"'allow_db_generated_files' is enabled.");
}
if (smallest_parsed.sequence == 0 && assigned_seqno != 0) {
UpdateInternalKey(file->smallest_internal_key.rep(), assigned_seqno,
smallest_parsed.type);
@@ -601,6 +642,10 @@ Status ExternalSstFileIngestionJob::AssignLevelsForOneBatch(
status = AssignGlobalSeqnoForIngestedFile(file, assigned_seqno);
if (!status.ok()) {
ROCKS_LOG_WARN(
db_options_.info_log,
"Failed to assign global sequence number for ingested file: %s",
status.ToString().c_str());
return status;
}
TEST_SYNC_POINT_CALLBACK("ExternalSstFileIngestionJob::Run",
@@ -608,11 +653,14 @@ Status ExternalSstFileIngestionJob::AssignLevelsForOneBatch(
assert(assigned_seqno == 0 || assigned_seqno == *last_seqno + 1);
if (assigned_seqno > *last_seqno) {
*last_seqno = assigned_seqno;
++consumed_seqno_count_;
}
max_assigned_seqno_ = std::max(max_assigned_seqno_, assigned_seqno);
status = GenerateChecksumForIngestedFile(file);
if (!status.ok()) {
ROCKS_LOG_WARN(db_options_.info_log,
"Failed to generate checksum for ingested file: %s",
status.ToString().c_str());
return status;
}
@@ -632,15 +680,24 @@ Status ExternalSstFileIngestionJob::AssignLevelsForOneBatch(
file->table_properties.num_range_deletions == 1 &&
(file->table_properties.num_entries ==
file->table_properties.num_range_deletions);
SequenceNumber smallest_seqno = file->assigned_seqno;
SequenceNumber largest_seqno = file->assigned_seqno;
if (ingestion_options_.allow_db_generated_files) {
assert(file->assigned_seqno == 0);
assert(file->smallest_seqno != kMaxSequenceNumber);
assert(file->largest_seqno != kMaxSequenceNumber);
smallest_seqno = file->smallest_seqno;
largest_seqno = file->largest_seqno;
max_assigned_seqno_ = std::max(max_assigned_seqno_, file->largest_seqno);
}
FileMetaData f_metadata(
file->fd.GetNumber(), file->fd.GetPathId(), file->fd.GetFileSize(),
file->smallest_internal_key, file->largest_internal_key,
file->assigned_seqno, file->assigned_seqno, false,
file->file_temperature, kInvalidBlobFileNumber, oldest_ancester_time,
current_time,
file->smallest_internal_key, file->largest_internal_key, smallest_seqno,
largest_seqno, false, file->file_temperature, kInvalidBlobFileNumber,
oldest_ancester_time, current_time,
ingestion_options_.ingest_behind
? kReservedEpochNumberForFileIngestedBehind
: cfd_->NewEpochNumber(),
: cfd_->NewEpochNumber(), // orders files ingested to L0
file->file_checksum, file->file_checksum_func_name, file->unique_id, 0,
tail_size, file->user_defined_timestamps_persisted);
f_metadata.temperature = file->file_temperature;
@@ -692,8 +749,7 @@ void ExternalSstFileIngestionJob::CreateEquivalentFileIngestingCompactions() {
cfd_->ioptions().compaction_style),
LLONG_MAX /* max compaction bytes, not applicable */,
0 /* output path ID, not applicable */, mutable_cf_options.compression,
mutable_cf_options.compression_opts,
mutable_cf_options.default_write_temperature,
mutable_cf_options.compression_opts, Temperature::kUnknown,
0 /* max_subcompaction, not applicable */,
{} /* grandparents, not applicable */,
std::nullopt /* earliest_snapshot */, nullptr /* snapshot_checker */,
@@ -787,7 +843,6 @@ void ExternalSstFileIngestionJob::Cleanup(const Status& status) {
// We failed to add the files to the database
// remove all the files we copied
DeleteInternalFiles();
consumed_seqno_count_ = 0;
files_overlap_ = false;
} else if (status.ok() && ingestion_options_.move_files) {
// The files were moved and added successfully, remove original file links
@@ -830,6 +885,10 @@ Status ExternalSstFileIngestionJob::ResetTableReader(
Status status =
fs_->NewRandomAccessFile(external_file, fo, &sst_file, nullptr);
if (!status.ok()) {
ROCKS_LOG_WARN(
db_options_.info_log,
"Failed to create random access file for external file %s: %s",
external_file.c_str(), status.ToString().c_str());
return status;
}
Temperature updated_temp = sst_file->GetTemperature();
@@ -952,6 +1011,10 @@ Status ExternalSstFileIngestionJob::SanityCheckTableProperties(
// user_defined_timestamps_persisted flag for the file.
file_to_ingest->user_defined_timestamps_persisted = false;
} else if (!s.ok()) {
ROCKS_LOG_WARN(
db_options_.info_log,
"ValidateUserDefinedTimestampsOptions failed for external file %s: %s",
external_file.c_str(), s.ToString().c_str());
return s;
}
@@ -976,6 +1039,9 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
Status status = fs_->GetFileSize(external_file, IOOptions(),
&file_to_ingest->file_size, nullptr);
if (!status.ok()) {
ROCKS_LOG_WARN(db_options_.info_log,
"Failed to get file size for external file %s: %s",
external_file.c_str(), status.ToString().c_str());
return status;
}
@@ -992,15 +1058,52 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
/*user_defined_timestamps_persisted=*/true, sv,
file_to_ingest, &table_reader);
if (!status.ok()) {
ROCKS_LOG_WARN(db_options_.info_log,
"Failed to reset table reader for external file %s: %s",
external_file.c_str(), status.ToString().c_str());
return status;
}
status = SanityCheckTableProperties(external_file, new_file_number, sv,
file_to_ingest, &table_reader);
if (!status.ok()) {
ROCKS_LOG_WARN(
db_options_.info_log,
"Failed to sanity check table properties for external file %s: %s",
external_file.c_str(), status.ToString().c_str());
return status;
}
const bool allow_data_in_errors = db_options_.allow_data_in_errors;
ParsedInternalKey key;
if (ingestion_options_.allow_db_generated_files) {
// We are ingesting a DB generated SST file for which we don't reassign
// sequence numbers. We need its smallest sequence number and largest
// sequence number for FileMetaData.
Status seqno_status = GetSeqnoBoundaryForFile(
table_reader.get(), sv, file_to_ingest, allow_data_in_errors);
if (!seqno_status.ok()) {
ROCKS_LOG_WARN(
db_options_.info_log,
"Failed to get sequence number boundary for external file %s: %s",
external_file.c_str(), seqno_status.ToString().c_str());
return seqno_status;
}
assert(file_to_ingest->smallest_seqno <= file_to_ingest->largest_seqno);
assert(file_to_ingest->largest_seqno < kMaxSequenceNumber);
} else {
SequenceNumber largest_seqno =
table_reader.get()->GetTableProperties()->key_largest_seqno;
// UINT64_MAX means unknown and the file is generated before table property
// `key_largest_seqno` is introduced.
if (largest_seqno != UINT64_MAX && largest_seqno > 0) {
return Status::Corruption(
"External file has non zero largest sequence number " +
std::to_string(largest_seqno));
}
}
if (ingestion_options_.verify_checksums_before_ingest) {
// If customized readahead size is needed, we can pass a user option
// all the way to here. Right now we just rely on the default readahead
@@ -1012,11 +1115,13 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
status = table_reader->VerifyChecksum(
ro, TableReaderCaller::kExternalSSTIngestion);
if (!status.ok()) {
ROCKS_LOG_WARN(db_options_.info_log,
"Failed to verify checksum for table reader: %s",
status.ToString().c_str());
return status;
}
}
ParsedInternalKey key;
// TODO: plumb Env::IOActivity, Env::IOPriority
ReadOptions ro;
ro.fill_cache = ingestion_options_.fill_cache;
@@ -1025,7 +1130,6 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
/*skip_filters=*/false, TableReaderCaller::kExternalSSTIngestion));
// Get first (smallest) and last (largest) key from file.
bool allow_data_in_errors = db_options_.allow_data_in_errors;
iter->SeekToFirst();
if (iter->Valid()) {
Status pik_status =
@@ -1034,7 +1138,7 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
return Status::Corruption("Corrupted key in external file. ",
pik_status.getState());
}
if (key.sequence != 0) {
if (key.sequence != 0 && !ingestion_options_.allow_db_generated_files) {
return Status::Corruption("External file has non zero sequence number");
}
file_to_ingest->smallest_internal_key.SetFrom(key);
@@ -1071,41 +1175,13 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
return Status::Corruption("Corrupted key in external file. ",
pik_status.getState());
}
if (key.sequence != 0) {
if (key.sequence != 0 && !ingestion_options_.allow_db_generated_files) {
return Status::Corruption("External file has non zero sequence number");
}
file_to_ingest->largest_internal_key.SetFrom(key);
} else if (!iter->status().ok()) {
return iter->status();
}
SequenceNumber largest_seqno =
table_reader.get()->GetTableProperties()->key_largest_seqno;
// UINT64_MAX means unknown and the file is generated before table property
// `key_largest_seqno` is introduced.
if (largest_seqno != UINT64_MAX && largest_seqno > 0) {
return Status::Corruption(
"External file has non zero largest sequence number " +
std::to_string(largest_seqno));
}
if (ingestion_options_.allow_db_generated_files &&
largest_seqno == UINT64_MAX) {
// Need to verify that all keys have seqno zero.
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
Status pik_status =
ParseInternalKey(iter->key(), &key, allow_data_in_errors);
if (!pik_status.ok()) {
return Status::Corruption("Corrupted key in external file. ",
pik_status.getState());
}
if (key.sequence != 0) {
return Status::NotSupported(
"External file has a key with non zero sequence number.");
}
}
if (!iter->status().ok()) {
return iter->status();
}
}
std::unique_ptr<InternalIterator> range_del_iter(
table_reader->NewRangeTombstoneIterator(ro));
@@ -1120,7 +1196,7 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
return Status::Corruption("Corrupted key in external file. ",
pik_status.getState());
}
if (key.sequence != 0) {
if (key.sequence != 0 && !ingestion_options_.allow_db_generated_files) {
return Status::Corruption(
"External file has a range deletion with non zero sequence "
"number.");
@@ -1168,12 +1244,14 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
const size_t ts_sz = ucmp_->timestamp_size();
assert(!prev_batch_uppermost_level.has_value() ||
prev_batch_uppermost_level.value() < cfd_->NumberLevels());
bool must_assign_to_l0 = prev_batch_uppermost_level.has_value() &&
prev_batch_uppermost_level.value() == 0;
if (force_global_seqno || files_overlap_ ||
compaction_style == kCompactionStyleFIFO || must_assign_to_l0) {
bool must_assign_to_l0 = (prev_batch_uppermost_level.has_value() &&
prev_batch_uppermost_level.value() == 0) ||
compaction_style == kCompactionStyleFIFO;
if (force_global_seqno || (!ingestion_options_.allow_db_generated_files &&
(files_overlap_ || must_assign_to_l0))) {
*assigned_seqno = last_seqno + 1;
if (compaction_style == kCompactionStyleFIFO || must_assign_to_l0) {
if (must_assign_to_l0) {
assert(ts_sz == 0);
file_to_ingest->picked_level = 0;
if (ingestion_options_.fail_if_not_bottommost_level &&
@@ -1194,16 +1272,26 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
ro.total_order_seek = true;
int target_level = 0;
auto* vstorage = cfd_->current()->storage_info();
assert(!must_assign_to_l0);
int exclusive_end_level = prev_batch_uppermost_level.has_value()
? prev_batch_uppermost_level.value()
: cfd_->NumberLevels();
assert(!must_assign_to_l0 || ingestion_options_.allow_db_generated_files);
int assigned_level_exclusive_end = cfd_->NumberLevels();
if (must_assign_to_l0) {
assigned_level_exclusive_end = 0;
} else if (prev_batch_uppermost_level.has_value()) {
assigned_level_exclusive_end = prev_batch_uppermost_level.value();
}
for (int lvl = 0; lvl < exclusive_end_level; lvl++) {
// When ingesting db generated files, we require that ingested files do not
// overlap with any file in the DB. So we need to check all levels.
int overlap_checking_exclusive_end =
ingestion_options_.allow_db_generated_files
? cfd_->NumberLevels()
: assigned_level_exclusive_end;
for (int lvl = 0; lvl < overlap_checking_exclusive_end; lvl++) {
if (lvl > 0 && lvl < vstorage->base_level()) {
continue;
}
if (atomic_replace_range_.has_value()) {
if (lvl < assigned_level_exclusive_end &&
atomic_replace_range_.has_value()) {
target_level = lvl;
continue;
}
@@ -1221,6 +1309,9 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
ro, env_options_, file_to_ingest->start_ukey,
file_to_ingest->limit_ukey, lvl, &overlap_with_level);
if (!status.ok()) {
ROCKS_LOG_WARN(db_options_.info_log,
"Failed to check overlap with level iterator: %s",
status.ToString().c_str());
return status;
}
if (overlap_with_level) {
@@ -1234,7 +1325,8 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
// We don't overlap with any keys in this level, but we still need to check
// if our file can fit in it
if (IngestedFileFitInLevel(file_to_ingest, lvl)) {
if (lvl < assigned_level_exclusive_end &&
IngestedFileFitInLevel(file_to_ingest, lvl)) {
target_level = lvl;
}
}
@@ -1243,8 +1335,9 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
target_level < cfd_->NumberLevels() - 1) {
status = Status::TryAgain(
"Files cannot be ingested to Lmax. Please make sure key range of Lmax "
"and ongoing compaction's output to Lmax"
"does not overlap with files to ingest.");
"and ongoing compaction's output to Lmax does not overlap with files "
"to ingest. Input files overlapping with each other can cause some "
"file to be assigned to non Lmax level.");
return status;
}
@@ -1265,11 +1358,6 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
}
}
if (ingestion_options_.allow_db_generated_files && *assigned_seqno != 0) {
return Status::InvalidArgument(
"An ingested file is assigned to a non-zero sequence number, which is "
"incompatible with ingestion option allow_db_generated_files.");
}
return status;
}
@@ -1286,13 +1374,13 @@ Status ExternalSstFileIngestionJob::CheckLevelForIngestedBehindFile(
"at the last level!");
}
// Second, check if despite allow_ingest_behind=true we still have 0 seqnums
// at some upper level
// Second, check if despite cf_allow_ingest_behind=true we still have 0
// seqnums at some upper level
for (int lvl = 0; lvl < cfd_->NumberLevels() - 1; lvl++) {
for (auto file : vstorage->LevelFiles(lvl)) {
if (file->fd.smallest_seqno == 0) {
return Status::InvalidArgument(
"Can't ingest_behind file as despite allow_ingest_behind=true "
"Can't ingest_behind file as despite cf_allow_ingest_behind=true "
"there are files with 0 seqno in database at upper levels!");
}
}
@@ -1304,8 +1392,12 @@ Status ExternalSstFileIngestionJob::CheckLevelForIngestedBehindFile(
Status ExternalSstFileIngestionJob::AssignGlobalSeqnoForIngestedFile(
IngestedFileInfo* file_to_ingest, SequenceNumber seqno) {
if (ingestion_options_.allow_db_generated_files) {
assert(seqno == 0);
assert(file_to_ingest->original_seqno == 0);
}
if (file_to_ingest->original_seqno == seqno) {
// This file already have the correct global seqno
// This file already has the correct global seqno.
return Status::OK();
} else if (!ingestion_options_.allow_global_seqno) {
return Status::InvalidArgument("Global seqno is required, but disabled");
@@ -1332,6 +1424,14 @@ Status ExternalSstFileIngestionJob::AssignGlobalSeqnoForIngestedFile(
PutFixed64(&seqno_val, seqno);
status = fsptr->Write(file_to_ingest->global_seqno_offset, seqno_val,
IOOptions(), nullptr);
if (!status.ok()) {
ROCKS_LOG_WARN(db_options_.info_log,
"Failed to write global seqno to %s: %s",
file_to_ingest->internal_file_path.c_str(),
status.ToString().c_str());
return status;
}
if (status.ok()) {
TEST_SYNC_POINT("ExternalSstFileIngestionJob::BeforeSyncGlobalSeqno");
status = SyncIngestedFile(fsptr.get());
@@ -1348,6 +1448,11 @@ Status ExternalSstFileIngestionJob::AssignGlobalSeqnoForIngestedFile(
return status;
}
} else if (!status.IsNotSupported()) {
ROCKS_LOG_WARN(
db_options_.info_log,
"Failed to open ingested file %s for random read/write: %s",
file_to_ingest->internal_file_path.c_str(),
status.ToString().c_str());
return status;
}
}
@@ -1380,6 +1485,9 @@ IOStatus ExternalSstFileIngestionJob::GenerateChecksumForIngestedFile(
db_options_.allow_mmap_reads, io_tracer_, db_options_.rate_limiter.get(),
ro, db_options_.stats, db_options_.clock);
if (!io_s.ok()) {
ROCKS_LOG_WARN(
db_options_.info_log, "Failed to generate checksum for %s: %s",
file_to_ingest->internal_file_path.c_str(), io_s.ToString().c_str());
return io_s;
}
file_to_ingest->file_checksum = std::move(file_checksum);
@@ -1419,4 +1527,91 @@ Status ExternalSstFileIngestionJob::SyncIngestedFile(TWritableFile* file) {
}
}
Status ExternalSstFileIngestionJob::GetSeqnoBoundaryForFile(
TableReader* table_reader, SuperVersion* sv,
IngestedFileInfo* file_to_ingest, bool allow_data_in_errors) {
const auto tp = table_reader->GetTableProperties();
const bool has_largest_seqno = tp->HasKeyLargestSeqno();
SequenceNumber largest_seqno = tp->key_largest_seqno;
if (has_largest_seqno) {
file_to_ingest->largest_seqno = largest_seqno;
if (largest_seqno == 0) {
file_to_ingest->smallest_seqno = 0;
return Status::OK();
}
if (tp->HasKeySmallestSeqno()) {
file_to_ingest->smallest_seqno = tp->key_smallest_seqno;
return Status::OK();
}
}
// For older SST files they may not be recorded in table properties, so
// we scan the file to find out.
TEST_SYNC_POINT(
"ExternalSstFileIngestionJob::GetSeqnoBoundaryForFile:FileScan");
SequenceNumber smallest_seqno = kMaxSequenceNumber;
SequenceNumber largest_seqno_from_iter = 0;
ReadOptions ro;
ro.fill_cache = ingestion_options_.fill_cache;
std::unique_ptr<InternalIterator> iter(table_reader->NewIterator(
ro, sv->mutable_cf_options.prefix_extractor.get(), /*arena=*/nullptr,
/*skip_filters=*/false, TableReaderCaller::kExternalSSTIngestion));
ParsedInternalKey key;
iter->SeekToFirst();
while (iter->Valid()) {
Status pik_status =
ParseInternalKey(iter->key(), &key, allow_data_in_errors);
if (!pik_status.ok()) {
return Status::Corruption("Corrupted key in external file. ",
pik_status.getState());
}
smallest_seqno = std::min(smallest_seqno, key.sequence);
largest_seqno_from_iter = std::max(largest_seqno_from_iter, key.sequence);
iter->Next();
}
if (!iter->status().ok()) {
return iter->status();
}
if (table_reader->GetTableProperties()->num_range_deletions > 0) {
std::unique_ptr<InternalIterator> range_del_iter(
table_reader->NewRangeTombstoneIterator(ro));
if (range_del_iter != nullptr) {
for (range_del_iter->SeekToFirst(); range_del_iter->Valid();
range_del_iter->Next()) {
Status pik_status =
ParseInternalKey(range_del_iter->key(), &key, allow_data_in_errors);
if (!pik_status.ok()) {
return Status::Corruption("Corrupted key in external file. ",
pik_status.getState());
}
smallest_seqno = std::min(smallest_seqno, key.sequence);
largest_seqno_from_iter =
std::max(largest_seqno_from_iter, key.sequence);
}
if (!range_del_iter->status().ok()) {
return range_del_iter->status();
}
}
}
file_to_ingest->smallest_seqno = smallest_seqno;
if (!has_largest_seqno) {
file_to_ingest->largest_seqno = largest_seqno_from_iter;
} else {
assert(largest_seqno == largest_seqno_from_iter);
file_to_ingest->largest_seqno = largest_seqno;
}
if (file_to_ingest->largest_seqno == kMaxSequenceNumber) {
return Status::InvalidArgument(
"Unknown smallest seqno for db generated file.");
}
if (file_to_ingest->smallest_seqno == kMaxSequenceNumber) {
return Status::InvalidArgument(
"Unknown largest seqno for db generated file.");
}
return Status::OK();
}
} // namespace ROCKSDB_NAMESPACE
+23 -5
View File
@@ -180,6 +180,9 @@ struct IngestedFileInfo : public KeyRangeInfo {
// the user key's format in the external file matches the column family's
// setting.
bool user_defined_timestamps_persisted = true;
SequenceNumber largest_seqno = kMaxSequenceNumber;
SequenceNumber smallest_seqno = kMaxSequenceNumber;
};
// A batch of files.
@@ -230,7 +233,7 @@ class ExternalSstFileIngestionJob {
directories_(directories),
event_logger_(event_logger),
job_start_time_(clock_->NowMicros()),
consumed_seqno_count_(0),
max_assigned_seqno_(0),
io_tracer_(io_tracer) {
assert(directories != nullptr);
assert(cfd_);
@@ -287,8 +290,16 @@ class ExternalSstFileIngestionJob {
return files_to_ingest_;
}
// How many sequence numbers did we consume as part of the ingestion job?
int ConsumedSequenceNumbersCount() const { return consumed_seqno_count_; }
// Return the maximum assigned sequence number for all files in this job.
// When allow_db_generated_files = false, we may assign global sequence
// numbers to ingested files. The global sequence numbers are sequence numbers
// following versions_->LastSequence().
// When allow_db_generated_files = true, we ingest files that already have
// sequence numbers assigned. max_assigned_seqno_ will be the max sequence
// number among ingested files.
SequenceNumber MaxAssignedSequenceNumber() const {
return max_assigned_seqno_;
}
private:
Status ResetTableReader(const std::string& external_file,
@@ -349,7 +360,7 @@ class ExternalSstFileIngestionJob {
std::optional<int> prev_batch_uppermost_level);
// File that we want to ingest behind always goes to the lowest level;
// we just check that it fits in the level, that DB allows ingest_behind,
// we just check that it fits in the level, that the CF allows ingest_behind,
// and that we don't have 0 seqnums at the upper levels.
// REQUIRES: Mutex held
Status CheckLevelForIngestedBehindFile(IngestedFileInfo* file_to_ingest);
@@ -369,6 +380,13 @@ class ExternalSstFileIngestionJob {
template <typename TWritableFile>
Status SyncIngestedFile(TWritableFile* file);
// Helper function to obtain the smallest and largest sequence number from a
// file. When OK is returned, file_to_ingest->smallest_seqno and
// file_to_ingest->largest_seqno will be updated.
Status GetSeqnoBoundaryForFile(TableReader* table_reader, SuperVersion* sv,
IngestedFileInfo* file_to_ingest,
bool allow_data_in_errors);
// Create equivalent `Compaction` objects to this file ingestion job
// , which will be used to check range conflict with other ongoing
// compactions.
@@ -395,7 +413,7 @@ class ExternalSstFileIngestionJob {
EventLogger* event_logger_;
VersionEdit edit_;
uint64_t job_start_time_;
int consumed_seqno_count_;
SequenceNumber max_assigned_seqno_;
// Set in ExternalSstFileIngestionJob::Prepare(), if true all files are
// ingested in L0
bool files_overlap_{false};
+614 -181
View File
@@ -7,6 +7,7 @@
#include <functional>
#include <memory>
#include <sstream>
#include "db/db_test_util.h"
#include "db/dbformat.h"
@@ -2417,102 +2418,130 @@ TEST_F(ExternalSSTFileTest, SnapshotInconsistencyBug) {
}
TEST_P(ExternalSSTFileTest, IngestBehind) {
Options options = CurrentOptions();
options.compaction_style = kCompactionStyleUniversal;
options.num_levels = 3;
options.disable_auto_compactions = false;
DestroyAndReopen(options);
std::vector<std::pair<std::string, std::string>> file_data;
std::map<std::string, std::string> true_data;
for (bool cf_option : {false, true}) {
SCOPED_TRACE("cf_option = " + std::to_string(cf_option));
Options options = CurrentOptions();
options.compaction_style = kCompactionStyleUniversal;
options.num_levels = 3;
options.disable_auto_compactions = false;
DestroyAndReopen(options);
std::vector<std::pair<std::string, std::string>> file_data;
std::map<std::string, std::string> true_data;
// Insert 100 -> 200 into the memtable
for (int i = 100; i <= 200; i++) {
ASSERT_OK(Put(Key(i), "memtable"));
}
// Insert 100 -> 200 using IngestExternalFile
file_data.clear();
for (int i = 0; i <= 20; i++) {
file_data.emplace_back(Key(i), "ingest_behind");
true_data[Key(i)] = "ingest_behind";
}
bool allow_global_seqno = true;
bool ingest_behind = true;
bool write_global_seqno = std::get<0>(GetParam());
bool verify_checksums_before_ingest = std::get<1>(GetParam());
// Can't ingest behind since allow_ingest_behind isn't set to true
ASSERT_NOK(GenerateAndAddExternalFile(
options, file_data, -1, allow_global_seqno, write_global_seqno,
verify_checksums_before_ingest, ingest_behind, false /*sort_data*/,
&true_data));
options.allow_ingest_behind = true;
// check that we still can open the DB, as num_levels should be
// sanitized to 3
options.num_levels = 2;
DestroyAndReopen(options);
options.num_levels = 3;
DestroyAndReopen(options);
true_data.clear();
// Insert 100 -> 200 into the memtable
for (int i = 100; i <= 200; i++) {
ASSERT_OK(Put(Key(i), "memtable"));
true_data[Key(i)] = "memtable";
}
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
// Universal picker should go at second from the bottom level
ASSERT_EQ("0,1", FilesPerLevel());
ASSERT_OK(GenerateAndAddExternalFile(
options, file_data, -1, allow_global_seqno, write_global_seqno,
verify_checksums_before_ingest, true /*ingest_behind*/,
false /*sort_data*/, &true_data));
ASSERT_EQ("0,1,1", FilesPerLevel());
// this time ingest should fail as the file doesn't fit to the bottom level
ASSERT_NOK(GenerateAndAddExternalFile(
options, file_data, -1, allow_global_seqno, write_global_seqno,
verify_checksums_before_ingest, true /*ingest_behind*/,
false /*sort_data*/, &true_data));
ASSERT_EQ("0,1,1", FilesPerLevel());
std::vector<std::vector<FileMetaData>> level_to_files;
dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(), &level_to_files);
uint64_t ingested_file_number = level_to_files[2][0].fd.GetNumber();
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
// Last level should not be compacted
ASSERT_EQ("0,1,1", FilesPerLevel());
dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(), &level_to_files);
ASSERT_EQ(ingested_file_number, level_to_files[2][0].fd.GetNumber());
size_t kcnt = 0;
VerifyDBFromMap(true_data, &kcnt, false);
// Auto-compaction should not include the last level.
// Trigger compaction if size amplification exceeds 110%.
options.compaction_options_universal.max_size_amplification_percent = 110;
options.level0_file_num_compaction_trigger = 4;
ASSERT_OK(TryReopen(options));
Random rnd(301);
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 10; j++) {
true_data[Key(j)] = rnd.RandomString(1000);
ASSERT_OK(Put(Key(j), true_data[Key(j)]));
// Insert 100 -> 200 into the memtable
for (int i = 100; i <= 200; i++) {
ASSERT_OK(Put(Key(i), "memtable"));
}
ASSERT_OK(Flush());
}
ASSERT_OK(dbfull()->TEST_WaitForCompact());
dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(), &level_to_files);
ASSERT_EQ(1, level_to_files[2].size());
ASSERT_EQ(ingested_file_number, level_to_files[2][0].fd.GetNumber());
// Turning off the option allows DB to compact ingested files.
options.allow_ingest_behind = false;
ASSERT_OK(TryReopen(options));
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(), &level_to_files);
ASSERT_EQ(1, level_to_files[2].size());
ASSERT_NE(ingested_file_number, level_to_files[2][0].fd.GetNumber());
VerifyDBFromMap(true_data, &kcnt, false);
// Insert 100 -> 200 using IngestExternalFile
file_data.clear();
for (int i = 0; i <= 20; i++) {
file_data.emplace_back(Key(i), "ingest_behind");
true_data[Key(i)] = "ingest_behind";
}
bool allow_global_seqno = true;
bool ingest_behind = true;
bool write_global_seqno = std::get<0>(GetParam());
bool verify_checksums_before_ingest = std::get<1>(GetParam());
// Can't ingest behind since allow_ingest_behind isn't set to true
ASSERT_NOK(GenerateAndAddExternalFile(
options, file_data, -1, allow_global_seqno, write_global_seqno,
verify_checksums_before_ingest, ingest_behind, false /*sort_data*/,
&true_data));
if (cf_option) {
options.cf_allow_ingest_behind = true;
} else {
options.allow_ingest_behind = true;
}
// check that we still can open the DB, as num_levels should be
// sanitized to 3
options.num_levels = 2;
DestroyAndReopen(options);
options.num_levels = 3;
DestroyAndReopen(options);
true_data.clear();
// Insert 100 -> 200 into the memtable
for (int i = 100; i <= 200; i++) {
ASSERT_OK(Put(Key(i), "memtable"));
true_data[Key(i)] = "memtable";
}
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
// Universal picker should go at second from the bottom level
ASSERT_EQ("0,1", FilesPerLevel());
ASSERT_OK(GenerateAndAddExternalFile(
options, file_data, -1, allow_global_seqno, write_global_seqno,
verify_checksums_before_ingest, true /*ingest_behind*/,
false /*sort_data*/, &true_data));
ASSERT_EQ("0,1,1", FilesPerLevel());
// this time ingest should fail as the file doesn't fit to the bottom level
ASSERT_NOK(GenerateAndAddExternalFile(
options, file_data, -1, allow_global_seqno, write_global_seqno,
verify_checksums_before_ingest, true /*ingest_behind*/,
false /*sort_data*/, &true_data));
ASSERT_EQ("0,1,1", FilesPerLevel());
std::vector<std::vector<FileMetaData>> level_to_files;
dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(),
&level_to_files);
uint64_t ingested_file_number = level_to_files[2][0].fd.GetNumber();
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
// Last level should not be compacted
ASSERT_EQ("0,1,1", FilesPerLevel());
dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(),
&level_to_files);
ASSERT_EQ(ingested_file_number, level_to_files[2][0].fd.GetNumber());
size_t kcnt = 0;
VerifyDBFromMap(true_data, &kcnt, false);
// Auto-compaction should not include the last level.
// Trigger compaction if size amplification exceeds 110%.
options.compaction_options_universal.max_size_amplification_percent = 110;
options.level0_file_num_compaction_trigger = 4;
ASSERT_OK(TryReopen(options));
Random rnd(301);
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 10; j++) {
true_data[Key(j)] = rnd.RandomString(1000);
ASSERT_OK(Put(Key(j), true_data[Key(j)]));
}
ASSERT_OK(Flush());
}
ASSERT_OK(dbfull()->TEST_WaitForCompact());
dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(),
&level_to_files);
ASSERT_EQ(1, level_to_files[2].size());
ASSERT_EQ(ingested_file_number, level_to_files[2][0].fd.GetNumber());
// Turning off the option allows DB to compact ingested files.
if (cf_option) {
// Test that another CF does not allow ingest behind
ColumnFamilyHandle* new_cfh;
Options new_cf_option;
ASSERT_OK(db_->CreateColumnFamily(new_cf_option, "new_cf", &new_cfh));
ASSERT_TRUE(GenerateAndAddExternalFile(
new_cf_option, file_data, -1, allow_global_seqno,
write_global_seqno, verify_checksums_before_ingest,
true /*ingest_behind*/, false /*sort_data*/, nullptr,
/*cfh=*/new_cfh)
.IsInvalidArgument());
ASSERT_OK(db_->DropColumnFamily(new_cfh));
ASSERT_OK(db_->DestroyColumnFamilyHandle(new_cfh));
options.cf_allow_ingest_behind = false;
} else {
options.allow_ingest_behind = false;
}
ASSERT_OK(TryReopen(options));
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(),
&level_to_files);
ASSERT_EQ(1, level_to_files[2].size());
ASSERT_NE(ingested_file_number, level_to_files[2][0].fd.GetNumber());
VerifyDBFromMap(true_data, &kcnt, false);
}
}
TEST_F(ExternalSSTFileTest, SkipBloomFilter) {
@@ -3514,19 +3543,26 @@ TEST_F(ExternalSSTFileWithTimestampTest, SanityCheck) {
// overlapping key ranges.
ASSERT_TRUE(IngestExternalUDTFile({file1, file2}).IsNotSupported());
options.allow_ingest_behind = true;
DestroyAndReopen(options);
IngestExternalFileOptions opts;
for (bool cf_option : {false, true}) {
SCOPED_TRACE("cf_option = " + std::to_string(cf_option));
if (cf_option) {
options.cf_allow_ingest_behind = true;
} else {
options.allow_ingest_behind = true;
}
DestroyAndReopen(options);
IngestExternalFileOptions opts;
// TODO(yuzhangyu): support ingestion behind for user-defined timestamps?
// Ingesting external files with user-defined timestamps requires searching
// through the whole lsm tree to make sure there is no key range overlap with
// the db. Ingestion behind currently is doing a simply placing it at the
// bottom level step without a search, so we don't allow it either.
opts.ingest_behind = true;
ASSERT_TRUE(db_->IngestExternalFile({file1}, opts).IsNotSupported());
// TODO(yuzhangyu): support ingestion behind for user-defined timestamps?
// Ingesting external files with user-defined timestamps requires searching
// through the whole lsm tree to make sure there is no key range overlap
// with the db. Ingestion behind currently is doing a simply placing it at
// the bottom level step without a search, so we don't allow it either.
opts.ingest_behind = true;
ASSERT_TRUE(db_->IngestExternalFile({file1}, opts).IsNotSupported());
DestroyAndRecreateExternalSSTFilesDir();
DestroyAndRecreateExternalSSTFilesDir();
}
}
TEST_F(ExternalSSTFileWithTimestampTest, UDTSettingsCompatibilityCheck) {
@@ -3818,99 +3854,32 @@ TEST_P(IngestDBGeneratedFileTest, FailureCase) {
ASSERT_OK(Put(1, Key(k), "cf1_" + Key(k)));
}
ASSERT_OK(Flush(/*cf=*/1));
{
// Verify that largest key of the file has non-zero seqno.
std::vector<std::vector<FileMetaData>> metadata;
dbfull()->TEST_GetFilesMetaData(handles_[1], &metadata, nullptr);
const FileMetaData& file = metadata[0][0];
ValueType vtype;
SequenceNumber seq;
UnPackSequenceAndType(ExtractInternalKeyFooter(file.largest.Encode()),
&seq, &vtype);
ASSERT_GE(seq, 0);
}
std::vector<LiveFileMetaData> live_meta;
db_->GetLiveFilesMetaData(&live_meta);
ASSERT_EQ(live_meta.size(), 1);
std::vector<std::string> to_ingest_files;
to_ingest_files.emplace_back(live_meta[0].directory + "/" +
live_meta[0].relative_filename);
// Ingesting a file whose boundary key has non-zero seqno.
Status s = db_->IngestExternalFile(to_ingest_files, ingest_opts);
// This error msg is from checking seqno of boundary keys.
ASSERT_TRUE(
s.ToString().find("External file has non zero sequence number") !=
std::string::npos);
ASSERT_NOK(s);
{
// Only non-boundary key with non-zero seqno.
const Snapshot* snapshot = db_->GetSnapshot();
ASSERT_OK(Put(1, Key(70), "cf1_" + Key(70)));
ASSERT_OK(Flush(1));
CompactRangeOptions cro;
cro.bottommost_level_compaction =
BottommostLevelCompaction::kForceOptimized;
ASSERT_OK(db_->CompactRange(cro, handles_[1], nullptr, nullptr));
// Verify that only the non-boundary key of the file has non-zero seqno.
std::vector<std::vector<FileMetaData>> metadata;
// File may be at different level for different options.
dbfull()->TEST_GetFilesMetaData(handles_[1], &metadata, nullptr);
bool found_file = false;
for (const auto& level : metadata) {
if (level.empty()) {
continue;
}
ASSERT_FALSE(found_file);
found_file = true;
ASSERT_EQ(1, level.size());
const FileMetaData& file = level[0];
ValueType vtype;
SequenceNumber seq;
UnPackSequenceAndType(ExtractInternalKeyFooter(file.largest.Encode()),
&seq, &vtype);
ASSERT_EQ(seq, 0);
UnPackSequenceAndType(ExtractInternalKeyFooter(file.smallest.Encode()),
&seq, &vtype);
ASSERT_EQ(seq, 0);
ASSERT_GT(file.fd.largest_seqno, 0);
}
ASSERT_TRUE(found_file);
live_meta.clear();
db_->GetLiveFilesMetaData(&live_meta);
ASSERT_EQ(live_meta.size(), 1);
to_ingest_files[0] =
live_meta[0].directory + "/" + live_meta[0].relative_filename;
s = db_->IngestExternalFile(to_ingest_files, ingest_opts);
ASSERT_NOK(s);
// This error msg is from checking largest seqno in table property.
ASSERT_TRUE(s.ToString().find("non zero largest sequence number") !=
std::string::npos);
db_->ReleaseSnapshot(snapshot);
}
Status s;
CompactRangeOptions cro;
cro.bottommost_level_compaction =
BottommostLevelCompaction::kForceOptimized;
ASSERT_OK(db_->CompactRange(cro, handles_[1], nullptr, nullptr));
live_meta.clear();
std::vector<LiveFileMetaData> live_meta;
std::vector<std::string> to_ingest_files;
db_->GetLiveFilesMetaData(&live_meta);
ASSERT_EQ(live_meta.size(), 1);
ASSERT_EQ(live_meta[0].column_family_name, "toto");
ASSERT_EQ(0, live_meta[0].largest_seqno);
to_ingest_files[0] =
live_meta[0].directory + "/" + live_meta[0].relative_filename;
to_ingest_files.emplace_back(live_meta[0].directory + "/" +
live_meta[0].relative_filename);
// Ingesting a DB generated file with allow_db_generated_files = false
ingest_opts.allow_db_generated_files = false;
// Ingesting a DB genrate file with allow_db_generated_files = false;
s = db_->IngestExternalFile(to_ingest_files, ingest_opts);
ASSERT_TRUE(s.ToString().find("External file version not found") !=
std::string::npos);
ASSERT_NOK(s);
const std::string err =
"An ingested file is assigned to a non-zero sequence number, which is "
"incompatible with ingestion option allow_db_generated_files";
"An ingested file overlaps with existing data in the DB and has been "
"assigned a non-zero sequence number";
ingest_opts.allow_db_generated_files = true;
s = db_->IngestExternalFile(to_ingest_files, ingest_opts);
ASSERT_TRUE(s.ToString().find(err) != std::string::npos);
@@ -4111,6 +4080,470 @@ TEST_P(IngestDBGeneratedFileTest2, NotOverlapWithDB) {
}
} while (ChangeOptions(kSkipPlainTable | kSkipFIFOCompaction));
}
TEST_P(IngestDBGeneratedFileTest2, NonZeroSeqno) {
// Test ingestion of DB-generated SST files that contain non-zero sequence
// numbers.
IngestExternalFileOptions ingest_opts;
ingest_opts.allow_db_generated_files = true;
// This only works since we are ingesting without snapshot
// Failure case will be tested below.
ingest_opts.snapshot_consistency = std::get<0>(GetParam());
ingest_opts.allow_global_seqno = std::get<1>(GetParam());
ingest_opts.allow_blocking_flush = std::get<2>(GetParam());
ingest_opts.fail_if_not_bottommost_level = std::get<3>(GetParam());
ingest_opts.link_files = std::get<4>(GetParam());
Random* rnd = Random::GetTLSInstance();
rnd->Reset(std::random_device{}());
std::ostringstream ingest_opts_trace;
ingest_opts_trace << "ingest_opts params: " << "snapshot_consistency="
<< ingest_opts.snapshot_consistency << ", "
<< "allow_global_seqno=" << ingest_opts.allow_global_seqno
<< ", " << "allow_blocking_flush="
<< ingest_opts.allow_blocking_flush << ", "
<< "fail_if_not_bottommost_level="
<< ingest_opts.fail_if_not_bottommost_level << ", "
<< "link_files=" << ingest_opts.link_files;
SCOPED_TRACE(ingest_opts_trace.str());
do {
SCOPED_TRACE("option_config_ = " + std::to_string(option_config_));
Options options = CurrentOptions();
options.statistics = CreateDBStatistics();
options.allow_concurrent_memtable_write =
false; // Required for VectorRepFactory
CreateAndReopenWithCF({"non_overlap", "overlap"}, options);
ColumnFamilyHandle* non_overlap_cf = handles_[1];
ColumnFamilyHandle* overlap_cf = handles_[2];
std::vector<std::string> expected_values;
expected_values.resize(100);
WriteOptions wo;
// Setup target CF with non-overlapping base data Key1 and Key99
// Will ingest keys [1, 98] below.
expected_values[0] = rnd->RandomString(100);
ASSERT_OK(db_->Put(wo, non_overlap_cf, Key(0), expected_values[0]));
ASSERT_OK(db_->Flush({}, non_overlap_cf));
expected_values[99] = rnd->RandomString(100);
ASSERT_OK(db_->Put(wo, non_overlap_cf, Key(99), expected_values[99]));
// Set up overlapping cf
ASSERT_OK(db_->Put(wo, overlap_cf, Key(50), rnd->RandomString(100)));
// Create temp CF/DB
Options temp_cf_opts;
ColumnFamilyHandle* temp_cfh = nullptr;
DB* from_db = nullptr;
std::string temp_db_name;
// Using a separate DB also validates that latest sequence number
// of target db is updated after ingestion (to the max sequence number
// in ingested files).
const bool use_temp_db = rnd->OneIn(2);
SCOPED_TRACE("use_temp_db: " + std::to_string(use_temp_db));
std::vector<std::string> sst_file_paths;
// optional L5: files in key range [70, 98]
// L6: files in key range [1, 79]
temp_cf_opts.target_file_size_base =
20 << 10; // Small files to create multiple SSTs
temp_cf_opts.num_levels = 7;
temp_cf_opts.disable_auto_compactions = true; // Manually set up LSM
temp_cf_opts.env = options.env;
if (use_temp_db) {
temp_cf_opts.create_if_missing = true;
temp_db_name = dbname_ + "/temp_db_" + std::to_string(rnd->Next());
ASSERT_OK(DB::Open(temp_cf_opts, temp_db_name, &from_db));
temp_cfh = from_db->DefaultColumnFamily();
} else {
from_db = db_;
ASSERT_OK(
from_db->CreateColumnFamily(temp_cf_opts, "temp_cf", &temp_cfh));
}
// Use snapshot to ensure non-zero sequence numbers after compaction
const Snapshot* snapshot = from_db->GetSnapshot();
for (int k = 1; k < 99; ++k) {
expected_values[k] = rnd->RandomString(2000);
ASSERT_OK(from_db->Put(wo, temp_cfh, Key(k), expected_values[k]));
}
ASSERT_OK(from_db->Flush({}, temp_cfh));
CompactRangeOptions cro;
cro.bottommost_level_compaction =
BottommostLevelCompaction::kForceOptimized;
ASSERT_OK(from_db->CompactRange(cro, temp_cfh, nullptr, nullptr));
ASSERT_GT(NumTableFilesAtLevel(6, temp_cfh, from_db), 1);
const bool multi_level_ingestion = rnd->OneIn(2);
SCOPED_TRACE("Multi-level ingestion: " +
std::to_string(multi_level_ingestion));
if (multi_level_ingestion) {
for (int k = 80; k < 99; ++k) {
expected_values[k] = rnd->RandomString(500);
ASSERT_OK(from_db->Put(wo, temp_cfh, Key(k), expected_values[k]));
}
ASSERT_OK(from_db->Flush({}, temp_cfh));
// Do some overwrites, and overlap with previous L0 to avoid trivial move
for (int k = 70; k < 82; ++k) {
expected_values[k] = rnd->RandomString(500);
ASSERT_OK(from_db->Put(wo, temp_cfh, Key(k), expected_values[k]));
}
ASSERT_OK(from_db->Flush({}, temp_cfh));
if (rnd->OneIn(2)) {
MoveFilesToLevel(5, temp_cfh, from_db);
ASSERT_GT(NumTableFilesAtLevel(5, temp_cfh, from_db), 0);
}
ASSERT_GT(NumTableFilesAtLevel(6, temp_cfh, from_db), 0);
}
SCOPED_TRACE("LSM of from_db " + FilesPerLevel(temp_cfh, from_db));
ColumnFamilyMetaData cf_meta;
from_db->GetColumnFamilyMetaData(temp_cfh, &cf_meta);
// Iterate in reverse since IngestExternalFiles expect files to be ordered
// from old to new
for (auto level_meta = cf_meta.levels.rbegin();
level_meta != cf_meta.levels.rend(); ++level_meta) {
// L0 files need to be added in reverse order.
for (auto file_meta = level_meta->files.rbegin();
file_meta != level_meta->files.rend(); ++file_meta) {
// Validate that files contain non-zero sequence numbers
ASSERT_GT(file_meta->smallest_seqno, 0);
ASSERT_GE(file_meta->largest_seqno, file_meta->smallest_seqno);
sst_file_paths.emplace_back(file_meta->directory + "/" +
file_meta->relative_filename);
}
}
from_db->ReleaseSnapshot(snapshot);
Status s;
// Perform ingestion and validate results
if (multi_level_ingestion && options.num_levels > 1) {
// fail_if_bottommost requres ingesting all files into the last level,
// so it fails if we are assiging files to multiple levels.
ingest_opts.fail_if_not_bottommost_level = true;
s = db_->IngestExternalFile(non_overlap_cf, sst_file_paths, ingest_opts);
ASSERT_NOK(s);
ASSERT_TRUE(s.ToString().find("Files cannot be ingested to Lmax") !=
std::string::npos);
ingest_opts.fail_if_not_bottommost_level = false;
}
if (ingest_opts.snapshot_consistency) {
// snapshot_consisteny requires global sequence number assignment to
// ingested files if there is any live snapshot.
snapshot = db_->GetSnapshot();
s = db_->IngestExternalFile(non_overlap_cf, sst_file_paths, ingest_opts);
ASSERT_NOK(s);
ASSERT_TRUE(s.ToString().find(
"An ingested file overlaps with existing data in the DB and has been "
"assigned a non-zero sequence number"));
db_->ReleaseSnapshot(snapshot);
}
std::atomic<int> file_scan_count{0};
SyncPoint::GetInstance()->SetCallBack(
"ExternalSstFileIngestionJob::GetSeqnoBoundaryForFile:FileScan",
[&](void* /*arg*/) { file_scan_count++; });
SyncPoint::GetInstance()->EnableProcessing();
ASSERT_OK(
db_->IngestExternalFile(non_overlap_cf, sst_file_paths, ingest_opts));
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
EXPECT_EQ(file_scan_count, 0);
// Validate ingested data.
ReadOptions ro;
std::string val;
for (int k = 0; k < 100; ++k) {
s = db_->Get(ro, handles_[1], Key(k), &val);
ASSERT_OK(s) << "Should find ingested key " << Key(k);
ASSERT_EQ(val, expected_values[k]) << "key: " << Key(k);
}
// Overlap with data in the CF
if (ingest_opts.allow_blocking_flush) {
s = db_->IngestExternalFile(overlap_cf, sst_file_paths, ingest_opts);
ASSERT_NOK(s);
if (ingest_opts.fail_if_not_bottommost_level) {
ASSERT_TRUE(s.ToString().find("Files cannot be ingested to Lmax") !=
std::string::npos)
<< s.ToString();
} else {
ASSERT_TRUE(s.ToString().find("An ingested file overlaps with existing "
"data in the DB and has been "
"assigned a non-zero sequence number") !=
std::string::npos)
<< s.ToString();
}
}
// Cleanup
// FIXME: Without this, the test triggers some data race between dropping
// CF and background compaction.
ASSERT_OK(db_->WaitForCompact({}));
if (use_temp_db) {
ASSERT_OK(from_db->Close());
delete from_db;
ASSERT_OK(DestroyDB(temp_db_name, temp_cf_opts));
} else {
ASSERT_OK(db_->DropColumnFamily(temp_cfh));
ASSERT_OK(db_->DestroyColumnFamilyHandle(temp_cfh));
}
} while (ChangeOptions(kSkipPlainTable | kSkipFIFOCompaction));
}
std::string GenSecondaryKey(const std::string& pk, const std::string& val) {
return "index_" + val + "_" + pk;
};
TEST_P(IngestDBGeneratedFileTest2, ZeroAndNonZeroSeqno) {
// Test ingestion of SST files with zero and with non-zero sequence numbers.
// Generate data using a temp CF and a temp DB:
// 1. Temp CF with cf_allow_ingest_behind enabled to preserve non-zero seqno.
// 2. Temp DB with everything compacted to have zero seqno.
// Then ingest both types of files together into a target CF.
// This mimics a user case where temp DB contains data read from a
// snapshot while temp CF contains live writes after a snapshot is taken.
IngestExternalFileOptions ingest_opts;
ingest_opts.allow_db_generated_files = true;
ingest_opts.snapshot_consistency = std::get<0>(GetParam());
ingest_opts.allow_global_seqno = std::get<1>(GetParam());
ingest_opts.allow_blocking_flush = std::get<2>(GetParam());
ingest_opts.fail_if_not_bottommost_level = std::get<3>(GetParam());
ingest_opts.link_files = std::get<4>(GetParam());
Random* rnd = Random::GetTLSInstance();
do {
SCOPED_TRACE("option_config_ = " + std::to_string(option_config_));
Options options = CurrentOptions();
options.allow_concurrent_memtable_write = false;
// Force more flushes/compactions and more files to be generated
options.target_file_size_base = 1 << 10; // 1KB
options.max_bytes_for_level_base = 2 << 10; // 2KB
options.max_bytes_for_level_multiplier = 2;
options.level0_file_num_compaction_trigger = 2;
options.level_compaction_dynamic_level_bytes = true;
DestroyAndReopen(options);
CreateAndReopenWithCF({"target_cf"}, options);
auto* target_cfh = handles_[1];
Options live_write_cf_opts = options;
live_write_cf_opts.memtable_factory.reset(new VectorRepFactory());
live_write_cf_opts.compaction_style = kCompactionStyleUniversal;
live_write_cf_opts.cf_allow_ingest_behind = true;
live_write_cf_opts.num_levels = 50;
ColumnFamilyHandle* live_write_cfh;
ASSERT_OK(db_->CreateColumnFamily(live_write_cf_opts, "live_write_cf",
&live_write_cfh));
// Expected value and key
std::map<std::string, std::string> expected;
std::unordered_set<std::string> deleted;
std::stringstream debug_info;
// Setup base data in target CF, will ingest keys with different prefixes
// so they don't overlap with the base data.
WriteOptions wo;
for (int k = 0; k < 100; ++k) {
int random_val = rnd->Uniform(20);
expected[Key(k)] = std::to_string(random_val);
ASSERT_OK(db_->Put(wo, target_cfh, Key(k), expected[Key(k)]));
// Force flush every 20 keys to create multiple SST files
if (rnd->OneIn(20)) {
ASSERT_OK(db_->Flush({}, target_cfh));
debug_info << "Flush after " << k
<< ", LSM state: " << FilesPerLevel(target_cfh) << "\n";
}
}
// Temp DB for snapshot data
Options temp_db_opts;
temp_db_opts.create_if_missing = true;
temp_db_opts.target_file_size_base = 1 << 10;
temp_db_opts.write_buffer_size = 1 << 10;
temp_db_opts.memtable_factory.reset(new VectorRepFactory());
temp_db_opts.allow_concurrent_memtable_write = false;
temp_db_opts.compaction_style = kCompactionStyleUniversal;
temp_db_opts.env = env_;
temp_db_opts.num_levels = 7;
std::string temp_db_name =
dbname_ + "/temp_db_" + std::to_string(rnd->Next());
DB* temp_db = nullptr;
ASSERT_OK(DB::Open(temp_db_opts, temp_db_name, &temp_db));
const Snapshot* snapshot = db_->GetSnapshot();
ReadOptions ro;
ro.snapshot = snapshot;
ro.total_order_seek = true;
std::unique_ptr<Iterator> iter{db_->NewIterator(ro, target_cfh)};
// transform data read from snapshot and write to temp DB
// Varying the number of files in temp DB.
const int kValSize = rnd->Uniform(200);
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
std::string key = iter->key().ToString();
std::string value = iter->value().ToString();
std::string sk = GenSecondaryKey(key, value);
// Usually value is empty, here we use a larger value to generate
// multiple SST files in temp_db.
std::string sk_val = rnd->RandomString(kValSize);
ASSERT_OK(temp_db->Put(wo, sk, sk_val));
expected[sk] = sk_val;
debug_info << "Snapshot data: " << sk << " -> \n";
}
ASSERT_OK(iter->status());
// Do some live writes into target CF and live write CF.
for (int i = 0; i < 10; ++i) {
WriteBatch wb;
for (int j = 0; j < 5; ++j) {
std::string key = Key(rnd->Uniform(100));
std::string old_val = expected[key];
// Value range is 0-19, allow some PK to have the same value.
int random_val = rnd->Uniform(20);
std::string new_val = std::to_string(random_val);
std::string old_index_key = GenSecondaryKey(key, old_val);
std::string new_index_key = GenSecondaryKey(key, new_val);
ASSERT_OK(wb.SingleDelete(live_write_cfh, old_index_key));
std::string sk_val = rnd->RandomString(kValSize);
ASSERT_OK(wb.Put(live_write_cfh, new_index_key, sk_val));
ASSERT_OK(wb.Put(target_cfh, key, new_val));
expected[key] = new_val;
expected.erase(old_index_key);
expected[new_index_key] = sk_val;
deleted.insert(old_index_key);
deleted.erase(new_index_key);
debug_info << "Live write: SD " << old_index_key << "\n";
debug_info << "Live write: " << key << " -> " << new_val << "\n";
debug_info << "Live write: " << new_index_key << " -> \n";
}
ASSERT_OK(db_->Write(wo, &wb));
if (rnd->OneIn(3)) {
debug_info << "Flush after " << i << " live writes\n";
ASSERT_OK(db_->Flush({}, live_write_cfh));
}
}
iter.reset();
db_->ReleaseSnapshot(snapshot);
// Compact temp_db to ensure zero sequence numbers
CompactRangeOptions cro;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
ASSERT_OK(temp_db->CompactRange(cro, nullptr, nullptr));
SCOPED_TRACE("Temp DB LSM: " +
FilesPerLevel(temp_db->DefaultColumnFamily(), temp_db));
// Base data from snapshot
std::vector<std::string> sst_file_paths_zero_seqno;
// Collect SST file paths with zero sequence numbers
ASSERT_OK(temp_db->DisableFileDeletions());
ColumnFamilyMetaData cf_meta_temp_db;
temp_db->GetColumnFamilyMetaData(&cf_meta_temp_db);
for (const auto& level_meta : cf_meta_temp_db.levels) {
if (level_meta.level == 6) {
for (const auto& file_meta : level_meta.files) {
// Verify files have zero sequence numbers
ASSERT_EQ(0, file_meta.largest_seqno)
<< "File " << file_meta.relative_filename
<< " should have zero sequence number\n"
<< debug_info.str();
sst_file_paths_zero_seqno.emplace_back(file_meta.directory + "/" +
file_meta.relative_filename);
}
} else {
// All files should be in L6
ASSERT_EQ(0, level_meta.files.size()) << debug_info.str();
}
}
// Flush remaining catch up writes in memtable
ASSERT_OK(db_->Flush({}, live_write_cfh));
SCOPED_TRACE("LSM of live write cfh " + FilesPerLevel(live_write_cfh));
// Collect SST file paths with non-zero sequence numbers
ColumnFamilyMetaData live_write_cf_meta;
ASSERT_OK(db_->DisableFileDeletions());
db_->GetColumnFamilyMetaData(live_write_cfh, &live_write_cf_meta);
// Live writes after snapshot
std::vector<std::string> sst_file_paths_nonzero_seqno;
for (auto level_meta = live_write_cf_meta.levels.rbegin();
level_meta != live_write_cf_meta.levels.rend(); ++level_meta) {
// Reverse order is important for L0, where recent updates are ordered
// first
for (auto file_meta = level_meta->files.rbegin();
file_meta != level_meta->files.rend(); ++file_meta) {
sst_file_paths_nonzero_seqno.emplace_back(file_meta->directory + "/" +
file_meta->relative_filename);
ASSERT_GT(file_meta->smallest_seqno, 0) << debug_info.str();
}
if (level_meta->level == 49) {
// Ingest behind does not compact to the last level
ASSERT_EQ(level_meta->files.size(), 0) << debug_info.str();
}
}
ASSERT_GT(sst_file_paths_zero_seqno.size(), 0) << debug_info.str();
ASSERT_GT(sst_file_paths_nonzero_seqno.size(), 0) << debug_info.str();
// Combine all SST file paths.
// File ingestion takes files from old to new.
std::vector<std::string> all_sst_files;
all_sst_files.insert(all_sst_files.end(), sst_file_paths_zero_seqno.begin(),
sst_file_paths_zero_seqno.end());
all_sst_files.insert(all_sst_files.end(),
sst_file_paths_nonzero_seqno.begin(),
sst_file_paths_nonzero_seqno.end());
if (ingest_opts.fail_if_not_bottommost_level && options.num_levels > 1) {
// overlapping files will be ingested into different levels, including non
// Lmax
Status s =
db_->IngestExternalFile(target_cfh, all_sst_files, ingest_opts);
ASSERT_NOK(s);
ASSERT_TRUE(s.ToString().find("Files cannot be ingested to Lmax") !=
std::string::npos);
} else {
ASSERT_OK(
db_->IngestExternalFile(target_cfh, all_sst_files, ingest_opts));
debug_info << "Zero seqno files: " << sst_file_paths_zero_seqno.size()
<< "\nNon-zero seqno files: "
<< sst_file_paths_nonzero_seqno.size() << "\n";
SCOPED_TRACE("Debug info:\n" + debug_info.str());
VerifyDBFromMap(expected, nullptr, false, nullptr, target_cfh, &deleted);
}
// clean up
ASSERT_OK(db_->EnableFileDeletions());
ASSERT_OK(temp_db->EnableFileDeletions());
// FIXME: Without this, the test triggers some data race between dropping
// CF and background compaction.
ASSERT_OK(db_->WaitForCompact({}));
ASSERT_OK(db_->DropColumnFamily(live_write_cfh));
ASSERT_OK(db_->DestroyColumnFamilyHandle(live_write_cfh));
ASSERT_OK(temp_db->Close());
delete temp_db;
ASSERT_OK(DestroyDB(temp_db_name, temp_db_opts));
} while (ChangeOptions(kSkipPlainTable | kSkipFIFOCompaction));
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+3 -4
View File
@@ -502,8 +502,7 @@ Status FlushJob::MemPurge() {
kMaxSequenceNumber, &job_context_->snapshot_seqs, earliest_snapshot_,
job_context_->earliest_write_conflict_snapshot,
job_context_->GetJobSnapshotSequence(), job_context_->snapshot_checker,
env, ShouldReportDetailedTime(env, ioptions.stats),
true /* internal key corruption is not ok */, range_del_agg.get(),
env, ShouldReportDetailedTime(env, ioptions.stats), range_del_agg.get(),
nullptr, ioptions.allow_data_in_errors,
ioptions.enforce_single_del_contracts,
/*manual_compaction_canceled=*/kManualCompactionCanceledFalse,
@@ -1105,13 +1104,13 @@ Status FlushJob::WriteLevel0Table() {
const uint64_t micros = clock_->NowMicros() - start_micros;
const uint64_t cpu_micros = clock_->CPUMicros() - start_cpu_micros;
flush_stats.micros = micros;
flush_stats.cpu_micros = cpu_micros;
flush_stats.cpu_micros += cpu_micros;
ROCKS_LOG_INFO(db_options_.info_log,
"[%s] [JOB %d] Flush lasted %" PRIu64
" microseconds, and %" PRIu64 " cpu microseconds.\n",
cfd_->GetName().c_str(), job_context_->job_id, micros,
cpu_micros);
flush_stats.cpu_micros);
if (has_output) {
flush_stats.bytes_written = meta_.fd.GetFileSize();
+7 -7
View File
@@ -142,13 +142,13 @@ 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,
test::kUnitTestDbId, /*db_session_id=*/"",
/*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr, /*read_only=*/false));
versions_.reset(new VersionSet(
dbname_, &db_options_, MutableDBOptions{options_}, env_options_,
table_cache_.get(), &write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
test::kUnitTestDbId, /*db_session_id=*/"",
/*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr, /*read_only=*/false));
EXPECT_OK(versions_->Recover(column_families, false));
}
+7 -17
View File
@@ -163,9 +163,7 @@ TEST_F(EventListenerTest, OnSingleDBCompactionTest) {
options.max_bytes_for_level_base = options.target_file_size_base * 2;
options.max_bytes_for_level_multiplier = 2;
options.compression = kNoCompression;
#ifdef ROCKSDB_USING_THREAD_STATUS
options.enable_thread_tracking = true;
#endif // ROCKSDB_USING_THREAD_STATUS
options.enable_thread_tracking = ThreadStatus::kEnabled;
options.level0_file_num_compaction_trigger = kNumL0Files;
options.table_properties_collector_factories.push_back(
std::make_shared<TestPropertiesCollectorFactory>());
@@ -229,7 +227,7 @@ class TestFlushListener : public EventListener {
ASSERT_EQ(info.file_checksum, kUnknownFileChecksum);
ASSERT_EQ(info.file_checksum_func_name, kUnknownFileChecksumFuncName);
#ifdef ROCKSDB_USING_THREAD_STATUS
#ifndef NROCKSDB_THREAD_STATUS
// Verify the id of the current thread that created this table
// file matches the id of any active flush or compaction thread.
uint64_t thread_id = env_->GetThreadID();
@@ -246,7 +244,7 @@ class TestFlushListener : public EventListener {
}
}
ASSERT_TRUE(found_match);
#endif // ROCKSDB_USING_THREAD_STATUS
#endif // !NROCKSDB_THREAD_STATUS
}
void OnFlushCompleted(DB* db, const FlushJobInfo& info) override {
@@ -310,9 +308,7 @@ TEST_F(EventListenerTest, OnSingleDBFlushTest) {
Options options;
options.env = CurrentOptions().env;
options.write_buffer_size = k110KB;
#ifdef ROCKSDB_USING_THREAD_STATUS
options.enable_thread_tracking = true;
#endif // ROCKSDB_USING_THREAD_STATUS
options.enable_thread_tracking = ThreadStatus::kEnabled;
TestFlushListener* listener = new TestFlushListener(options.env, this);
options.listeners.emplace_back(listener);
std::vector<std::string> cf_names = {"pikachu", "ilya", "muromec",
@@ -357,9 +353,7 @@ TEST_F(EventListenerTest, MultiCF) {
Options options;
options.env = CurrentOptions().env;
options.write_buffer_size = k110KB;
#ifdef ROCKSDB_USING_THREAD_STATUS
options.enable_thread_tracking = true;
#endif // ROCKSDB_USING_THREAD_STATUS
options.enable_thread_tracking = ThreadStatus::kEnabled;
options.atomic_flush = atomic_flush;
options.create_if_missing = true;
DestroyAndReopen(options);
@@ -407,9 +401,7 @@ TEST_F(EventListenerTest, MultiCF) {
TEST_F(EventListenerTest, MultiDBMultiListeners) {
Options options;
options.env = CurrentOptions().env;
#ifdef ROCKSDB_USING_THREAD_STATUS
options.enable_thread_tracking = true;
#endif // ROCKSDB_USING_THREAD_STATUS
options.enable_thread_tracking = ThreadStatus::kEnabled;
options.table_properties_collector_factories.push_back(
std::make_shared<TestPropertiesCollectorFactory>());
std::vector<TestFlushListener*> listeners;
@@ -497,9 +489,7 @@ TEST_F(EventListenerTest, MultiDBMultiListeners) {
TEST_F(EventListenerTest, DisableBGCompaction) {
Options options;
options.env = CurrentOptions().env;
#ifdef ROCKSDB_USING_THREAD_STATUS
options.enable_thread_tracking = true;
#endif // ROCKSDB_USING_THREAD_STATUS
options.enable_thread_tracking = ThreadStatus::kEnabled;
TestFlushListener* listener = new TestFlushListener(options.env, this);
const int kCompactionTrigger = 1;
const int kSlowdownTrigger = 5;
+5 -2
View File
@@ -380,8 +380,11 @@ void Reader::MaybeVerifyPredecessorWALInfo(
} else {
if (observed_predecessor_wal_info_.GetLogNumber() !=
recorded_predecessor_log_number) {
std::string reason = "Missing WAL of log number " +
std::to_string(recorded_predecessor_log_number);
std::string reason =
"Mismatched predecessor log number of WAL file " +
file_->file_name() + " Recorded " +
std::to_string(recorded_predecessor_log_number) + ". Observed " +
std::to_string(observed_predecessor_wal_info_.GetLogNumber());
ReportCorruption(fragment.size(), reason.c_str(),
recorded_predecessor_log_number);
} else if (observed_predecessor_wal_info_.GetLastSeqnoRecorded() !=

Some files were not shown because too many files have changed in this diff Show More