Compare commits

..
46 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
116 changed files with 4415 additions and 1536 deletions

No files matched your search

+14 -7
View File
@@ -7,20 +7,27 @@ outputs:
runs:
using: composite
steps:
- name: Extract FOLLY_COMMIT_HASH from Makefile
- name: Extract FOLLY_MK_HASH
id: extract-folly-hash
shell: bash
run: |
FOLLY_COMMIT_HASH=$(grep '^FOLLY_COMMIT_HASH' Makefile | awk '{print $3}')
echo "hash=$FOLLY_COMMIT_HASH" >> $GITHUB_OUTPUT
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: /tmp/fbcode_builder_getdeps-Z__wZrocksdbZrocksdbZthird-partyZfollyZbuildZfbcode_builder-root/installed
path: ${{ steps.extract-folly-install-dir.outputs.dir }}
# Key is based on:
# - OS and architecture
# - The specific folly commit hash from Makefile
# - The container image version to account for different compiler/library versions
key: folly-build-${{ runner.os }}-${{ runner.arch }}-${{ steps.extract-folly-hash.outputs.hash }}-ubuntu22.1-v1
# - 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 }}
+3 -1
View File
@@ -5,5 +5,7 @@ runs:
- name: Checkout folly sources
run: |
make checkout_folly
echo "GETDEPS_USE_WGET=1" >> "$GITHUB_ENV"
shell: bash
- name: Install patchelf
run: apt-get update -y && apt-get install -y patchelf
shell: bash
+25 -4
View File
@@ -58,7 +58,7 @@ 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
@@ -68,7 +68,28 @@ jobs:
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' }}
@@ -117,7 +138,7 @@ jobs:
- name: Build fuzzers
run: cd fuzz && make sst_file_writer_fuzzer db_fuzzer db_map_fuzzer
- uses: "./.github/actions/post-steps"
build-linux-cmake-with-folly-lite-no-test:
build-linux-cmake-with-folly-lite:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
@@ -128,5 +149,5 @@ jobs:
- 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 -DCMAKE_CXX_FLAGS=-DGLOG_USE_GLOG_EXPORT .. && 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"
+4 -22
View File
@@ -92,24 +92,6 @@ 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: 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"
- 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 V=1 -j20 && ctest -j20)"
- uses: "./.github/actions/post-steps"
build-linux-make-with-folly:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
@@ -157,9 +139,9 @@ jobs:
- uses: "./.github/actions/build-folly"
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 V=1 -j20 && ctest -j20)"
- 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
@@ -169,7 +151,7 @@ jobs:
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' }}
@@ -372,7 +354,7 @@ jobs:
- 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 -j8
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 }}
+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"
+4 -8
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",
@@ -5200,12 +5202,6 @@ cpp_unittest_wrapper(name="import_column_family_test",
extra_compiler_flags=[])
cpp_unittest_wrapper(name="index_builder_test",
srcs=["table/block_based/index_builder_test.cc"],
deps=[":rocksdb_test_lib"],
extra_compiler_flags=[])
cpp_unittest_wrapper(name="inlineskiplist_test",
srcs=["memtable/inlineskiplist_test.cc"],
deps=[":rocksdb_test_lib"],
+22 -2
View File
@@ -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)
@@ -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)
@@ -642,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)
@@ -1082,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()
@@ -1453,7 +1474,6 @@ if(WITH_TESTS)
table/block_based/block_based_table_reader_test.cc
table/block_based/block_test.cc
table/block_based/data_block_hash_index_test.cc
table/block_based/index_builder_test.cc
table/block_based/full_filter_block_test.cc
table/block_based/partitioned_filter_block_test.cc
table/cleanable_test.cc
+19 -5
View File
@@ -1,18 +1,32 @@
# Rocksdb Change Log
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
## 10.8.3 (11/10/2025)
### New Features
* Add a GetColumnFamilyMetaData API variant in DB to get the SST files intersecting a given key range.
## 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.8.2 (11/07/2025)
## 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.
## 10.8.1 (10/29/2025)
### 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
+2 -116
View File
@@ -446,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)
@@ -683,6 +607,7 @@ am__v_CCH_1 =
%.h.pub: %.h # .h.pub not actually created, so re-checked on each invocation
$(AM_V_CCH) cd include/ && echo '#include "$(patsubst include/%,%,$<)"' | \
$(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)
@@ -1742,9 +1667,6 @@ block_test: $(OBJ_DIR)/table/block_based/block_test.o $(TEST_LIBRARY) $(LIBRARY)
data_block_hash_index_test: $(OBJ_DIR)/table/block_based/data_block_hash_index_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
index_builder_test: $(OBJ_DIR)/table/block_based/index_builder_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
inlineskiplist_test: $(OBJ_DIR)/memtable/inlineskiplist_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
@@ -2498,42 +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;
FOLLY_COMMIT_HASH = e95383b7c8b5b1e46cf47acf2f317d54f93c8268
# 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 $(FOLLY_COMMIT_HASH)
@# 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
# ---------------------------------------------------------------------------
+3
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",
+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}")
"""
+6 -2
View File
@@ -311,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>
@@ -320,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>
@@ -772,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
@@ -816,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!"
+1 -1
View File
@@ -119,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");
+366 -130
View File
@@ -635,10 +635,11 @@ static bool SaveError(char** errptr, const Status& s) {
return true;
}
// Copies str to a new malloc()-ed buffer. The buffer is not NUL terminated.
static char* CopyString(const std::string& str) {
char* result = reinterpret_cast<char*>(malloc(sizeof(char) * str.size()));
memcpy(result, str.data(), sizeof(char) * str.size());
// Helper function to copy string data to a malloc'd buffer
// Works with std::string, Slice, and PinnableSlice through implicit conversion
static inline char* CopyString(const Slice& slice) {
char* result = reinterpret_cast<char*>(malloc(slice.size()));
memcpy(result, slice.data(), slice.size());
return result;
}
@@ -1440,11 +1441,14 @@ char* rocksdb_get(rocksdb_t* db, const rocksdb_readoptions_t* options,
const char* key, size_t keylen, size_t* vallen,
char** errptr) {
char* result = nullptr;
std::string tmp;
Status s = db->rep->Get(options->rep, Slice(key, keylen), &tmp);
// Use PinnableSlice to avoid unnecessary copy
PinnableSlice pinnable_val;
Status s = db->rep->Get(options->rep, db->rep->DefaultColumnFamily(),
Slice(key, keylen), &pinnable_val);
if (s.ok()) {
*vallen = tmp.size();
result = CopyString(tmp);
*vallen = pinnable_val.size();
// Only one copy: from PinnableSlice to malloc'd buffer
result = CopyString(pinnable_val);
} else {
*vallen = 0;
if (!s.IsNotFound()) {
@@ -1459,12 +1463,14 @@ char* rocksdb_get_cf(rocksdb_t* db, const rocksdb_readoptions_t* options,
const char* key, size_t keylen, size_t* vallen,
char** errptr) {
char* result = nullptr;
std::string tmp;
Status s =
db->rep->Get(options->rep, column_family->rep, Slice(key, keylen), &tmp);
// Use PinnableSlice to avoid unnecessary copy
PinnableSlice pinnable_val;
Status s = db->rep->Get(options->rep, column_family->rep, Slice(key, keylen),
&pinnable_val);
if (s.ok()) {
*vallen = tmp.size();
result = CopyString(tmp);
*vallen = pinnable_val.size();
// Only one copy: from PinnableSlice to malloc'd buffer
result = CopyString(pinnable_val);
} else {
*vallen = 0;
if (!s.IsNotFound()) {
@@ -1537,12 +1543,17 @@ void rocksdb_multi_get(rocksdb_t* db, const rocksdb_readoptions_t* options,
size_t num_keys, const char* const* keys_list,
const size_t* keys_list_sizes, char** values_list,
size_t* values_list_sizes, char** errs) {
std::vector<Slice> keys(num_keys);
// Use unique_ptr for efficiency (avoids vector overhead for fixed-size array)
std::unique_ptr<Slice[]> keys(new Slice[num_keys]);
for (size_t i = 0; i < num_keys; i++) {
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
std::vector<std::string> values(num_keys);
std::vector<Status> statuses = db->rep->MultiGet(options->rep, keys, &values);
// Use PinnableSlice to avoid unnecessary allocations
auto cfh = db->rep->DefaultColumnFamily();
std::vector<PinnableSlice> values(num_keys);
std::vector<Status> statuses(num_keys);
db->rep->MultiGet(options->rep, cfh, num_keys, keys.get(), values.data(),
statuses.data());
for (size_t i = 0; i < num_keys; i++) {
if (statuses[i].ok()) {
values_list[i] = CopyString(values[i]);
@@ -1567,10 +1578,13 @@ void rocksdb_multi_get_with_ts(rocksdb_t* db,
char** values_list, size_t* values_list_sizes,
char** timestamp_list,
size_t* timestamp_list_sizes, char** errs) {
std::vector<Slice> keys(num_keys);
// Use unique_ptr for efficiency
std::unique_ptr<Slice[]> keys_arr(new Slice[num_keys]);
for (size_t i = 0; i < num_keys; i++) {
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
keys_arr[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
// Note: MultiGet with timestamps only has vector-based API
std::vector<Slice> keys(keys_arr.get(), keys_arr.get() + num_keys);
std::vector<std::string> values(num_keys);
std::vector<std::string> timestamps(num_keys);
std::vector<Status> statuses =
@@ -1602,15 +1616,19 @@ void rocksdb_multi_get_cf(
size_t num_keys, const char* const* keys_list,
const size_t* keys_list_sizes, char** values_list,
size_t* values_list_sizes, char** errs) {
std::vector<Slice> keys(num_keys);
std::vector<ColumnFamilyHandle*> cfs(num_keys);
// Use unique_ptr for efficiency (avoids vector overhead for fixed-size
// arrays)
std::unique_ptr<Slice[]> keys(new Slice[num_keys]);
std::unique_ptr<ColumnFamilyHandle*[]> cfs(new ColumnFamilyHandle*[num_keys]);
for (size_t i = 0; i < num_keys; i++) {
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
cfs[i] = column_families[i]->rep;
}
std::vector<std::string> values(num_keys);
std::vector<Status> statuses =
db->rep->MultiGet(options->rep, cfs, keys, &values);
// Use PinnableSlice to avoid unnecessary allocations
std::vector<PinnableSlice> values(num_keys);
std::vector<Status> statuses(num_keys);
db->rep->MultiGet(options->rep, num_keys, cfs.get(), keys.get(),
values.data(), statuses.data());
for (size_t i = 0; i < num_keys; i++) {
if (statuses[i].ok()) {
values_list[i] = CopyString(values[i]);
@@ -1635,16 +1653,20 @@ void rocksdb_multi_get_cf_with_ts(
const size_t* keys_list_sizes, char** values_list,
size_t* values_list_sizes, char** timestamps_list,
size_t* timestamps_list_sizes, char** errs) {
std::vector<Slice> keys(num_keys);
std::vector<ColumnFamilyHandle*> cfs(num_keys);
// Use unique_ptr for efficiency (avoids vector overhead for fixed-size
// arrays)
std::unique_ptr<Slice[]> keys(new Slice[num_keys]);
std::unique_ptr<ColumnFamilyHandle*[]> cfs(new ColumnFamilyHandle*[num_keys]);
for (size_t i = 0; i < num_keys; i++) {
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
cfs[i] = column_families[i]->rep;
}
std::vector<std::string> values(num_keys);
// Use PinnableSlice to avoid unnecessary allocations
std::vector<PinnableSlice> values(num_keys);
std::vector<std::string> timestamps(num_keys);
std::vector<Status> statuses =
db->rep->MultiGet(options->rep, cfs, keys, &values, &timestamps);
std::vector<Status> statuses(num_keys);
db->rep->MultiGet(options->rep, num_keys, cfs.get(), keys.get(),
values.data(), timestamps.data(), statuses.data());
for (size_t i = 0; i < num_keys; i++) {
if (statuses[i].ok()) {
values_list[i] = CopyString(values[i]);
@@ -1703,6 +1725,41 @@ void rocksdb_batched_multi_get_cf(rocksdb_t* db,
delete[] statuses;
}
// Batched MultiGet that takes pre-built Slice array, avoiding key conversion
// overhead
void rocksdb_batched_multi_get_cf_slice(
rocksdb_t* db, const rocksdb_readoptions_t* options,
rocksdb_column_family_handle_t* column_family, size_t num_keys,
const rocksdb_slice_t* keys_list, rocksdb_pinnableslice_t** values,
char** errs, const bool sorted_input) {
PinnableSlice* value_slices = new PinnableSlice[num_keys];
Status* statuses = new Status[num_keys];
// Cast rocksdb_slice_t* to Slice* - they have identical memory layout
const Slice* key_slices = reinterpret_cast<const Slice*>(keys_list);
db->rep->MultiGet(options->rep, column_family->rep, num_keys, key_slices,
value_slices, statuses, sorted_input);
for (size_t i = 0; i < num_keys; ++i) {
if (statuses[i].ok()) {
values[i] = new (rocksdb_pinnableslice_t);
values[i]->rep = std::move(value_slices[i]);
errs[i] = nullptr;
} else {
values[i] = nullptr;
if (!statuses[i].IsNotFound()) {
errs[i] = strdup(statuses[i].ToString().c_str());
} else {
errs[i] = nullptr;
}
}
}
delete[] value_slices;
delete[] statuses;
}
unsigned char rocksdb_key_may_exist(rocksdb_t* db,
const rocksdb_readoptions_t* options,
const char* key, size_t key_len,
@@ -2123,6 +2180,32 @@ void rocksdb_iter_get_error(const rocksdb_iterator_t* iter, char** errptr) {
SaveError(errptr, iter->rep->status());
}
// Iterator functions that return rocksdb_slice_t directly for better
// performance
rocksdb_slice_t rocksdb_iter_key_slice(const rocksdb_iterator_t* iter) {
Slice s = iter->rep->key();
rocksdb_slice_t result;
result.data = s.data();
result.size = s.size();
return result;
}
rocksdb_slice_t rocksdb_iter_value_slice(const rocksdb_iterator_t* iter) {
Slice s = iter->rep->value();
rocksdb_slice_t result;
result.data = s.data();
result.size = s.size();
return result;
}
rocksdb_slice_t rocksdb_iter_timestamp_slice(const rocksdb_iterator_t* iter) {
Slice s = iter->rep->timestamp();
rocksdb_slice_t result;
result.data = s.data();
result.size = s.size();
return result;
}
void rocksdb_iter_refresh(const rocksdb_iterator_t* iter, char** errptr) {
SaveError(errptr, iter->rep->Refresh());
}
@@ -2178,16 +2261,18 @@ void rocksdb_writebatch_putv(rocksdb_writebatch_t* b, int num_keys,
const size_t* keys_list_sizes, int num_values,
const char* const* values_list,
const size_t* values_list_sizes) {
std::vector<Slice> key_slices(num_keys);
// Use unique_ptr instead of vector to avoid overhead
// Safe because WriteBatch::Put immediately copies the data
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
for (int i = 0; i < num_keys; i++) {
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
std::vector<Slice> value_slices(num_values);
std::unique_ptr<Slice[]> value_slices(new Slice[num_values]);
for (int i = 0; i < num_values; i++) {
value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
}
b->rep.Put(SliceParts(key_slices.data(), num_keys),
SliceParts(value_slices.data(), num_values));
b->rep.Put(SliceParts(key_slices.get(), num_keys),
SliceParts(value_slices.get(), num_values));
}
void rocksdb_writebatch_putv_cf(rocksdb_writebatch_t* b,
@@ -2196,16 +2281,18 @@ void rocksdb_writebatch_putv_cf(rocksdb_writebatch_t* b,
const size_t* keys_list_sizes, int num_values,
const char* const* values_list,
const size_t* values_list_sizes) {
std::vector<Slice> key_slices(num_keys);
// Use unique_ptr instead of vector to avoid overhead
// Safe because WriteBatch::Put immediately copies the data
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
for (int i = 0; i < num_keys; i++) {
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
std::vector<Slice> value_slices(num_values);
std::unique_ptr<Slice[]> value_slices(new Slice[num_values]);
for (int i = 0; i < num_values; i++) {
value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
}
b->rep.Put(column_family->rep, SliceParts(key_slices.data(), num_keys),
SliceParts(value_slices.data(), num_values));
b->rep.Put(column_family->rep, SliceParts(key_slices.get(), num_keys),
SliceParts(value_slices.get(), num_values));
}
void rocksdb_writebatch_merge(rocksdb_writebatch_t* b, const char* key,
@@ -2225,16 +2312,18 @@ void rocksdb_writebatch_mergev(rocksdb_writebatch_t* b, int num_keys,
const size_t* keys_list_sizes, int num_values,
const char* const* values_list,
const size_t* values_list_sizes) {
std::vector<Slice> key_slices(num_keys);
// Use unique_ptr instead of vector to avoid overhead
// Safe because WriteBatch::Merge immediately copies the data
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
for (int i = 0; i < num_keys; i++) {
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
std::vector<Slice> value_slices(num_values);
std::unique_ptr<Slice[]> value_slices(new Slice[num_values]);
for (int i = 0; i < num_values; i++) {
value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
}
b->rep.Merge(SliceParts(key_slices.data(), num_keys),
SliceParts(value_slices.data(), num_values));
b->rep.Merge(SliceParts(key_slices.get(), num_keys),
SliceParts(value_slices.get(), num_values));
}
void rocksdb_writebatch_mergev_cf(rocksdb_writebatch_t* b,
@@ -2243,16 +2332,18 @@ void rocksdb_writebatch_mergev_cf(rocksdb_writebatch_t* b,
const size_t* keys_list_sizes, int num_values,
const char* const* values_list,
const size_t* values_list_sizes) {
std::vector<Slice> key_slices(num_keys);
// Use unique_ptr instead of vector to avoid overhead
// Safe because WriteBatch::Merge immediately copies the data
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
for (int i = 0; i < num_keys; i++) {
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
std::vector<Slice> value_slices(num_values);
std::unique_ptr<Slice[]> value_slices(new Slice[num_values]);
for (int i = 0; i < num_values; i++) {
value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
}
b->rep.Merge(column_family->rep, SliceParts(key_slices.data(), num_keys),
SliceParts(value_slices.data(), num_values));
b->rep.Merge(column_family->rep, SliceParts(key_slices.get(), num_keys),
SliceParts(value_slices.get(), num_values));
}
void rocksdb_writebatch_delete(rocksdb_writebatch_t* b, const char* key,
@@ -2292,21 +2383,25 @@ void rocksdb_writebatch_singledelete_cf_with_ts(
void rocksdb_writebatch_deletev(rocksdb_writebatch_t* b, int num_keys,
const char* const* keys_list,
const size_t* keys_list_sizes) {
std::vector<Slice> key_slices(num_keys);
// Use unique_ptr instead of vector to avoid overhead
// Safe because WriteBatch::Delete immediately copies the data
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
for (int i = 0; i < num_keys; i++) {
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
b->rep.Delete(SliceParts(key_slices.data(), num_keys));
b->rep.Delete(SliceParts(key_slices.get(), num_keys));
}
void rocksdb_writebatch_deletev_cf(
rocksdb_writebatch_t* b, rocksdb_column_family_handle_t* column_family,
int num_keys, const char* const* keys_list, const size_t* keys_list_sizes) {
std::vector<Slice> key_slices(num_keys);
// Use unique_ptr instead of vector to avoid overhead
// Safe because WriteBatch::Delete immediately copies the data
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
for (int i = 0; i < num_keys; i++) {
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
b->rep.Delete(column_family->rep, SliceParts(key_slices.data(), num_keys));
b->rep.Delete(column_family->rep, SliceParts(key_slices.get(), num_keys));
}
void rocksdb_writebatch_delete_range(rocksdb_writebatch_t* b,
@@ -2330,14 +2425,16 @@ void rocksdb_writebatch_delete_rangev(rocksdb_writebatch_t* b, int num_keys,
const size_t* start_keys_list_sizes,
const char* const* end_keys_list,
const size_t* end_keys_list_sizes) {
std::vector<Slice> start_key_slices(num_keys);
std::vector<Slice> end_key_slices(num_keys);
// Use unique_ptr instead of vector to avoid overhead
// Safe because WriteBatch::DeleteRange immediately copies the data
std::unique_ptr<Slice[]> start_key_slices(new Slice[num_keys]);
std::unique_ptr<Slice[]> end_key_slices(new Slice[num_keys]);
for (int i = 0; i < num_keys; i++) {
start_key_slices[i] = Slice(start_keys_list[i], start_keys_list_sizes[i]);
end_key_slices[i] = Slice(end_keys_list[i], end_keys_list_sizes[i]);
}
b->rep.DeleteRange(SliceParts(start_key_slices.data(), num_keys),
SliceParts(end_key_slices.data(), num_keys));
b->rep.DeleteRange(SliceParts(start_key_slices.get(), num_keys),
SliceParts(end_key_slices.get(), num_keys));
}
void rocksdb_writebatch_delete_rangev_cf(
@@ -2345,15 +2442,17 @@ void rocksdb_writebatch_delete_rangev_cf(
int num_keys, const char* const* start_keys_list,
const size_t* start_keys_list_sizes, const char* const* end_keys_list,
const size_t* end_keys_list_sizes) {
std::vector<Slice> start_key_slices(num_keys);
std::vector<Slice> end_key_slices(num_keys);
// Use unique_ptr instead of vector to avoid overhead
// Safe because WriteBatch::DeleteRange immediately copies the data
std::unique_ptr<Slice[]> start_key_slices(new Slice[num_keys]);
std::unique_ptr<Slice[]> end_key_slices(new Slice[num_keys]);
for (int i = 0; i < num_keys; i++) {
start_key_slices[i] = Slice(start_keys_list[i], start_keys_list_sizes[i]);
end_key_slices[i] = Slice(end_keys_list[i], end_keys_list_sizes[i]);
}
b->rep.DeleteRange(column_family->rep,
SliceParts(start_key_slices.data(), num_keys),
SliceParts(end_key_slices.data(), num_keys));
SliceParts(start_key_slices.get(), num_keys),
SliceParts(end_key_slices.get(), num_keys));
}
void rocksdb_writebatch_put_log_data(rocksdb_writebatch_t* b, const char* blob,
@@ -2514,16 +2613,17 @@ void rocksdb_writebatch_wi_putv(rocksdb_writebatch_wi_t* b, int num_keys,
const size_t* keys_list_sizes, int num_values,
const char* const* values_list,
const size_t* values_list_sizes) {
std::vector<Slice> key_slices(num_keys);
// Use unique_ptr for better performance (avoids vector overhead)
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
for (int i = 0; i < num_keys; i++) {
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
std::vector<Slice> value_slices(num_values);
std::unique_ptr<Slice[]> value_slices(new Slice[num_values]);
for (int i = 0; i < num_values; i++) {
value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
}
b->rep->Put(SliceParts(key_slices.data(), num_keys),
SliceParts(value_slices.data(), num_values));
b->rep->Put(SliceParts(key_slices.get(), num_keys),
SliceParts(value_slices.get(), num_values));
}
void rocksdb_writebatch_wi_putv_cf(
@@ -2559,16 +2659,17 @@ void rocksdb_writebatch_wi_mergev(rocksdb_writebatch_wi_t* b, int num_keys,
const size_t* keys_list_sizes, int num_values,
const char* const* values_list,
const size_t* values_list_sizes) {
std::vector<Slice> key_slices(num_keys);
// Use unique_ptr for better performance (avoids vector overhead)
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
for (int i = 0; i < num_keys; i++) {
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
std::vector<Slice> value_slices(num_values);
std::unique_ptr<Slice[]> value_slices(new Slice[num_values]);
for (int i = 0; i < num_values; i++) {
value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
}
b->rep->Merge(SliceParts(key_slices.data(), num_keys),
SliceParts(value_slices.data(), num_values));
b->rep->Merge(SliceParts(key_slices.get(), num_keys),
SliceParts(value_slices.get(), num_values));
}
void rocksdb_writebatch_wi_mergev_cf(
@@ -2576,16 +2677,17 @@ void rocksdb_writebatch_wi_mergev_cf(
int num_keys, const char* const* keys_list, const size_t* keys_list_sizes,
int num_values, const char* const* values_list,
const size_t* values_list_sizes) {
std::vector<Slice> key_slices(num_keys);
// Use unique_ptr for better performance (avoids vector overhead)
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
for (int i = 0; i < num_keys; i++) {
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
std::vector<Slice> value_slices(num_values);
std::unique_ptr<Slice[]> value_slices(new Slice[num_values]);
for (int i = 0; i < num_values; i++) {
value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
}
b->rep->Merge(column_family->rep, SliceParts(key_slices.data(), num_keys),
SliceParts(value_slices.data(), num_values));
b->rep->Merge(column_family->rep, SliceParts(key_slices.get(), num_keys),
SliceParts(value_slices.get(), num_values));
}
void rocksdb_writebatch_wi_delete(rocksdb_writebatch_wi_t* b, const char* key,
@@ -2623,11 +2725,12 @@ void rocksdb_writebatch_wi_deletev(rocksdb_writebatch_wi_t* b, int num_keys,
void rocksdb_writebatch_wi_deletev_cf(
rocksdb_writebatch_wi_t* b, rocksdb_column_family_handle_t* column_family,
int num_keys, const char* const* keys_list, const size_t* keys_list_sizes) {
std::vector<Slice> key_slices(num_keys);
// Use unique_ptr for better performance (avoids vector overhead)
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
for (int i = 0; i < num_keys; i++) {
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
b->rep->Delete(column_family->rep, SliceParts(key_slices.data(), num_keys));
b->rep->Delete(column_family->rep, SliceParts(key_slices.get(), num_keys));
}
void rocksdb_writebatch_wi_delete_range(rocksdb_writebatch_wi_t* b,
@@ -2653,14 +2756,15 @@ void rocksdb_writebatch_wi_delete_rangev(rocksdb_writebatch_wi_t* b,
const size_t* start_keys_list_sizes,
const char* const* end_keys_list,
const size_t* end_keys_list_sizes) {
std::vector<Slice> start_key_slices(num_keys);
std::vector<Slice> end_key_slices(num_keys);
// Use unique_ptr for better performance (avoids vector overhead)
std::unique_ptr<Slice[]> start_key_slices(new Slice[num_keys]);
std::unique_ptr<Slice[]> end_key_slices(new Slice[num_keys]);
for (int i = 0; i < num_keys; i++) {
start_key_slices[i] = Slice(start_keys_list[i], start_keys_list_sizes[i]);
end_key_slices[i] = Slice(end_keys_list[i], end_keys_list_sizes[i]);
}
b->rep->DeleteRange(SliceParts(start_key_slices.data(), num_keys),
SliceParts(end_key_slices.data(), num_keys));
b->rep->DeleteRange(SliceParts(start_key_slices.get(), num_keys),
SliceParts(end_key_slices.get(), num_keys));
}
void rocksdb_writebatch_wi_delete_rangev_cf(
@@ -2668,15 +2772,16 @@ void rocksdb_writebatch_wi_delete_rangev_cf(
int num_keys, const char* const* start_keys_list,
const size_t* start_keys_list_sizes, const char* const* end_keys_list,
const size_t* end_keys_list_sizes) {
std::vector<Slice> start_key_slices(num_keys);
std::vector<Slice> end_key_slices(num_keys);
// Use unique_ptr for better performance (avoids vector overhead)
std::unique_ptr<Slice[]> start_key_slices(new Slice[num_keys]);
std::unique_ptr<Slice[]> end_key_slices(new Slice[num_keys]);
for (int i = 0; i < num_keys; i++) {
start_key_slices[i] = Slice(start_keys_list[i], start_keys_list_sizes[i]);
end_key_slices[i] = Slice(end_keys_list[i], end_keys_list_sizes[i]);
}
b->rep->DeleteRange(column_family->rep,
SliceParts(start_key_slices.data(), num_keys),
SliceParts(end_key_slices.data(), num_keys));
SliceParts(start_key_slices.get(), num_keys),
SliceParts(end_key_slices.get(), num_keys));
}
void rocksdb_writebatch_wi_put_log_data(rocksdb_writebatch_wi_t* b,
@@ -2794,12 +2899,13 @@ char* rocksdb_writebatch_wi_get_from_batch_and_db(
const rocksdb_readoptions_t* options, const char* key, size_t keylen,
size_t* vallen, char** errptr) {
char* result = nullptr;
std::string tmp;
// Use PinnableSlice to avoid unnecessary allocations
PinnableSlice pinnable_val;
Status s = wbwi->rep->GetFromBatchAndDB(db->rep, options->rep,
Slice(key, keylen), &tmp);
Slice(key, keylen), &pinnable_val);
if (s.ok()) {
*vallen = tmp.size();
result = CopyString(tmp);
*vallen = pinnable_val.size();
result = CopyString(pinnable_val);
} else {
*vallen = 0;
if (!s.IsNotFound()) {
@@ -2832,12 +2938,14 @@ char* rocksdb_writebatch_wi_get_from_batch_and_db_cf(
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, size_t* vallen, char** errptr) {
char* result = nullptr;
std::string tmp;
Status s = wbwi->rep->GetFromBatchAndDB(
db->rep, options->rep, column_family->rep, Slice(key, keylen), &tmp);
// Use PinnableSlice to avoid unnecessary allocations
PinnableSlice pinnable_val;
Status s =
wbwi->rep->GetFromBatchAndDB(db->rep, options->rep, column_family->rep,
Slice(key, keylen), &pinnable_val);
if (s.ok()) {
*vallen = tmp.size();
result = CopyString(tmp);
*vallen = pinnable_val.size();
result = CopyString(pinnable_val);
} else {
*vallen = 0;
if (!s.IsNotFound()) {
@@ -6922,11 +7030,11 @@ char* rocksdb_transaction_get(rocksdb_transaction_t* txn,
const char* key, size_t klen, size_t* vlen,
char** errptr) {
char* result = nullptr;
std::string tmp;
Status s = txn->rep->Get(options->rep, Slice(key, klen), &tmp);
PinnableSlice pinnable_val;
Status s = txn->rep->Get(options->rep, Slice(key, klen), &pinnable_val);
if (s.ok()) {
*vlen = tmp.size();
result = CopyString(tmp);
*vlen = pinnable_val.size();
result = CopyString(pinnable_val);
} else {
*vlen = 0;
if (!s.IsNotFound()) {
@@ -6957,12 +7065,12 @@ char* rocksdb_transaction_get_cf(rocksdb_transaction_t* txn,
const char* key, size_t klen, size_t* vlen,
char** errptr) {
char* result = nullptr;
std::string tmp;
Status s =
txn->rep->Get(options->rep, column_family->rep, Slice(key, klen), &tmp);
PinnableSlice pinnable_val;
Status s = txn->rep->Get(options->rep, column_family->rep, Slice(key, klen),
&pinnable_val);
if (s.ok()) {
*vlen = tmp.size();
result = CopyString(tmp);
*vlen = pinnable_val.size();
result = CopyString(pinnable_val);
} else {
*vlen = 0;
if (!s.IsNotFound()) {
@@ -6996,12 +7104,12 @@ char* rocksdb_transaction_get_for_update(rocksdb_transaction_t* txn,
size_t* vlen, unsigned char exclusive,
char** errptr) {
char* result = nullptr;
std::string tmp;
Status s =
txn->rep->GetForUpdate(options->rep, Slice(key, klen), &tmp, exclusive);
PinnableSlice pinnable_val;
Status s = txn->rep->GetForUpdate(options->rep, Slice(key, klen),
&pinnable_val, exclusive);
if (s.ok()) {
*vlen = tmp.size();
result = CopyString(tmp);
*vlen = pinnable_val.size();
result = CopyString(pinnable_val);
} else {
*vlen = 0;
if (!s.IsNotFound()) {
@@ -7033,12 +7141,12 @@ char* rocksdb_transaction_get_for_update_cf(
rocksdb_column_family_handle_t* column_family, const char* key, size_t klen,
size_t* vlen, unsigned char exclusive, char** errptr) {
char* result = nullptr;
std::string tmp;
PinnableSlice pinnable_val;
Status s = txn->rep->GetForUpdate(options->rep, column_family->rep,
Slice(key, klen), &tmp, exclusive);
Slice(key, klen), &pinnable_val, exclusive);
if (s.ok()) {
*vlen = tmp.size();
result = CopyString(tmp);
*vlen = pinnable_val.size();
result = CopyString(pinnable_val);
} else {
*vlen = 0;
if (!s.IsNotFound()) {
@@ -7072,10 +7180,13 @@ void rocksdb_transaction_multi_get(rocksdb_transaction_t* txn,
const size_t* keys_list_sizes,
char** values_list,
size_t* values_list_sizes, char** errs) {
std::vector<Slice> keys(num_keys);
// Use unique_ptr for efficiency
std::unique_ptr<Slice[]> keys_arr(new Slice[num_keys]);
for (size_t i = 0; i < num_keys; i++) {
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
keys_arr[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
// Note: Transaction only has vector-based MultiGet API
std::vector<Slice> keys(keys_arr.get(), keys_arr.get() + num_keys);
std::vector<std::string> values(num_keys);
std::vector<Status> statuses =
txn->rep->MultiGet(options->rep, keys, &values);
@@ -7101,10 +7212,14 @@ void rocksdb_transaction_multi_get_for_update(
size_t num_keys, const char* const* keys_list,
const size_t* keys_list_sizes, char** values_list,
size_t* values_list_sizes, char** errs) {
std::vector<Slice> keys(num_keys);
// Use unique_ptr for efficiency
std::unique_ptr<Slice[]> keys_arr(new Slice[num_keys]);
for (size_t i = 0; i < num_keys; i++) {
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
keys_arr[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
// Note: GetForUpdate only has vector-based API, no array-based PinnableSlice
// variant
std::vector<Slice> keys(keys_arr.get(), keys_arr.get() + num_keys);
std::vector<std::string> values(num_keys);
std::vector<Status> statuses =
txn->rep->MultiGetForUpdate(options->rep, keys, &values);
@@ -7131,12 +7246,15 @@ void rocksdb_transaction_multi_get_cf(
size_t num_keys, const char* const* keys_list,
const size_t* keys_list_sizes, char** values_list,
size_t* values_list_sizes, char** errs) {
std::vector<Slice> keys(num_keys);
// Use unique_ptr for efficiency
std::unique_ptr<Slice[]> keys_arr(new Slice[num_keys]);
std::vector<ColumnFamilyHandle*> cfs(num_keys);
for (size_t i = 0; i < num_keys; i++) {
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
keys_arr[i] = Slice(keys_list[i], keys_list_sizes[i]);
cfs[i] = column_families[i]->rep;
}
// Note: Transaction only has vector-based MultiGet API
std::vector<Slice> keys(keys_arr.get(), keys_arr.get() + num_keys);
std::vector<std::string> values(num_keys);
std::vector<Status> statuses =
txn->rep->MultiGet(options->rep, cfs, keys, &values);
@@ -7163,12 +7281,16 @@ void rocksdb_transaction_multi_get_for_update_cf(
size_t num_keys, const char* const* keys_list,
const size_t* keys_list_sizes, char** values_list,
size_t* values_list_sizes, char** errs) {
std::vector<Slice> keys(num_keys);
// Use unique_ptr for efficiency
std::unique_ptr<Slice[]> keys_arr(new Slice[num_keys]);
std::vector<ColumnFamilyHandle*> cfs(num_keys);
for (size_t i = 0; i < num_keys; i++) {
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
keys_arr[i] = Slice(keys_list[i], keys_list_sizes[i]);
cfs[i] = column_families[i]->rep;
}
// Note: GetForUpdate only has vector-based API, no array-based PinnableSlice
// variant
std::vector<Slice> keys(keys_arr.get(), keys_arr.get() + num_keys);
std::vector<std::string> values(num_keys);
std::vector<Status> statuses =
txn->rep->MultiGetForUpdate(options->rep, cfs, keys, &values);
@@ -7195,11 +7317,12 @@ char* rocksdb_transactiondb_get(rocksdb_transactiondb_t* txn_db,
const char* key, size_t klen, size_t* vlen,
char** errptr) {
char* result = nullptr;
std::string tmp;
Status s = txn_db->rep->Get(options->rep, Slice(key, klen), &tmp);
PinnableSlice pinnable_val;
Status s = txn_db->rep->Get(options->rep, txn_db->rep->DefaultColumnFamily(),
Slice(key, klen), &pinnable_val);
if (s.ok()) {
*vlen = tmp.size();
result = CopyString(tmp);
*vlen = pinnable_val.size();
result = CopyString(pinnable_val);
} else {
*vlen = 0;
if (!s.IsNotFound()) {
@@ -7230,12 +7353,12 @@ char* rocksdb_transactiondb_get_cf(
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, size_t* vallen, char** errptr) {
char* result = nullptr;
std::string tmp;
PinnableSlice pinnable_val;
Status s = txn_db->rep->Get(options->rep, column_family->rep,
Slice(key, keylen), &tmp);
Slice(key, keylen), &pinnable_val);
if (s.ok()) {
*vallen = tmp.size();
result = CopyString(tmp);
*vallen = pinnable_val.size();
result = CopyString(pinnable_val);
} else {
*vallen = 0;
if (!s.IsNotFound()) {
@@ -7269,13 +7392,17 @@ void rocksdb_transactiondb_multi_get(rocksdb_transactiondb_t* txn_db,
const size_t* keys_list_sizes,
char** values_list,
size_t* values_list_sizes, char** errs) {
std::vector<Slice> keys(num_keys);
// Use unique_ptr for efficiency
std::unique_ptr<Slice[]> keys(new Slice[num_keys]);
for (size_t i = 0; i < num_keys; i++) {
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
std::vector<std::string> values(num_keys);
std::vector<Status> statuses =
txn_db->rep->MultiGet(options->rep, keys, &values);
// Use PinnableSlice to avoid unnecessary allocations
auto cfh = txn_db->rep->DefaultColumnFamily();
std::vector<PinnableSlice> values(num_keys);
std::vector<Status> statuses(num_keys);
txn_db->rep->MultiGet(options->rep, cfh, num_keys, keys.get(), values.data(),
statuses.data());
for (size_t i = 0; i < num_keys; i++) {
if (statuses[i].ok()) {
values_list[i] = CopyString(values[i]);
@@ -7299,15 +7426,18 @@ void rocksdb_transactiondb_multi_get_cf(
size_t num_keys, const char* const* keys_list,
const size_t* keys_list_sizes, char** values_list,
size_t* values_list_sizes, char** errs) {
std::vector<Slice> keys(num_keys);
std::vector<ColumnFamilyHandle*> cfs(num_keys);
// Use unique_ptr for efficiency
std::unique_ptr<Slice[]> keys(new Slice[num_keys]);
std::unique_ptr<ColumnFamilyHandle*[]> cfs(new ColumnFamilyHandle*[num_keys]);
for (size_t i = 0; i < num_keys; i++) {
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
cfs[i] = column_families[i]->rep;
}
std::vector<std::string> values(num_keys);
std::vector<Status> statuses =
txn_db->rep->MultiGet(options->rep, cfs, keys, &values);
// Use PinnableSlice to avoid unnecessary allocations
std::vector<PinnableSlice> values(num_keys);
std::vector<Status> statuses(num_keys);
txn_db->rep->MultiGet(options->rep, num_keys, cfs.get(), keys.get(),
values.data(), statuses.data());
for (size_t i = 0; i < num_keys; i++) {
if (statuses[i].ok()) {
values_list[i] = CopyString(values[i]);
@@ -7900,4 +8030,110 @@ uint64_t rocksdb_wait_for_compact_options_get_timeout(
return opt->rep.timeout.count();
}
/* High-performance zero-copy Get implementations */
struct rocksdb_pinnable_handle_t {
PinnableSlice rep;
};
rocksdb_pinnable_handle_t* rocksdb_get_pinned_v2(
rocksdb_t* db, const rocksdb_readoptions_t* options, const char* key,
size_t keylen, char** errptr) {
rocksdb_pinnable_handle_t* handle = new rocksdb_pinnable_handle_t;
Status s = db->rep->Get(options->rep, db->rep->DefaultColumnFamily(),
Slice(key, keylen), &handle->rep);
if (!s.ok()) {
delete handle;
if (!s.IsNotFound()) {
SaveError(errptr, s);
}
return nullptr;
}
return handle;
}
rocksdb_pinnable_handle_t* rocksdb_get_pinned_cf_v2(
rocksdb_t* db, const rocksdb_readoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, char** errptr) {
rocksdb_pinnable_handle_t* handle = new rocksdb_pinnable_handle_t;
Status s = db->rep->Get(options->rep, column_family->rep, Slice(key, keylen),
&handle->rep);
if (!s.ok()) {
delete handle;
if (!s.IsNotFound()) {
SaveError(errptr, s);
}
return nullptr;
}
return handle;
}
const char* rocksdb_pinnable_handle_get_value(
const rocksdb_pinnable_handle_t* handle, size_t* vallen) {
if (!handle) {
*vallen = 0;
return nullptr;
}
*vallen = handle->rep.size();
return handle->rep.data();
}
void rocksdb_pinnable_handle_destroy(rocksdb_pinnable_handle_t* handle) {
delete handle;
}
unsigned char rocksdb_get_into_buffer(rocksdb_t* db,
const rocksdb_readoptions_t* options,
const char* key, size_t keylen,
char* buffer, size_t buffer_size,
size_t* vallen, unsigned char* found,
char** errptr) {
PinnableSlice pinnable_val;
Status s = db->rep->Get(options->rep, db->rep->DefaultColumnFamily(),
Slice(key, keylen), &pinnable_val);
if (s.ok()) {
*found = 1;
*vallen = pinnable_val.size();
if (buffer_size >= pinnable_val.size()) {
memcpy(buffer, pinnable_val.data(), pinnable_val.size());
return 1; // Success - data copied
}
return 0; // Buffer too small
} else {
*found = 0;
*vallen = 0;
if (!s.IsNotFound()) {
SaveError(errptr, s);
}
return 0;
}
}
unsigned char rocksdb_get_into_buffer_cf(
rocksdb_t* db, const rocksdb_readoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, char* buffer, size_t buffer_size, size_t* vallen,
unsigned char* found, char** errptr) {
PinnableSlice pinnable_val;
Status s = db->rep->Get(options->rep, column_family->rep, Slice(key, keylen),
&pinnable_val);
if (s.ok()) {
*found = 1;
*vallen = pinnable_val.size();
if (buffer_size >= pinnable_val.size()) {
memcpy(buffer, pinnable_val.data(), pinnable_val.size());
return 1; // Success - data copied
}
return 0; // Buffer too small
} else {
*found = 0;
*vallen = 0;
if (!s.IsNotFound()) {
SaveError(errptr, s);
}
return 0;
}
}
} // end extern "C"
+276
View File
@@ -1255,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();
@@ -1420,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);
@@ -1505,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");
@@ -1922,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",
@@ -1998,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;
@@ -3469,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);
+4
View File
@@ -180,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];
}
+31 -25
View File
@@ -1480,11 +1480,11 @@ CompactionJob::CreateFileHandlers(SubcompactionState* sub_compact,
const CompactionFileCloseFunc close_file_func =
[this, sub_compact, start_user_key, end_user_key](
const Status& status,
const ParsedInternalKey& prev_table_last_internal_key,
const ParsedInternalKey& prev_iter_output_internal_key,
const Slice& next_table_min_key, const CompactionIterator* c_iter,
CompactionOutputs& outputs) {
return this->FinishCompactionOutputFile(
status, prev_table_last_internal_key, next_table_min_key,
status, prev_iter_output_internal_key, next_table_min_key,
start_user_key, end_user_key, c_iter, sub_compact, outputs);
};
@@ -1499,8 +1499,8 @@ Status CompactionJob::ProcessKeyValue(
const uint64_t kRecordStatsEvery = 1000;
[[maybe_unused]] const std::optional<const Slice> end = sub_compact->end;
IterKey last_output_key;
ParsedInternalKey last_output_ikey;
IterKey prev_iter_output_key;
ParsedInternalKey prev_iter_output_internal_key;
TEST_SYNC_POINT_CALLBACK(
"CompactionJob::ProcessKeyValueCompaction()::Processing",
@@ -1551,9 +1551,9 @@ Status CompactionJob::ProcessKeyValue(
// and `close_file_func`.
// TODO: it would be better to have the compaction file open/close moved
// into `CompactionOutputs` which has the output file information.
status =
sub_compact->AddToOutput(*c_iter, use_proximal_output, open_file_func,
close_file_func, last_output_ikey);
status = sub_compact->AddToOutput(*c_iter, use_proximal_output,
open_file_func, close_file_func,
prev_iter_output_internal_key);
if (!status.ok()) {
break;
}
@@ -1562,9 +1562,10 @@ Status CompactionJob::ProcessKeyValue(
static_cast<void*>(const_cast<std::atomic<bool>*>(
&manual_compaction_canceled_)));
last_output_key.SetInternalKey(c_iter->key(), &last_output_ikey);
last_output_ikey.sequence = ikey.sequence;
last_output_ikey.type = ikey.type;
prev_iter_output_key.SetInternalKey(c_iter->key(),
&prev_iter_output_internal_key);
prev_iter_output_internal_key.sequence = ikey.sequence;
prev_iter_output_internal_key.type = ikey.type;
c_iter->Next();
#ifndef NDEBUG
@@ -1871,7 +1872,7 @@ void CompactionJob::RecordDroppedKeys(
Status CompactionJob::FinishCompactionOutputFile(
const Status& input_status,
const ParsedInternalKey& prev_table_last_internal_key,
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) {
@@ -2049,7 +2050,7 @@ Status CompactionJob::FinishCompactionOutputFile(
}
if (s.ok() && ShouldUpdateSubcompactionProgress(sub_compact, c_iter,
prev_table_last_internal_key,
prev_iter_output_internal_key,
next_table_min_key, meta)) {
UpdateSubcompactionProgress(c_iter, next_table_min_key, sub_compact);
s = PersistSubcompactionProgress(sub_compact);
@@ -2060,10 +2061,10 @@ Status CompactionJob::FinishCompactionOutputFile(
bool CompactionJob::ShouldUpdateSubcompactionProgress(
const SubcompactionState* sub_compact, const CompactionIterator* c_iter,
const ParsedInternalKey& prev_table_last_internal_key,
const ParsedInternalKey& prev_iter_output_internal_key,
const Slice& next_table_min_internal_key, const FileMetaData* meta) const {
const auto* cfd = sub_compact->compaction->column_family_data();
// No need to update when the output will not get persisted
// No need to update when the progress will not get persisted
if (compaction_progress_writer_ == nullptr) {
return false;
}
@@ -2087,19 +2088,24 @@ bool CompactionJob::ShouldUpdateSubcompactionProgress(
}
// LIMITATION: Compaction progress persistence disabled for file boundaries
// contaning range deletions. Range deletions can span file boundaries, making
// it difficult (but possible) to ensure adjacent output tables have different
// user keys. See the last check for why different users keys of adjacent
// output tables are needed
// containing range deletions. Range deletions can span file boundaries,
// making it difficult to ensure adjacent output tables have different user
// keys. See the last check for why different users keys of adjacent output
// tables are needed
const ValueType next_table_min_internal_key_type =
ExtractValueType(next_table_min_internal_key);
const ValueType prev_table_last_internal_key_type =
prev_table_last_internal_key.user_key.empty()
const ValueType prev_iter_output_internal_key_type =
prev_iter_output_internal_key.user_key.empty()
? ValueType::kTypeValue
: prev_table_last_internal_key.type;
: prev_iter_output_internal_key.type;
if (next_table_min_internal_key_type == ValueType::kTypeRangeDeletion ||
prev_table_last_internal_key_type == ValueType::kTypeRangeDeletion) {
// Range deletes truncated to align with file boundaries may be output by the
// compaction iterator with `ValueType::kTypeMaxValid` instead of the original
// type.
if ((next_table_min_internal_key_type == ValueType::kTypeRangeDeletion ||
next_table_min_internal_key_type == ValueType::kTypeMaxValid) ||
(prev_iter_output_internal_key_type == ValueType::kTypeRangeDeletion ||
prev_iter_output_internal_key_type == ValueType::kTypeMaxValid)) {
return false;
}
@@ -2109,9 +2115,9 @@ bool CompactionJob::ShouldUpdateSubcompactionProgress(
const Slice next_table_min_user_key =
ExtractUserKey(next_table_min_internal_key);
const Slice prev_table_last_user_key =
prev_table_last_internal_key.user_key.empty()
prev_iter_output_internal_key.user_key.empty()
? Slice()
: prev_table_last_internal_key.user_key;
: prev_iter_output_internal_key.user_key;
if (cfd->user_comparator()->EqualWithoutTimestamp(next_table_min_user_key,
prev_table_last_user_key)) {
+2 -2
View File
@@ -430,7 +430,7 @@ class CompactionJob {
Status FinishCompactionOutputFile(
const Status& input_status,
const ParsedInternalKey& prev_table_last_internal_key,
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);
@@ -545,7 +545,7 @@ class CompactionJob {
bool ShouldUpdateSubcompactionProgress(
const SubcompactionState* sub_compact, const CompactionIterator* c_iter,
const ParsedInternalKey& prev_table_last_internal_key,
const ParsedInternalKey& prev_iter_output_internal_key,
const Slice& next_table_min_internal_key, const FileMetaData* meta) const;
void UpdateSubcompactionProgress(const CompactionIterator* c_iter,
+31 -23
View File
@@ -2424,6 +2424,7 @@ class ResumableCompactionJobTest : public CompactionJobTestBase {
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();
@@ -2437,7 +2438,9 @@ class ResumableCompactionJobTest : public CompactionJobTestBase {
if (enable_cancel_) {
ParsedInternalKey parsed_key;
if (ParseInternalKey(pair->second, &parsed_key, true).ok()) {
if (parsed_key.user_key == kCancelBeforeThisKey) {
if (parsed_key.user_key == kCancelBeforeThisKey &&
(cancel_before_seqno == kMaxSequenceNumber ||
parsed_key.sequence == cancel_before_seqno)) {
cancel_.store(true);
}
}
@@ -2665,7 +2668,7 @@ class ResumableCompactionJobTest : public CompactionJobTestBase {
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 exists_progress,
const std::vector<std::string>& expected_input_keys,
bool cancelled_past_mid_point = false) {
std::shared_ptr<Statistics> stats = ROCKSDB_NAMESPACE::CreateDBStatistics();
@@ -2701,7 +2704,7 @@ class ResumableCompactionJobTest : public CompactionJobTestBase {
// Resume compaction
CompactionProgress compaction_progress;
if (exists_progress) {
if (expected_next_key_to_compact != "") {
compaction_progress.push_back(
ReadAndParseProgress(compaction_progress_file));
}
@@ -2774,35 +2777,43 @@ TEST_F(ResumableCompactionJobTest, BasicProgressResume) {
4U /* last_sequence */, {} /* snapshots */,
kCancelBeforeThisKey /* expected_next_key_to_compact */,
{"a", "b", "bb", kCancelBeforeThisKey} /* expected_input_keys */,
true /* exists_progress */, true /* cancelled_past_mid_point*/);
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),
"val2"}} /* input_file_2 */,
2U /* last_sequence */, {1U} /* snapshots */,
{{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} /* expected_input_keys */,
false /* exists_progress */);
{kCancelBeforeThisKey, kCancelBeforeThisKey,
"d"} /* expected_input_keys */);
}
TEST_F(ResumableCompactionJobTest, NoProgressResumeOnDeleteRange) {
NewDB();
RunCancelAndResumeTest(
{{KeyStr(kCancelBeforeThisKey, 1U, kTypeValue),
"val1"}} /* input_file_1 */,
{{KeyStr(kCancelBeforeThisKey, 2U, kTypeRangeDeletion),
"val2"}} /* input_file_2 */,
2U /* last_sequence */, {1U} /* snapshots */,
"" /* expected_next_key_to_compact */,
{kCancelBeforeThisKey, kCancelBeforeThisKey} /* expected_input_keys */,
false /* exists_progress */);
{{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) {
@@ -2817,8 +2828,7 @@ TEST_F(ResumableCompactionJobTest, NoProgressResumeOnMerge) {
"val4"}} /* input_file_2 */,
4U /* last_sequence */, {} /* snapshots */,
"bb" /* expected_next_key_to_compact */,
{"a", "b", "bb", kCancelBeforeThisKey} /* expected_input_keys */,
true /* exists_progress */);
{"a", "b", "bb", kCancelBeforeThisKey} /* expected_input_keys */);
}
TEST_F(ResumableCompactionJobTest, NoProgressResumeOnSingleDelete) {
@@ -2834,8 +2844,7 @@ TEST_F(ResumableCompactionJobTest, NoProgressResumeOnSingleDelete) {
5U /* last_sequence */, {3U} /* snapshots */,
"b" /* expected_next_key_to_compact */,
{"a", "b", kCancelBeforeThisKey, kCancelBeforeThisKey,
"d"} /* expected_input_keys */,
true /* exists_progress */);
"d"} /* expected_input_keys */);
}
TEST_F(ResumableCompactionJobTest, NoProgressResumeOnDeletionAtBottom) {
@@ -2851,8 +2860,7 @@ TEST_F(ResumableCompactionJobTest, NoProgressResumeOnDeletionAtBottom) {
5U /* last_sequence */, {3U} /* snapshots */,
"b" /* expected_next_key_to_compact */,
{"a", "b", kCancelBeforeThisKey, kCancelBeforeThisKey,
"d"} /* expected_input_keys */,
true /* exists_progress */);
"d"} /* expected_input_keys */);
}
} // namespace ROCKSDB_NAMESPACE
+8 -4
View File
@@ -278,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;
}
@@ -360,7 +364,7 @@ Status CompactionOutputs::AddToOutput(
const CompactionIterator& c_iter,
const CompactionFileOpenFunc& open_file_func,
const CompactionFileCloseFunc& close_file_func,
const ParsedInternalKey& prev_table_last_internal_key) {
const ParsedInternalKey& prev_iter_output_internal_key) {
Status s;
bool is_range_del = c_iter.IsDeleteRangeSentinelKey();
if (is_range_del && compaction_->bottommost_level()) {
@@ -371,8 +375,8 @@ Status CompactionOutputs::AddToOutput(
}
const Slice& key = c_iter.key();
if (ShouldStopBefore(c_iter) && HasBuilder()) {
s = close_file_func(c_iter.InputStatus(), prev_table_last_internal_key, key,
&c_iter, *this);
s = close_file_func(c_iter.InputStatus(), prev_iter_output_internal_key,
key, &c_iter, *this);
if (!s.ok()) {
return s;
}
+1 -1
View File
@@ -262,7 +262,7 @@ class CompactionOutputs {
Status AddToOutput(const CompactionIterator& c_iter,
const CompactionFileOpenFunc& open_file_func,
const CompactionFileCloseFunc& close_file_func,
const ParsedInternalKey& prev_table_last_internal_key);
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.
+11 -6
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);
@@ -465,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;
@@ -521,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)) {
@@ -1231,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());
@@ -1242,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
+8 -2
View File
@@ -203,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,
@@ -222,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);
+47
View File
@@ -4716,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) {
+12 -2
View File
@@ -1544,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;
}
}
@@ -1557,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);
+2 -2
View File
@@ -109,12 +109,12 @@ Status SubcompactionState::AddToOutput(
const CompactionIterator& iter, bool use_proximal_output,
const CompactionFileOpenFunc& open_file_func,
const CompactionFileCloseFunc& close_file_func,
const ParsedInternalKey& prev_table_last_internal_key) {
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,
prev_table_last_internal_key);
prev_iter_output_internal_key);
}
} // namespace ROCKSDB_NAMESPACE
+1 -1
View File
@@ -221,7 +221,7 @@ class SubcompactionState {
Status AddToOutput(const CompactionIterator& iter, bool use_proximal_output,
const CompactionFileOpenFunc& open_file_func,
const CompactionFileCloseFunc& close_file_func,
const ParsedInternalKey& prev_table_last_internal_key);
const ParsedInternalKey& prev_iter_output_internal_key);
// Close all compaction output files, both output_to_proximal_level outputs
// and normal outputs.
+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;
};
+65
View File
@@ -11541,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() {}
+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();
}
+2 -2
View File
@@ -5519,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) {
@@ -5546,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
+3
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();
}
}
});
+38 -24
View File
@@ -1102,11 +1102,6 @@ Status DBImplSecondary::InitializeCompactionWorkspace(
return s;
}
ROCKS_LOG_INFO(immutable_db_options_.info_log,
"Initialized compaction workspace with %zu subcompaction "
"progress to resume",
compaction_progress_.size());
return Status::OK();
}
@@ -1219,6 +1214,11 @@ Status DBImplSecondary::PrepareCompactionProgressState() {
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(
@@ -1299,24 +1299,33 @@ Status DBImplSecondary::CompactWithoutInstallation(
}
Status s;
// TODO(hx235): Resuming compaction is currently incompatible with
// paranoid_file_checks=true because OutputValidator hash verification would
// fail during compaction resumption. Before interruption, resuming
// compaction needs to persist the hash of each output file to enable
// validation after resumption. Alternatively and preferably, we could move
// the output verification to happen immediately after each output file is
// created. This workaround currently disables resuming compaction when
// paranoid_file_checks is enabled. Note that paranoid_file_checks is
// disabled by default.
bool allow_resumption =
options.allow_resumption &&
!cfd->GetLatestMutableCFOptions().paranoid_file_checks;
const auto& mutable_cf_options = cfd->GetLatestMutableCFOptions();
if (options.allow_resumption &&
cfd->GetLatestMutableCFOptions().paranoid_file_checks) {
// 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 "
"incompatible with paranoid_file_checks=true");
"incompatibility with output hash verification "
"(paranoid_file_checks=true or verify_output_flags "
"containing kVerifyIteration)");
}
mutex_.Unlock();
@@ -1345,8 +1354,8 @@ Status DBImplSecondary::CompactWithoutInstallation(
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;
@@ -1384,8 +1393,8 @@ Status DBImplSecondary::CompactWithoutInstallation(
}
c.reset(cfd->compaction_picker()->PickCompactionForCompactFiles(
comp_options, input_files, input.output_level, vstorage,
cfd->GetLatestMutableCFOptions(), mutable_db_options_, 0,
earliest_snapshot, job_context.snapshot_checker));
mutable_cf_options, mutable_db_options_, 0, earliest_snapshot,
job_context.snapshot_checker));
assert(c != nullptr);
c->FinalizeInputInfo(version);
@@ -1731,6 +1740,11 @@ Status DBImplSecondary::FinalizeCompactionProgressWriter(
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
+361
View File
@@ -4154,6 +4154,9 @@ 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;
@@ -4196,6 +4199,8 @@ TEST_P(DBMultiScanIteratorTest, BasicTest) {
}
TEST_P(DBMultiScanIteratorTest, MixedBoundsTest) {
auto options = CurrentOptions();
DestroyAndReopen(options);
// Create a file
for (int i = 0; i < 100; ++i) {
std::stringstream ss;
@@ -4602,6 +4607,11 @@ TEST_P(DBMultiScanIteratorTest, FragmentedRangeTombstones) {
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
@@ -4666,6 +4676,357 @@ TEST_P(DBMultiScanIteratorTest, FragmentedRangeTombstones) {
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) {
+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) {
+4 -4
View File
@@ -5179,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) {
@@ -5637,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;
@@ -6372,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) {
+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;
+41
View File
@@ -303,6 +303,47 @@ TEST_F(ObsoleteFilesTest, BlobFiles) {
ASSERT_EQ(deleted_files, expected_deleted_files);
}
TEST_F(ObsoleteFilesTest, GetSortedWalFilesHangsAfterNoopPurge) {
// This test used to trigger a hang in `DB::GetSortedWalFiles()`, where it
// would wait for a no-op purge that did not signal the CV upon completion.
// Grab an iterator and flush to switch the super version. That way, when the
// iterator is destroyed, it will go through the purge path.
DB* db = db_; // Only using `db` makes it clear we only use DB-level APIs.
ASSERT_OK(db->Put(WriteOptions(), "key", "value"));
std::unique_ptr<Iterator> iter(db->NewIterator(ReadOptions()));
ASSERT_OK(db->Flush(FlushOptions()));
// Sync points ensure `GetSortedWalFiles()` waits for a purge after
// `FindObsoleteFiles()` releases the mutex but before its corresponding purge
// completes.
SyncPoint::GetInstance()->SetCallBack(
"FindObsoleteFiles::PostMutexUnlock", [&](void* /* arg */) {
TEST_SYNC_POINT(
"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:"
"InCallback:1");
TEST_SYNC_POINT(
"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:"
"InCallback:2");
});
SyncPoint::GetInstance()->LoadDependency({
{"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:InCallback:1",
"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:Thread:Begin"},
{"DBImpl::GetSortedWalFilesImpl:WaitPurge",
"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:InCallback:2"},
});
SyncPoint::GetInstance()->EnableProcessing();
port::Thread get_sorted_wal_files_thread([db]() {
TEST_SYNC_POINT(
"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:Thread:Begin");
VectorWalPtr files;
ASSERT_OK(db->GetSortedWalFiles(files));
});
iter.reset();
get_sorted_wal_files_thread.join();
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+7 -4
View File
@@ -557,16 +557,19 @@ struct SubcompactionProgress {
Slice key_slice(next_internal_key_to_compact);
if (ParseInternalKey(key_slice, &parsed_key, false /* log_err_key */)
.ok()) {
oss << "user_key=\"" << parsed_key.user_key.ToString(false /* hex */)
<< "\" (hex:" << parsed_key.user_key.ToString(true /* hex */)
<< ")";
oss << "user_key(hex)=" << parsed_key.user_key.ToString(true /* hex */);
oss << ", seq=";
if (parsed_key.sequence == kMaxSequenceNumber) {
oss << "kMaxSequenceNumber";
} else {
oss << parsed_key.sequence;
}
oss << ", type=" << static_cast<int>(parsed_key.type);
oss << ", type=";
if (parsed_key.type == kValueTypeForSeek) {
oss << "kValueTypeForSeek";
} else {
oss << static_cast<int>(parsed_key.type);
}
} else {
oss << "raw=" << key_slice.ToString(true /* hex */);
}
+166 -79
View File
@@ -16,6 +16,7 @@
#include <list>
#include <map>
#include <set>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <vector>
@@ -980,7 +981,8 @@ class LevelIterator final : public InternalIterator {
nullptr,
bool allow_unprepared_value = false,
std::unique_ptr<TruncatedRangeDelIterator>*** range_tombstone_iter_ptr_ =
nullptr)
nullptr,
Statistics* db_statistics = nullptr, SystemClock* clock = nullptr)
: table_cache_(table_cache),
read_options_(read_options),
file_options_(file_options),
@@ -1005,7 +1007,9 @@ class LevelIterator final : public InternalIterator {
allow_unprepared_value_(allow_unprepared_value),
is_next_read_sequential_(false),
to_return_sentinel_(false),
scan_opts_(nullptr) {
scan_opts_(nullptr),
db_statistics_(db_statistics),
clock_(clock) {
// Empty level is not supported.
assert(flevel_ != nullptr && flevel_->num_files > 0);
if (range_tombstone_iter_ptr_) {
@@ -1013,7 +1017,15 @@ class LevelIterator final : public InternalIterator {
}
}
~LevelIterator() override { delete file_iter_.Set(nullptr); }
~LevelIterator() override {
delete file_iter_.Set(nullptr);
// Clean up any prepared iterators that weren't used
for (auto& entry : prepared_iters_) {
delete entry.second;
}
prepared_iters_.clear();
assert(prepared_iters_.size() == 0);
}
// Seek to the first file with a key >= target.
// If range_tombstone_iter_ is not nullptr, then we pretend that file
@@ -1124,10 +1136,12 @@ class LevelIterator final : public InternalIterator {
void Prepare(const MultiScanArgs* so) override {
// We assume here that scan_opts is sorted such that
// scan_opts[0].range.start < scan_opts[1].range.start, and non overlapping
// scan_opts[0].range.start < scan_opts[1].range.start, and non
// overlapping
if (so == nullptr) {
return;
}
scan_opts_ = so;
// Verify comparator is consistent
@@ -1188,8 +1202,8 @@ class LevelIterator final : public InternalIterator {
continue;
}
auto const metadata = flevel_->files[i].file_metadata;
if (metadata->num_entries == metadata->num_range_deletions) {
// Skip range deletion only files.
if (metadata->FileIsStandAloneRangeTombstone()) {
// Skip stand alone range deletion files.
continue;
}
auto& args = GetMultiScanArgForFile(i);
@@ -1197,9 +1211,35 @@ class LevelIterator final : public InternalIterator {
}
}
}
StopWatch timer(clock_, db_statistics_, MULTISCAN_PREPARE_ITERATORS);
// Propagate multiscan configs
for (auto& file_to_arg : *file_to_scan_opts_) {
file_to_arg.second.CopyConfigFrom(*so);
assert(OverlapRange(*file_to_arg.second.GetScanRanges().begin(),
file_to_arg.first) &&
OverlapRange(*file_to_arg.second.GetScanRanges().rbegin(),
file_to_arg.first));
}
if (so->use_async_io) {
auto before = file_index_;
// Pre-create and prepare only relevant file iterators
for (auto& file_to_arg : *file_to_scan_opts_) {
size_t file_index = file_to_arg.first;
file_index_ = file_index;
// Create iterator for this file
auto iter = NewFileIterator();
if (iter != nullptr) {
// If we have async enabled, lets prepare all our iterators.
iter->Prepare(&file_to_arg.second);
// Store the prepared iterator
prepared_iters_[file_index] = iter;
}
}
file_index_ = before;
}
}
@@ -1276,7 +1316,7 @@ class LevelIterator final : public InternalIterator {
}
#ifndef NDEBUG
bool OverlapRange(const ScanOptions& opts);
bool OverlapRange(const ScanOptions& opts, size_t file_index);
#endif
TableCache* table_cache_;
@@ -1334,9 +1374,15 @@ class LevelIterator final : public InternalIterator {
bool to_return_sentinel_ = false;
const MultiScanArgs* scan_opts_ = nullptr;
Statistics* db_statistics_ = nullptr;
SystemClock* clock_ = nullptr;
// Our stored scan_opts for each prefix
std::unique_ptr<ScanOptionsMap> file_to_scan_opts_ = nullptr;
// Map to store pre-created iterators by file index
std::unordered_map<size_t, InternalIterator*> prepared_iters_;
// Sets flags for if we should return the sentinel key next.
// The condition for returning sentinel is reaching the end of current
// file_iter_: !Valid() && status.().ok().
@@ -1613,8 +1659,19 @@ bool LevelIterator::SkipEmptyFileForward() {
const ScanOptions& opts =
GetMultiScanArgForFile(file_index_).GetScanRanges().front();
if (opts.range.start.has_value()) {
InternalKey target(*opts.range.start.AsPtr(), kMaxSequenceNumber,
kValueTypeForSeek);
InternalKey target;
const size_t ts_size =
user_comparator_.user_comparator()->timestamp_size();
if (ts_size == 0) {
target = InternalKey(opts.range.start.value(), kMaxSequenceNumber,
kValueTypeForSeek);
} else {
std::string seek_key;
AppendKeyWithMaxTimestamp(&seek_key, opts.range.start.value(),
ts_size);
target =
InternalKey(seek_key, kMaxSequenceNumber, kValueTypeForSeek);
}
file_iter_.Seek(target.Encode());
}
} else {
@@ -1662,14 +1719,14 @@ void LevelIterator::SkipEmptyFileBackward() {
}
#ifndef NDEBUG
bool LevelIterator::OverlapRange(const ScanOptions& opts) {
bool LevelIterator::OverlapRange(const ScanOptions& opts, size_t file_index) {
return (user_comparator_.CompareWithoutTimestamp(
opts.range.start.value(), /*a_has_ts=*/false,
ExtractUserKey(flevel_->files[file_index_].largest_key),
ExtractUserKey(flevel_->files[file_index].largest_key),
/*b_has_ts=*/true) <= 0 &&
user_comparator_.CompareWithoutTimestamp(
opts.range.limit.value(), /*a_has_ts=*/false,
ExtractUserKey(flevel_->files[file_index_].smallest_key),
ExtractUserKey(flevel_->files[file_index].smallest_key),
/*b_has_ts=*/true) > 0);
}
#endif
@@ -1680,15 +1737,6 @@ void LevelIterator::SetFileIterator(InternalIterator* iter) {
}
InternalIterator* old_iter = file_iter_.Set(iter);
if (iter && scan_opts_) {
if (FileHasMultiScanArg(file_index_)) {
const MultiScanArgs& new_opts = GetMultiScanArgForFile(file_index_);
assert(OverlapRange(*new_opts.GetScanRanges().begin()) &&
OverlapRange(*new_opts.GetScanRanges().rbegin()));
file_iter_.Prepare(&new_opts);
}
}
// Update the read pattern for PrefetchBuffer.
if (is_next_read_sequential_) {
file_iter_.UpdateReadaheadState(old_iter);
@@ -1718,7 +1766,24 @@ void LevelIterator::InitFileIterator(size_t new_file_index) {
// no need to change anything
} else {
file_index_ = new_file_index;
if (!prepared_iters_.empty()) {
auto prepared_it = prepared_iters_.find(file_index_);
if (prepared_it != prepared_iters_.end()) {
InternalIterator* iter = prepared_it->second;
prepared_iters_.erase(prepared_it);
SetFileIterator(iter);
return;
}
}
InternalIterator* iter = NewFileIterator();
if (FileHasMultiScanArg(file_index_)) {
auto& args = GetMultiScanArgForFile(file_index_);
assert(OverlapRange(*args.GetScanRanges().begin(), file_index_) &&
OverlapRange(*args.GetScanRanges().rbegin(), file_index_));
iter->Prepare(&args);
}
SetFileIterator(iter);
}
}
@@ -2181,7 +2246,7 @@ InternalIterator* Version::TEST_GetLevelIterator(
cfd_->internal_stats()->GetFileReadHist(level),
TableReaderCaller::kUserIterator, IsFilterSkipped(level), level,
nullptr /* range_del_agg */, nullptr /* compaction_boundaries */,
allow_unprepared_value, &tombstone_iter_ptr);
allow_unprepared_value, &tombstone_iter_ptr, db_statistics_, clock_);
if (read_options.ignore_range_deletions) {
merge_iter_builder->AddIterator(level_iter);
} else {
@@ -2321,7 +2386,7 @@ void Version::AddIteratorsForLevel(const ReadOptions& read_options,
TableReaderCaller::kUserIterator, IsFilterSkipped(level), level,
/*range_del_agg=*/nullptr,
/*compaction_boundaries=*/nullptr, allow_unprepared_value,
&tombstone_iter_ptr);
&tombstone_iter_ptr, db_statistics_, clock_);
if (read_options.ignore_range_deletions) {
merge_iter_builder->AddIterator(level_iter);
} else {
@@ -2378,7 +2443,7 @@ Status Version::OverlapWithLevelIterator(const ReadOptions& read_options,
mutable_cf_options_, should_sample_file_read(),
cfd_->internal_stats()->GetFileReadHist(level),
TableReaderCaller::kUserIterator, IsFilterSkipped(level), level,
&range_del_agg, nullptr, false));
&range_del_agg, nullptr, false, nullptr, db_statistics_, clock_));
status = OverlapWithIterator(ucmp, smallest_user_key, largest_user_key,
iter.get(), overlap);
}
@@ -4560,7 +4625,8 @@ bool VersionStorageInfo::OverlapInLevel(int level,
void VersionStorageInfo::GetOverlappingInputs(
int level, const InternalKey* begin, const InternalKey* end,
std::vector<FileMetaData*>* inputs, int hint_index, int* file_index,
bool expand_range, InternalKey** next_smallest) const {
bool expand_range, const FileMetaData* starting_l0_file,
InternalKey** next_smallest) const {
if (level >= num_non_empty_levels_) {
// this level is empty, no overlapping inputs
return;
@@ -4593,7 +4659,19 @@ void VersionStorageInfo::GetOverlappingInputs(
// index stores the file index need to check.
std::list<size_t> index;
for (size_t i = 0; i < level_files_brief_[level].num_files; i++) {
size_t start_index = 0;
if (starting_l0_file != nullptr) {
uint64_t starting_file_number = starting_l0_file->fd.GetNumber();
for (size_t i = 0; i < level_files_brief_[level].num_files; i++) {
if (level_files_brief_[level].files[i].fd.GetNumber() ==
starting_file_number) {
start_index = i;
break;
}
}
assert(start_index < level_files_brief_[level].num_files);
}
for (size_t i = start_index; i < level_files_brief_[level].num_files; i++) {
index.emplace_back(i);
}
@@ -5525,8 +5603,11 @@ void VersionSet::Reset() {
void VersionSet::UpdatedMutableDbOptions(
const MutableDBOptions& updated_options, InstrumentedMutex* mu) {
// Must be holding mutex if not called during initialization
if (manifest_file_size_ > 0) {
if (mu) {
mu->AssertHeld();
} else {
// manifest_file_size_ must be 0 if called from the constructor
assert(manifest_file_size_ == 0);
}
file_options_.writable_file_max_buffer_size =
updated_options.writable_file_max_buffer_size;
@@ -5626,8 +5707,8 @@ Status VersionSet::ProcessManifestWrites(
// the preceding version edits in the same atomic group, and update
// their `remaining_entries_` member variable because we are NOT going
// to write the version edits' of dropped CF to the MANIFEST. If we
// don't update, then Recover can report corrupted atomic group because
// the `remaining_entries_` do not match.
// don't update, then Recover can report corrupted atomic group
// because the `remaining_entries_` do not match.
if (!batch_edits.empty()) {
if (batch_edits.back()->IsInAtomicGroup() &&
batch_edits.back()->GetRemainingEntries() > 0) {
@@ -5932,10 +6013,12 @@ Status VersionSet::ProcessManifestWrites(
#ifndef NDEBUG
if (batch_edits.size() > 1 && batch_edits.size() - 1 == idx) {
TEST_SYNC_POINT_CALLBACK(
"VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:0",
"VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:"
"0",
nullptr);
TEST_SYNC_POINT(
"VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:1");
"VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:"
"1");
}
++idx;
#endif /* !NDEBUG */
@@ -5972,8 +6055,8 @@ Status VersionSet::ProcessManifestWrites(
file_options_.temperature, dir_contains_current_file);
if (!io_s.ok()) {
s = io_s;
// Quarantine old manifest file in case new manifest file's CURRENT file
// wasn't created successfully and the old manifest is needed.
// Quarantine old manifest file in case new manifest file's CURRENT
// file wasn't created successfully and the old manifest is needed.
limbo_descriptor_log_file_number.push_back(manifest_file_number_);
files_to_quarantine_if_commit_fail.push_back(
&limbo_descriptor_log_file_number);
@@ -6122,21 +6205,21 @@ Status VersionSet::ProcessManifestWrites(
// that renaming tmp file to CURRENT failed.
//
// On local POSIX-compliant FS, the CURRENT must point to the original
// MANIFEST. We can delete the new MANIFEST for simplicity, but we can also
// keep it. Future recovery will ignore this MANIFEST. It's also ok for the
// process not to crash and continue using the db. Any future LogAndApply()
// call will switch to a new MANIFEST and update CURRENT, still ignoring
// this one.
// MANIFEST. We can delete the new MANIFEST for simplicity, but we can
// also keep it. Future recovery will ignore this MANIFEST. It's also ok
// for the process not to crash and continue using the db. Any future
// LogAndApply() call will switch to a new MANIFEST and update CURRENT,
// still ignoring this one.
//
// On non-local FS, it is
// possible that the rename operation succeeded on the server (remote)
// side, but the client somehow returns a non-ok status to RocksDB. Note
// that this does not violate atomicity. Should we delete the new MANIFEST
// successfully, a subsequent recovery attempt will likely see the CURRENT
// pointing to the new MANIFEST, thus fail. We will not be able to open the
// DB again. Therefore, if manifest operations succeed, we should keep the
// the new MANIFEST. If the process proceeds, any future LogAndApply() call
// will switch to a new MANIFEST and update CURRENT. If user tries to
// pointing to the new MANIFEST, thus fail. We will not be able to open
// the DB again. Therefore, if manifest operations succeed, we should keep
// the the new MANIFEST. If the process proceeds, any future LogAndApply()
// call will switch to a new MANIFEST and update CURRENT. If user tries to
// re-open the DB,
// a) CURRENT points to the new MANIFEST, and the new MANIFEST is present.
// b) CURRENT points to the original MANIFEST, and the original MANIFEST
@@ -6265,9 +6348,9 @@ Status VersionSet::LogAndApply(
first_writer.cv.Wait();
}
if (first_writer.done) {
// All non-CF-manipulation operations can be grouped together and committed
// to MANIFEST. They should all have finished. The status code is stored in
// the first manifest writer.
// All non-CF-manipulation operations can be grouped together and
// committed to MANIFEST. They should all have finished. The status code
// is stored in the first manifest writer.
#ifndef NDEBUG
for (const auto& writer : writers) {
assert(writer.done);
@@ -6321,8 +6404,8 @@ void VersionSet::LogAndApplyCFHelper(VersionEdit* edit,
assert(!edit->HasLastSequence());
edit->SetLastSequence(*max_last_sequence);
if (edit->IsColumnFamilyDrop()) {
// if we drop column family, we have to make sure to save max column family,
// so that we don't reuse existing ID
// if we drop column family, we have to make sure to save max column
// family, so that we don't reuse existing ID
edit->SetMaxColumnFamily(column_family_set_->GetMaxColumnFamily());
}
}
@@ -6611,7 +6694,8 @@ void VersionSet::RecoverEpochNumbers() {
Status VersionSet::ListColumnFamilies(std::vector<std::string>* column_families,
const std::string& dbname,
FileSystem* fs) {
// Read "CURRENT" file, which contains a pointer to the current manifest file
// Read "CURRENT" file, which contains a pointer to the current manifest
// file
std::string manifest_path;
uint64_t manifest_file_number;
Status s = GetCurrentManifestPath(dbname, fs, /*is_retry=*/false,
@@ -6768,9 +6852,9 @@ Status VersionSet::ReduceNumberOfLevels(const std::string& dbname,
// Get the checksum information including the checksum and checksum function
// name of all SST and blob files in VersionSet. Store the information in
// FileChecksumList which contains a map from file number to its checksum info.
// If DB is not running, make sure call VersionSet::Recover() to load the file
// metadata from Manifest to VersionSet before calling this function.
// FileChecksumList which contains a map from file number to its checksum
// info. If DB is not running, make sure call VersionSet::Recover() to load
// the file metadata from Manifest to VersionSet before calling this function.
Status VersionSet::GetLiveFilesChecksumInfo(FileChecksumList* checksum_list) {
// Clean the previously stored checksum information if any.
Status s;
@@ -6912,8 +6996,8 @@ Status VersionSet::WriteCurrentStateToManifest(
// WARNING: This method doesn't hold a mutex!!
// This is done without DB mutex lock held, but only within single-threaded
// LogAndApply. Column family manipulations can only happen within LogAndApply
// (the same single thread), so we're safe to iterate.
// LogAndApply. Column family manipulations can only happen within
// LogAndApply (the same single thread), so we're safe to iterate.
assert(io_s.ok());
if (db_options_->write_dbid_to_manifest) {
@@ -6947,9 +7031,9 @@ Status VersionSet::WriteCurrentStateToManifest(
}
// New manifest should rollover the WAL deletion record from previous
// manifest. Otherwise, when an addition record of a deleted WAL gets added to
// this new manifest later (which can happens in e.g, SyncWAL()), this new
// manifest creates an illusion that such WAL hasn't been deleted.
// manifest. Otherwise, when an addition record of a deleted WAL gets added
// to this new manifest later (which can happens in e.g, SyncWAL()), this
// new manifest creates an illusion that such WAL hasn't been deleted.
VersionEdit wal_deletions;
wal_deletions.DeleteWalsBefore(min_log_number_to_keep());
std::string wal_deletions_record;
@@ -7081,9 +7165,9 @@ Status VersionSet::WriteCurrentStateToManifest(
// TODO(aekmekji): in CompactionJob::GenSubcompactionBoundaries(), this
// function is called repeatedly with consecutive pairs of slices. For example
// if the slice list is [a, b, c, d] this function is called with arguments
// (a,b) then (b,c) then (c,d). Knowing this, an optimization is possible where
// we avoid doing binary search for the keys b and c twice and instead somehow
// maintain state of where they first appear in the files.
// (a,b) then (b,c) then (c,d). Knowing this, an optimization is possible
// where we avoid doing binary search for the keys b and c twice and instead
// somehow maintain state of where they first appear in the files.
uint64_t VersionSet::ApproximateSize(const SizeApproximationOptions& options,
const ReadOptions& read_options,
Version* v, const Slice& start,
@@ -7104,19 +7188,20 @@ uint64_t VersionSet::ApproximateSize(const SizeApproximationOptions& options,
}
// Outline of the optimization that uses options.files_size_error_margin.
// When approximating the files total size that is used to store a keys range,
// we first sum up the sizes of the files that fully fall into the range.
// Then we sum up the sizes of all the files that may intersect with the range
// (this includes all files in L0 as well). Then, if total_intersecting_size
// is smaller than total_full_size * options.files_size_error_margin - we can
// infer that the intersecting files have a sufficiently negligible
// contribution to the total size, and we can approximate the storage required
// for the keys in range as just half of the intersecting_files_size.
// E.g., if the value of files_size_error_margin is 0.1, then the error of the
// approximation is limited to only ~10% of the total size of files that fully
// fall into the keys range. In such case, this helps to avoid a costly
// process of binary searching the intersecting files that is required only
// for a more precise calculation of the total size.
// When approximating the files total size that is used to store a keys
// range, we first sum up the sizes of the files that fully fall into the
// range. Then we sum up the sizes of all the files that may intersect with
// the range (this includes all files in L0 as well). Then, if
// total_intersecting_size is smaller than total_full_size *
// options.files_size_error_margin - we can infer that the intersecting
// files have a sufficiently negligible contribution to the total size, and
// we can approximate the storage required for the keys in range as just
// half of the intersecting_files_size. E.g., if the value of
// files_size_error_margin is 0.1, then the error of the approximation is
// limited to only ~10% of the total size of files that fully fall into the
// keys range. In such case, this helps to avoid a costly process of binary
// searching the intersecting files that is required only for a more precise
// calculation of the total size.
autovector<FdWithKeyRange*, 32> first_files;
autovector<FdWithKeyRange*, 16> last_files;
@@ -7188,10 +7273,11 @@ uint64_t VersionSet::ApproximateSize(const SizeApproximationOptions& options,
total_intersecting_size += file_ptr->fd.GetFileSize();
}
// Now scan all the first & last files at each level, and estimate their size.
// If the total_intersecting_size is less than X% of the total_full_size - we
// want to approximate the result in order to avoid the costly binary search
// inside ApproximateSize. We use half of file size as an approximation below.
// Now scan all the first & last files at each level, and estimate their
// size. If the total_intersecting_size is less than X% of the
// total_full_size - we want to approximate the result in order to avoid the
// costly binary search inside ApproximateSize. We use half of file size as
// an approximation below.
const double margin = options.files_size_error_margin;
if (margin > 0 && total_intersecting_size <
@@ -7459,7 +7545,8 @@ InternalIterator* VersionSet::MakeInputIterator(
/*no per level latency histogram=*/nullptr,
TableReaderCaller::kCompaction, /*skip_filters=*/false,
/*level=*/static_cast<int>(c->level(which)), range_del_agg,
c->boundaries(which), false, &tombstone_iter_ptr);
c->boundaries(which), false, &tombstone_iter_ptr,
db_options_->statistics.get(), clock_);
range_tombstones.emplace_back(nullptr, tombstone_iter_ptr);
}
}
@@ -7864,8 +7951,8 @@ Status ReactiveVersionSet::MaybeSwitchManifest(
}
} else if (s.IsPathNotFound()) {
// This can happen if the primary switches to a new MANIFEST after the
// secondary reads the CURRENT file but before the secondary actually tries
// to open the MANIFEST.
// secondary reads the CURRENT file but before the secondary actually
// tries to open the MANIFEST.
s = Status::TryAgain(
"The primary may have switched to a new MANIFEST and deleted the old "
"one.");
+11 -4
View File
@@ -268,8 +268,13 @@ class VersionStorageInfo {
bool expand_range = true, // if set, returns files which overlap the
// range and overlap each other. If false,
// then just files intersecting the range
InternalKey** next_smallest = nullptr) // if non-null, returns the
const; // smallest key of next file not included
const FileMetaData* starting_l0_file =
nullptr, // If not null, restricts L0 file selection to only include
// files at or older than starting_l0_file.
InternalKey** next_smallest =
nullptr // if non-null, returns the
// smallest key of next file not included
) const;
void GetCleanInputsWithinInterval(
int level, const InternalKey* begin, // nullptr means before all keys
const InternalKey* end, // nullptr means after all keys
@@ -286,8 +291,10 @@ class VersionStorageInfo {
int hint_index, // index of overlap file
int* file_index, // return index of overlap file
bool within_interval = false, // if set, force the inputs within interval
InternalKey** next_smallest = nullptr) // if non-null, returns the
const; // smallest key of next file not included
InternalKey** next_smallest =
nullptr // if non-null, returns the
// smallest key of next file not included
) const;
// Returns true iff some file in the specified level overlaps
// some part of [*smallest_user_key,*largest_user_key].
+7 -1
View File
@@ -192,7 +192,13 @@ void WalManager::PurgeObsoleteWALFiles() {
s.ToString().c_str());
continue;
}
if (now_seconds - file_m_time > db_options_.WAL_ttl_seconds) {
// Avoid expression `now_seconds - file_m_time` when
// `file_m_time > now_seconds` to prevent unsigned underflow in case
// system clock goes backwards. Both timestamps are based on wall clock
// time, which is not guaranteed to be monotonic.
if (file_m_time <= now_seconds &&
now_seconds - file_m_time > db_options_.WAL_ttl_seconds) {
s = DeleteDBFile(&db_options_, file_path, archival_dir, false,
/*force_fg=*/!wal_in_db_path_);
if (!s.ok()) {
+46 -8
View File
@@ -19,6 +19,7 @@
#include "rocksdb/write_batch.h"
#include "rocksdb/write_buffer_manager.h"
#include "table/mock_table.h"
#include "test_util/mock_time_env.h"
#include "test_util/testharness.h"
#include "test_util/testutil.h"
#include "util/string_util.h"
@@ -39,7 +40,7 @@ class WalManagerTest : public testing::Test {
EXPECT_OK(DestroyDB(dbname_, Options()));
}
void Init() {
void Init(SystemClock* clock_override) {
ASSERT_OK(env_->CreateDirIfMissing(dbname_));
ASSERT_OK(env_->CreateDirIfMissing(ArchivalDirectory(dbname_)));
db_options_.db_paths.emplace_back(dbname_,
@@ -47,7 +48,11 @@ class WalManagerTest : public testing::Test {
db_options_.wal_dir = dbname_;
db_options_.env = env_.get();
db_options_.fs = env_->GetFileSystem();
db_options_.clock = env_->GetSystemClock().get();
if (clock_override == nullptr) {
db_options_.clock = env_->GetSystemClock().get();
} else {
db_options_.clock = clock_override;
}
versions_.reset(new VersionSet(
dbname_, &db_options_, MutableDBOptions{}, env_options_,
@@ -124,7 +129,7 @@ class WalManagerTest : public testing::Test {
};
TEST_F(WalManagerTest, ReadFirstRecordCache) {
Init();
Init(nullptr /* clock_override */);
std::string path = dbname_ + "/000001.log";
std::unique_ptr<FSWritableFile> file;
ASSERT_OK(env_->GetFileSystem()->NewWritableFile(path, FileOptions(), &file,
@@ -221,7 +226,7 @@ int CountRecords(TransactionLogIterator* iter) {
TEST_F(WalManagerTest, WALArchivalSizeLimit) {
db_options_.WAL_ttl_seconds = 0;
db_options_.WAL_size_limit_MB = 1000;
Init();
Init(nullptr /* clock_override */);
// TEST : Create WalManager with huge size limit and no ttl.
// Create some archived files and call PurgeObsoleteWALFiles().
@@ -258,7 +263,7 @@ TEST_F(WalManagerTest, WALArchivalSizeLimit) {
TEST_F(WalManagerTest, WALArchivalTtl) {
db_options_.WAL_ttl_seconds = 1000;
Init();
Init(nullptr /* clock_override */);
// TEST : Create WalManager with a ttl and no size limit.
// Create some archived log files and call PurgeObsoleteWALFiles().
@@ -282,8 +287,41 @@ TEST_F(WalManagerTest, WALArchivalTtl) {
ASSERT_TRUE(log_files.empty());
}
TEST_F(WalManagerTest, WALArchivalTtlClockGoesBackwards) {
// This test used to trigger an unsigned underflow bug, where WAL files were
// incorrectly deleted when the system time moved backwards between writing
// to a WAL and running `WalManager::PurgeObsoleteWALFiles()`.
constexpr int kNumLogs = 5;
constexpr int kEntriesPerLog = 100;
db_options_.WAL_ttl_seconds = 86400; // One day
// Configure mock clock to lag one second behind system time. That way, the
// WAL file's mtime will appear to be in the future when
// `WalManager::PurgeObsoleteWALFiles()` runs.
int64_t now_seconds;
ASSERT_OK(env_->GetSystemClock()->GetCurrentTime(&now_seconds));
auto mock_clock = std::make_shared<MockSystemClock>(env_->GetSystemClock());
mock_clock->SetCurrentTime(static_cast<uint64_t>(now_seconds - 1));
db_options_.clock = mock_clock.get();
Init(mock_clock.get() /* clock */);
CreateArchiveLogs(kNumLogs, kEntriesPerLog);
const std::string archive_dir = ArchivalDirectory(dbname_);
ASSERT_EQ(kNumLogs,
ListSpecificFiles(env_.get(), archive_dir, kWalFile).size());
wal_manager_->PurgeObsoleteWALFiles();
// All files must still be present because TTL has not elapsed.
ASSERT_EQ(kNumLogs,
ListSpecificFiles(env_.get(), archive_dir, kWalFile).size());
}
TEST_F(WalManagerTest, TransactionLogIteratorMoveOverZeroFiles) {
Init();
Init(nullptr /* clock_override */);
RollTheLog(false);
Put("key1", std::string(1024, 'a'));
// Create a zero record WAL file.
@@ -297,7 +335,7 @@ TEST_F(WalManagerTest, TransactionLogIteratorMoveOverZeroFiles) {
}
TEST_F(WalManagerTest, TransactionLogIteratorJustEmptyFile) {
Init();
Init(nullptr /* clock_override */);
RollTheLog(false);
auto iter = OpenTransactionLogIter(0);
// Check that an empty iterator is returned
@@ -305,7 +343,7 @@ TEST_F(WalManagerTest, TransactionLogIteratorJustEmptyFile) {
}
TEST_F(WalManagerTest, TransactionLogIteratorNewFileWhileScanning) {
Init();
Init(nullptr /* clock_override */);
CreateArchiveLogs(2, 100);
auto iter = OpenTransactionLogIter(0);
CreateArchiveLogs(1, 100);
+15 -5
View File
@@ -25,6 +25,7 @@
#include "db_stress_tool/db_stress_filters.h"
#include "db_stress_tool/db_stress_table_properties_collector.h"
#include "db_stress_tool/db_stress_wide_merge_operator.h"
#include "file/file_util.h"
#include "options/options_parser.h"
#include "rocksdb/convenience.h"
#include "rocksdb/filter_policy.h"
@@ -194,10 +195,10 @@ std::shared_ptr<Cache> StressTest::NewCache(size_t capacity,
exit(1);
} else if (EndsWith(cache_type, "hyper_clock_cache")) {
size_t estimated_entry_charge;
if (cache_type == "fixed_hyper_clock_cache" ||
cache_type == "hyper_clock_cache") {
if (cache_type == "fixed_hyper_clock_cache") {
estimated_entry_charge = FLAGS_block_size;
} else if (cache_type == "auto_hyper_clock_cache") {
} else if (cache_type == "auto_hyper_clock_cache" ||
cache_type == "hyper_clock_cache") {
estimated_entry_charge = 0;
} else {
fprintf(stderr, "Cache type not supported.");
@@ -349,7 +350,6 @@ bool StressTest::BuildOptionsTable() {
"1",
"2",
}},
{"max_sequential_skip_in_iterations", {"4", "8", "12"}},
{"block_based_table_factory",
{
keepRibbonFilterPolicyOnly ? "{filter_policy=ribbonfilter:2.35}"
@@ -362,6 +362,13 @@ bool StressTest::BuildOptionsTable() {
std::to_string(FLAGS_block_size + (FLAGS_seed & 0xFFFU)) + "}",
}},
};
if (FLAGS_use_multiscan == 0) {
// TODO: this can fail MultiScan when consecutive data blocks share the
// same user at boundary. MultiScan uses user key to locate the block to
// reach which can move the scan earlier than its current block.
options_tbl.emplace("max_sequential_skip_in_iterations",
std::vector<std::string>{"4", "8", "12"});
}
if (FLAGS_compaction_style == kCompactionStyleUniversal &&
FLAGS_universal_max_read_amp > 0) {
// level0_file_num_compaction_trigger needs to be at most max_read_amp
@@ -1695,7 +1702,10 @@ Status StressTest::TestMultiScan(ThreadState* thread,
std::vector<std::string> end_key_strs;
// TODO support reverse BytewiseComparator in the stress test
MultiScanArgs scan_opts(options_.comparator);
scan_opts.use_async_io = FLAGS_multiscan_use_async_io;
scan_opts.use_async_io =
FLAGS_multiscan_use_async_io &&
CheckFSFeatureSupport(options_.env->GetFileSystem().get(),
FSSupportedOps::kAsyncIO);
start_key_strs.reserve(num_scans);
end_key_strs.reserve(num_scans);
+148
View File
@@ -0,0 +1,148 @@
# This file contains the vast majority of folly-related build configuration
# for the checkout_folly and build_folly targets, so that this file can be
# hashed for purposes of caching folly builds and not hitting that cache when
# something here changes.
# 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 $(LIBEVENT_PATH)/lib/libevent-2.1.so $(LIBSODIUM_PATH)/lib/libsodium.a -ldl
ifneq ($(DEBUG_LEVEL),0)
PLATFORM_LDFLAGS += $(FMT_LIB_PATH)/libfmtd.a $(GLOG_LIB_PATH)/libglogd.so $(GFLAGS_PATH)/lib/libgflags_debug.so.2.2
else
PLATFORM_LDFLAGS += $(FMT_LIB_PATH)/libfmt.a $(GLOG_LIB_PATH)/libglog.so $(GFLAGS_PATH)/lib/libgflags.so.2.2
endif
PLATFORM_LDFLAGS += -Wl,-rpath=$(LIBEVENT_PATH)/lib -Wl,-rpath=$(GLOG_LIB_PATH) -Wl,-rpath=$(GFLAGS_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
ifneq ($(strip $(FMT_SOURCE_PATH)),)
FMT_INCLUDE = $(shell (ls -d $(FMT_SOURCE_PATH)/fmt*/include/))
# AIX: pre-defined system headers are surrounded by an extern "C" block
ifeq ($(PLATFORM), OS_AIX)
PLATFORM_CCFLAGS += -I$(FMT_INCLUDE)
PLATFORM_CXXFLAGS += -I$(FMT_INCLUDE)
else
PLATFORM_CCFLAGS += -isystem $(FMT_INCLUDE)
PLATFORM_CXXFLAGS += -isystem $(FMT_INCLUDE)
endif
endif # FMT_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
FOLLY_COMMIT_HASH = abe68f7e917e8b7a0ee2fe066c972dc98fd35aa1
# 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 $(FOLLY_COMMIT_HASH)
@# Apparently missing include
perl -pi -e 's/(#include <atomic>)/$$1\n#include <cstring>/' third-party/folly/folly/lang/Exception.h
@# const mismatch
perl -pi -e 's/: environ/: (const char**)(environ)/' third-party/folly/folly/Subprocess.cpp
@# Use gnu.org mirrors to improve download speed (ftp.gnu.org is often super slow)
cd third-party/folly && perl -pi -e 's/ftp.gnu.org/ftpmirror.gnu.org/' `git grep -l ftp.gnu.org` README.md
@# NOTE: boost and fmt source will be needed for any build including `USE_FOLLY_LITE` builds as those depend on those headers
cd third-party/folly && GETDEPS_USE_WGET=1 $(PYTHON) build/fbcode_builder/getdeps.py fetch boost && GETDEPS_USE_WGET=1 $(PYTHON) build/fbcode_builder/getdeps.py fetch fmt
CXX_M_FLAGS = $(filter -m%, $(CXXFLAGS))
FOLLY_BUILD_FLAGS = --no-tests
# NOTE: To avoid ODR violations, we must build folly in debug mode iff
# building RocksDB in debug mode.
ifneq ($(DEBUG_LEVEL),0)
FOLLY_BUILD_FLAGS += --build-type Debug
endif
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 " GETDEPS_USE_WGET=1 $(PYTHON) build/fbcode_builder/getdeps.py build $(FOLLY_BUILD_FLAGS)
@# In the folly build, glog and gflags are only built as dynamic libraries,
@# not static. This patchelf command is needed to reliably have the glog
@# library find its dependency gflags, because apparently the rpath of the
@# final binary is not used in resolving that transitive dependency.
FOLLY_INST_PATH=`cd third-party/folly && $(PYTHON) build/fbcode_builder/getdeps.py show-inst-dir`; \
cd "$$FOLLY_INST_PATH" && patchelf --add-rpath $$PWD/../gflags-*/lib ../glog-*/lib*/libglog*.so.*.*.*
+29 -3
View File
@@ -90,6 +90,10 @@ class Compressor {
return CompressionType::kDisableCompressionOption;
}
// Return a distinct but functionally equivalent Compressor. This is often
// needed to implement MaybeCloneSpecialized() in wrapper compressors.
virtual std::unique_ptr<Compressor> Clone() const = 0;
// Utility struct for providing sample data for the compression dictionary.
// Potentially extensible by callers of Compressor (but not recommended)
struct DictSampleArgs {
@@ -121,7 +125,7 @@ class Compressor {
// dictionary associated with a returned compressor must be read from
// GetSerializedDict().
virtual std::unique_ptr<Compressor> MaybeCloneSpecialized(
CacheEntryRole block_type, DictSampleArgs&& dict_samples) {
CacheEntryRole block_type, DictSampleArgs&& dict_samples) const {
// Default implementation: no specialization
(void)block_type;
(void)dict_samples;
@@ -131,6 +135,18 @@ class Compressor {
return nullptr;
}
// A convenience function when a clone is needed and may or may not be
// specialized.
std::unique_ptr<Compressor> CloneMaybeSpecialized(
CacheEntryRole block_type, DictSampleArgs&& dict_samples) const {
auto clone = MaybeCloneSpecialized(block_type, std::move(dict_samples));
if (clone == nullptr) {
clone = Clone();
assert(clone != nullptr);
}
return clone;
}
// A WorkingArea is an optional structure (both for callers and
// implementations) that can enable optimizing repeated compressions by
// reusing working space or thread-local tracking of statistics or trends.
@@ -473,9 +489,19 @@ class CompressorWrapper : public Compressor {
return wrapped_->GetPreferredCompressionType();
}
// NOTE: Clone() not implemented here because it needs to be in the derived
// class
// NOTE: MaybeCloneSpecialized() is only implemented here for convenience
// when the wrapped Compressor uses the default implementation of
// MaybeCloneSpecialized(). This needs to be overridden if not.
std::unique_ptr<Compressor> MaybeCloneSpecialized(
CacheEntryRole block_type, DictSampleArgs&& dict_samples) override {
return wrapped_->MaybeCloneSpecialized(block_type, std::move(dict_samples));
CacheEntryRole block_type, DictSampleArgs&& dict_samples) const override {
auto clone =
wrapped_->MaybeCloneSpecialized(block_type, std::move(dict_samples));
// Assert default no-op MaybeCloneSpecialized()
assert(clone == nullptr);
return clone;
}
ManagedWorkingArea ObtainWorkingArea() override {
+11
View File
@@ -528,6 +528,17 @@ struct AdvancedColumnFamilyOptions {
// Dynamically changeable through SetOptions() API
int target_file_size_multiplier = 1;
// If true, RocksDB will consider the estimated tail size (filter + index +
// meta blocks) when deciding whether to cut a compaction output file. This
// helps prevent output files from exceeding the target_file_size_base due to
// large tail blocks. When disabled, only the data block size is considered,
// which may result in SST files exceeding the target_file_size_base.
//
// Default: false
//
// Dynamically changeable through SetOptions() API
bool target_file_size_is_upper_bound = false;
// If true, RocksDB will pick target size of each level dynamically.
// We will pick a base level b >= 1. L0 will be directly merged into level b,
// instead of always into level 1. Level 1 to b-1 need to be empty.
+72
View File
@@ -148,6 +148,14 @@ typedef struct rocksdb_statistics_histogram_data_t
rocksdb_statistics_histogram_data_t;
typedef struct rocksdb_wait_for_compact_options_t
rocksdb_wait_for_compact_options_t;
/* rocksdb_slice_t: Optimized slice type for high-performance C API operations
* This struct is ABI-compatible with rocksdb::Slice for zero-copy interop.
* Used by slice iterator functions and batched operations. */
typedef struct rocksdb_slice_t {
const char* data;
size_t size;
} rocksdb_slice_t;
typedef struct rocksdb_flushjobinfo_t rocksdb_flushjobinfo_t;
typedef struct rocksdb_compactionjobinfo_t rocksdb_compactionjobinfo_t;
typedef struct rocksdb_subcompactionjobinfo_t rocksdb_subcompactionjobinfo_t;
@@ -610,6 +618,16 @@ extern ROCKSDB_LIBRARY_API void rocksdb_batched_multi_get_cf(
const char* const* keys_list, const size_t* keys_list_sizes,
rocksdb_pinnableslice_t** values, char** errs, const bool sorted_input);
/* Batched MultiGet with slice array: Takes rocksdb_slice_t array directly,
* avoiding key conversion. faster than rocksdb_batched_multi_get_cf for
* operations with many keys. Eliminates overhead of converting keys from
* separate pointer+size arrays to Slice objects. */
extern ROCKSDB_LIBRARY_API void rocksdb_batched_multi_get_cf_slice(
rocksdb_t* db, const rocksdb_readoptions_t* options,
rocksdb_column_family_handle_t* column_family, size_t num_keys,
const rocksdb_slice_t* keys_list, rocksdb_pinnableslice_t** values,
char** errs, const bool sorted_input);
// The value is only allocated (using malloc) and returned if it is found and
// value_found isn't NULL. In that case the user is responsible for freeing it.
extern ROCKSDB_LIBRARY_API unsigned char rocksdb_key_may_exist(
@@ -776,6 +794,18 @@ extern ROCKSDB_LIBRARY_API const char* rocksdb_iter_timestamp(
const rocksdb_iterator_t*, size_t* tslen);
extern ROCKSDB_LIBRARY_API void rocksdb_iter_get_error(
const rocksdb_iterator_t*, char** errptr);
/* Slice iterator functions: Return rocksdb_slice_t directly for better
* performance. These functions avoid the overhead of passing output parameters
* and provide zero-copy access to key/value/timestamp data. faster than
* traditional rocksdb_iter_key/value/timestamp functions. */
extern ROCKSDB_LIBRARY_API rocksdb_slice_t
rocksdb_iter_key_slice(const rocksdb_iterator_t* iter);
extern ROCKSDB_LIBRARY_API rocksdb_slice_t
rocksdb_iter_value_slice(const rocksdb_iterator_t* iter);
extern ROCKSDB_LIBRARY_API rocksdb_slice_t
rocksdb_iter_timestamp_slice(const rocksdb_iterator_t* iter);
extern ROCKSDB_LIBRARY_API void rocksdb_iter_refresh(
const rocksdb_iterator_t* iter, char** errptr);
@@ -3440,6 +3470,48 @@ extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_wait_for_compact_options_get_timeout(
rocksdb_wait_for_compact_options_t* opt);
/* High-performance zero-copy Get variants
These functions avoid unnecessary memory allocations and copies.
The returned buffer is valid until the handle is destroyed.
Bindings should migrate to these for better performance. */
/* Zero-copy get that returns a handle to pinned data.
The data remains valid until rocksdb_pinnable_handle_destroy is called.
Returns NULL on error or not found. Check errptr to distinguish. */
typedef struct rocksdb_pinnable_handle_t rocksdb_pinnable_handle_t;
extern ROCKSDB_LIBRARY_API rocksdb_pinnable_handle_t* rocksdb_get_pinned_v2(
rocksdb_t* db, const rocksdb_readoptions_t* options, const char* key,
size_t keylen, char** errptr);
extern ROCKSDB_LIBRARY_API rocksdb_pinnable_handle_t* rocksdb_get_pinned_cf_v2(
rocksdb_t* db, const rocksdb_readoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, char** errptr);
/* Get the data pointer and size from a pinnable handle.
The data pointer is valid until the handle is destroyed. */
extern ROCKSDB_LIBRARY_API const char* rocksdb_pinnable_handle_get_value(
const rocksdb_pinnable_handle_t* handle, size_t* vallen);
extern ROCKSDB_LIBRARY_API void rocksdb_pinnable_handle_destroy(
rocksdb_pinnable_handle_t* handle);
/* Direct get into caller-provided buffer.
Returns 1 if value fits in buffer, 0 if buffer too small.
Sets *vallen to actual value size.
If buffer is too small, no data is copied but *vallen is set. */
extern ROCKSDB_LIBRARY_API unsigned char rocksdb_get_into_buffer(
rocksdb_t* db, const rocksdb_readoptions_t* options, const char* key,
size_t keylen, char* buffer, size_t buffer_size, size_t* vallen,
unsigned char* found, char** errptr);
extern ROCKSDB_LIBRARY_API unsigned char rocksdb_get_into_buffer_cf(
rocksdb_t* db, const rocksdb_readoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, char* buffer, size_t buffer_size, size_t* vallen,
unsigned char* found, char** errptr);
#ifdef __cplusplus
} /* end extern "C" */
#endif
-2
View File
@@ -284,9 +284,7 @@ class CompactionFilter : public Customizable {
std::string* new_value,
std::vector<std::pair<std::string, std::string>>* /* new_columns */,
std::string* skip_until) const {
#ifdef NDEBUG
(void)existing_columns;
#endif
assert(!existing_value || !existing_columns);
assert(value_type == ValueType::kWideColumnEntity || existing_value);
+15 -9
View File
@@ -247,15 +247,7 @@ class ManagedPtr {
public:
ManagedPtr() = default;
ManagedPtr(T* ptr, Owner* owner) : ptr_(ptr), owner_(owner) {}
~ManagedPtr() {
if (ptr_ && owner_) {
if constexpr (std::is_member_function_pointer_v<decltype(Fn)>) {
(owner_->*Fn)(ptr_);
} else {
Fn(owner_, ptr_);
}
}
}
~ManagedPtr() { Free(); }
// No copies
ManagedPtr(const ManagedPtr&) = delete;
ManagedPtr& operator=(const ManagedPtr&) = delete;
@@ -267,6 +259,10 @@ class ManagedPtr {
other.owner_ = nullptr;
}
ManagedPtr& operator=(ManagedPtr&& other) noexcept {
if (this == &other) {
return *this;
}
Free();
ptr_ = other.ptr_;
owner_ = other.owner_;
other.ptr_ = nullptr;
@@ -284,6 +280,16 @@ class ManagedPtr {
private:
T* ptr_ = nullptr;
Owner* owner_ = nullptr;
void Free() {
if (ptr_ && owner_) {
if constexpr (std::is_member_function_pointer_v<decltype(Fn)>) {
(owner_->*Fn)(ptr_);
} else {
Fn(owner_, ptr_);
}
}
}
};
template <typename T, typename comp>
-6
View File
@@ -35,12 +35,6 @@
#include "rocksdb/version.h"
#include "rocksdb/wide_columns.h"
#if defined(__GNUC__) || defined(__clang__)
#define ROCKSDB_DEPRECATED_FUNC __attribute__((__deprecated__))
#elif _WIN32
#define ROCKSDB_DEPRECATED_FUNC __declspec(deprecated)
#endif
namespace ROCKSDB_NAMESPACE {
struct ColumnFamilyOptions;
+1 -1
View File
@@ -39,7 +39,7 @@
#undef LoadLibrary
#endif
#if defined(__GNUC__) || defined(__clang__)
#if defined(__GNUC__) || defined(__clang__) // ODR-SAFE (essentially)
#define ROCKSDB_PRINTF_FORMAT_ATTR(format_param, dots_param) \
__attribute__((__format__(__printf__, format_param, dots_param)))
#else
+2 -2
View File
@@ -558,7 +558,7 @@ class FileSystem : public Customizable {
}
// This seems to clash with a macro on Windows, so #undef it here
#ifdef DeleteFile
#ifdef DeleteFile // ODR-SAFE
#undef DeleteFile
#endif
// Delete the named file.
@@ -719,7 +719,7 @@ class FileSystem : public Customizable {
const ImmutableDBOptions& db_options) const;
// This seems to clash with a macro on Windows, so #undef it here
#ifdef GetFreeSpace
#ifdef GetFreeSpace // ODR-SAFE
#undef GetFreeSpace
#endif
+1 -1
View File
@@ -32,7 +32,7 @@
#include "rocksdb/version.h"
#include "rocksdb/write_buffer_manager.h"
#ifdef max
#ifdef max // ODR-SAFE
#undef max
#endif
+1 -7
View File
@@ -15,12 +15,6 @@
#include "rocksdb/types.h"
#include "rocksdb/wide_columns.h"
#if defined(__GNUC__) || defined(__clang__)
#define ROCKSDB_DEPRECATED_FUNC __attribute__((__deprecated__))
#elif _WIN32
#define ROCKSDB_DEPRECATED_FUNC __declspec(deprecated)
#endif
namespace ROCKSDB_NAMESPACE {
class Comparator;
@@ -117,7 +111,7 @@ class SstFileWriter {
// REQUIRES: user_key is after any previously added point (Put/Merge/Delete)
// key according to the comparator.
// REQUIRES: comparator is *not* timestamp-aware.
ROCKSDB_DEPRECATED_FUNC Status Add(const Slice& user_key, const Slice& value);
[[deprecated]] Status Add(const Slice& user_key, const Slice& value);
// Add a Put key with value to currently opened file
// REQUIRES: user_key is after any previously added point (Put/Merge/Delete)
+3
View File
@@ -692,6 +692,9 @@ enum Histograms : uint32_t {
// Number of operations per transaction.
NUM_OP_PER_TRANSACTION,
// MultiScan Prefill iterator Prepare cost
MULTISCAN_PREPARE_ITERATORS,
HISTOGRAM_ENUM_MAX
};
+6 -14
View File
@@ -22,24 +22,16 @@
#include "rocksdb/rocksdb_namespace.h"
#if !defined(NROCKSDB_THREAD_STATUS)
#define ROCKSDB_USING_THREAD_STATUS
#endif
namespace ROCKSDB_NAMESPACE {
// TODO(yhchiang): remove this function once c++14 is available
// as std::max will be able to cover this.
// Current MS compiler does not support constexpr
template <int A, int B>
struct constexpr_max {
static const int result = (A > B) ? A : B;
};
// A structure that describes the current status of a thread.
// The status of active threads can be fetched using
// ROCKSDB_NAMESPACE::GetThreadList().
struct ThreadStatus {
// Whether RocksDB was built with !NROCKSDB_THREAD_STATUS for
// ROCKSDB_NAMESPACE::GetThreadList() to be supported.
static const bool kEnabled;
// The type of a thread.
enum ThreadType : int {
HIGH_PRIORITY = 0, // RocksDB BG thread in high-pri thread pool
@@ -102,8 +94,8 @@ struct ThreadStatus {
// The maximum number of properties of an operation.
// This number should be set to the biggest NUM_XXX_PROPERTIES.
static const int kNumOperationProperties =
constexpr_max<NUM_COMPACTION_PROPERTIES, NUM_FLUSH_PROPERTIES>::result;
static constexpr int kNumOperationProperties =
std::max(int{NUM_COMPACTION_PROPERTIES}, int{NUM_FLUSH_PROPERTIES});
// The type used to refer to a thread state.
// A state describes lower-level action of a thread
+2 -2
View File
@@ -68,7 +68,7 @@ class EnvMirror : public EnvWrapper {
assert(as == bs);
return as;
}
#if defined(_MSC_VER)
#if defined(_MSC_VER) // ODR-SAFE
#pragma warning(push)
// logical operation on address of string constant
#pragma warning(disable : 4130)
@@ -87,7 +87,7 @@ class EnvMirror : public EnvWrapper {
*r = ar;
return as;
}
#if defined(_MSC_VER)
#if defined(_MSC_VER) // ODR-SAFE
#pragma warning(pop)
#endif
Status DeleteFile(const std::string& f) override {
@@ -9,10 +9,6 @@
#include "rocksdb/rocksdb_namespace.h"
#ifdef FAILED
#undef FAILED
#endif
namespace ROCKSDB_NAMESPACE {
class LDBCommandExecuteResult {
@@ -6,19 +6,47 @@
#pragma once
#include <string>
#include <vector>
#include "rocksdb/db.h"
#include "rocksdb/options.h"
#include "rocksdb/status.h"
namespace ROCKSDB_NAMESPACE {
// Try to migrate DB created with old_opts to be use new_opts.
// Multiple column families is not supported.
// It is best-effort. No guarantee to succeed.
// A full compaction may be executed.
// Prepares a database to be compatible with new_opts after using old_opts.
// Restructures the LSM tree but does NOT apply new_opts - you must call
// DB::Open(new_opts, dbname) afterward to actually use the new configuration.
// It is best-effort with no guarantee to succeed. A full compaction may be
// executed.
//
// Limitations: single column family only
//
// WARNING: using this to migrate from non-FIFO to FIFO compaction
// with `Options::compaction_options_fifo.max_table_files_size` > 0 can cause
// the whole DB to be dropped right after migration if the migrated data is
// larger than `max_table_files_size`
Status OptionChangeMigration(std::string dbname, const Options& old_opts,
Status OptionChangeMigration(const std::string& dbname, const Options& old_opts,
const Options& new_opts);
// Multi-CF version: Prepares a database with multiple column families to be
// compatible with new options after using old options.
//
// REQUIREMENTS:
// - old_cf_descs and new_cf_descs MUST have the same number of CFs
// - old_cf_descs and new_cf_descs MUST have the same CF names IN THE SAME ORDER
// - Adding or dropping CFs is NOT supported - use CreateColumnFamily() or
// DropColumnFamily() separately before/after migration
//
// The function will return InvalidArgument status if these requirements are
// violated.
//
// WARNING: using this to migrate from non-FIFO to FIFO compaction
// with `max_table_files_size` > 0 can cause the whole DB to be dropped right
// after migration if the migrated data is larger than `max_table_files_size`
Status OptionChangeMigration(
const std::string& dbname, const DBOptions& old_db_opts,
const std::vector<ColumnFamilyDescriptor>& old_cf_descs,
const DBOptions& new_db_opts,
const std::vector<ColumnFamilyDescriptor>& new_cf_descs);
} // namespace ROCKSDB_NAMESPACE
+2 -2
View File
@@ -12,8 +12,8 @@
// NOTE: in 'main' development branch, this should be the *next*
// minor or major version number planned for release.
#define ROCKSDB_MAJOR 10
#define ROCKSDB_MINOR 8
#define ROCKSDB_PATCH 3
#define ROCKSDB_MINOR 9
#define ROCKSDB_PATCH 1
// Make it easy to do conditional compilation based on version checks, i.e.
// #if ROCKSDB_VERSION_GE(4, 5, 6)
+57 -30
View File
@@ -1210,6 +1210,9 @@ jint Java_org_rocksdb_RocksDB_getDirect(JNIEnv* env, jclass /*jdb*/,
db->DefaultColumnFamily(), key.slice(), &value.pinnable_slice());
}
if (s.IsNotFound()) {
return ROCKSDB_NAMESPACE::KVException::kNotFound;
}
ROCKSDB_NAMESPACE::KVException::ThrowOnError(env, s);
return value.Fetch();
} catch (ROCKSDB_NAMESPACE::KVException& e) {
@@ -1453,10 +1456,13 @@ jbyteArray Java_org_rocksdb_RocksDB_get__J_3BII(JNIEnv* env, jclass,
try {
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len);
ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env);
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
env,
ROCKSDB_NAMESPACE::Status s =
db->Get(ROCKSDB_NAMESPACE::ReadOptions(), db->DefaultColumnFamily(),
key.slice(), &value.pinnable_slice()));
key.slice(), &value.pinnable_slice());
if (s.IsNotFound()) {
return nullptr;
}
ROCKSDB_NAMESPACE::KVException::ThrowOnError(env, s);
return value.NewByteArray();
} catch (ROCKSDB_NAMESPACE::KVException&) {
@@ -1484,9 +1490,13 @@ jbyteArray Java_org_rocksdb_RocksDB_get__J_3BIIJ(JNIEnv* env, jclass,
try {
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len);
ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env);
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
env, db->Get(ROCKSDB_NAMESPACE::ReadOptions(), cf_handle, key.slice(),
&value.pinnable_slice()));
ROCKSDB_NAMESPACE::Status s =
db->Get(ROCKSDB_NAMESPACE::ReadOptions(), cf_handle, key.slice(),
&value.pinnable_slice());
if (s.IsNotFound()) {
return nullptr;
}
ROCKSDB_NAMESPACE::KVException::ThrowOnError(env, s);
return value.NewByteArray();
} catch (ROCKSDB_NAMESPACE::KVException&) {
@@ -1509,11 +1519,13 @@ jbyteArray Java_org_rocksdb_RocksDB_get__JJ_3BII(JNIEnv* env, jclass,
try {
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len);
ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env);
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
env,
db->Get(
*reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jropt_handle),
db->DefaultColumnFamily(), key.slice(), &value.pinnable_slice()));
ROCKSDB_NAMESPACE::Status s = db->Get(
*reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jropt_handle),
db->DefaultColumnFamily(), key.slice(), &value.pinnable_slice());
if (s.IsNotFound()) {
return nullptr;
}
ROCKSDB_NAMESPACE::KVException::ThrowOnError(env, s);
return value.NewByteArray();
} catch (ROCKSDB_NAMESPACE::KVException&) {
return nullptr;
@@ -1538,10 +1550,13 @@ jbyteArray Java_org_rocksdb_RocksDB_get__JJ_3BIIJ(
try {
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len);
ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env);
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
env, db->Get(*reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(
jropt_handle),
cf_handle, key.slice(), &value.pinnable_slice()));
ROCKSDB_NAMESPACE::Status s = db->Get(
*reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jropt_handle),
cf_handle, key.slice(), &value.pinnable_slice());
if (s.IsNotFound()) {
return nullptr;
}
ROCKSDB_NAMESPACE::KVException::ThrowOnError(env, s);
return value.NewByteArray();
} catch (ROCKSDB_NAMESPACE::KVException&) {
return nullptr;
@@ -1563,10 +1578,13 @@ jint Java_org_rocksdb_RocksDB_get__J_3BII_3BII(JNIEnv* env, jclass,
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len);
ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env, jval, jval_off,
jval_len);
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
env,
ROCKSDB_NAMESPACE::Status s =
db->Get(ROCKSDB_NAMESPACE::ReadOptions(), db->DefaultColumnFamily(),
key.slice(), &value.pinnable_slice()));
key.slice(), &value.pinnable_slice());
if (s.IsNotFound()) {
return ROCKSDB_NAMESPACE::KVException::kNotFound;
}
ROCKSDB_NAMESPACE::KVException::ThrowOnError(env, s);
return value.Fetch();
} catch (ROCKSDB_NAMESPACE::KVException& e) {
@@ -1595,9 +1613,13 @@ jint Java_org_rocksdb_RocksDB_get__J_3BII_3BIIJ(JNIEnv* env, jclass,
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len);
ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env, jval, jval_off,
jval_len);
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
env, db->Get(ROCKSDB_NAMESPACE::ReadOptions(), cf_handle, key.slice(),
&value.pinnable_slice()));
ROCKSDB_NAMESPACE::Status s =
db->Get(ROCKSDB_NAMESPACE::ReadOptions(), cf_handle, key.slice(),
&value.pinnable_slice());
if (s.IsNotFound()) {
return ROCKSDB_NAMESPACE::KVException::kNotFound;
}
ROCKSDB_NAMESPACE::KVException::ThrowOnError(env, s);
return value.Fetch();
} catch (ROCKSDB_NAMESPACE::KVException& e) {
@@ -1621,11 +1643,13 @@ jint Java_org_rocksdb_RocksDB_get__JJ_3BII_3BII(JNIEnv* env, jclass,
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len);
ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env, jval, jval_off,
jval_len);
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
env,
db->Get(
*reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jropt_handle),
db->DefaultColumnFamily(), key.slice(), &value.pinnable_slice()));
ROCKSDB_NAMESPACE::Status s = db->Get(
*reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jropt_handle),
db->DefaultColumnFamily(), key.slice(), &value.pinnable_slice());
if (s.IsNotFound()) {
return ROCKSDB_NAMESPACE::KVException::kNotFound;
}
ROCKSDB_NAMESPACE::KVException::ThrowOnError(env, s);
return value.Fetch();
} catch (ROCKSDB_NAMESPACE::KVException& e) {
@@ -1652,10 +1676,13 @@ jint Java_org_rocksdb_RocksDB_get__JJ_3BII_3BIIJ(
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len);
ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env, jval, jval_off,
jval_len);
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
env, db->Get(*reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(
jropt_handle),
cf_handle, key.slice(), &value.pinnable_slice()));
ROCKSDB_NAMESPACE::Status s = db->Get(
*reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jropt_handle),
cf_handle, key.slice(), &value.pinnable_slice());
if (s.IsNotFound()) {
return ROCKSDB_NAMESPACE::KVException::kNotFound;
}
ROCKSDB_NAMESPACE::KVException::ThrowOnError(env, s);
return value.Fetch();
} catch (ROCKSDB_NAMESPACE::KVException& e) {
+2
View File
@@ -352,6 +352,8 @@ const std::vector<std::pair<Histograms, std::string>> HistogramsNameMap = {
{TABLE_OPEN_PREFETCH_TAIL_READ_BYTES,
"rocksdb.table.open.prefetch.tail.read.bytes"},
{NUM_OP_PER_TRANSACTION, "rocksdb.num.op.per.transaction"},
{MULTISCAN_PREPARE_ITERATORS,
"rocksdb.multiscan.op.prepare.iterators.micros"},
};
std::shared_ptr<Statistics> CreateDBStatistics() {
+5 -2
View File
@@ -13,7 +13,9 @@
namespace ROCKSDB_NAMESPACE {
#ifdef ROCKSDB_USING_THREAD_STATUS
#ifndef NROCKSDB_THREAD_STATUS
const bool ThreadStatus::kEnabled = true;
std::string ThreadStatus::GetThreadTypeName(
ThreadStatus::ThreadType thread_type) {
switch (thread_type) {
@@ -117,6 +119,7 @@ std::map<std::string, uint64_t> ThreadStatus::InterpretOperationProperties(
}
#else
const bool ThreadStatus::kEnabled = false;
std::string ThreadStatus::GetThreadTypeName(
ThreadStatus::ThreadType /*thread_type*/) {
@@ -159,5 +162,5 @@ std::map<std::string, uint64_t> ThreadStatus::InterpretOperationProperties(
return std::map<std::string, uint64_t>();
}
#endif // ROCKSDB_USING_THREAD_STATUS
#endif // !NROCKSDB_THREAD_STATUS
} // namespace ROCKSDB_NAMESPACE
+2 -2
View File
@@ -14,7 +14,7 @@
namespace ROCKSDB_NAMESPACE {
#ifdef ROCKSDB_USING_THREAD_STATUS
#ifndef NROCKSDB_THREAD_STATUS
thread_local ThreadStatusData* ThreadStatusUpdater::thread_status_data_ =
nullptr;
@@ -324,5 +324,5 @@ void ThreadStatusUpdater::SetThreadOperationProperty(int /*i*/,
void ThreadStatusUpdater::IncreaseThreadOperationProperty(int /*i*/,
uint64_t /*delta*/) {}
#endif // ROCKSDB_USING_THREAD_STATUS
#endif // !NROCKSDB_THREAD_STATUS
} // namespace ROCKSDB_NAMESPACE
+6 -6
View File
@@ -47,7 +47,7 @@ class ColumnFamilyHandle;
// The structure that keeps constant information about a column family.
struct ConstantColumnFamilyInfo {
#ifdef ROCKSDB_USING_THREAD_STATUS
#ifndef NROCKSDB_THREAD_STATUS
public:
ConstantColumnFamilyInfo(const void* _db_key, const std::string& _db_name,
const std::string& _cf_name)
@@ -55,13 +55,13 @@ struct ConstantColumnFamilyInfo {
const void* db_key;
const std::string db_name;
const std::string cf_name;
#endif // ROCKSDB_USING_THREAD_STATUS
#endif // !NROCKSDB_THREAD_STATUS
};
// the internal data-structure that is used to reflect the current
// status of a thread using a set of atomic pointers.
struct ThreadStatusData {
#ifdef ROCKSDB_USING_THREAD_STATUS
#ifndef NROCKSDB_THREAD_STATUS
explicit ThreadStatusData() {
enable_tracking.store(false);
thread_id.store(0);
@@ -86,7 +86,7 @@ struct ThreadStatusData {
std::atomic<ThreadStatus::OperationStage> operation_stage;
std::atomic<uint64_t> op_properties[ThreadStatus::kNumOperationProperties];
std::atomic<ThreadStatus::StateType> state_type;
#endif // ROCKSDB_USING_THREAD_STATUS
#endif // !NROCKSDB_THREAD_STATUS
};
// The class that stores and updates the status of the current thread
@@ -190,7 +190,7 @@ class ThreadStatusUpdater {
const std::vector<ColumnFamilyHandle*>& handles, bool check_exist);
protected:
#ifdef ROCKSDB_USING_THREAD_STATUS
#ifndef NROCKSDB_THREAD_STATUS
// The thread-local variable for storing thread status.
static thread_local ThreadStatusData* thread_status_data_;
@@ -220,7 +220,7 @@ class ThreadStatusUpdater {
#else
static ThreadStatusData* thread_status_data_;
#endif // ROCKSDB_USING_THREAD_STATUS
#endif // !NROCKSDB_THREAD_STATUS
};
} // namespace ROCKSDB_NAMESPACE
+2 -2
View File
@@ -12,7 +12,7 @@
namespace ROCKSDB_NAMESPACE {
#ifndef NDEBUG
#ifdef ROCKSDB_USING_THREAD_STATUS
#ifndef NROCKSDB_THREAD_STATUS
void ThreadStatusUpdater::TEST_VerifyColumnFamilyInfoMap(
const std::vector<ColumnFamilyHandle*>& handles, bool check_exist) {
std::unique_lock<std::mutex> lock(thread_list_mutex_);
@@ -37,7 +37,7 @@ void ThreadStatusUpdater::TEST_VerifyColumnFamilyInfoMap(
const std::vector<ColumnFamilyHandle*>& /*handles*/, bool /*check_exist*/) {
}
#endif // ROCKSDB_USING_THREAD_STATUS
#endif // !NROCKSDB_THREAD_STATUS
#endif // !NDEBUG
} // namespace ROCKSDB_NAMESPACE
+10 -5
View File
@@ -11,7 +11,7 @@
namespace ROCKSDB_NAMESPACE {
#ifdef ROCKSDB_USING_THREAD_STATUS
#ifndef NROCKSDB_THREAD_STATUS
thread_local ThreadStatusUpdater*
ThreadStatusUtil::thread_updater_local_cache_ = nullptr;
thread_local bool ThreadStatusUtil::thread_updater_initialized_ = false;
@@ -171,9 +171,10 @@ AutoThreadOperationStageUpdater::~AutoThreadOperationStageUpdater() {
ThreadStatusUpdater* ThreadStatusUtil::thread_updater_local_cache_ = nullptr;
bool ThreadStatusUtil::thread_updater_initialized_ = false;
bool ThreadStatusUtil::MaybeInitThreadLocalUpdater(const Env* /*env*/) {
return false;
}
void ThreadStatusUtil::RegisterThread(
const Env* /*env*/, ThreadStatus::ThreadType /*thread_type*/) {}
void ThreadStatusUtil::UnregisterThread() {}
void ThreadStatusUtil::SetEnableTracking(bool /*enable_tracking*/) {}
@@ -204,11 +205,15 @@ void ThreadStatusUtil::EraseDatabaseInfo(const DB* /*db*/) {}
void ThreadStatusUtil::ResetThreadStatus() {}
bool ThreadStatusUtil::MaybeInitThreadLocalUpdater(const Env* /*env*/) {
return false;
}
AutoThreadOperationStageUpdater::AutoThreadOperationStageUpdater(
ThreadStatus::OperationStage /*stage*/) {}
AutoThreadOperationStageUpdater::~AutoThreadOperationStageUpdater() {}
#endif // ROCKSDB_USING_THREAD_STATUS
#endif // !NROCKSDB_THREAD_STATUS
} // namespace ROCKSDB_NAMESPACE
+2 -2
View File
@@ -90,7 +90,7 @@ class ThreadStatusUtil {
// a non-null pointer.
static bool MaybeInitThreadLocalUpdater(const Env* env);
#ifdef ROCKSDB_USING_THREAD_STATUS
#ifndef NROCKSDB_THREAD_STATUS
// A boolean flag indicating whether thread_updater_local_cache_
// is initialized. It is set to true when an Env uses any
// ThreadStatusUtil functions using the current thread other
@@ -130,7 +130,7 @@ class AutoThreadOperationStageUpdater {
explicit AutoThreadOperationStageUpdater(ThreadStatus::OperationStage stage);
~AutoThreadOperationStageUpdater();
#ifdef ROCKSDB_USING_THREAD_STATUS
#ifndef NROCKSDB_THREAD_STATUS
private:
ThreadStatus::OperationStage prev_stage_;
#endif
+11 -3
View File
@@ -454,6 +454,10 @@ static std::unordered_map<std::string, OptionTypeInfo>
{offsetof(struct MutableCFOptions, target_file_size_multiplier),
OptionType::kInt, OptionVerificationType::kNormal,
OptionTypeFlags::kMutable}},
{"target_file_size_is_upper_bound",
{offsetof(struct MutableCFOptions, target_file_size_is_upper_bound),
OptionType::kBoolean, OptionVerificationType::kNormal,
OptionTypeFlags::kMutable}},
{"arena_block_size",
{offsetof(struct MutableCFOptions, arena_block_size),
OptionType::kSizeT, OptionVerificationType::kNormal,
@@ -1074,10 +1078,12 @@ uint64_t MultiplyCheckOverflow(uint64_t op1, double op2) {
if (op1 == 0 || op2 <= 0) {
return 0;
}
if (std::numeric_limits<uint64_t>::max() / op1 < op2) {
return op1;
if (op1 * op2 < static_cast<double>(std::numeric_limits<uint64_t>::max())) {
return static_cast<uint64_t>(op1 * op2);
}
return static_cast<uint64_t>(op1 * op2);
return op1;
}
// when level_compaction_dynamic_level_bytes is true and leveled compaction
@@ -1172,6 +1178,8 @@ void MutableCFOptions::Dump(Logger* log) const {
target_file_size_base);
ROCKS_LOG_INFO(log, " target_file_size_multiplier: %d",
target_file_size_multiplier);
ROCKS_LOG_INFO(log, " target_file_size_is_upper_bound: %d",
target_file_size_is_upper_bound);
ROCKS_LOG_INFO(log, " max_bytes_for_level_base: %" PRIu64,
max_bytes_for_level_base);
ROCKS_LOG_INFO(log, " max_bytes_for_level_multiplier: %f",
+4
View File
@@ -132,6 +132,8 @@ struct MutableCFOptions {
max_compaction_bytes(options.max_compaction_bytes),
target_file_size_base(options.target_file_size_base),
target_file_size_multiplier(options.target_file_size_multiplier),
target_file_size_is_upper_bound(
options.target_file_size_is_upper_bound),
max_bytes_for_level_base(options.max_bytes_for_level_base),
max_bytes_for_level_multiplier(options.max_bytes_for_level_multiplier),
ttl(options.ttl),
@@ -207,6 +209,7 @@ struct MutableCFOptions {
max_compaction_bytes(0),
target_file_size_base(0),
target_file_size_multiplier(0),
target_file_size_is_upper_bound(false),
max_bytes_for_level_base(0),
max_bytes_for_level_multiplier(0),
ttl(0),
@@ -306,6 +309,7 @@ struct MutableCFOptions {
uint64_t max_compaction_bytes;
uint64_t target_file_size_base;
int target_file_size_multiplier;
bool target_file_size_is_upper_bound;
uint64_t max_bytes_for_level_base;
double max_bytes_for_level_multiplier;
uint64_t ttl;
+4
View File
@@ -63,6 +63,7 @@ AdvancedColumnFamilyOptions::AdvancedColumnFamilyOptions(const Options& options)
level0_stop_writes_trigger(options.level0_stop_writes_trigger),
target_file_size_base(options.target_file_size_base),
target_file_size_multiplier(options.target_file_size_multiplier),
target_file_size_is_upper_bound(options.target_file_size_is_upper_bound),
level_compaction_dynamic_level_bytes(
options.level_compaction_dynamic_level_bytes),
max_bytes_for_level_multiplier(options.max_bytes_for_level_multiplier),
@@ -269,6 +270,9 @@ void ColumnFamilyOptions::Dump(Logger* log) const {
target_file_size_base);
ROCKS_LOG_HEADER(log, " Options.target_file_size_multiplier: %d",
target_file_size_multiplier);
ROCKS_LOG_HEADER(log,
" Options.target_file_size_is_upper_bound: %d",
target_file_size_is_upper_bound);
ROCKS_LOG_HEADER(log,
" Options.max_bytes_for_level_base: %" PRIu64,
max_bytes_for_level_base);
+2
View File
@@ -254,6 +254,8 @@ void UpdateColumnFamilyOptions(const MutableCFOptions& moptions,
cf_opts->max_compaction_bytes = moptions.max_compaction_bytes;
cf_opts->target_file_size_base = moptions.target_file_size_base;
cf_opts->target_file_size_multiplier = moptions.target_file_size_multiplier;
cf_opts->target_file_size_is_upper_bound =
moptions.target_file_size_is_upper_bound;
cf_opts->max_bytes_for_level_base = moptions.max_bytes_for_level_base;
cf_opts->max_bytes_for_level_multiplier =
moptions.max_bytes_for_level_multiplier;
+1
View File
@@ -611,6 +611,7 @@ TEST_F(OptionsSettableTest, ColumnFamilyOptionsAllFieldsSettable) {
"max_sequential_skip_in_iterations=4294971408;"
"arena_block_size=1893;"
"target_file_size_multiplier=35;"
"target_file_size_is_upper_bound=false;"
"min_write_buffer_number_to_merge=9;"
"max_write_buffer_number=84;"
"write_buffer_size=1653;"
-1
View File
@@ -590,7 +590,6 @@ TEST_MAIN_SOURCES = \
table/block_based/block_based_table_reader_test.cc \
table/block_based/block_test.cc \
table/block_based/data_block_hash_index_test.cc \
table/block_based/index_builder_test.cc \
table/block_based/full_filter_block_test.cc \
table/block_based/partitioned_filter_block_test.cc \
table/cleanable_test.cc \
+155 -32
View File
@@ -110,6 +110,19 @@ FilterBlockBuilder* CreateFilterBlockBuilder(
}
}
// A convenience function for populating the Compressor* fields; see ~Rep()
Compressor* MaybeCloneSpecialized(
Compressor* compressor, CacheEntryRole block_type,
Compressor::DictSampleArgs&& dict_samples = {}) {
auto specialized =
compressor->MaybeCloneSpecialized(block_type, std::move(dict_samples));
if (specialized) {
// Caller is responsible for freeing when distinct
return specialized.release();
} else {
return compressor;
}
}
} // namespace
// kBlockBasedTableMagicNumber was picked by running
@@ -824,15 +837,17 @@ struct BlockBasedTableBuilder::Rep {
// *** Compressors & decompressors - Yes, it seems like a lot here but ***
// *** these are distinct fields to minimize extra conditionals and ***
// *** field reads on hot code paths. ***
// *** field reads on hot code paths. And to avoid interlocked ***
// *** instructions associated with shared_ptr. ***
// A compressor for blocks in general, without dictionary compression
std::unique_ptr<Compressor> basic_compressor;
// A compressor using dictionary compression (when applicable)
std::unique_ptr<Compressor> compressor_with_dict;
// Once configured/determined, points to one of the above Compressors to
// use on data blocks.
Compressor* data_block_compressor = nullptr;
// A compressor for data blocks, which might be tuned differently and might
// use dictionary compression (when applicable). See ~Rep() for some details.
UnownedPtr<Compressor> data_block_compressor = nullptr;
// A compressor for index blocks, which might be tuned differently from
// basic_compressor. See ~Rep() for some details.
UnownedPtr<Compressor> index_block_compressor = nullptr;
// A decompressor corresponding to basic_compressor (when non-nullptr).
// Used for verification and cache warming.
std::shared_ptr<Decompressor> basic_decompressor;
@@ -853,7 +868,7 @@ struct BlockBasedTableBuilder::Rep {
compression_types_used;
// Working area for basic_compressor when compression_parallel_threads==1
WorkingAreaPair basic_working_area;
WorkingAreaPair index_block_working_area;
// Working area for data_block_compressor, for emit/compaction thread
WorkingAreaPair data_block_working_area;
@@ -894,6 +909,7 @@ struct BlockBasedTableBuilder::Rep {
std::unique_ptr<FilterBlockBuilder> filter_builder;
OffsetableCacheKey base_cache_key;
const TableFileCreationReason reason;
const bool target_file_size_is_upper_bound;
BlockHandle pending_handle; // Handle to add to index block
@@ -1041,6 +1057,8 @@ struct BlockBasedTableBuilder::Rep {
use_delta_encoding_for_index_values(table_opt.format_version >= 4 &&
!table_opt.block_align),
reason(tbo.reason),
target_file_size_is_upper_bound(
tbo.moptions.target_file_size_is_upper_bound),
flush_block_policy(
table_options.flush_block_policy_factory->NewFlushBlockPolicy(
table_options, data_block)),
@@ -1096,7 +1114,10 @@ struct BlockBasedTableBuilder::Rep {
filter_context, tbo.compression_opts, tbo.compression_type);
if (basic_compressor) {
if (table_options.enable_index_compression) {
basic_working_area.compress = basic_compressor->ObtainWorkingArea();
index_block_compressor = MaybeCloneSpecialized(
basic_compressor.get(), CacheEntryRole::kIndexBlock);
index_block_working_area.compress =
index_block_compressor->ObtainWorkingArea();
}
max_dict_sample_bytes = basic_compressor->GetMaxSampleSizeIfWantDict(
CacheEntryRole::kDataBlock);
@@ -1111,8 +1132,10 @@ struct BlockBasedTableBuilder::Rep {
tbo.compression_opts.max_dict_buffer_bytes);
}
} else {
// No distinct data block compressor using dictionary
data_block_compressor = basic_compressor.get();
// No distinct data block compressor using dictionary, but
// implementation might still want to specialize for data blocks
data_block_compressor = MaybeCloneSpecialized(
basic_compressor.get(), CacheEntryRole::kDataBlock);
data_block_working_area.compress =
data_block_compressor->ObtainWorkingArea();
}
@@ -1126,8 +1149,9 @@ struct BlockBasedTableBuilder::Rep {
if (table_options.verify_compression) {
verify_decompressor = basic_decompressor.get();
if (table_options.enable_index_compression) {
basic_working_area.verify = verify_decompressor->ObtainWorkingArea(
basic_compressor->GetPreferredCompressionType());
index_block_working_area.verify =
verify_decompressor->ObtainWorkingArea(
index_block_compressor->GetPreferredCompressionType());
}
if (state == State::kUnbuffered) {
assert(data_block_compressor);
@@ -1292,8 +1316,19 @@ struct BlockBasedTableBuilder::Rep {
}
~Rep() {
// Delete working areas before their compressors.
index_block_working_area = {};
data_block_working_area = {};
// Must have been cleaned up by StopParallelCompression
assert(pc_rep == nullptr);
// Delete specialized compressors if they were distinct (avoiding extra
// fields and interlocked instructions with shared_ptr)
if (data_block_compressor.get() != basic_compressor.get()) {
delete data_block_compressor.get();
}
if (index_block_compressor.get() != basic_compressor.get()) {
delete index_block_compressor.get();
}
}
Rep(const Rep&) = delete;
@@ -1611,6 +1646,17 @@ void BlockBasedTableBuilder::Flush(const Slice* first_key_in_next_block) {
rep_->data_begin_offset += uncompressed_block_data.size();
MaybeEnterUnbuffered(first_key_in_next_block);
} else {
// Increment num_data_blocks when a data block is finalized in the
// emit thread to avoid data races with write worker threads
++r->props.num_data_blocks;
// Notify filter builder that a data block has been finalized
// This must happen on the emit thread before the block is added to the
// ring buffer to avoid race conditions with worker threads
if (r->filter_builder) {
r->filter_builder->OnDataBlockFinalized(r->props.num_data_blocks);
}
if (r->IsParallelCompressionActive()) {
EmitBlockForParallel(r->data_block.MutableBuffer(), r->last_ikey,
first_key_in_next_block);
@@ -1715,9 +1761,11 @@ void BlockBasedTableBuilder::WriteBlock(const Slice& uncompressed_block_data,
assert(!r->IsParallelCompressionActive());
CompressionType type;
bool is_data_block = block_type == BlockType::kData;
// NOTE: only index and data blocks are currently compressed
assert(is_data_block || block_type == BlockType::kIndex);
Status compress_status = CompressAndVerifyBlock(
uncompressed_block_data, is_data_block,
is_data_block ? r->data_block_working_area : r->basic_working_area,
is_data_block ? r->data_block_working_area : r->index_block_working_area,
&r->single_threaded_compressed_output, &type);
r->SetStatus(compress_status);
if (UNLIKELY(!ok())) {
@@ -1735,7 +1783,6 @@ void BlockBasedTableBuilder::WriteBlock(const Slice& uncompressed_block_data,
if (is_data_block) {
r->props.data_size = r->get_offset();
r->props.uncompressed_data_size += uncompressed_block_data.size();
++r->props.num_data_blocks;
}
}
@@ -1784,7 +1831,6 @@ void BlockBasedTableBuilder::BGWorker(WorkingAreaPair& working_area) {
if (LIKELY(ios.ok())) {
rep_->props.data_size = rep_->get_offset();
rep_->props.uncompressed_data_size += block_rep->uncompressed.size();
++rep_->props.num_data_blocks;
rep_->index_builder->FinishIndexEntry(
rep_->pending_handle, block_rep->prepared_index_entry.get(),
@@ -1833,13 +1879,13 @@ Status BlockBasedTableBuilder::CompressAndVerifyBlock(
Rep* r = rep_.get();
Status status;
Compressor* compressor = nullptr;
UnownedPtr<Compressor> compressor = nullptr;
Decompressor* verify_decomp = nullptr;
if (is_data_block) {
compressor = r->data_block_compressor;
verify_decomp = r->data_block_verify_decompressor.get();
} else {
compressor = r->basic_compressor.get();
compressor = r->index_block_compressor;
verify_decomp = r->verify_decompressor.get();
}
@@ -1940,6 +1986,9 @@ void BlockBasedTableBuilder::WriteMaybeCompressedBlock(
const Slice& block_contents, CompressionType comp_type, BlockHandle* handle,
BlockType block_type, const Slice* uncompressed_block_data,
bool* skip_delta_encoding) {
// Must have pre-checked status in single-threaded context
assert(status().ok());
assert(io_status().ok());
rep_->SetIOStatus(WriteMaybeCompressedBlockImpl(
block_contents, comp_type, handle, block_type, uncompressed_block_data,
skip_delta_encoding));
@@ -2014,8 +2063,6 @@ IOStatus BlockBasedTableBuilder::WriteMaybeCompressedBlockImpl(
handle->set_offset(offset);
handle->set_size(block_contents.size());
assert(status().ok());
assert(io_status().ok());
if (uncompressed_block_data == nullptr) {
uncompressed_block_data = &block_contents;
assert(comp_type == kNoCompression);
@@ -2103,7 +2150,7 @@ void BlockBasedTableBuilder::MaybeStartParallelCompression() {
// that latency. So even with some optimizations, turning on the parallel
// framework when compression is disabled just eats more CPU with little-to-no
// improvement in throughput.
if (rep_->data_block_compressor == nullptr) {
if (!rep_->data_block_compressor) {
// Force the generally best configuration for no compression: no parallelism
return;
}
@@ -2140,8 +2187,9 @@ void BlockBasedTableBuilder::StopParallelCompression(bool abort) {
pc_rep.SetAbort(pc_rep.emit_thread_state);
} else if (pc_rep.emit_thread_state !=
ParallelCompressionRep::ThreadState::kEnd) {
// In case we didn't do a final flush with no next key
assert(rep_->props.num_data_blocks == 0);
// In case we didn't do a final flush with no next key, which might have
// been skipped if !ok() was set after the start of Finish()
assert(rep_->props.num_data_blocks == 0 || !ok());
pc_rep.SetNoMoreToEmit(pc_rep.emit_thread_state, pc_rep.emit_slot);
}
#ifdef BBTB_PC_WATCHDOG
@@ -2449,8 +2497,8 @@ void BlockBasedTableBuilder::WritePropertiesBlock(
void BlockBasedTableBuilder::WriteCompressionDictBlock(
MetaIndexBuilder* meta_index_builder) {
Slice compression_dict;
if (rep_->compressor_with_dict) {
compression_dict = rep_->compressor_with_dict->GetSerializedDict();
if (rep_->data_block_compressor) {
compression_dict = rep_->data_block_compressor->GetSerializedDict();
}
if (!compression_dict.empty()) {
BlockHandle compression_dict_block_handle;
@@ -2545,6 +2593,7 @@ void BlockBasedTableBuilder::MaybeEnterUnbuffered(
// The below code is neither safe nor necessary for handling zero data
// blocks.
// For PostPopulateCompressionProperties()
assert(!r->data_block_compressor);
r->data_block_compressor = r->basic_compressor.get();
return;
}
@@ -2586,15 +2635,12 @@ void BlockBasedTableBuilder::MaybeEnterUnbuffered(
assert(samples.sample_data.size() > 0);
// final sample data block flushed, now we can generate dictionary
r->compressor_with_dict = r->basic_compressor->MaybeCloneSpecialized(
CacheEntryRole::kDataBlock, std::move(samples));
// final sample data block flushed, now we can generate dictionary (or it
// might opt not to use a dictionary and that's ok)
r->data_block_compressor =
MaybeCloneSpecialized(r->basic_compressor.get(),
CacheEntryRole::kDataBlock, std::move(samples));
// The compressor might opt not to use a dictionary, in which case we
// can use the same compressor as for e.g. index blocks.
r->data_block_compressor = r->compressor_with_dict
? r->compressor_with_dict.get()
: r->basic_compressor.get();
Slice serialized_dict = r->data_block_compressor->GetSerializedDict();
if (r->verify_decompressor) {
if (serialized_dict.empty()) {
@@ -2688,6 +2734,20 @@ void BlockBasedTableBuilder::MaybeEnterUnbuffered(
Status BlockBasedTableBuilder::Finish() {
Rep* r = rep_.get();
assert(r->state != Rep::State::kClosed);
#ifndef NDEBUG
{
// This sync point callback is a simple approximation of a failure detected
// in parallel compression after the start of calling Finish() but before
// Finish() calls Flush()
IOStatus s = rep_->GetIOStatus();
TEST_SYNC_POINT_CALLBACK("BlockBasedTableBuilder::Finish:ParallelIOStatus",
&s);
if (!s.ok()) {
rep_->SetIOStatus(s);
}
}
#endif // !NDEBUG
// To make sure properties block is able to keep the accurate size of index
// block, we will finish writing all index entries first, in Flush().
Flush(/*first_key_in_next_block=*/nullptr);
@@ -2701,6 +2761,8 @@ Status BlockBasedTableBuilder::Finish() {
r->props.tail_start_offset = r->offset.LoadRelaxed();
uint64_t last_estimated_tail_size = EstimatedTailSize();
// Write meta blocks, metaindex block and footer in the following order.
// 1. [meta block: filter]
// 2. [meta block: index]
@@ -2727,6 +2789,24 @@ Status BlockBasedTableBuilder::Finish() {
r->state = Rep::State::kClosed;
r->tail_size = r->offset.LoadRelaxed() - r->props.tail_start_offset;
// Assert tail size estimation is an overestimate only when tail size
// estimation option is enabled for compaction files with supported
// index/filter types:
// - Shortened indexes (kBinarySearch, kBinarySearchWithFirstKey)
// - Partitioned indexes (kTwoLevelIndexSearch)
// - Full filters
// - Partitioned filters
if (r->target_file_size_is_upper_bound &&
r->reason == TableFileCreationReason::kCompaction &&
r->table_options.index_type != BlockBasedTableOptions::kHashSearch) {
ROCKS_LOG_WARN(r->ioptions.info_log,
"File number: %" PRIu64 ", Estimated tail size = %" PRIu64
" bytes, Actual tail size = %" PRIu64 " bytes",
r->props.orig_file_number, last_estimated_tail_size,
r->tail_size);
assert(r->tail_size <= last_estimated_tail_size);
}
return r->GetStatus();
}
@@ -2764,6 +2844,49 @@ uint64_t BlockBasedTableBuilder::EstimatedFileSize() const {
}
}
uint64_t BlockBasedTableBuilder::EstimatedTailSize() const {
uint64_t estimated_tail_size = 0;
// 1. Estimate index size
if (rep_->table_options.index_type ==
BlockBasedTableOptions::kTwoLevelIndexSearch) {
assert(rep_->p_index_builder_);
estimated_tail_size += rep_->p_index_builder_->CurrentIndexSizeEstimate();
} else {
assert(rep_->index_builder);
estimated_tail_size += rep_->index_builder->CurrentIndexSizeEstimate();
}
// 2. Estimate filter size
if (rep_->filter_builder) {
estimated_tail_size += rep_->filter_builder->CurrentFilterSizeEstimate();
}
// 3. Estimate compression dictionary size
if (rep_->data_block_compressor) {
Slice dict = rep_->data_block_compressor->GetSerializedDict();
if (!dict.empty()) {
estimated_tail_size += dict.size();
}
}
// 4. Estimate range deletion block size
if (!rep_->range_del_block.empty()) {
estimated_tail_size += rep_->range_del_block.CurrentSizeEstimate();
}
// 5. Estimate properties block size conservatively (~1-2KB)
estimated_tail_size += 2048;
// 6. Estimate meta-index block size conservatively (~1KB)
estimated_tail_size += 1024;
// 7. Add footer size
estimated_tail_size += Footer::kMaxEncodedLength;
return estimated_tail_size;
}
uint64_t BlockBasedTableBuilder::GetTailSize() const { return rep_->tail_size; }
bool BlockBasedTableBuilder::NeedCompact() const {
@@ -89,11 +89,15 @@ class BlockBasedTableBuilder : public TableBuilder {
// Finish() call, returns the size of the final generated file.
uint64_t FileSize() const override;
// Estimated size of the file generated so far. This is used when
// FileSize() cannot estimate final SST size, e.g. parallel compression
// is enabled.
// Estimated size of the file generated so far (based on data blocks, this
// estimate does not include meta blocks). This is used when FileSize() cannot
// estimate final SST size, e.g. parallel compression is enabled.
uint64_t EstimatedFileSize() const override;
// Estimated tail size of the SST file generated so far. The "tail" refers to
// all blocks written after data blocks (index + filter).
uint64_t EstimatedTailSize() const override;
// Get the size of the "tail" part of a SST file. "Tail" refers to
// all blocks after data blocks till the end of the SST file.
uint64_t GetTailSize() const override;
+167 -123
View File
@@ -1045,13 +1045,6 @@ void BlockBasedTableIterator::Prepare(const MultiScanArgs* multiscan_opts) {
}
void BlockBasedTableIterator::SeekMultiScan(const Slice* seek_target) {
if (SeekMultiScanImpl(seek_target)) {
is_out_of_bound_ = true;
assert(!Valid());
}
}
bool BlockBasedTableIterator::SeekMultiScanImpl(const Slice* seek_target) {
assert(multi_scan_ && multi_scan_status_.ok());
// This is a MultiScan and Preapre() has been called.
@@ -1063,49 +1056,59 @@ bool BlockBasedTableIterator::SeekMultiScanImpl(const Slice* seek_target) {
if (!seek_target) {
// start key must be set for multi-scan
multi_scan_status_ = Status::InvalidArgument("No seek key for MultiScan");
return false;
return;
}
constexpr auto out_of_bound = true;
// Check the case where there is no range prepared on this table
if (multi_scan_->scan_opts->size() == 0) {
// out of bound
return out_of_bound;
MarkPreparedRangeExhausted();
return;
}
// Check whether seek key is moving forward.
if (!multi_scan_->prev_seek_key_.empty()) {
if (user_comparator_.CompareWithoutTimestamp(ExtractUserKey(*seek_target),
/*a_has_ts=*/true,
multi_scan_->prev_seek_key_,
/*b_has_ts=*/false) < 0) {
// The seek target moved backward
multi_scan_status_ =
Status::InvalidArgument("Unexpected seek key moving backward");
return false;
}
if (multi_scan_->prev_seek_key_.empty() ||
icomp_.Compare(*seek_target, multi_scan_->prev_seek_key_) > 0) {
// If seek key is empty or is larger than previous seek key, update the
// previous seek key. Otherwise use the previous seek key as the adjusted
// seek target moving forward. This prevents seek target going backward,
// which would visit pages that have been unpinned.
// This issue is caused by sub-optimal range delete handling inside merge
// iterator.
// TODO xingbo issues:14068 : Optimize the handling of range delete iterator
// inside merge iterator, so that it doesn't move seek key backward. After
// that we could return error if the key moves backward here.
multi_scan_->prev_seek_key_ = seek_target->ToString();
} else {
// Seek key is adjusted to previous one, we can return here directly.
return;
}
multi_scan_->prev_seek_key_ = ExtractUserKey(*seek_target).ToString();
// There are still a few cases we need to handle
// table: _____[prepared range 1]_____[prepared range 2]_____
// seek : 1 2 3 4 5
// Case 1: seek before the first prepared ranges, return out of bound
// Case 2: seek at the beginning of a prepared range (expected case)
// Case 3: seek within a prepared range (unexpected, but supported)
// Case 4: seek between 2 of the prepared ranges, return out of bound
// Case 5: seek after all of the prepared ranges, should move on to next file
// The reason this could happen is due to seek key adjustment due to delete
// range file.
// E.g. LSM has 3 levels, each level has only 1 file:
// L1 : key : 0---10
// L2 : Delete range key : 0-5
// L3 : key : 0---10
// When a range 2-8 was prepared, the prepared key would be 2 on L3 file, but
// the seek key would be 5, as the seek key was updated by the largest key of
// delete range. This causes all of the cases above to be possible, when the
// ranges are adjusted in the above examples.
// There are 3 different Cases we need to handle:
// The following diagram explain different seek targets seeking at various
// position on the table, while the next_scan_idx points to the PreparedRange
// 2.
//
// next_scan_idx: -------------------┐
// ▼
// table: : __[PreparedRange 1]__[PreparedRange 2]__[PreparedRange 3]__
// Seek target: <----- Case 1 ------>▲<------------- Case 2 -------------->
// │
// Case 3
//
// Case 1: seek before the start of next prepared ranges. This could happen
// due to too many delete tomestone triggered reseek or delete range.
// Case 2: seek after the start of next prepared range.
// This could happen due to seek key adjustment from delete range file.
// E.g. LSM has 3 levels, each level has only 1 file:
// L1 : key : 0---10
// L2 : Delete range key : 0-5
// L3 : key : 0---10
// When a range 2-8 was prepared, the prepared key would be 2 on L3 file,
// but the seek key would be 5, as the seek key was updated by the largest
// key of delete range. This causes all of the cases above to be possible,
// when the ranges are adjusted in the above examples.
// Case 3: seek at the beginning of a prepared range (expected case)
// Allow reseek on the start of the last prepared range due to too many
// tombstone
@@ -1113,83 +1116,151 @@ bool BlockBasedTableIterator::SeekMultiScanImpl(const Slice* seek_target) {
std::min(multi_scan_->next_scan_idx,
multi_scan_->block_index_ranges_per_scan.size() - 1);
auto user_seek_target = ExtractUserKey(*seek_target);
auto compare_next_scan_start_result =
user_comparator_.CompareWithoutTimestamp(
ExtractUserKey(*seek_target), /*a_has_ts=*/true,
user_seek_target, /*a_has_ts=*/true,
multi_scan_->scan_opts->GetScanRanges()[multi_scan_->next_scan_idx]
.range.start.value(),
/*b_has_ts=*/false);
if (compare_next_scan_start_result != 0) {
// The seek key is not exactly same as what was prepared.
// The seek target is not exactly same as what was prepared.
if (compare_next_scan_start_result < 0) {
// Needs to handle Cases: 1, 3, 4
//
// next_scan_idx : |
// V
// table: _____[prepared range 1]_____[prepared range 2]_____
// seek : 1 3 4
// Case 1: Seek key is before the start key of the first range
// Case 1:
if (multi_scan_->next_scan_idx == 0) {
return out_of_bound;
// This should not happen, even when seek target is adjusted by delete
// range. The reason is that if the seek target is before the start key
// of the first prepared range, its end key needs to be >= the smallest
// key of this file, otherwise it is skipped in level iterator. If its
// end key is >= the smallest key of this file, then this range will be
// prepared for this file. As delete range could only adjust seek
// target forward, so it would never be before the start key of the
// first prepared range.
assert(false && "Seek target before the first prepared range");
MarkPreparedRangeExhausted();
return;
}
// Case: 3, 4
MultiScanUnexpectedSeekTarget(
seek_target, std::get<0>(multi_scan_->block_index_ranges_per_scan
[multi_scan_->next_scan_idx - 1]));
auto seek_target_before_previous_prepared_range =
user_comparator_.CompareWithoutTimestamp(
user_seek_target, /*a_has_ts=*/true,
multi_scan_->scan_opts
->GetScanRanges()[multi_scan_->next_scan_idx - 1]
.range.start.value(),
/*b_has_ts=*/false) < 0;
// Not expected to happen
// This should never happen, the reason is that the
// multi_scan_->next_scan_idx is set to a non zero value is due to a seek
// target larger or equal to the start key of multi_scan_->next_scan_idx-1
// happended earlier. If a seek happens before the start key of
// multi_scan_->next_scan_idx-1, it would seek a key that is less than
// what was seeked before.
assert(!seek_target_before_previous_prepared_range);
if (seek_target_before_previous_prepared_range) {
multi_scan_status_ = Status::InvalidArgument(
"Seek target is before the previous prepared range at index " +
std::to_string(multi_scan_->next_scan_idx));
return;
}
// It should only be possible to seek a key between the start of current
// prepared scan and start of next prepared range.
MultiScanUnexpectedSeekTarget(seek_target, &user_seek_target);
} else {
// Needs to handle Cases: 3, 4, 5
// next_scan_idx :|
// V
// table: ____[prepared range 1]_____[prepared range 2]_____
// seek : 3 4 5
MultiScanUnexpectedSeekTarget(
seek_target,
std::get<0>(
multi_scan_
->block_index_ranges_per_scan[multi_scan_->next_scan_idx]));
// Case 2:
MultiScanUnexpectedSeekTarget(seek_target, &user_seek_target);
}
} else {
if (multi_scan_->next_scan_idx >=
multi_scan_->block_index_ranges_per_scan.size()) {
// Seeking a range that is out side of prepared ranges.
return out_of_bound;
}
// Case 2:
assert(multi_scan_->next_scan_idx <
multi_scan_->block_index_ranges_per_scan.size());
auto [cur_scan_start_idx, cur_scan_end_idx] =
multi_scan_->block_index_ranges_per_scan[multi_scan_->next_scan_idx];
// We should have the data block already loaded
++multi_scan_->next_scan_idx;
if (cur_scan_start_idx >= cur_scan_end_idx) {
if (multi_scan_->next_scan_idx <
multi_scan_->block_index_ranges_per_scan.size()) {
return out_of_bound;
} else {
ResetDataIter();
return false;
}
} else {
is_out_of_bound_ = false;
// No blocks are prepared for this range at current file.
MarkPreparedRangeExhausted();
return;
}
MultiScanSeekTargetFromBlock(seek_target, cur_scan_start_idx);
// max_sequential_skip_in_iterations can trigger a reseek on the start
// key of a scan range, even though the multiscan is already past
// `cur_scan_start_idx` (e.g., a user key spans multiple data blocks).
size_t block_idx =
std::max(cur_scan_start_idx, multi_scan_->cur_data_block_idx);
MultiScanSeekTargetFromBlock(seek_target, block_idx);
}
return false;
}
void BlockBasedTableIterator::MultiScanUnexpectedSeekTarget(
const Slice* seek_target, size_t block_idx) {
const Slice* seek_target, const Slice* user_seek_target) {
// linear search the block that contains the seek target, and unpin blocks
// that are before it.
// The logic here could be confusing when there is a delete range involved.
// E.g. we have an LSM with 3 levels, each level has only 1 file:
// L1: data file : 0---10
// L2: Delete range : 0-5
// L3: data file : 0---10
//
// MultiScan on ranges 1-2, 3-4, and 5-6.
// When user first do Seek(1), on level 2, due to delete range 0-5, the seek
// key is adjusted to 5 at level 3. Therefore, we will internally do Seek(5)
// and unpins all blocks until 5 at level 3. Then the next scan's blocks from
// 3-4 are unpinned at level 3. It is confusing that maybe block 3-4 should
// not be unpinned, as next scan would need it. But Seek(5) implies that these
// keys are all covered by some range deletion, so the next Seek(3) will also
// do Seek(5) internally, so the blocks from 3-4 could be safely unpinned.
// advance to the right prepared range
while (
multi_scan_->next_scan_idx <
multi_scan_->block_index_ranges_per_scan.size() &&
(user_comparator_.CompareWithoutTimestamp(
*user_seek_target, /*a_has_ts=*/true,
multi_scan_->scan_opts->GetScanRanges()[multi_scan_->next_scan_idx]
.range.start.value(),
/*b_has_ts=*/false) >= 0)) {
multi_scan_->next_scan_idx++;
}
// next_scan_idx is guaranteed to be higher than 0. If the seek key is before
// the start key of first prepared range, it is already handled by caller
// SeekMultiScan. It is equal, it would not call this funciton. If it is
// after, next_scan_idx would be advanced by the loop above.
assert(multi_scan_->next_scan_idx > 0);
// Get the current range
auto cur_scan_idx = multi_scan_->next_scan_idx - 1;
auto [cur_scan_start_idx, cur_scan_end_idx] =
multi_scan_->block_index_ranges_per_scan[cur_scan_idx];
if (cur_scan_start_idx >= cur_scan_end_idx) {
// No blocks are prepared for this range at current file.
MarkPreparedRangeExhausted();
return;
}
// Unpin all the blocks from multi_scan_->cur_data_block_idx to
// cur_scan_start_idx
for (auto unpin_block_idx = multi_scan_->cur_data_block_idx;
unpin_block_idx < cur_scan_start_idx; unpin_block_idx++) {
if (!multi_scan_->pinned_data_blocks[unpin_block_idx].IsEmpty()) {
multi_scan_->pinned_data_blocks[unpin_block_idx].Reset();
}
}
// Take the max here to ensure we don't move backwards.
size_t block_idx =
std::max(cur_scan_start_idx, multi_scan_->cur_data_block_idx);
auto const& data_block_separators = multi_scan_->data_block_separators;
while (block_idx < data_block_separators.size() &&
(user_comparator_.CompareWithoutTimestamp(
ExtractUserKey(*seek_target), /*a_has_ts=*/true,
*user_seek_target, /*a_has_ts=*/true,
data_block_separators[block_idx],
/*b_has_ts=*/false) > 0)) {
// Unpin the blocks that are passed
if (!multi_scan_->pinned_data_blocks[block_idx].IsEmpty()) {
multi_scan_->pinned_data_blocks[block_idx].Reset();
}
@@ -1197,30 +1268,11 @@ void BlockBasedTableIterator::MultiScanUnexpectedSeekTarget(
}
if (block_idx >= data_block_separators.size()) {
// Handle case 5, when seek key is larger than the last block in the last
// prepared range.
ResetDataIter();
assert(!Valid());
// All of the prepared blocks for this file is exhausted.
MarkPreparedRangeExhausted();
return;
}
// // The iterator from previous seek may have moved forward a few blocks,
// // In that case, have block_idx catch up the cur_data_block_idx
// // Note no need to handle block unpin, as it has been handled during
// iterating block_idx = std::max(block_idx, multi_scan_->cur_data_block_idx);
// advance to the right prepared range
while (
multi_scan_->next_scan_idx <
multi_scan_->block_index_ranges_per_scan.size() &&
(user_comparator_.CompareWithoutTimestamp(
ExtractUserKey(*seek_target), /*a_has_ts=*/true,
multi_scan_->scan_opts->GetScanRanges()[multi_scan_->next_scan_idx]
.range.start.value(),
/*b_has_ts=*/false) >= 0)) {
multi_scan_->next_scan_idx++;
}
// The current block may contain the data for the target key
MultiScanSeekTargetFromBlock(seek_target, block_idx);
}
@@ -1257,6 +1309,7 @@ void BlockBasedTableIterator::MultiScanSeekTargetFromBlock(
block_iter_points_to_real_block_ = true;
block_iter_.Seek(*seek_target);
FindKeyForward();
CheckOutOfBound();
}
void BlockBasedTableIterator::FindBlockForwardInMultiScan() {
@@ -1275,20 +1328,7 @@ void BlockBasedTableIterator::FindBlockForwardInMultiScan() {
// for this file, it may need to continue to scan into the next file, so
// we do not set is_out_of_bound_ in this case.
if (multi_scan_->cur_data_block_idx + 1 >= cur_scan_end_idx) {
if (multi_scan_->next_scan_idx >=
multi_scan_->block_index_ranges_per_scan.size()) {
// We are done with this file, should let LevelIter advance to the
// next file instead of ending the scan
ResetDataIter();
assert(!is_out_of_bound_);
assert(!Valid());
return;
}
// We don't ResetDataIter() here since next scan might be reading from
// the same block. ResetDataIter() will free the underlying block cache
// handle and we don't want the block to be unpinned.
is_out_of_bound_ = true;
assert(!Valid());
MarkPreparedRangeExhausted();
return;
}
// Move to the next pinned data block
@@ -1419,7 +1459,7 @@ Status BlockBasedTableIterator::CollectBlockHandles(
std::vector<std::tuple<size_t, size_t>>* block_index_ranges_per_scan,
std::vector<std::string>* data_block_separators) {
// print file name and level
if (kVerbose) {
if (UNLIKELY(kVerbose)) {
auto file_name = table_->get_rep()->file->file_name();
auto level = table_->get_rep()->level;
printf("file name : %s, level %d\n", file_name.c_str(), level);
@@ -1480,11 +1520,16 @@ Status BlockBasedTableIterator::CollectBlockHandles(
}
block_index_ranges_per_scan->emplace_back(
scan_block_handles->size() - num_blocks, scan_block_handles->size());
if (kVerbose) {
if (UNLIKELY(kVerbose)) {
printf("separators :");
for (const auto& separator : *data_block_separators) {
printf("%s, ", separator.c_str());
}
printf("\nblock_index_ranges_per_scan :");
for (auto const& block_index_range : *block_index_ranges_per_scan) {
printf("[%zu, %zu], ", std::get<0>(block_index_range),
std::get<1>(block_index_range));
}
printf("\n");
}
}
@@ -1664,7 +1709,6 @@ Status BlockBasedTableIterator::ExecuteIO(
assert(false);
return s;
}
assert(async_read.io_handle);
for (auto& req : *read_reqs) {
if (!req.status.ok()) {
assert(false);
+22 -5
View File
@@ -381,6 +381,27 @@ class BlockBasedTableIterator : public InternalIteratorBase<Slice> {
bool block_iter_points_to_real_block_;
// See InternalIteratorBase::IsOutOfBound().
bool is_out_of_bound_ = false;
// Mark prepared ranges as exhausted for multiscan.
void MarkPreparedRangeExhausted() {
assert(multi_scan_ != nullptr);
if (multi_scan_->next_scan_idx <
multi_scan_->block_index_ranges_per_scan.size()) {
// If there are more prepared ranges, we don't ResetDataIter() here,
// because next scan might be reading from the same block. ResetDataIter()
// will free the underlying block cache handle and we don't want the
// block to be unpinned.
// Set out of bound to mark the current prepared range as exhausted.
is_out_of_bound_ = true;
} else {
// This is the last prepared range of this file, there might be more
// data on next file. Reset data iterator to indicate the iterator is
// no longer valid on this file. Let LevelIter advance to the next file
// instead of ending the scan.
ResetDataIter();
}
}
// During cache lookup to find readahead size, index_iter_ is iterated and it
// can point to a different block.
// If Prepare() is called, index_iter_ is used to prefetch data blocks for the
@@ -612,12 +633,8 @@ class BlockBasedTableIterator : public InternalIteratorBase<Slice> {
// *** BEGIN APIs relevant to multiscan ***
// Wrapper for SeekMultiScanImpl for handling out of bound
void SeekMultiScan(const Slice* target);
// Return true if the result is out of bound
bool SeekMultiScanImpl(const Slice* seek_target);
void FindBlockForwardInMultiScan();
void PrepareReadAsyncCallBack(FSReadRequest& req, void* cb_arg) {
@@ -642,7 +659,7 @@ class BlockBasedTableIterator : public InternalIteratorBase<Slice> {
void MultiScanSeekTargetFromBlock(const Slice* seek_target, size_t block_idx);
void MultiScanUnexpectedSeekTarget(const Slice* seek_target,
size_t block_idx);
const Slice* user_seek_target);
// Return true, if there is an error, or end of file
bool MultiScanLoadDataBlock(size_t idx) {
@@ -1274,13 +1274,13 @@ TEST_P(BlockBasedTableReaderMultiScanAsyncIOTest, MultiScanPrepare) {
ExtractUserKey(kv[33 * kEntriesPerBlock].first));
iter->Prepare(&scan_options);
iter->Seek(kv[32 * kEntriesPerBlock].first);
auto key = iter->key();
ASSERT_OK(iter->status());
iter->Seek(kv[34 * kEntriesPerBlock].first);
ASSERT_OK(iter->status());
// Seek key could not going backward
iter->Seek(kv[30 * kEntriesPerBlock].first);
ASSERT_EQ(iter->status(),
Status::InvalidArgument("Unexpected seek key moving backward"));
// When seek key goes backward, it is adjusted to the last seeked position.
// Assert the key read is same as before.
ASSERT_EQ(key, iter->key());
ASSERT_OK(iter->status());
// Test prefetch limit reached.
iter.reset(table->NewIterator(
@@ -1333,9 +1333,10 @@ TEST_P(BlockBasedTableReaderMultiScanAsyncIOTest, MultiScanPrepare) {
std::cout << random_seed << std::endl;
SCOPED_TRACE("Random seed " + std::to_string(random_seed));
int last_read_key_index = rnd.Uniform(100);
while (last_read_key_index < 100) {
iter->Seek(kv[last_read_key_index * kEntriesPerBlock].first);
// Search key always start from the start key of first prepared range.
int last_read_key_index = rnd.Uniform(100) + 5 * kEntriesPerBlock;
while (last_read_key_index < 100 * kEntriesPerBlock) {
iter->Seek(kv[last_read_key_index].first);
EXPECT_OK(iter->status());
// iterate for a few keys
while (iter->Valid()) {
+17
View File
@@ -68,6 +68,18 @@ class FilterBlockBuilder {
// For reporting stats on how many entries the builder considered unique
virtual size_t EstimateEntriesAdded() = 0;
// Returns an estimate of the current filter size based on the builder's
// state. Implementations should cache the estimate and update it via
// UpdateFilterSizeEstimate() to avoid recalculating on every key add.
//
// Can be called at any time during table construction, even before calling
// Finish(). Used during table construction to determine when to cut files.
virtual size_t CurrentFilterSizeEstimate() = 0;
// Provides a hook for filter builder when a data block is finalized, such as
// to update cached filter size estimates.
virtual void OnDataBlockFinalized(uint64_t /* num_data_blocks */) {}
// When using AddWithPrevKey, this must be called before Finish(). (May also
// be called without AddWithPrevKey, but prev_key_without_ts must be
// accurate regardless.)
@@ -110,6 +122,11 @@ class FilterBlockBuilder {
return filter;
}
#endif // NDEBUG
protected:
// Update cached filter size estimate. Subclasses should override to update
// estimates based on their internal state.
virtual void UpdateFilterSizeEstimate(uint64_t /* num_data_blocks */) {}
};
// A FilterBlockReader is used to parse filter from SST table.
+15 -5
View File
@@ -17,7 +17,6 @@
#include <limits>
#include <memory>
#include "cache/cache_entry_roles.h"
#include "cache/cache_reservation_manager.h"
#include "logging/logging.h"
#include "port/lang.h"
@@ -29,8 +28,8 @@
#include "table/block_based/block_based_table_reader.h"
#include "table/block_based/filter_policy_internal.h"
#include "table/block_based/full_filter_block.h"
#include "util/atomic.h"
#include "util/bloom_impl.h"
#include "util/coding.h"
#include "util/hash.h"
#include "util/math.h"
#include "util/ribbon_config.h"
@@ -61,7 +60,7 @@ Slice FinishAlwaysTrue(std::unique_ptr<const char[]>* /*buf*/) {
// Base class for filter builders using the XXH3 preview hash,
// also known as Hash64 or GetSliceHash64.
class XXPH3FilterBitsBuilder : public BuiltinFilterBitsBuilder {
class XXPH3FilterBitsBuilder : public FilterBitsBuilder {
public:
explicit XXPH3FilterBitsBuilder(
std::atomic<int64_t>* aggregate_rounding_balance,
@@ -126,8 +125,11 @@ class XXPH3FilterBitsBuilder : public BuiltinFilterBitsBuilder {
}
}
// Returns an estimate of the number of entries added to the
// filter. This method is thread-safe and can be safely called
// from background threads during parallel compression.
size_t EstimateEntriesAdded() override {
return hash_entries_info_.entries.size();
return hash_entries_info_.entries_count.LoadRelaxed();
}
Status MaybePostVerify(const Slice& filter_content) override;
@@ -147,6 +149,7 @@ class XXPH3FilterBitsBuilder : public BuiltinFilterBitsBuilder {
hash_entries_info_.xor_checksum ^= hash;
}
hash_entries_info_.entries.push_back(hash);
hash_entries_info_.entries_count.FetchAddRelaxed(1);
if (cache_res_mgr_ &&
// Traditional rounding to whole bucket size
((hash_entries_info_.entries.size() %
@@ -314,6 +317,10 @@ class XXPH3FilterBitsBuilder : public BuiltinFilterBitsBuilder {
// and has near-minimal peak memory use.
std::deque<uint64_t> entries;
// Tracks the number of entries added for thread-safe
// size estimation.
RelaxedAtomic<size_t> entries_count{0};
// If cache_res_mgr_ != nullptr,
// it manages cache charge for buckets of hash entries in (new) Bloom
// or Ribbon Filter construction.
@@ -332,6 +339,8 @@ class XXPH3FilterBitsBuilder : public BuiltinFilterBitsBuilder {
void Swap(HashEntriesInfo* other) {
assert(other != nullptr);
std::swap(entries, other->entries);
entries_count.StoreRelaxed(
other->entries_count.ExchangeRelaxed(entries_count.LoadRelaxed()));
std::swap(cache_res_bucket_handles, other->cache_res_bucket_handles);
std::swap(xor_checksum, other->xor_checksum);
std::swap(prev_alt_hash, other->prev_alt_hash);
@@ -339,6 +348,7 @@ class XXPH3FilterBitsBuilder : public BuiltinFilterBitsBuilder {
void Reset() {
entries.clear();
entries_count.StoreRelaxed(0);
cache_res_bucket_handles.clear();
xor_checksum = 0;
prev_alt_hash = {};
@@ -1066,7 +1076,7 @@ class Standard128RibbonBitsReader : public BuiltinFilterBitsReader {
using LegacyBloomImpl = LegacyLocalityBloomImpl</*ExtraRotates*/ false>;
class LegacyBloomBitsBuilder : public BuiltinFilterBitsBuilder {
class LegacyBloomBitsBuilder : public FilterBitsBuilder {
public:
explicit LegacyBloomBitsBuilder(const int bits_per_key, Logger* info_log);
+13 -18
View File
@@ -90,6 +90,19 @@ class FilterBitsBuilder {
// <= the specified number of bytes. Callers (including RocksDB) should
// only use this result for optimizing performance and not as a guarantee.
virtual size_t ApproximateNumEntries(size_t bytes) = 0;
// Calculate number of bytes needed for a new filter, including
// metadata. Passing the result to ApproximateNumEntries should
// (ideally, usually) return >= the num_entry passed in.
// When optimize_filters_for_memory is enabled, this function
// is not authoritative but represents a target size that should
// be close to the average size.
virtual size_t CalculateSpace(size_t num_entries) = 0;
// Returns an estimate of the FP rate of the returned filter if
// `num_entries` keys are added and the filter returned by Finish
// is `bytes` bytes.
virtual double EstimatedFpRate(size_t num_entries, size_t bytes) = 0;
};
// A class that checks if a key can be in filter
@@ -109,24 +122,6 @@ class FilterBitsReader {
}
};
// Exposes any extra information needed for testing built-in
// FilterBitsBuilders
class BuiltinFilterBitsBuilder : public FilterBitsBuilder {
public:
// Calculate number of bytes needed for a new filter, including
// metadata. Passing the result to ApproximateNumEntries should
// (ideally, usually) return >= the num_entry passed in.
// When optimize_filters_for_memory is enabled, this function
// is not authoritative but represents a target size that should
// be close to the average size.
virtual size_t CalculateSpace(size_t num_entries) = 0;
// Returns an estimate of the FP rate of the returned filter if
// `num_entries` keys are added and the filter returned by Finish
// is `bytes` bytes.
virtual double EstimatedFpRate(size_t num_entries, size_t bytes) = 0;
};
// Base class for RocksDB built-in filter reader with
// extra useful functionalities for inernal.
class BuiltinFilterBitsReader : public FilterBitsReader {
+29
View File
@@ -30,6 +30,35 @@ size_t FullFilterBlockBuilder::EstimateEntriesAdded() {
return filter_bits_builder_->EstimateEntriesAdded();
}
void FullFilterBlockBuilder::OnDataBlockFinalized(uint64_t num_data_blocks) {
UpdateFilterSizeEstimate(num_data_blocks);
}
size_t FullFilterBlockBuilder::CurrentFilterSizeEstimate() {
return estimated_filter_size_;
}
void FullFilterBlockBuilder::UpdateFilterSizeEstimate(
uint64_t num_data_blocks) {
size_t entries_added = filter_bits_builder_->EstimateEntriesAdded();
if (entries_added == 0) {
estimated_filter_size_ = 0;
return;
}
size_t filter_size = filter_bits_builder_->CalculateSpace(entries_added);
// Reserve filter space for next data block ~2x the average.
size_t buffer_size = 0;
if (num_data_blocks > 0) {
buffer_size = (filter_size / num_data_blocks) * 2;
estimated_filter_size_ = filter_size + buffer_size;
} else {
estimated_filter_size_ = filter_size;
}
}
void FullFilterBlockBuilder::AddWithPrevKey(
const Slice& key_without_ts, const Slice& /*prev_key_without_ts*/) {
FullFilterBlockBuilder::Add(key_without_ts);
+6
View File
@@ -57,6 +57,8 @@ class FullFilterBlockBuilder : public FilterBlockBuilder {
return filter_bits_builder_->EstimateEntriesAdded() == 0;
}
size_t EstimateEntriesAdded() override;
size_t CurrentFilterSizeEstimate() override;
void OnDataBlockFinalized(uint64_t num_data_blocks) override;
Status Finish(const BlockHandle& last_partition_block_handle, Slice* filter,
std::unique_ptr<const char[]>* filter_owner = nullptr) override;
using FilterBlockBuilder::Finish;
@@ -73,6 +75,8 @@ class FullFilterBlockBuilder : public FilterBlockBuilder {
std::unique_ptr<FilterBitsBuilder> filter_bits_builder_;
void UpdateFilterSizeEstimate(uint64_t num_data_blocks_written) override;
private:
// important: all of these might point to invalid addresses
// at the time of destruction of this filter block. destructor
@@ -80,6 +84,8 @@ class FullFilterBlockBuilder : public FilterBlockBuilder {
const SliceTransform* const prefix_extractor_;
const bool whole_key_filtering_;
std::unique_ptr<const char[]> filter_data_;
size_t estimated_filter_size_ = 0;
};
// A FilterBlockReader is used to parse filter from SST table.
@@ -52,6 +52,13 @@ class TestFilterBitsBuilder : public FilterBitsBuilder {
size_t ApproximateNumEntries(size_t bytes) override { return bytes / 4; }
size_t CalculateSpace(size_t num_entries) override { return num_entries * 4; }
double EstimatedFpRate(size_t /* num_entries */,
size_t /* bytes */) override {
return 0.0;
}
private:
std::vector<uint32_t> hash_entries_;
};
@@ -229,6 +236,14 @@ class CountUniqueFilterBitsBuilderWrapper : public FilterBitsBuilder {
return b_->ApproximateNumEntries(bytes);
}
size_t CalculateSpace(size_t num_entries) override {
return b_->CalculateSpace(num_entries);
}
double EstimatedFpRate(size_t num_entries, size_t bytes) override {
return b_->EstimatedFpRate(num_entries, bytes);
}
size_t CountUnique() { return uniq_.size(); }
};
+79 -19
View File
@@ -117,20 +117,18 @@ Slice ShortenedIndexBuilder::FindShortInternalKeySuccessor(
}
}
uint64_t ShortenedIndexBuilder::EstimateCurrentIndexSize() const {
void ShortenedIndexBuilder::UpdateIndexSizeEstimate() {
uint64_t current_size =
must_use_separator_with_seq_
must_use_separator_with_seq_.LoadRelaxed()
? index_block_builder_.CurrentSizeEstimate()
: index_block_builder_without_seq_.CurrentSizeEstimate();
if (num_index_entries_ == 0) {
return current_size;
uint64_t final_estimate = current_size;
if (num_index_entries_ > 0) {
// Add buffer to generously account (in most cases) for the next index entry
final_estimate += (2 * (current_size / num_index_entries_));
}
uint64_t avg_entry_size = current_size / num_index_entries_;
// Add buffer to generously account (in most cases) for the next index entry
return current_size + (2 * avg_entry_size);
estimated_index_size_.StoreRelaxed(final_estimate);
}
PartitionedIndexBuilder* PartitionedIndexBuilder::CreateIndexBuilder(
@@ -188,8 +186,8 @@ void PartitionedIndexBuilder::MakeNewSubIndexBuilder() {
// must_use_separator_with_seq_ is true (internal-key mode) (set to false by
// default on Creation) so that flush policy can point to
// sub_index_builder_->index_block_builder_
if (must_use_separator_with_seq_) {
sub_index_builder_->must_use_separator_with_seq_ = true;
if (must_use_separator_with_seq_.LoadRelaxed()) {
sub_index_builder_->must_use_separator_with_seq_.StoreRelaxed(true);
builder_to_monitor = &sub_index_builder_->index_block_builder_;
} else {
builder_to_monitor = &sub_index_builder_->index_block_builder_without_seq_;
@@ -237,6 +235,11 @@ void PartitionedIndexBuilder::MaybeFlush(const Slice& index_key,
index_key, EncodedBlockHandle(index_value).AsSlice()));
if (do_flush) {
assert(entries_.back().value.get() == sub_index_builder_);
// Update estimate of completed partitions when a partition is flushed
estimated_completed_partitions_size_.FetchAddRelaxed(
sub_index_builder_->CurrentIndexSizeEstimate());
cut_filter_block = true;
MakeNewSubIndexBuilder();
}
@@ -254,9 +257,15 @@ void PartitionedIndexBuilder::FinishIndexEntry(const BlockHandle& block_handle,
skip_delta_encoding);
std::swap(entries_.back().key, entry->separator_with_seq);
if (!must_use_separator_with_seq_ && entry->must_use_separator_with_seq) {
// Update cached size estimate when data blocks are finalized for more
// accurate tail size estimation. This is needed for parallel compression
// which uses FinishIndexEntry() instead of AddIndexEntry().
UpdateIndexSizeEstimate();
if (!must_use_separator_with_seq_.LoadRelaxed() &&
entry->must_use_separator_with_seq) {
// We need to apply !must_use_separator_with_seq to all sub-index builders
must_use_separator_with_seq_ = true;
must_use_separator_with_seq_.StoreRelaxed(true);
flush_policy_->Retarget(sub_index_builder_->index_block_builder_);
}
// NOTE: not compatible with coupled partitioned filters so don't need to
@@ -278,10 +287,15 @@ Slice PartitionedIndexBuilder::AddIndexEntry(
separator_scratch, skip_delta_encoding);
entries_.back().key.assign(sep.data(), sep.size());
if (!must_use_separator_with_seq_ &&
sub_index_builder_->must_use_separator_with_seq_) {
// Update cached size estimate when data blocks are finalized for more
// accurate tail size estimation. This ensures the estimate reflects current
// state after each data block is added.
UpdateIndexSizeEstimate();
if (!must_use_separator_with_seq_.LoadRelaxed() &&
sub_index_builder_->must_use_separator_with_seq_.LoadRelaxed()) {
// We need to apply !must_use_separator_with_seq to all sub-index builders
must_use_separator_with_seq_ = true;
must_use_separator_with_seq_.StoreRelaxed(true);
flush_policy_->Retarget(sub_index_builder_->index_block_builder_);
}
if (UNLIKELY(first_key_in_next_block == nullptr)) {
@@ -315,7 +329,7 @@ Status PartitionedIndexBuilder::Finish(
const Slice handle_delta_encoding_slice(handle_delta_encoding);
index_block_builder_.Add(last_entry.key, handle_encoding.AsSlice(),
&handle_delta_encoding_slice);
if (!must_use_separator_with_seq_) {
if (!must_use_separator_with_seq_.LoadRelaxed()) {
index_block_builder_without_seq_.Add(ExtractUserKey(last_entry.key),
handle_encoding.AsSlice(),
&handle_delta_encoding_slice);
@@ -324,7 +338,7 @@ Status PartitionedIndexBuilder::Finish(
}
// If there is no sub_index left, then return the 2nd level index.
if (UNLIKELY(entries_.empty())) {
if (must_use_separator_with_seq_) {
if (must_use_separator_with_seq_.LoadRelaxed()) {
index_blocks->index_block_contents = index_block_builder_.Finish();
} else {
index_blocks->index_block_contents =
@@ -338,7 +352,8 @@ Status PartitionedIndexBuilder::Finish(
// expect more calls to Finish
Entry& entry = entries_.front();
// Apply the policy to all sub-indexes
entry.value->must_use_separator_with_seq_ = must_use_separator_with_seq_;
entry.value->must_use_separator_with_seq_.StoreRelaxed(
must_use_separator_with_seq_.LoadRelaxed());
auto s = entry.value->Finish(index_blocks);
index_size_ += index_blocks->index_block_contents.size();
finishing_indexes_ = true;
@@ -347,4 +362,49 @@ Status PartitionedIndexBuilder::Finish(
}
size_t PartitionedIndexBuilder::NumPartitions() const { return partition_cnt_; }
void PartitionedIndexBuilder::UpdateIndexSizeEstimate() {
uint64_t total_size = 0;
// Ignore last entry which is a placeholder for the partition being built
size_t completed_partitions = entries_.size() > 0 ? entries_.size() - 1 : 0;
// Use running estimate of completed partitions instead of IndexSize() which
// is only available after calling Finish().
uint64_t completed_partitions_size =
estimated_completed_partitions_size_.LoadRelaxed();
total_size += completed_partitions_size;
// Add current active partition size if it exists
uint64_t current_sub_index_size = 0;
if (sub_index_builder_ != nullptr) {
current_sub_index_size = sub_index_builder_->CurrentIndexSizeEstimate();
total_size += current_sub_index_size;
}
// Add buffer for top-level index and next partition
uint64_t buffer_size = 0;
if (completed_partitions > 0) {
// Calculate top-level index size. Each top-level entry consists of:
// separator key (~20-50 bytes) + BlockHandle (~20 bytes) + overhead
// Estimate ~70 bytes per top-level entry as a reasonable average
auto estimated_top_level_size = completed_partitions * 70;
total_size += completed_partitions * 70;
// Buffer for next partition + next top-level entry
uint64_t avg_partition_size =
completed_partitions_size / completed_partitions;
uint64_t avg_top_level_entry_size =
estimated_top_level_size / completed_partitions;
buffer_size = 2 * (avg_partition_size + avg_top_level_entry_size);
total_size += buffer_size;
} else if (sub_index_builder_ != nullptr) {
// For the first partition, estimate using the current partition's state
buffer_size = 2 * current_sub_index_size;
total_size += buffer_size;
}
estimated_index_size_.StoreRelaxed(total_size);
}
} // namespace ROCKSDB_NAMESPACE
+48 -20
View File
@@ -158,12 +158,15 @@ class IndexBuilder {
// Get the size for index block. Must be called after ::Finish.
virtual size_t IndexSize() const = 0;
// Get an estimate for current total index size based on current builder
// state.
// Returns an estimate of the current index size based on the builder's state.
// Implementations should cache the estimate and update it via
// UpdateIndexSizeEstimate() to avoid recalculating on every key add,
// which is critical for performance in the compaction hot path.
//
// Called during compaction to estimate final index size for file cutting
// decisions.
virtual uint64_t EstimateCurrentIndexSize() const = 0;
// This function is only called by the SST "emit thread" but must be
// thread safe with concurrent calls to UpdateIndexSizeEstimate() from another
// thread (such as during parallel compression).
virtual uint64_t CurrentIndexSizeEstimate() const = 0;
virtual bool separator_is_key_plus_seq() { return true; }
@@ -187,6 +190,13 @@ class IndexBuilder {
l_user_key, r_user_key) == 0;
}
// Updates the cached index size estimate used by CurrentIndexSizeEstimate().
//
// This function can be called from the SST "write thread" (via
// FinishIndexEntry()), and needs to be thread safe with
// CurrentIndexSizeEstimate() called from the SST "emit thread".
virtual void UpdateIndexSizeEstimate() {}
const InternalKeyComparator* comparator_;
// Size of user-defined timestamp in bytes.
size_t ts_sz_;
@@ -234,7 +244,7 @@ class ShortenedIndexBuilder : public IndexBuilder {
include_first_key_(include_first_key),
shortening_mode_(shortening_mode) {
// Making the default true will disable the feature for old versions
must_use_separator_with_seq_ = (format_version <= 2);
must_use_separator_with_seq_.StoreRelaxed(format_version <= 2);
}
void OnKeyAdded(const Slice& key,
@@ -257,10 +267,10 @@ class ShortenedIndexBuilder : public IndexBuilder {
} else {
separator_with_seq = last_key_in_current_block;
}
if (!must_use_separator_with_seq_ &&
if (!must_use_separator_with_seq_.LoadRelaxed() &&
ShouldUseKeyPlusSeqAsSeparator(last_key_in_current_block,
*first_key_in_next_block)) {
must_use_separator_with_seq_ = true;
must_use_separator_with_seq_.StoreRelaxed(true);
}
} else {
if (shortening_mode_ == BlockBasedTableOptions::IndexShorteningMode::
@@ -333,6 +343,7 @@ class ShortenedIndexBuilder : public IndexBuilder {
}
++num_index_entries_;
UpdateIndexSizeEstimate();
}
Slice AddIndexEntry(const Slice& last_key_in_current_block,
@@ -347,7 +358,8 @@ class ShortenedIndexBuilder : public IndexBuilder {
Slice first_internal_key = GetFirstInternalKey(&first_internal_key_buf);
AddIndexEntryImpl(separator_with_seq, first_internal_key, block_handle,
must_use_separator_with_seq_, skip_delta_encoding);
must_use_separator_with_seq_.LoadRelaxed(),
skip_delta_encoding);
current_block_first_internal_key_.clear();
return separator_with_seq;
}
@@ -396,7 +408,7 @@ class ShortenedIndexBuilder : public IndexBuilder {
&entry->separator_with_seq);
Slice first_internal_key = GetFirstInternalKey(&entry->first_internal_key);
entry->SaveFrom(separator, first_internal_key,
must_use_separator_with_seq_);
must_use_separator_with_seq_.LoadRelaxed());
current_block_first_internal_key_.clear();
}
@@ -413,7 +425,7 @@ class ShortenedIndexBuilder : public IndexBuilder {
using IndexBuilder::Finish;
Status Finish(IndexBlocks* index_blocks,
const BlockHandle& /*last_partition_block_handle*/) override {
if (must_use_separator_with_seq_) {
if (must_use_separator_with_seq_.LoadRelaxed()) {
index_blocks->index_block_contents = index_block_builder_.Finish();
} else {
index_blocks->index_block_contents =
@@ -425,10 +437,15 @@ class ShortenedIndexBuilder : public IndexBuilder {
size_t IndexSize() const override { return index_size_; }
uint64_t EstimateCurrentIndexSize() const override;
uint64_t CurrentIndexSizeEstimate() const override {
return estimated_index_size_.LoadRelaxed();
}
// Updates the cached size estimate to minimize CPU usage in hot path
void UpdateIndexSizeEstimate() override;
bool separator_is_key_plus_seq() override {
return must_use_separator_with_seq_;
return must_use_separator_with_seq_.LoadRelaxed();
}
// Changes *key to a short string >= *key.
@@ -452,12 +469,14 @@ class ShortenedIndexBuilder : public IndexBuilder {
// before).
BlockBuilder index_block_builder_without_seq_;
const bool use_value_delta_encoding_;
bool must_use_separator_with_seq_;
RelaxedAtomic<bool> must_use_separator_with_seq_;
const bool include_first_key_;
BlockBasedTableOptions::IndexShorteningMode shortening_mode_;
BlockHandle last_encoded_handle_ = BlockHandle::NullBlockHandle();
std::string current_block_first_internal_key_;
uint64_t num_index_entries_ = 0;
// Cache for index size estimate to avoid recalculating in hot path
RelaxedAtomic<uint64_t> estimated_index_size_{0};
};
// HashIndexBuilder contains a binary-searchable primary index and the
@@ -579,8 +598,7 @@ class HashIndexBuilder : public IndexBuilder {
prefix_meta_block_.size();
}
// TODO: implement
uint64_t EstimateCurrentIndexSize() const override { return 0; }
uint64_t CurrentIndexSizeEstimate() const override { return 0; }
bool separator_is_key_plus_seq() override {
return primary_index_builder_.separator_is_key_plus_seq();
@@ -658,8 +676,11 @@ class PartitionedIndexBuilder : public IndexBuilder {
size_t TopLevelIndexSize(uint64_t) const { return top_level_index_size_; }
size_t NumPartitions() const;
// TODO: implement
uint64_t EstimateCurrentIndexSize() const override { return 0; }
// Returns a cached estimate of the current index size. This
// estimate is updated when data blocks are added.
uint64_t CurrentIndexSizeEstimate() const override {
return estimated_index_size_.LoadRelaxed();
}
inline bool ShouldCutFilterBlock() {
// Current policy is to align the partitions of index and filters
@@ -679,8 +700,10 @@ class PartitionedIndexBuilder : public IndexBuilder {
// cutting the next partition
void RequestPartitionCut();
// This function must be thread safe because multiple worker threads might
// update the index builder state during parallel compression.
bool separator_is_key_plus_seq() override {
return must_use_separator_with_seq_;
return must_use_separator_with_seq_.LoadRelaxed();
}
bool get_use_value_delta_encoding() const {
@@ -694,6 +717,7 @@ class PartitionedIndexBuilder : public IndexBuilder {
size_t partition_cnt_ = 0;
void MakeNewSubIndexBuilder();
void UpdateIndexSizeEstimate() override;
struct Entry {
std::string key;
@@ -713,7 +737,7 @@ class PartitionedIndexBuilder : public IndexBuilder {
// true if Finish is called once but not complete yet.
bool finishing_indexes_ = false;
const BlockBasedTableOptions& table_opt_;
bool must_use_separator_with_seq_;
RelaxedAtomic<bool> must_use_separator_with_seq_;
bool use_value_delta_encoding_;
// true if an external entity (such as filter partition builder) request
// cutting the next partition
@@ -721,5 +745,9 @@ class PartitionedIndexBuilder : public IndexBuilder {
// true if it should cut the next filter partition block
bool cut_filter_block = false;
BlockHandle last_encoded_handle_;
// Cached estimate of current index size, updated when data blocks are added
RelaxedAtomic<uint64_t> estimated_index_size_{0};
// Running estimate of completed partitions total size
RelaxedAtomic<uint64_t> estimated_completed_partitions_size_{0};
};
} // namespace ROCKSDB_NAMESPACE
-183
View File
@@ -1,183 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#include "table/block_based/index_builder.h"
#include <gtest/gtest.h>
#include <memory>
#include <string>
#include "db/dbformat.h"
#include "rocksdb/comparator.h"
#include "table/format.h"
#include "test_util/testharness.h"
namespace ROCKSDB_NAMESPACE {
class IndexBuilderTest
: public testing::Test,
public testing::WithParamInterface<BlockBasedTableOptions::IndexType> {
public:
IndexBuilderTest() : icomp_(BytewiseComparator()) {}
std::unique_ptr<IndexBuilder> CreateIndexBuilder() {
BlockBasedTableOptions table_options;
BlockBasedTableOptions::IndexType index_type = GetParam();
return std::unique_ptr<IndexBuilder>(IndexBuilder::CreateIndexBuilder(
index_type, &icomp_, nullptr, false /* use_value_delta_encoding */,
table_options, 0 /* ts_sz */,
true /* persist_user_defined_timestamps */));
}
std::string MakeKey(int i) {
return InternalKey(std::string("key") + std::to_string(i), 100 - i,
kTypeValue)
.Encode()
.ToString();
}
BlockHandle MakeBlockHandle(uint64_t offset, uint64_t size) {
BlockHandle handle;
handle.set_offset(offset);
handle.set_size(size);
return handle;
}
void AddEntriesToBuilder(IndexBuilder* builder, int num_entries,
std::vector<uint64_t>* estimates = nullptr) {
for (int i = 1; i <= num_entries; ++i) {
std::string key_current = MakeKey(i);
BlockHandle handle = MakeBlockHandle(i * kBlockOffset, kBlockSize);
std::string separator_scratch;
if (i == num_entries) {
// Last entry - no next key
builder->AddIndexEntry(key_current, nullptr, handle, &separator_scratch,
false);
} else {
std::string key_next = MakeKey(i + 1);
Slice key_next_slice(key_next);
builder->AddIndexEntry(key_current, &key_next_slice, handle,
&separator_scratch, false);
}
if (estimates) {
uint64_t current_estimate = builder->EstimateCurrentIndexSize();
estimates->push_back(current_estimate);
}
}
}
protected:
InternalKeyComparator icomp_;
static const uint64_t kBlockOffset = 1000;
static const uint64_t kBlockSize = 4096;
// BlockBuilder initial overhead
// See BlockBuilder constructor and Reset()
static const uint64_t kBlockBuilderInitialOverhead = 2 * sizeof(uint32_t);
};
const uint64_t IndexBuilderTest::kBlockOffset;
const uint64_t IndexBuilderTest::kBlockSize;
const uint64_t IndexBuilderTest::kBlockBuilderInitialOverhead;
TEST_P(IndexBuilderTest, EstimateCurrentIndexSize) {
auto builder = CreateIndexBuilder();
BlockBasedTableOptions::IndexType index_type = GetParam();
// Empty builder
uint64_t empty_size = builder->EstimateCurrentIndexSize();
if (index_type == BlockBasedTableOptions::kBinarySearch) {
EXPECT_EQ(empty_size, kBlockBuilderInitialOverhead)
<< "Empty ShortenedIndexBuilder should return BlockBuilder initial "
"overhead ("
<< kBlockBuilderInitialOverhead;
} else {
EXPECT_EQ(empty_size, 0) << "Other builders should return 0 when empty";
}
// Add one entry
AddEntriesToBuilder(builder.get(), 1);
uint64_t size_after_one = builder->EstimateCurrentIndexSize();
if (index_type == BlockBasedTableOptions::kBinarySearch) {
EXPECT_GT(size_after_one, kBlockBuilderInitialOverhead)
<< "Estimate should be greater than initial overhead";
} else {
// Other builders currently return 0 (which is expected)
EXPECT_EQ(size_after_one, 0) << "Other index builders currently return 0";
}
// Add multiple entries and capture all estimates
std::vector<uint64_t> estimates;
auto new_builder = CreateIndexBuilder();
AddEntriesToBuilder(new_builder.get(), 5, &estimates);
// Validate reported estimates
for (size_t i = 0; i < estimates.size(); ++i) {
uint64_t estimate = estimates[i];
if (index_type == BlockBasedTableOptions::kBinarySearch) {
EXPECT_GT(estimate, 0)
<< "Estimate should be positive for " << i << " entry";
if (i > 0) {
EXPECT_GT(estimate, estimates[i - 1])
<< "Estimate should not decrease with more entries (entry " << i - 1
<< ": " << estimates[i - 1] << ", entry " << i << ": " << estimate
<< ")";
}
} else {
EXPECT_EQ(estimate, 0) << "Other index builders currently return 0";
}
}
// Multiple calls should return the same value if the builder state is not
// modified
uint64_t estimate1 = builder->EstimateCurrentIndexSize();
uint64_t estimate2 = builder->EstimateCurrentIndexSize();
uint64_t estimate3 = builder->EstimateCurrentIndexSize();
EXPECT_EQ(estimate1, estimate2);
EXPECT_EQ(estimate2, estimate3);
// Test behavior after Finish() - only for builders that can be finished
// successfully
if (index_type == BlockBasedTableOptions::kBinarySearch) {
uint64_t estimate_before_finish = builder->EstimateCurrentIndexSize();
IndexBuilder::IndexBlocks index_blocks;
Status s = builder->Finish(&index_blocks);
EXPECT_TRUE(s.ok()) << "ShortenedIndexBuilder should finish successfully: "
<< s.ToString();
uint64_t estimate_after_finish = builder->EstimateCurrentIndexSize();
EXPECT_GT(estimate_after_finish, 0);
EXPECT_LE(estimate_before_finish, estimate_after_finish)
<< "Estimate should not decrease after finish";
// Ensure that the actual index size is not greater than the estimated size
// after finish is called to prevent underestimation.
uint64_t actual_index_size = builder->IndexSize();
EXPECT_LE(actual_index_size, estimate_after_finish)
<< "Actual index size should not be greater than estimated size: "
"actual size: "
<< actual_index_size << ", estimated size: " << estimate_after_finish;
}
}
INSTANTIATE_TEST_CASE_P(
IndexBuilderTypes, IndexBuilderTest,
::testing::Values(BlockBasedTableOptions::kBinarySearch,
BlockBasedTableOptions::kHashSearch,
BlockBasedTableOptions::kTwoLevelIndexSearch));
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
@@ -143,6 +143,7 @@ void PartitionedFilterBlockBuilder::CutAFilterBlock(const Slice* next_key,
ikey = p_index_builder_->GetPartitionKey();
}
filters_.push_back({std::move(ikey), std::move(filter_data), filter});
completed_partitions_size_.FetchAddRelaxed(filter.size());
partitioned_filters_construction_status_.UpdateIfOk(
filter_construction_status);
@@ -209,6 +210,56 @@ size_t PartitionedFilterBlockBuilder::EstimateEntriesAdded() {
return total_added_in_built_ + filter_bits_builder_->EstimateEntriesAdded();
}
size_t PartitionedFilterBlockBuilder::CurrentFilterSizeEstimate() {
size_t active_partition_size =
filter_bits_builder_->EstimateEntriesAdded() * 2; // 2 bytes per key
return estimated_filter_size_.LoadRelaxed() + active_partition_size;
}
void PartitionedFilterBlockBuilder::OnDataBlockFinalized(
uint64_t num_data_blocks) {
UpdateFilterSizeEstimate(num_data_blocks);
}
void PartitionedFilterBlockBuilder::UpdateFilterSizeEstimate(
uint64_t num_data_blocks) {
size_t partitions_size = completed_partitions_size_.LoadRelaxed();
// Reserve space if no partitions have been cut
size_t active_filter_estimate = 0;
if (partitions_size == 0) {
size_t avg_bytes_per_entry =
2; // 2 bytes per entry, approx 15 bits per key
// Estimate using keys_per_partition_ since we expect to cut the first
// partition once it reaches approx. this many entries.
active_filter_estimate = keys_per_partition_ * avg_bytes_per_entry;
// Add a 2x buffer (for top-level index, etc.)
active_filter_estimate = active_filter_estimate * 2;
}
size_t filter_estimate = std::max(partitions_size, active_filter_estimate);
// Estimate top-level partition index size
if (p_index_builder_->separator_is_key_plus_seq()) {
filter_estimate += index_on_filter_block_builder_.CurrentSizeEstimate();
} else {
filter_estimate +=
index_on_filter_block_builder_without_seq_.CurrentSizeEstimate();
}
// Reserve filter space for the next data block
size_t reserved = 0;
if (num_data_blocks > 0) {
reserved = (filter_estimate / num_data_blocks) *
2; // 2x average size per data block
estimated_filter_size_.StoreRelaxed(filter_estimate + reserved);
} else {
estimated_filter_size_.StoreRelaxed(filter_estimate);
}
}
void PartitionedFilterBlockBuilder::PrevKeyBeforeFinish(
const Slice& prev_key_without_ts) {
assert(prev_key_without_ts.compare(DEBUG_add_with_prev_key_called_
@@ -18,6 +18,7 @@
#include "table/block_based/filter_block_reader_common.h"
#include "table/block_based/full_filter_block.h"
#include "table/block_based/index_builder.h"
#include "util/atomic.h"
#include "util/autovector.h"
#include "util/hash_containers.h"
@@ -46,6 +47,8 @@ class PartitionedFilterBlockBuilder : public FullFilterBlockBuilder {
}
size_t EstimateEntriesAdded() override;
size_t CurrentFilterSizeEstimate() override;
void OnDataBlockFinalized(uint64_t num_data_blocks) override;
void PrevKeyBeforeFinish(const Slice& prev_key_without_ts) override;
Status Finish(const BlockHandle& last_partition_block_handle, Slice* filter,
@@ -67,6 +70,11 @@ class PartitionedFilterBlockBuilder : public FullFilterBlockBuilder {
return Status::OK();
}
protected:
// Needs to be thread-safe to be invoked from background worker
// thread when parallel compression is enabled.
void UpdateFilterSizeEstimate(uint64_t num_data_blocks) override;
private: // fns
// Whether to cut a filter block before the next key
bool DecideCutAFilterBlock();
@@ -92,6 +100,11 @@ class PartitionedFilterBlockBuilder : public FullFilterBlockBuilder {
};
std::deque<FilterEntry> filters_; // list of partitioned filters and keys
// used in building the index
// Running total of completed filter partition sizes to avoid
// iterating over filters_ deque, which can be concurrently modified by
// the main thread when parallel compression is enabled.
RelaxedAtomic<size_t> completed_partitions_size_{0};
// The desired number of keys per partition
uint32_t keys_per_partition_;
// According to the bits builders, how many keys/prefixes added
@@ -107,6 +120,12 @@ class PartitionedFilterBlockBuilder : public FullFilterBlockBuilder {
// For Add without prev key
std::string prev_key_without_ts_;
// Cached filter size estimate for hot path performance - updated only when
// data blocks are written for meaningful estimate updates.
// Must be atomic since UpdateFilterSizeEstimate() can be called from
// background worker threads when parallel compression is enabled.
RelaxedAtomic<size_t> estimated_filter_size_{0};
#ifndef NDEBUG
// For verifying accurate previous keys are provided by the caller, so that
// release code can be fast
@@ -158,7 +158,7 @@ class UserDefinedIndexBuilderWrapper : public IndexBuilder {
size_t IndexSize() const override { return index_size_; }
uint64_t EstimateCurrentIndexSize() const override { return 0; }
uint64_t CurrentIndexSizeEstimate() const override { return 0; }
bool separator_is_key_plus_seq() override {
return internal_index_builder_->separator_is_key_plus_seq();
+5
View File
@@ -225,6 +225,11 @@ class TableBuilder {
// is enabled.
virtual uint64_t EstimatedFileSize() const { return FileSize(); }
// Estimated tail size of the SST file generated so far. The "tail" refers to
// all blocks written after data blocks (index + filter). This value helps
// estimate the total file size when deciding when to cut files.
virtual uint64_t EstimatedTailSize() const { return 0; }
virtual uint64_t GetTailSize() const { return 0; }
// If the user defined table properties collector suggest the file to
+48 -39
View File
@@ -9140,6 +9140,7 @@ class UserDefinedIndexStressTest
const std::vector<DataRange>& ranges,
bool& data_added) {
std::unique_ptr<SstFileWriter> writer;
data_added = false;
std::vector<DataRange> ranges_in_file;
@@ -9151,9 +9152,12 @@ class UserDefinedIndexStressTest
}
if (writer == nullptr) {
// lazy create writer until there is data to be written to avoid
// unchecked status error
writer = std::make_unique<SstFileWriter>(EnvOptions(), options_);
ASSERT_OK(writer->Open(ingest_file));
}
ranges_in_file.push_back(range);
data_added = true;
@@ -9256,6 +9260,7 @@ class UserDefinedIndexStressTest
if (kVerbose) {
std::cout << "iteration " << i << std::endl;
}
SCOPED_TRACE("Iteration " + std::to_string(i));
// randomly generate 1 to 3 ranges
auto ranges = GenerateKeyRanges(rnd.Uniform(3) + 4, 2, "");
@@ -9299,13 +9304,14 @@ class UserDefinedIndexStressTest
size_t& ingest_file_count,
const IngestExternalFileOptions& ifo,
bool combine_ranges = false) {
std::vector<std::string> ingest_files;
// Generate SST file and bulk load them one level at a time
std::vector<std::string> ingest_files;
if (combine_ranges) {
size_t i = 0;
while (i < ranges_in_level.size()) {
// if combine ranges, generate 1 SST file that combines muliple ranges
// together
// Randomly combine ranges to SST file.
size_t batch_end_idx =
std::min(i + rnd.Uniform(3) + 2, ranges_in_level.size());
bool data_added = false;
@@ -9361,22 +9367,7 @@ class UserDefinedIndexStressTest
}
};
// TODO(xingbo)
// This test is disabled due to following test case condition:
// level n: delete range 4-6
// level n+1: data range 0-------10
// query: 3-9, count=2.
// Becuase query count == 2, level n+1 would only prepare 3-5. but since 4-6
// got deleted in the upper level, they are not returned, so only 3 is
// returned. Meantime the query should have return [3, 6]
// One way to fix this is by preparing more data blocks once prepared blocks
// are exhausted, but upper bound is not reached yet. This requires following
// changes:
// 1. Fix out of bound flag in block table iterator. Only set it if the key is
// larger than the upper bound.
// 2. Refactor the prepared block single dimension vector into 2 dimension of
// vectors, so that more blocks could be prepared if needed.
TEST_P(UserDefinedIndexStressTest, DISABLED_PartialDeleteRange) {
TEST_P(UserDefinedIndexStressTest, PartialDeleteRange) {
// Create 2 column families. One use normal put/del, the other uses sst
// ingest Randomly generate multiple non overlapping range for multiple
// levels Range scan same range between the 2 CF and validate the result is
@@ -9387,6 +9378,22 @@ TEST_P(UserDefinedIndexStressTest, DISABLED_PartialDeleteRange) {
SCOPED_TRACE("dbname: " + dbname_);
ASSERT_NO_FATAL_FAILURE(SetupDB(dbname_));
if (enable_udi_) {
// Skip UDI for now.
// The issue is that with UDI enabled, prepare might not prepare enough keys
// at lower level due to range delete from upper level.
// E.g. consider a LSM tree:
// L1: Data [0-1]
// L2: Delete Range [0-6]
// L3: Data [0-9]
// When multiscan queries range [0-9) with UDI count as 3, the L3 file
// will only prepare range [0-3). However, this range is masked out by upper
// layer delete range from [0-6] from L2. This causes query to only return
// [0,1], while [0,1,7] is the right result. Until prepare is able to
// preparing additional block supported, UDI is skipped.
return;
}
for (int i = 0; i < 5; i++) {
ranges_in_levels_.push_back(
GenerateKeyRanges(rnd.Uniform(3) + 4, 2,
@@ -9408,9 +9415,9 @@ TEST_P(UserDefinedIndexStressTest, DeleteRangeMixedWithDataFile) {
// Create 2 column families. One use normal put/del, the other uses sst
// ingest.
// Test the case where there are 3 levels, the middle level is a delete
// range file that span across the entire key space. The top level file have
// multiple files and each one has both data and delete range Scan same
// range between the 2 CF and validate the result is same
// range file that span across the entire key space. The top and bottom level
// file have multiple files and each one has both data and delete range. Scan
// same range between the 2 CF and validate the result is same
SCOPED_TRACE("Start with random seed: " + std::to_string(rand_seed_));
dbname_ = test::PerThreadDBPath(
"UserDefinedIndexStressTest_DeleteRangeMixedWithDataFile");
@@ -9418,9 +9425,9 @@ TEST_P(UserDefinedIndexStressTest, DeleteRangeMixedWithDataFile) {
ASSERT_NO_FATAL_FAILURE(SetupDB(dbname_));
// Test 3 levels.
// bottom level is normal data files.
ranges_in_levels_.push_back(GenerateKeyRanges(rnd.Uniform(3) + 4, 2, "L6"));
// middle level delete range between each level
// Bottom level is mixed data with delete range.
ranges_in_levels_.push_back(GenerateKeyRanges(rnd.Uniform(3) + 6, 2, "L6"));
// Middle level delete range across entire key space.
if (is_reverse_comparator_) {
ranges_in_levels_.push_back({{.start = 100,
.end = 0,
@@ -9437,8 +9444,8 @@ TEST_P(UserDefinedIndexStressTest, DeleteRangeMixedWithDataFile) {
.end_key = "keyz"}});
}
// Top level is normal data files
ranges_in_levels_.push_back(GenerateKeyRanges(rnd.Uniform(3) + 4, 2, "L4"));
// Top level is mixed data with delete range.
ranges_in_levels_.push_back(GenerateKeyRanges(rnd.Uniform(3) + 6, 2, "L4"));
IngestExternalFileOptions ifo;
ifo.snapshot_consistency = false;
@@ -9448,7 +9455,7 @@ TEST_P(UserDefinedIndexStressTest, DeleteRangeMixedWithDataFile) {
for (auto const& ranges_in_level : ranges_in_levels_) {
ASSERT_NO_FATAL_FAILURE(
IngestFilesInOneLevel(ranges_in_level, ingest_file_name_prefix,
ingest_file_count, ifo, true));
ingest_file_count, ifo, /*combine_ranges=*/true));
if (first_level) {
first_level = false;
if (enable_compaction_with_sst_partitioner_) {
@@ -9475,9 +9482,10 @@ TEST_P(UserDefinedIndexStressTest, DeleteRange) {
ASSERT_NO_FATAL_FAILURE(SetupDB(dbname_));
// Test 3 levels.
// bottom level is normal data files.
// bottom level constains multiple files, each could have data or delete
// ranges or both.
ranges_in_levels_.push_back(GenerateKeyRanges(rnd.Uniform(3) + 4, 2, "L6"));
// middle level delete range between each level
// middle level delete range across entire key space
if (is_reverse_comparator_) {
ranges_in_levels_.push_back({{.start = 100,
.end = 0,
@@ -9493,7 +9501,8 @@ TEST_P(UserDefinedIndexStressTest, DeleteRange) {
.start_key = "key",
.end_key = "keyz"}});
}
// Top level is normal data files
// Top level constains multiple files, each could have data or delete
// ranges or both.
ranges_in_levels_.push_back(GenerateKeyRanges(rnd.Uniform(3) + 4, 2, "L4"));
IngestExternalFileOptions ifo;
@@ -9519,20 +9528,19 @@ TEST_P(UserDefinedIndexStressTest, DeleteRange) {
}
TEST_P(UserDefinedIndexStressTest, AtomicReplaceBulkLoad) {
// Create 2 column families. One use normal put/del, the other uses sst
// ingest.
// Test the case where there are 3 levels, the middle level is a delete
// range file that span across the entire key space. Range scan same range
// between the 2 CF and validate the result is same
// Create 2 column families. One use normal put/del, the other uses SST
// ingest. The SST ingest uses atomic range replace.
SCOPED_TRACE("Start with random seed: " + std::to_string(rand_seed_));
dbname_ = test::PerThreadDBPath("UserDefinedIndexStressTest_DeleteRange");
dbname_ =
test::PerThreadDBPath("UserDefinedIndexStressTest_AtomicReplaceBulkLoad");
SCOPED_TRACE("dbname: " + dbname_);
ASSERT_NO_FATAL_FAILURE(SetupDB(dbname_));
// Test 3 levels.
// bottom level is normal data files.
// bottom level constains multiple files, each could have data or delete
// ranges or both.
ranges_in_levels_.push_back(GenerateKeyRanges(rnd.Uniform(3) + 4, 2, "L6"));
// middle level delete range between each level
// middle level delete range across entire key space
if (is_reverse_comparator_) {
ranges_in_levels_.push_back({{.start = 100,
.end = 0,
@@ -9548,7 +9556,8 @@ TEST_P(UserDefinedIndexStressTest, AtomicReplaceBulkLoad) {
.start_key = "key",
.end_key = "keyz"}});
}
// Top level is normal data files
// Top level constains multiple files, each could have data or delete
// ranges or both.
ranges_in_levels_.push_back(GenerateKeyRanges(rnd.Uniform(3) + 4, 2, "L4"));
IngestExternalFileOptions ifo;
@@ -9569,7 +9578,7 @@ TEST_P(UserDefinedIndexStressTest, AtomicReplaceBulkLoad) {
}
// Ingest the a new file with atomic replace with full key space, this layer
// is exactly same as the one at Level 4
// is exactly same as the one at the top level
bool data_added;
ASSERT_NO_FATAL_FAILURE(CreateSstFileWithRanges(
ingest_file_name_prefix + std::to_string(++ingest_file_count),
+6 -2
View File
@@ -766,6 +766,10 @@ struct CompressorCustomAlg : public CompressorWrapper {
return kCompression;
}
std::unique_ptr<Compressor> Clone() const override {
return std::make_unique<CompressorCustomAlg>(wrapped_->Clone());
}
Status CompressBlock(Slice uncompressed_data, char* compressed_output,
size_t* compressed_output_size,
CompressionType* out_compression_type,
@@ -792,9 +796,9 @@ struct CompressorCustomAlg : public CompressorWrapper {
}
std::unique_ptr<Compressor> MaybeCloneSpecialized(
CacheEntryRole block_type, DictSampleArgs&& dict_samples) override {
CacheEntryRole block_type, DictSampleArgs&& dict_samples) const override {
auto clone =
wrapped_->MaybeCloneSpecialized(block_type, std::move(dict_samples));
wrapped_->CloneMaybeSpecialized(block_type, std::move(dict_samples));
return std::make_unique<CompressorCustomAlg>(std::move(clone));
}
+1 -1
View File
@@ -137,7 +137,7 @@ EOF
# To check for DB forward compatibility with loading options (old version
# reading data from new), as well as backward compatibility
declare -a db_forward_with_options_refs=("8.6.fb" "8.7.fb" "8.8.fb" "8.9.fb" "8.10.fb" "8.11.fb" "9.0.fb" "9.1.fb" "9.2.fb" "9.3.fb" "9.4.fb" "9.5.fb" "9.6.fb" "9.7.fb" "9.8.fb" "9.9.fb" "9.10.fb" "9.11.fb" "10.0.fb" "10.1.fb" "10.2.fb" "10.3.fb" "10.4.fb" "10.5.fb" "10.6.fb" "10.7.fb")
declare -a db_forward_with_options_refs=("8.6.fb" "8.7.fb" "8.8.fb" "8.9.fb" "8.10.fb" "8.11.fb" "9.0.fb" "9.1.fb" "9.2.fb" "9.3.fb" "9.4.fb" "9.5.fb" "9.6.fb" "9.7.fb" "9.8.fb" "9.9.fb" "9.10.fb" "9.11.fb" "10.0.fb" "10.1.fb" "10.2.fb" "10.3.fb" "10.4.fb" "10.5.fb" "10.6.fb" "10.7.fb" "10.8.fb" "10.9.fb")
# To check for DB forward compatibility without loading options (in addition
# to the "with loading options" set), as well as backward compatibility
declare -a db_forward_no_options_refs=() # N/A at the moment
+4 -4
View File
@@ -587,7 +587,7 @@ DEFINE_double(cache_high_pri_pool_ratio, 0.0,
DEFINE_double(cache_low_pri_pool_ratio, 0.0,
"Ratio of block cache reserve for low pri blocks.");
DEFINE_string(cache_type, "lru_cache", "Type of block cache.");
DEFINE_string(cache_type, "hyper_clock_cache", "Type of block cache.");
DEFINE_bool(use_compressed_secondary_cache, false,
"Use the CompressedSecondaryCache as the secondary cache.");
@@ -3260,10 +3260,10 @@ class Benchmark {
db_bench_exit(1);
} else if (EndsWith(FLAGS_cache_type, "hyper_clock_cache")) {
size_t estimated_entry_charge;
if (FLAGS_cache_type == "fixed_hyper_clock_cache" ||
FLAGS_cache_type == "hyper_clock_cache") {
if (FLAGS_cache_type == "fixed_hyper_clock_cache") {
estimated_entry_charge = FLAGS_block_size;
} else if (FLAGS_cache_type == "auto_hyper_clock_cache") {
} else if (FLAGS_cache_type == "auto_hyper_clock_cache" ||
FLAGS_cache_type == "hyper_clock_cache") {
estimated_entry_charge = 0;
} else {
fprintf(stderr, "Cache type not supported.");
Loaded 100 of 116 files, more files were not shown because too many files have changed in this diff. Show more