Compare commits

..
116 Commits
Author SHA1 Message Date
anand76 a8b5e4a6b7 Update HISTORY and version for 10.3.4 2025-06-17 16:36:32 -07:00
anand76 a59ca8d5da Change NewExternalTableFactory to return unique_ptr (#13705)
Summary:
Change NewExternalTableFactory API to return unique_ptr for use with the object registry.

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

Reviewed By: jaykorean

Differential Revision: D76827580

Pulled By: anand1976

fbshipit-source-id: 251ad0e498b62059b8417ff967ca74146de43e2f
2025-06-17 16:22:16 -07:00
Peter Dillinger 740240e3b1 Update version and HISTORY for 10.3.3 2025-06-04 19:14:23 -07:00
anand76 633631ac79 Fix iterator errors for CFs with disallow_memtable_writes (#13663)
Summary:
Iterator seek returns "SeekAndValidate() not implemented" error if the disallow_memtable_writes CF option is set along with paranoid_memory_checks. The fix is to sanitize the paranoid_memory_checks option to false, which should be safe since the memtable is guaranteed to be empty.

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

Test Plan: Update unit test in db_basic_test.cc

Reviewed By: pdillinger

Differential Revision: D75973515

Pulled By: anand1976

fbshipit-source-id: 3f381f19dcda72e3b78ee375f755fb4809c6b99c
2025-06-04 19:12:49 -07:00
Peter Dillinger e7d28965ce Clean up stale unreleased_history (not sure what happened) 2025-06-04 12:02:40 -07:00
Peter Dillinger c1b29dfe70 Fix tests broken by gtest upgrade (#13661)
Summary:
Some tests were failing due to apparent missing include of iomanip. I suspect this was from a gtest upgrade, because in open source, the include iomanip comes from gtest.h. To ensure we maintain compatibility with older gtest as well as the newer one, I pulled the include iomanip out of the in-repo gtest.h. Note that other places in gtest code only instantiate floating-point related templates with `float` and `double` types.

Also, to avoid `make format` being insanely slow on gtest.h, I've excluded third-party from the formatting check.

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

Test Plan: make check, internal CI, manually ensure formatting check works outside of third-party/

Reviewed By: jaykorean

Differential Revision: D75963897

Pulled By: pdillinger

fbshipit-source-id: ed5737dd456e74068185f1ac5d57046d7509df7a
2025-06-04 10:53:41 -07:00
Peter Dillinger a116092ee6 Fix XPRESS compression and enable in CI (#13649)
Summary:
Somehow this was previously not being tested in our Windows CI jobs so was accidentally broken in https://github.com/facebook/rocksdb/pull/13540  This fix will need to be backported to 10.3.

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

Test Plan: CI

Reviewed By: hx235

Differential Revision: D75655418

Pulled By: pdillinger

fbshipit-source-id: a56bb213270904a1b7a13b905c2cc1919116df1c
2025-05-30 09:31:36 -07:00
Jay Huh f90d89ea52 Update HISTORY and version for 10.3.2 2025-05-30 07:18:13 -07:00
Jay Huh cf149842a2 Include file_size in CompactionServiceOutputFile (#13620)
Summary:
Instead of using FileSystem::GetFileSize() for each CompactionOutputFile, use the file size that is being tracked internally as part of the output file's metadata. FileSize is now part of `CompactionServiceOutputFile` and serialized in the `CompactionServiceResult`

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

Test Plan:
Tested with logging Meta's internal offload Infra

```
./compaction_job_test
```

Reviewed By: jowlyzhang

Differential Revision: D75006961

Pulled By: jaykorean

fbshipit-source-id: 008f9dc22bd672746ac180380ada4188713a6b85
2025-05-30 07:12:47 -07:00
Changyu Bi f2d64341f5 Update HISTORY and version for 10.3.1 2025-05-22 20:34:07 -07:00
Changyu Bi 9c7f4946bb Update default value for large txn options (#13636)
Summary:
to make it easier to use 0 for disabled. And deprecate the use of txn db option `txn_commit_bypass_memtable_threshold`.

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

Test Plan: updated unit tests.

Reviewed By: jowlyzhang

Differential Revision: D75262136

Pulled By: cbi42

fbshipit-source-id: 9040e5a9c918c1d0906a2db4600cc012d2436b22
2025-05-22 20:27:57 -07:00
Changyu Bi 9885ced363 Enable large txn optimization by transaction write batch size (#13634)
Summary:
Larger key/values can cause memtable write to take longer time. Add new option `TransactionOptions::large_txn_commit_optimize_byte_threshold` that enables the optimization by transaction write batch size.

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

Test Plan:
- new unit test
- added option to stress test and ran stress test for some time: `python3 ./tools/db_crashtest.py --txn blackbox  --txn_write_policy=0 --commit_bypass_memtable_one_in=50 --test_batches_snapshots=0`

Reviewed By: jowlyzhang

Differential Revision: D75248126

Pulled By: cbi42

fbshipit-source-id: 9522db93457729ba60e4176f7d47f7c2c7778567
2025-05-22 20:27:49 -07:00
Peter Dillinger cda1e40498 Fix another format compatibility failure (#13628)
Summary:
Some specific old versions around RocksDB 2.5 would compress the metaindex and properties blocks. This hasn't been done since, probably because it interferes with the properties block indicating how to set up for decompression (so the reader can read those blocks before doing any decompression).

To fix backward compatibility, we establish a decompressor early if format_version indicates the file could come from a sufficiently old version.

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

Test Plan: local and CI runs of tools/check_format_compatible.sh. (I don't believe we need special code to set up a unit test for this case.)

Reviewed By: jowlyzhang

Differential Revision: D75107623

Pulled By: pdillinger

fbshipit-source-id: 97132b8c5e0602e8e27254a11386d866b23cb4f5
2025-05-21 09:29:12 -07:00
Peter Dillinger 0c3f08008a Update HISTORY for 10.3.0 release 2025-05-17 16:25:23 -07:00
Peter Dillinger 77af042413 Fix some compression-related assertion failures (#13621)
Summary:
showing up in the crash test after https://github.com/facebook/rocksdb/issues/13540
* For an assertion `dict_samples.sample_data.size() <= opts_.max_dict_bytes` we needed to ensure that `zstd_max_train_bytes` only takes effect with kZSTD compression.
* For an assertion with `r->table_options.verify_compression == (verify_decomp != nullptr)` we needed to ensure that `data_block_verify_decompressor` is set even when dictionary compression is attempted but not used.
* Noticed along the way: finish an optimization in `CompressAndVerifyBlock` that was incomplete.

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

Test Plan:
Both failures were reproducible with hard-coding of some crash test params, and now not getting a failure.
```
--compression_type=zstd --compression_max_dict_bytes=16384 --compression_zstd_max_train_bytes=65536 --compression_max_dict_buffer_bytes=131071 --compression_use_zstd_dict_trainer=1
```
Write performance test like in https://github.com/facebook/rocksdb/issues/13540 shows essentially no change, maybe slightly faster (+0.4%) with verify_compression.

Reviewed By: virajthakur

Differential Revision: D74939103

Pulled By: pdillinger

fbshipit-source-id: 8bac8891bc08e1356eff52cc524e5bb409b0f86f
2025-05-17 14:43:29 -07:00
virajthakur acab405fc1 propagate request_id from app -> Rocks -> FS (#13616)
Summary:
[internal use] Allow the application to pass a request_id per read request to RocksDB and pass it down to the FileSystem (via IODebugContext)

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

Test Plan:
./db_test --gtest_filter=DBTest.RequestIdPlumbingTest

Validates that RocksDB Api calls with request_id set result in request_id being passed to the filesystem through IODebugContext

Reviewed By: pdillinger

Differential Revision: D74912824

Pulled By: virajthakur

fbshipit-source-id: 4f15fef3ff7b5d700563f993f9b211c991020fb6
2025-05-16 21:25:50 -07:00
Zaidoon Abd Al Hadi 9a9a403a89 add support for event listener to C API (#13601)
Summary:
mostly copied from tikv's fork of rocksdb: https://github.com/tikv/rust-rocksdb/blob/master/librocksdb_sys/crocksdb/c.cc#L2445

fixed https://github.com/facebook/rocksdb/issues/13525

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

Reviewed By: hx235

Differential Revision: D74588333

Pulled By: cbi42

fbshipit-source-id: dedfc5866cf9025f9d8b6a33a8133e432554476d
2025-05-16 17:31:19 -07:00
Peter Dillinger 83026c7db2 Fix handling of old files with compression dictionary but no compression (#13618)
Summary:
Before the fix to https://github.com/facebook/rocksdb/issues/12409 in https://github.com/facebook/rocksdb/issues/12453, SST files could have a compression dictionary but be configured for no compression. Recent PR https://github.com/facebook/rocksdb/issues/13540 regressed on handling this safely on the read side, which was caught by the format compatibile nightly test (recently expanded to cover dictionary compression in https://github.com/facebook/rocksdb/issues/13414).

This change fixes that regression.

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

Test Plan: manual and ongoing format compatibility test runs. (I don't think this case is worth introducing a back door to create a uselessly inefficient SST file, considering it's covered by nightly CI.)

Reviewed By: cbi42

Differential Revision: D74914868

Pulled By: pdillinger

fbshipit-source-id: 5a4ab058d0d6da275eefb2df1a7454d8a4b2031f
2025-05-16 17:19:15 -07:00
anand76 06d4f569a8 Fix external table ingestion workflow (#13608)
Summary:
Remove the dependency on `allow_db_generated_files` option in `IngestExternalFile` to be set for ingesting external tables. The files are created by SstFileWriter, and we should be able to ingest them. We could make it work by having the external table implementation provide the version and global sequence number related properties, but its safer to have RocksDB generate the table properties block and store it as is in the file.

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

Test Plan: Add unit test to test basic ingestion and ingestion with atomic_replace_range

Reviewed By: pdillinger

Differential Revision: D74830707

Pulled By: anand1976

fbshipit-source-id: 4a9bea4a4f38f7c24c584262095c5c98cd771ddc
2025-05-16 14:41:51 -07:00
Changyu Bi b42bf48310 Add stats for WBWI ingestion and transaction size (#13611)
Summary:
Add stats to monitor the large transaction optimization. A stat is added for how many times wbwi ingestion is used. A histogram is added to track transaction size. We could also just track write batch size for all writes but I don't want to add the overhead to all writes yet.

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

Test Plan:
ran `python3 ./tools/db_crashtest.py --txn blackbox  --txn_write_policy=0 --commit_bypass_memtable_one_in=50 --test_batches_snapshots=0 --stats_dump_period_sec=2 --dump_malloc_stats=0 --statistics=1` and manually check LOG files
```
rocksdb.number.wbwi.ingest COUNT : 57
...
rocksdb.num.op.per.transaction P50 : 1.000000 P95 : 1.000000 P99 : 1.000000 P100 : 1.000000 COUNT : 2265 SUM : 2265
```

Reviewed By: jowlyzhang

Differential Revision: D74829087

Pulled By: cbi42

fbshipit-source-id: 5a9c3ab2d4cb6071cedfc47201ce2cf65a77d3c6
2025-05-16 11:51:58 -07:00
Jay Huh 024194420c Add ColumnFamily Info to CompactionServiceJobInfo (#13615)
Summary:
Similar to https://github.com/facebook/rocksdb/pull/13555, add more info, ColumnFamily Id and name, to `CompactionServiceJobInfo`.

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

Test Plan:
Updated Unit Test
```
./compaction_service_test
```

Reviewed By: archang19

Differential Revision: D74845661

Pulled By: jaykorean

fbshipit-source-id: e2fc61006092b9febec1c6637b92cb00fb6cb73e
2025-05-15 17:19:34 -07:00
Peter Dillinger 7c9b580681 Big refactor for preliminary custom compression API (#13540)
Summary:
Adds new classes etc. in internal compression.h that are intended to become public APIs for supporting custom/pluggable compression. Some steps remain to allow for pluggable compression and to remove a lot of legacy code (e.g. now called `OLD_CompressData` and `OLD_UncompressData`), but this change refactors the key integration points of SST building and reading and compressed secondary cache over to the new APIs.

Compared with the proposed https://github.com/facebook/rocksdb/issues/7650, this fixes a number of issues including
* Making a clean divide between public and internal APIs (currently just indicated with comments)
* Enough generality that built-in compressions generally fit into the framework rather than needing special treatment
* Avoid exposing obnoxious idioms like `compress_format_version` to the user.
* Enough generality that a compressor mixing algorithms/strategies from other compressors is pretty well supported without an extra schema layer
* Explicit thread-safety contracts (carefully considered)
* Contract details around schema compatibility and extension with code changes (more detail in next PR)
* Customizable "working areas" (e.g. for ZSTD "context")
* Decompression into an arbitrary memory location (rather than involving the decompressor in memory allocation; should facilitate reducing number of objects in block cache)

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

Test Plan:
This is currently an internal refactor. More testing will come when the new API is migrated to the public API. A test in db_block_cache_test is updated to meaningfully cover a case (cache warming compression dictionary block) that was previously only covered in the crash test.

SST write performance test, like https://github.com/facebook/rocksdb/issues/13583. Compile with CLANG, run before & after simultaneously:

```
SUFFIX=`tty | sed 's|/|_|g'`; for ARGS in "-compression_parallel_threads=1 -compression_type=none" "-compression_parallel_threads=1 -compression_type=snappy" "-compression_parallel_threads=1 -compression_type=zstd" "-compression_parallel_threads=1 -compression_type=zstd -verify_compression=1" "-compression_parallel_threads=1 -compression_type=zstd -compression_max_dict_bytes=8180" "-compression_parallel_threads=4 -compression_type=snappy"; do echo $ARGS; (for I in `seq 1 20`; do ./db_bench -db=/dev/shm/dbbench$SUFFIX --benchmarks=fillseq -num=10000000 -compaction_style=2 -fifo_compaction_max_table_files_size_mb=1000 -fifo_compaction_allow_compaction=0 -disable_wal -write_buffer_size=12000000 $ARGS 2>&1 | grep micros/op; done) | awk '{n++; sum += $5;} END { print int(sum / n); }'; done
```

Before (this PR and with https://github.com/facebook/rocksdb/issues/13583 reverted):
-compression_parallel_threads=1 -compression_type=none
1908372
-compression_parallel_threads=1 -compression_type=snappy
1926093
-compression_parallel_threads=1 -compression_type=zstd
1208259
-compression_parallel_threads=1 -compression_type=zstd -verify_compression=1
997583
-compression_parallel_threads=1 -compression_type=zstd -compression_max_dict_bytes=8180
934246
-compression_parallel_threads=4 -compression_type=snappy
1644849

After:
-compression_parallel_threads=1 -compression_type=none
1956054 (+2.5%)
-compression_parallel_threads=1 -compression_type=snappy
1911433 (-0.8%)
-compression_parallel_threads=1 -compression_type=zstd
1205668 (-0.3%)
-compression_parallel_threads=1 -compression_type=zstd -verify_compression=1
999263 (+0.2%)
-compression_parallel_threads=1 -compression_type=zstd -compression_max_dict_bytes=8180
934322 (+0.0%)
-compression_parallel_threads=4 -compression_type=snappy
1642519 (-0.2%)

Pretty neutral change(s) overall.

SST read performance test (related to https://github.com/facebook/rocksdb/issues/13583). Set up:
```
for COMP in none snappy zstd; do echo $ARGS; ./db_bench -db=/dev/shm/dbbench-$COMP --benchmarks=fillseq,flush -num=10000000 -compaction_style=2 -fifo_compaction_max_table_files_size_mb=1000 -fifo_compaction_allow_compaction=0 -disable_wal -write_buffer_size=12000000 -compression_type=$COMP; done
```
Test (compile with CLANG, run before & after simultaneously):
```
for COMP in none snappy zstd; do echo $COMP; (for I in `seq 1 5`; do ./db_bench -readonly -db=/dev/shm/dbbench-$COMP --benchmarks=readrandom -num=10000000 -duration=20 -threads=8 2>&1 | grep micros/op; done) | awk '{n++; sum += $5;} END { print int(sum / n); }'; done
```

Before (this PR and with https://github.com/facebook/rocksdb/issues/13583 reverted):
none
1495646
snappy
1172443
zstd
706036
zstd (after constructing with -compression_max_dict_bytes=8180)
656182

After:
none
1494981 (-0.0%)
snappy
1171846 (-0.1%)
zstd
696363 (-1.4%)
zstd (after constructing with -compression_max_dict_bytes=8180)
667585 (+1.7%)

Pretty neutral.

Reviewed By: hx235

Differential Revision: D74626863

Pulled By: pdillinger

fbshipit-source-id: dc8ff3178da9b4eaa7c16aa1bb910c872afaf14a
2025-05-15 17:14:23 -07:00
Miroslav Kovar fc2cf7ead2 Expose optimized TransactionBaseImpl::MultiGet through JNI (#13589)
Summary:
Addresses https://github.com/facebook/rocksdb/issues/13587.

This PR exposes the optimized implementation of batched reads through a `Transaction` object to Java clients.

The latency improvement of transactional multiget on production workload achieved by switching the implementation is roughly:
```
quantile=0.2: 21%
quantile=0.5: 28%
quantile=0.8: 46%
quantile=1.0: 239%
```

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

Reviewed By: jaykorean

Differential Revision: D74660169

Pulled By: cbi42

fbshipit-source-id: d01780173e0500c96e5e431ff6645008cbf6e8b5
2025-05-14 13:19:06 -07:00
anand76 df7a3a7168 Add debug printfs in secondary cache adapter destructor (#13606)
Summary:
Add debug printfs to troubleshoot an intermittent crash test assertion failure.

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

Reviewed By: mszeszko-meta

Differential Revision: D74661545

Pulled By: anand1976

fbshipit-source-id: 1b2a30fbbea3dcea5ce1a199344e946da687ff1f
2025-05-13 14:41:28 -07:00
Till Rohrmann 2a0886b9a7 Expose pinned WriteBatchWithIndex::GetFromBatchAndDB through C bindings (#12970)
Summary:
Expose pinned WriteBatchWithIndex::GetFromBatchAndDB through C bindings so that one can read data from the `WriteBatchWithIndex` and db w/o copying the data.

This fixes https://github.com/facebook/rocksdb/issues/12969.

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

Reviewed By: cbi42

Differential Revision: D74586418

Pulled By: jaykorean

fbshipit-source-id: a5a4d2e8ce3ddf4c2371fdfdb4e9c3309966a05d
2025-05-13 14:06:28 -07:00
Yu Zhang 9c4b94b9e7 Remove flaky test for file ingestion wait time metric (#13605)
Summary:
As titled.

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

Test Plan: This is removing a test

Reviewed By: mszeszko-meta

Differential Revision: D74660230

Pulled By: jowlyzhang

fbshipit-source-id: 9c1d46b56d2f9ee43eba645563d4f954645d1ace
2025-05-13 11:19:53 -07:00
ran-openai 35e1c6c402 Add internal_merge_point_lookup_count perfstats to c interface (#13599)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/13599

Reviewed By: virajthakur

Differential Revision: D74586452

Pulled By: cbi42

fbshipit-source-id: 58f31d96c040ae465afa1caba8cbb7434c72a366
2025-05-13 09:54:37 -07:00
Changyu Bi 8cb2bfa233 Fix race in accessing MANIFEST number in crash test (#13603)
Summary:
https://github.com/facebook/rocksdb/issues/13594 introduced the following data race. This PR attempts to fix it by acquiring DB mutex before accessing MANIFEST file number.
```
WARNING: ThreadSanitizer: data race (pid=9993)
  Write of size 8 at 0x7b60000014e8 by thread T50 (mutexes: write M143969571504678848):
    #0 rocksdb::ParseFileName(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, unsigned long*, rocksdb::Slice const&, rocksdb::FileType*, rocksdb::WalFileType*) file/filename.cc:326 (librocksdb.so.10.3+0xaa142f)
    https://github.com/facebook/rocksdb/issues/1 rocksdb::ParseFileName(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, unsigned long*, rocksdb::FileType*, rocksdb::WalFileType*) file/filename.cc:270 (librocksdb.so.10.3+0xaa1e91)
    https://github.com/facebook/rocksdb/issues/2 rocksdb::GetCurrentManifestPath(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, rocksdb::FileSystem*, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >*, unsigned long*) db/manifest_ops.cc:35 (librocksdb.so.10.3+0x80bd3f)
    https://github.com/facebook/rocksdb/issues/3 rocksdb::ReactiveVersionSet::MaybeSwitchManifest(rocksdb::log::Reader::Reporter*, std::unique_ptr<rocksdb::log::FragmentBufferedReader, std::default_delete<rocksdb::log::FragmentBufferedReader> >*) db/version_set.cc:7553 (librocksdb.so.10.3+0x91ca45)
    https://github.com/facebook/rocksdb/issues/4 rocksdb::ReactiveVersionSet::ReadAndApply(rocksdb::InstrumentedMutex*, std::unique_ptr<rocksdb::log::FragmentBufferedReader, std::default_delete<rocksdb::log::FragmentBufferedReader> >*, rocksdb::Status*, std::unordered_set<rocksdb::ColumnFamilyData*, std::hash<rocksdb::ColumnFamilyData*>, std::equal_to<rocksdb::ColumnFamilyData*>, std::allocator<rocksdb::ColumnFamilyData*> >*, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >*) db/version_set.cc:7531 (librocksdb.so.10.3+0x91de03)
    https://github.com/facebook/rocksdb/issues/5 rocksdb::DBImplSecondary::TryCatchUpWithPrimary() db/db_impl/db_impl_secondary.cc:709 (librocksdb.so.10.3+0x7006d5)
    https://github.com/facebook/rocksdb/issues/6 rocksdb::NonBatchedOpsStressTest::VerifyDb(rocksdb::ThreadState*) const db_stress_tool/no_batched_ops_stress.cc:235 (db_stress+0x48806b)
    https://github.com/facebook/rocksdb/issues/7 rocksdb::ThreadBody(void*) db_stress_tool/db_stress_driver.cc:23 (db_stress+0x4e5019)
    https://github.com/facebook/rocksdb/issues/8 StartThreadWrapper env/env_posix.cc:469 (librocksdb.so.10.3+0xa0977f)

  Previous read of size 8 at 0x7b60000014e8 by thread T44:
    #0 rocksdb::VersionSet::manifest_file_number() const db/version_set.h:1342 (librocksdb.so.10.3+0x69019b)
    https://github.com/facebook/rocksdb/issues/1 rocksdb::DBImpl::TEST_Current_Manifest_FileNo() db/db_impl/db_impl_debug.cc:87 (librocksdb.so.10.3+0x69019b)
    https://github.com/facebook/rocksdb/issues/2 rocksdb::NonBatchedOpsStressTest::VerifyDb(rocksdb::ThreadState*) const db_stress_tool/no_batched_ops_stress.cc:238 (db_stress+0x4880b6)
    https://github.com/facebook/rocksdb/issues/3 rocksdb::ThreadBody(void*) db_stress_tool/db_stress_driver.cc:23 (db_stress+0x4e5019)
    https://github.com/facebook/rocksdb/issues/4 StartThreadWrapper env/env_posix.cc:469 (librocksdb.so.10.3+0xa0977f)
```

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

Test Plan:
compile with TSAN, run `python3 ./tools/db_crashtest.py blackbox --test_secondary=1 --interval=10`
I could not reproduce it on main, but we can monitor if crash test fails with this race again.

Reviewed By: mszeszko-meta

Differential Revision: D74601810

Pulled By: cbi42

fbshipit-source-id: 46e13dcde9b0834053ed74c6f0937954dd36fea2
2025-05-12 15:58:33 -07:00
Changyu Bi 0e3e349369 Fix an infinite-loop bug in transaction locking (#13585)
Summary:
when a transaction reaches lock limit and times out before it attempts to wait for it (https://github.com/facebook/rocksdb/blob/9d1a071194de8093bbf3f8f57ffd176278359bf0/utilities/transactions/lock/point/point_lock_manager.cc#L320), it can busy-loop forever even though its timeout is expired. This PR fixes this bug by setting a timeout status when its timeout is reached.

This PR also updates the `LockLimit` status from `Busy` to `Aborted`, this matches the check in `Status::IsLockLimit()` and matches the customer usage (https://github.com/facebook/mysql-5.6/blob/c6e4b9f3f93dce206370105fe73ee337ece0c5e7/storage/rocksdb/ha_rocksdb.cc#L10745-L10746).

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

Test Plan: added a unit test that would infinite-loop before this fix.

Reviewed By: jaykorean

Differential Revision: D74077824

Pulled By: cbi42

fbshipit-source-id: 4993d4e4c71bb1594835e9ec6ff4a74d453a9190
2025-05-12 15:42:25 -07:00
Changyu Bi 0102b1769b Log pre-compression size written per level in compaction stats (#13596)
Summary:
Add a new field to Compaction Stats to track the pre-compression size written to each level. This logged in LOG files as column WPreComp(GB). Also improved logging of compaction_started event to include cf name.

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

Test Plan:
* Manually check LOG of db_bench runs:
With no compression
```
** Compaction Stats [default] **
Level    Files   Size     Score Read(GB)  Rn(GB) Rnp1(GB) Write(GB) WPreComp(GB) Wnew(GB) Moved(GB) W-Amp Rd(MB/s) Wr(MB/s) Comp(sec) CompMergeCPU(sec) Comp(cnt) Avg(sec) KeyIn KeyDrop Rblob(GB) Wblob(GB)
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  L0     21/9     96.06 MB   3.0      0.0     0.0      0.0       0.4       0.4      0.4       0.0   1.0      0.0    202.0      2.22              1.27        98    0.023   3829K      0       0.0       0.0
  L1      6/6    344.89 MB   0.0      1.5     0.3      1.2       1.5       1.5      0.3       0.0   4.4    280.4    279.1      5.52              5.15        10    0.552     13M    44K       0.0       0.0
 Sum     27/15   440.95 MB   0.0      1.5     0.3      1.2       1.9       1.9      0.8       0.0   4.4    200.0    257.0      7.74              6.42       108    0.072     17M    44K       0.0       0.0
 Int      0/0      0.00 KB   0.0      0.3     0.1      0.3       0.4       0.4      0.1       0.0   6.8    219.2    255.7      1.58              1.36        14    0.113   3484K    12K       0.0       0.0

** Compaction Stats [default] **
Priority    Files   Size     Score Read(GB)  Rn(GB) Rnp1(GB) Write(GB) WPreComp(GB) Wnew(GB) Moved(GB) W-Amp Rd(MB/s) Wr(MB/s) Comp(sec) CompMergeCPU(sec) Comp(cnt) Avg(sec) KeyIn KeyDrop Rblob(GB) Wblob(GB)
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Low      0/0      0.00 KB   0.0      1.5     0.3      1.2       1.5       1.5      0.3       0.0   0.0    280.4    279.1      5.52              5.15        10    0.552     13M    44K       0.0       0.0
High      0/0      0.00 KB   0.0      0.0     0.0      0.0       0.4       0.4      0.4       0.0   0.0      0.0    202.0      2.22              1.27        98    0.023   3829K      0       0.0       0.0
```

With expected compression ratio = 0.5
```
** Compaction Stats [default] **
Level    Files   Size     Score Read(GB)  Rn(GB) Rnp1(GB) Write(GB) WPreComp(GB) Wnew(GB) Moved(GB) W-Amp Rd(MB/s) Wr(MB/s) Comp(sec) CompMergeCPU(sec) Comp(cnt) Avg(sec) KeyIn KeyDrop Rblob(GB) Wblob(GB)
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  L0     21/10    54.23 MB   2.8      0.0     0.0      0.0       0.2       0.4      0.2       0.0   1.0      0.0    105.2      1.96              1.29        80    0.025   3126K      0       0.0       0.0
  L1      3/3    140.18 MB   0.0      0.5     0.1      0.4       0.5       0.9      0.1       0.0   3.4    131.1    128.1      3.99              3.89         8    0.499   8324K    26K       0.0       0.0
 Sum     24/13   194.41 MB   0.0      0.5     0.1      0.4       0.7       1.3      0.3       0.0   3.5     87.9    120.5      5.96              5.17        88    0.068     11M    26K       0.0       0.0
 Int      0/0      0.00 KB   0.0      0.3     0.1      0.2       0.3       0.6      0.1       0.0   5.7    105.7    125.9      2.45              2.23        23    0.107   4973K    15K       0.0       0.0

** Compaction Stats [default] **
Priority    Files   Size     Score Read(GB)  Rn(GB) Rnp1(GB) Write(GB) WPreComp(GB) Wnew(GB) Moved(GB) W-Amp Rd(MB/s) Wr(MB/s) Comp(sec) CompMergeCPU(sec) Comp(cnt) Avg(sec) KeyIn KeyDrop Rblob(GB) Wblob(GB)
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Low      0/0      0.00 KB   0.0      0.5     0.1      0.4       0.5       0.9      0.1       0.0   0.0    131.1    128.1      3.99              3.89         8    0.499   8324K    26K       0.0       0.0
High      0/0      0.00 KB   0.0      0.0     0.0      0.0       0.2       0.4      0.2       0.0   0.0      0.0    105.2      1.96              1.29        80    0.025   3126K      0       0.0       0.0
```

Reviewed By: hx235

Differential Revision: D74588464

Pulled By: cbi42

fbshipit-source-id: a998c0433230db4f3d7808636215b886b9ca5220
2025-05-12 11:53:16 -07:00
Changyu Bi ef67339175 Small fix in secondary DB and stress test (#13594)
Summary:
We saw some crash test failure for secondary db. It happens during crash recovery verification. This PR logs the manifest number when such failure happens. This PR also includes a small fix in `TryCatchUpWithPrimary()` that could incorrectly check WAL not found case.

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

Test Plan: monitor further secondary DB crash test failure.

Reviewed By: archang19

Differential Revision: D74488769

Pulled By: cbi42

fbshipit-source-id: 226e55b2f99a739e93abda3ee91c05b80f59bf6a
2025-05-09 12:55:40 -07:00
anand76 36600d8fa0 Pass wrapped WritableFileWriter to ExternalTableBuilder (#13591)
Summary:
This PR fixes a bug where the file checksum for an external table file was not being calculated by SstFileWriter. The checksum is calculated in WritableFileWriter, so we need to pass that the the external table builder rather than the FSWritableFile pointer directly. However, WritableFileWriter is private to RocksDB, so wrap it in an FSWritableFile and pass it.

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

Test Plan: Add a new test in table_test.cc

Reviewed By: jaykorean

Differential Revision: D74410563

Pulled By: anand1976

fbshipit-source-id: c7fa8142e20da8836589dee5fa50919951cf4046
2025-05-08 17:39:40 -07:00
Michael C Huang 13d865f6f1 Add trivial copy support when FIFO compaction reason is kChangeTemperature (#13562)
Summary:
Prior to this PR, for FIFO kChangeTemperature compaction was done by iterating and reading thru the input sst and generate the output sst. This was wasteful since for FIFO we could apply the "trivial" move by copying the input sst to the out sst without need decompress/compress and reading thru the input sst content at all. This PR added "allow_trivial_copy_when_change_temperature" to the CompactionOptionsFIFO.

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

Reviewed By: cbi42

Differential Revision: D73295404

Pulled By: mikechuangmeta

fbshipit-source-id: 02241c7389797730ecd4a3b636837cb5f912b424
2025-05-08 15:51:37 -07:00
Till Rohrmann 947a63400f Allow specifying ReadOptions for WBWI iterator (#12968)
Summary:
Allow specifying ReadOptions for WBWI iterator when creating it through the C bindings. This allows to specify upper and lower bounds for the created iterator.

This fixes https://github.com/facebook/rocksdb/issues/12963.

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

Reviewed By: pdillinger

Differential Revision: D74188049

Pulled By: jaykorean

fbshipit-source-id: 970d9910472dfedaa29a800c6d52bec14c656f3c
2025-05-06 11:42:10 -07:00
Changyu Bi f49d76b7ad Clarify that memtable_op_scan_flush_trigger does not support tailing iterator (#13586)
Summary:
clarify in comments and fix one implementation under NewIterator where option `memtable_op_scan_flush_trigger` does not work correctly with tailing iterator yet. This is because tailing iterator can rebuild iterator internally which reads from a newer memtable, and DBIter's reference to active memtable needs to be refreshed. This PR clarifies that `memtable_op_scan_flush_trigger` will have no effect on tailing iterator. We can add the support in the future if needed.

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

Test Plan: existing tests.

Reviewed By: jaykorean

Differential Revision: D74108099

Pulled By: cbi42

fbshipit-source-id: 7c6608485d57755abc44f3be0b3c5d82a7bc5ca9
2025-05-05 17:42:57 -07:00
Peter Dillinger 1428e950bd Bug fix and refactoring on parallel compression (#13583)
Summary:
While working on some compression refactoring, I noticed that `NotifyCollectTableCollectorsOnBlockAdd()` was being called from multiple threads (with `parallel_threads` > 1), meaning we were violating the promise that TablePropertiesCollectors need not be thread safe (and typically will not be, for efficiency).

Fixing this is a bit awkward or intrusive. Even though it seems weird to expose `block_compressed_bytes_fast` and `block_compressed_bytes_fast` in the public `BlockAdd()` function, and NOT the actual compressed block size used, there are some Meta-internal uses that would at least require negotiation / coordination to deprecate and remove. So it's probably easiest to just keep the awkward functionality and do the necessary modifications to call from a single thread.

The simplest solution that preserves the functionality with `parallel_threads` > 1 (provide the sampling data, expected ordering between `BlockAdd()` and `AddUserKey()`, no races) is to do the compression sampling in the thread building uncompressed blocks. Specifically, moving `NotifyCollectTableCollectorsOnBlockAdd()` and the compression sampling from `CompressAndVerifyBlock()`, which is called in parallel, to table builder `Flush()`, which is only called serially (per file). Even though this adds some compression to that single thread when sampling is enabled, that should be tolerable without complicating the code or regressing performance. Some related or nearby optimizations are included to ensure this.

* Got rid of a lot of unnecessary indirection and unnecessary fields in BlockRep, which should be a step in improving parallel compression performance (still bad IMHO).
* Restructured some `if`s etc. to streamline some logic

This satisfies my original refactoring need to moving the sampling code higher up the stack from `CompressBlock()`, to set up some other upcoming refactorings. The other caller of `CompressBlock()` (legacy BlobDB) doesn't need it, and in fact is better off calling `CompressData()` directly because it does not appear to be dealing with the various "no compression" outcomes introduced by `CompressBlock()`.

Eventual follow-up:
* Performance data below shows how the overhead of parallel compression can make it slower, with available CPUs, compared to serial compression. This infrastructure should be re-designed/re-engineered to reduce thread creation, context switches, etc. Also, more of the processing such as checksumming could be parallelized. (Things dependent on the block location in the file, such as ChecksumModifierForContext and cache warming, cannot be parallelized.)

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13583
ThreadSanitizer: data race /data/users/peterd/rocksdb/./db_stress_tool/db_stress_table_properties_collector.h:36:5 in rocksdb::DbStressTablePropertiesCollector::BlockAdd(unsigned long, unsigned long, unsigned long)
```

Performance:
```
SUFFIX=`tty | sed 's|/|_|g'`; for ARGS in "-compression_parallel_threads=1 -compression_type=none" "-compression_parallel_threads=1 -compression_type=snappy" "-compression_parallel_threads=4 -compression_type=snappy"; do echo $ARGS; (for I in `seq 1 100`; do ./db_bench -db=/dev/shm/dbbench$SUFFIX --benchmarks=fillseq -num=10000000 -compaction_style=2 -fifo_compaction_max_table_files_size_mb=1000 -fifo_compaction_allow_compaction=0 -disable_wal -write_buffer_size=12000000 $ARGS 2>&1 | grep micros/op; done) | awk '{n++; sum += $5;} END { print int(sum / n); }'; done
```

Average ops/s of 100 runs, running before & after at the same time, using clang DEBUG_LEVEL=0:

-compression_parallel_threads=1 -compression_type=none
Before: 1976319
After: 1983840 (+0.3%)
-compression_parallel_threads=1 -compression_type=snappy
Before: 1945576
After: 1953473 (+0.4%)
-compression_parallel_threads=4 -compression_type=snappy
Before: 1573190
After: 1611881 (+2.4%)
-compression_parallel_threads=4 -sample_for_compression=100 (pretty high sample rate)
Before: 1577167
After: 1589704 (+0.8%)
-compression_parallel_threads=4 -sample_for_compression=10 (crazy high sample rate)
Before: 1581276
After: 1393453 (-11.9%)

As seen, you need a very very high compression sample rate to see a regression. I would expect a setting like 1000 to be more typical.

Test Plan:
Along with existing unit tests + CI, expanded crash test to make its TablePropertiesCollector non-trivial, to exercise the bug (and other potential bugs), which was confirmed with local run of whitebox_crash_test with TSAN:

```

Reviewed By: hx235

Differential Revision: D73944593

Pulled By: pdillinger

fbshipit-source-id: f1dcba4ebdc01e735251037395003945c9b34e62
2025-05-02 13:10:06 -07:00
Changyu Bi e3b7dd7b56 Add a new transaction option for large transaction optimization (#13582)
Summary:
I added `TransactionDBOptions::txn_commit_bypass_memtable_threshold` previously but per DB option is not dynamically changeable. Adding it as a per transaction option to make it easier to use. The option naming is updated to make it easier for customer to understand `large_txn_commit_optimize_threshold`. The transaction DB option `TransactionDBOptions::txn_commit_bypass_memtable_threshold` is marked as deprecated.

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

Test Plan:
- new unit test
- updated stress test to use this new transaction option

Reviewed By: jowlyzhang

Differential Revision: D73960981

Pulled By: cbi42

fbshipit-source-id: 406f6e0f5f4eb6b336976f9a93b0bc08e61a9662
2025-05-02 12:16:02 -07:00
Jay Huh 9d1a071194 Use Hex for DebugString (#13580)
Summary:
Addressing belated comment in https://github.com/facebook/rocksdb/pull/13452.

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

Test Plan:
Temp change in the Unit Test to add a null char to the key and printed

Before the fix
```
DEBUG STRING BEFORE: --- level 0 --- version# 185 ---
 287:1286[1201 .. 1210]['key000000
DEBUG STRING AFTER: --- level 0 --- version# 185 ---
 287:1286[1201 .. 1210]['key000000
```

After the fix
```
DEBUG STRING BEFORE: --- level 0 --- version# 185 ---
 287:1286[1201 .. 1210]['6B657930303030303000' seq:1201, type:1 .. '6B657930303030313800' seq:1210, type:1]
 72:1261[261 .. 270]['6B6579303030313230' seq:261, type:1 .. '6B6579303030313338' seq:270, type:1]
 67:1259[241 .. 250]['6B6579303030303830' seq:241, type:1 .. '6B6579303030303938' seq:250, type:1]
 61:1259[211 .. 220]['6B6579303030303230' seq:211, type:1 .. '6B6579303030303338' seq:220, type:1]
 --- level 1 --- version# 185 ---
 70:1353[0 .. 0]['6B6579303030303030' seq:0, type:1 .. '6B6579303030303139' seq:0, type:1]
 23:1268[21 .. 30]['6B6579303030303230' seq:21, type:1 .. '6B6579303030303239' seq:30, type:1]
 25:1268[31 .. 40]['6B6579303030303330' seq:31, type:1 .. '6B6579303030303339' seq:40, type:1]
 86:1327[0 .. 0]['6B6579303030303430' seq:0, type:1 .. '6B6579303030303539' seq:0, type:1]
 74:1326[0 .. 0]['6B6579303030303630' seq:0, type:1 .. '6B6579303030303739' seq:0, type:1]
 35:1268[81 .. 90]['6B6579303030303830' seq:81, type:1 .. '6B6579303030303839' seq:90, type:1]
 37:1268[91 .. 100]['6B6579303030303930' seq:91, type:1 .. '6B6579303030303939' seq:100, type:1]
 78:1335[0 .. 0]['6B6579303030313030' seq:0, type:1 .. '6B6579303030313139' seq:0, type:1]
 43:1270[121 .. 130]['6B6579303030313230' seq:121, type:1 .. '6B6579303030313239' seq:130, type:1]
 45:1270[131 .. 140]['6B6579303030313330' seq:131, type:1 .. '6B6579303030313339' seq:140, type:1]
 82:1332[0 .. 0]['6B6579303030313430' seq:0, type:1 .. '6B6579303030313539' seq:0, type:1]
 90:1333[0 .. 0]['6B6579303030313630' seq:0, type:1 .. '6B6579303030313739' seq:0, type:1]
 94:1332[0 .. 0]['6B6579303030313830' seq:0, type:1 .. '6B6579303030313939' seq:0, type:1]
 --- level 2 --- version# 185 ---
 --- level 3 --- version# 185 ---
 --- level 4 --- version# 185 ---
 --- level 5 --- version# 185 ---
 --- level 6 --- version# 185 ---

DEBUG STRING AFTER: --- level 0 --- version# 185 ---
 287:1286[1201 .. 1210]['6B657930303030303000' seq:1201, type:1 .. '6B657930303030313800' seq:1210, type:1]
 72:1261[261 .. 270]['6B6579303030313230' seq:261, type:1 .. '6B6579303030313338' seq:270, type:1]
 67:1259[241 .. 250]['6B6579303030303830' seq:241, type:1 .. '6B6579303030303938' seq:250, type:1]
 61:1259[211 .. 220]['6B6579303030303230' seq:211, type:1 .. '6B6579303030303338' seq:220, type:1]
 --- level 1 --- version# 185 ---
 70:1353[0 .. 0]['6B6579303030303030' seq:0, type:1 .. '6B6579303030303139' seq:0, type:1]
 23:1268[21 .. 30]['6B6579303030303230' seq:21, type:1 .. '6B6579303030303239' seq:30, type:1]
 25:1268[31 .. 40]['6B6579303030303330' seq:31, type:1 .. '6B6579303030303339' seq:40, type:1]
 86:1327[0 .. 0]['6B6579303030303430' seq:0, type:1 .. '6B6579303030303539' seq:0, type:1]
 74:1326[0 .. 0]['6B6579303030303630' seq:0, type:1 .. '6B6579303030303739' seq:0, type:1]
 35:1268[81 .. 90]['6B6579303030303830' seq:81, type:1 .. '6B6579303030303839' seq:90, type:1]
 37:1268[91 .. 100]['6B6579303030303930' seq:91, type:1 .. '6B6579303030303939' seq:100, type:1]
 78:1335[0 .. 0]['6B6579303030313030' seq:0, type:1 .. '6B6579303030313139' seq:0, type:1]
 43:1270[121 .. 130]['6B6579303030313230' seq:121, type:1 .. '6B6579303030313239' seq:130, type:1]
 45:1270[131 .. 140]['6B6579303030313330' seq:131, type:1 .. '6B6579303030313339' seq:140, type:1]
 82:1332[0 .. 0]['6B6579303030313430' seq:0, type:1 .. '6B6579303030313539' seq:0, type:1]
 90:1333[0 .. 0]['6B6579303030313630' seq:0, type:1 .. '6B6579303030313739' seq:0, type:1]
 94:1332[0 .. 0]['6B6579303030313830' seq:0, type:1 .. '6B6579303030313939' seq:0, type:1]
 --- level 2 --- version# 185 ---
 --- level 3 --- version# 185 ---
 --- level 4 --- version# 185 ---
 --- level 5 --- version# 185 ---
 --- level 6 --- version# 185 ---
```

Reviewed By: hx235

Differential Revision: D73793661

Pulled By: jaykorean

fbshipit-source-id: d553ad24489cb2eff499b1ece457c6295a1ec697
2025-04-29 11:29:22 -07:00
Jay Huh 72c3887167 Fix build (#13579)
Summary:
- [Failed CI run](https://productionresultssa17.blob.core.windows.net/actions-results/fd083599-6c98-4aec-8732-fcb280c96021/workflow-job-run-2f73efd7-c93d-53ea-a18f-1c7e17604f7e/logs/job/job-logs.txt?rsct=text%2Fplain&se=2025-04-28T17%3A15%3A01Z&sig=YJevYF5xH4RClY3klBe6Z3tnCWuYZFLlBYRHwftW9lc%3D&ske=2025-04-29T01%3A55%3A36Z&skoid=ca7593d4-ee42-46cd-af88-8b886a2f84eb&sks=b&skt=2025-04-28T13%3A55%3A36Z&sktid=398a6654-997b-47e9-b12b-9515b896b4de&skv=2025-01-05&sp=r&spr=https&sr=b&st=2025-04-28T17%3A04%3A56Z&sv=2025-01-05)

```
2025-04-28T16:56:00.5775476Z In file included from <stdin>:1:
2025-04-28T16:56:00.5776056Z db/blob/blob_file_meta.h:28:7: error: 'uint64_t' has not been declared
2025-04-28T16:56:00.5776715Z    28 |       uint64_t blob_file_number, uint64_t total_blob_count,
2025-04-28T16:56:00.5777153Z       |       ^~~~~~~~
2025-04-28T16:56:00.5778083Z db/blob/blob_file_meta.h:15:1: note: 'uint64_t' is defined in header '<cstdint>'; this is probably fixable by adding '#include <cstdint>'
2025-04-28T16:56:00.5779293Z    14 | #include "rocksdb/rocksdb_namespace.h"
2025-04-28T16:56:00.5782126Z   +++ |+#include <cstdint>
2025-04-28T16:56:00.5782780Z    15 |
2025-04-28T16:56:00.5783204Z db/blob/blob_file_meta.h:28:34: error: 'uint64_t' has not been declared
2025-04-28T16:56:00.5783832Z    28 |       uint64_t blob_file_number, uint64_t total_blob_count,
2025-04-28T16:56:00.5784301Z       |                                  ^~~~~~~~
```

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

Test Plan: [CI](https://github.com/facebook/rocksdb/actions/runs/14713618495/job/41291839382?pr=13579)

Reviewed By: archang19, cbi42

Differential Revision: D73799590

Pulled By: jaykorean

fbshipit-source-id: 7ead97914c05958bb7146f1934c48615599bc4f8
2025-04-28 13:35:48 -07:00
Jay Huh b2815b6b46 Update folly lib (#13576)
Summary:
After some bisecting, we were able to pinpoint that https://github.com/facebook/folly/commit/7881d1e7858f35ce7176dded26162cf8f575b24c is the commit that breaks the RocksDB build-with-folly.

https://github.com/facebook/folly/commit/8e8186f67de7a23d3a07366946b1617343927d84 is the latest folly that we can update to without additional change.

Fix for the incompatible change will be followed as a separate PR.

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

Test Plan: CI

Reviewed By: hx235

Differential Revision: D73693236

Pulled By: jaykorean

fbshipit-source-id: ff94e023a361c64dea8388cb8bb9db91a2762894
2025-04-28 08:43:59 -07:00
Changyu Bi 6c0e55a2a9 Fix a bug where lock upgrade can incorrectly return deadlock status (#13575)
Summary:
AcquireLocked() returns transaction ids that currently hold the lock for deadlock detection purpose. We should not include the id of the transaction that is trying to acquire the lock, since this would lead to a false-positive deadlock detection where the deadlock is a self-loop. Note that since `wait_ids` is never cleared, there is another bug where if AcquireLocked() fails with kLockLimit, we could do deadlock detection based on `wait_ids` from a previous lock acquire attempt. This PR fixes both bugs.

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

Test Plan: added a unit test repro that shows deadlock status can be incorrectly returned.

Reviewed By: jaykorean

Differential Revision: D73617887

Pulled By: cbi42

fbshipit-source-id: a6388b3ec53db13e2c502d60199378ea95885841
2025-04-25 17:15:03 -07:00
anand76 0560544e86 Fix ExternalTableOptions initialization (#13572)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/13572

Reviewed By: moakbari

Differential Revision: D73568773

Pulled By: anand1976

fbshipit-source-id: d61d76cb864e3af111bb05dc1ee51a8b3f1eaf17
2025-04-24 12:27:10 -07:00
Hui Xiao 613e1a9a38 Verify flush output file record count + minor clean up (#13556)
Summary:
**Context/Summary:**
Similar to https://github.com/facebook/rocksdb/commit/0a43d8a261b9c633c0a4e369b1ef33aa5ee32810, this is to verify flush output file contains the exact number of keys (represented by its `TableProperties::num_entries`) as added to table builder for block-based and plain table format. The implementation reuses a temporary compaction stats to record output record and existing input record (with some refactoring)

**Bonus:**
following https://github.com/facebook/rocksdb/commit/0a43d8a261b9c633c0a4e369b1ef33aa5ee32810#r154313564, limit compaction output record count check within block based table and plain table format as well as removing extra test setting; fix some typo

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

Test Plan: New test

Reviewed By: jaykorean

Differential Revision: D73229644

Pulled By: hx235

fbshipit-source-id: 2a7796450048b3bcb2d5c38f2b5fc6b53e4aae37
2025-04-23 14:52:56 -07:00
Jesson Yo bcda3bda04 add SST file manager to C api (#13404)
Summary:
we want to limit the maximum disk space used by RocksDB in one of our Go services, as it runs on a highly disk-constrained network switch.

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

Reviewed By: cbi42

Differential Revision: D73517940

Pulled By: jaykorean

fbshipit-source-id: ae91fc7a4992399e20f06cc67dad8130cf19049e
2025-04-23 10:33:06 -07:00
Peter Dillinger 9998478c64 Deflake test DBPropertiesTest.AggregatedTableProperties (#13568)
Summary:
This test was failing sporadically for me, like

```
db/db_properties_test.cc:247: Failure
Expected: (static_cast<double>(dbl_a - dbl_b) / (dbl_a + dbl_b)) <
(bias), actual: 0.113964 vs 0.1
```

I tried waiting for compaction in the test, but that made it fail consistently. Based on inspection of the test and the related test AggregatedTablePropertiesAtLevel already using `disable_auto_compactions = true`, I'm applying that to this test.

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

Test Plan: Parallel runs of the unit test, before and after

Reviewed By: jaykorean

Differential Revision: D73463685

Pulled By: pdillinger

fbshipit-source-id: 84df7cc9bdcd1caa108a7be254ffbebbe9a77de7
2025-04-22 15:31:46 -07:00
Peter Dillinger c368c6afe8 Minor compression refactoring (#13539)
Summary:
* Mostly, remove `sample_for_compression` from CompressionInfo because it's not used by the core function it serves, `CompressData()`. Confusing (and inefficient), especially in db_bench where it appears to use `FLAGS_sample_for_compression` in places where it is actually ignored.
* Various clarifying comments, clean-ups, and tiny optimizations
* Prepare some structures like `CompressionDict` for more usage
* Some TODOs and FIXMEs about some things I've noticed are amiss, confusing, or excessive
* A notable optimization opportunity that might become a "pay as you go" improvement for the potential indirection costs of customizable compression: use C++23's resize_and_overwrite() in compress functions to avoid zeroing the string buffer contents before populating it.

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

Test Plan: existing tests / CI

Reviewed By: hx235

Differential Revision: D73451273

Pulled By: pdillinger

fbshipit-source-id: 0373627466d695043d21146ce34d52f189ae9432
2025-04-22 13:02:36 -07:00
Jay Huh 1614345a52 add missing version.h change for 10.3 release (#13567)
Summary:
Follow up for https://github.com/facebook/rocksdb/pull/13566

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

Test Plan: CI

Reviewed By: pdillinger

Differential Revision: D73407482

Pulled By: jaykorean

fbshipit-source-id: 0bb7492473c0691a50d25288f0350ab097958de7
2025-04-22 09:08:36 -07:00
Jay Huh c237022831 Update for next release 10.3.0 (#13566)
Summary:
Updated version, HISTORY and compatibility script for 10.3 release (no folly hash update in this release).

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

Test Plan: CI

Reviewed By: anand1976

Differential Revision: D73391839

Pulled By: jaykorean

fbshipit-source-id: 075bb1f9f25caf96c4fcca7f4a315666acd5a288
2025-04-21 15:58:58 -07:00
anand76 7eb1adb532 Pass FSWritableFile pointer to ExternalTableBuilder (#13560)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/13560

Reviewed By: jaykorean

Differential Revision: D73296242

Pulled By: anand1976

fbshipit-source-id: b692a5c6ad32b40b3c2c1ca7a93bd04139856bce
2025-04-21 10:36:45 -07:00
Jay Huh 0be3abf7b6 Arbitrary string map in CompactionServiceOptionsOverride (#13552)
Summary:
Adding an arbitrary options map so that any additional overridable options can be added without RocksDB change. Unknown options will be ignored

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

Test Plan:
Unit Test added
```
./db_secondary_test -- --gtest_filter="*OptionsOverrideTest*"
```

Reviewed By: hx235

Differential Revision: D73203789

Pulled By: jaykorean

fbshipit-source-id: 176bd9849d2bc60e78657c119e10a1a2a0988cd1
2025-04-21 10:19:14 -07:00
Jay Huh 05fa171beb Add Logger to CompactionServiceOptionsOverride (#13559)
Summary:
As title

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

Test Plan: CI

Reviewed By: anand1976

Differential Revision: D73267683

Pulled By: jaykorean

fbshipit-source-id: 6a3d3da07a36ad3bbfad3f749e7dfd67b7b626c8
2025-04-18 16:43:56 -07:00
Jay Huh 9b186c8d11 Add base_input_level and output_level in CompactionServiceJobInfo (#13555)
Summary:
Similar to https://github.com/facebook/rocksdb/pull/13029, add `base_input_level` (a.k.a. start_level) and `output_level` to `CompactionServiceJobInfo`

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

Test Plan:
Updated Unit Tests
```
./compaction_service_test
```

Reviewed By: anand1976

Differential Revision: D73213504

Pulled By: jaykorean

fbshipit-source-id: abb3b0025bc12245b812ef589fe77e9a30ba0c46
2025-04-17 17:43:05 -07:00
Yu Zhang 476a98ca30 Add a new GetNewestUserDefinedTimestamp API (#13547)
Summary:
This PR adds a DB::GetNewestUserDefinedTimestamp API to get the newest timestamp of the column family. This is only for when the column family enables user defined timestamp.
It checks the mutable memtable, the immutable memtable and the SST files, and returns the first newest user defined timestamp found. When user defined timestamp is not persisted in SST files, there is metadata in MANIFEST tracking upperbound of flushed timestamps, so the newest timestamp in SST files can be found. If user defined timestamps are
persisted in SST files, currently no timestamp metadata info is persisted. A NotSupported status will be returned if SST files need to be checked in that case.

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

Test Plan: Added tests

Reviewed By: cbi42

Differential Revision: D73123575

Pulled By: jowlyzhang

fbshipit-source-id: 460ac4f9c96926d3c8fcf7944edab8dc0feae1dd
2025-04-17 13:19:52 -07:00
Changyu Bi 925c63a96b Experimental API IngestWriteBatchWithIndex() (#13550)
Summary:
add support for ingesting a WriteBatchWithIndex into the DB with the new API `IngestWriteBatchWithIndex()`. This ingestion works similarly as `TransactionOptions::commit_bypass_memtable` where the WBWI will be ingested as an immutable memtable. Since this skips memtable writes, it improves the write performance when writing a large write batch into the DB. Currently this API only supports `disableWAL=true`. Support for WAL write will be in a follow up if needed.

For a WBWI to be ingestable, we needed to call `SetTrackPerCFStat()` at WBWI creation. This PR removes this step for simpler usage and per CF stats will always be tracked in WBWI. `WBWIIteratorImpl::TestOutOfBound()` is optimized to offset the performance impact.

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

Test Plan:
- new unit test
- stress test option ingest_wbwi_one_in and ran a few runs of `python3 ./tools/db_crashtest.py blackbox --enable_pipelined_write=0 --use_timed_put_one_in=0 --use_put_entity_one_in=0 --ingest_wbwi_one_in=10 --test_batches_snapshots=0 --enable_blob_files=0 --preserve_unverified_changes=1 --avoid_flush_during_recovery=1 --disable_wal=1 --inplace_update_support=0 --interval=40`

Reviewed By: jowlyzhang

Differential Revision: D73152223

Pulled By: cbi42

fbshipit-source-id: 339f8ed26ac5a798238870df3ba857ba1add759b
2025-04-17 12:06:40 -07:00
anand76 6d83a75595 Pass FileSystem pointer and FileOptions to ExternalTableReader (#13551)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/13551

Reviewed By: jaykorean

Differential Revision: D73157052

Pulled By: anand1976

fbshipit-source-id: 580a9104a86b11e3b0b624bb8aa2cf176dc7a27a
2025-04-17 11:25:11 -07:00
Zaidoon Abd Al Hadi 31b2397470 Expose Options::memtable_op_scan_flush_trigger through C API (#13537)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/13537

Reviewed By: jowlyzhang

Differential Revision: D73141407

Pulled By: cbi42

fbshipit-source-id: c7e04b403a17773e651f4922976f213b817f7adc
2025-04-16 20:45:38 -07:00
Yu Zhang 695c653e11 Correctly initialize file size for reopened writable file (#13534)
Summary:
A reopened writable file's size is not correctly tracked in the `WritableFile`'s internal state.  This PR adds a querying to the file system to get the initial file size in the reopen case and use it to populate posix `WritableFile`'s internal state.

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

Reviewed By: anand1976

Differential Revision: D72756628

Pulled By: jowlyzhang

fbshipit-source-id: 6f02b5c5da069fe49055d7b75bec9e7e47d5cd71
2025-04-16 17:24:12 -07:00
Yu Zhang 0e736666a0 Add a test for using atomic_replace_range to ingeset and replace data (#13549)
Summary:
Add a test to cover an internal user's expected behavior of using atomic_replace_range feature to atomically ingest a version key and a data file.

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

Test Plan: This is a test

Reviewed By: cbi42

Differential Revision: D73142626

Pulled By: jowlyzhang

fbshipit-source-id: a5bdc24b762cbe91dd4d94242b9e1539c9feaf61
2025-04-16 16:32:45 -07:00
Changyu Bi 1ec5a07d8e Support atomic_flush for ingesting WBWI (#13545)
Summary:
add support for atomic_flush when using WBWI ingestion [feature](https://github.com/facebook/rocksdb/blob/29c6610617ddc1b486f12b99c16e7c9851e80430/include/rocksdb/utilities/transaction_db.h#L387). Transaction DB usually uses WAL so atomic_flush is not as helpful. This is to prepare for a follow up PR that enables ingesting WBWI without using transaction DB.

This PR also removes a redundant parameter `prep_log` for the WBWI ingestion feature.

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

Test Plan:
- unti test added
- stress test will be added as we add support to ingest WBWI without using transaction DB.

Reviewed By: jowlyzhang

Differential Revision: D73062342

Pulled By: cbi42

fbshipit-source-id: e05da55dfabb8241a042214b9d50b1b49d42613e
2025-04-16 15:18:48 -07:00
Hui Xiao 29c6610617 Add compaction explicit prefetch stats (#13520)
Summary:
**Context/Summary:**
This PR adds new stats to measure compaction readahead size for rocksdb managed prefetching (not FS prefetching). It can be used to verify compaction read-ahead is doing what's configured. This PR also excludes compaction readahead stats from user scan readahead stats measured in existing stats so there is a cleaner separating between these two.

Bonus: this PR also included some typo fixing about "io activities"

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

Test Plan: Modified existing test to verify stats

Reviewed By: archang19

Differential Revision: D72892850

Pulled By: hx235

fbshipit-source-id: 1a73182061baa044c9c9193a2b0fd967ffe75c4a
2025-04-14 12:08:38 -07:00
anand76 84a8dd994c Some MultiScan code cleanup (#13530)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/13530

Reviewed By: pdillinger

Differential Revision: D72677865

Pulled By: anand1976

fbshipit-source-id: 63e7a15b6e8cd61b676e3b22e1c04c7446adcbd3
2025-04-11 11:35:57 -07:00
Peter Dillinger 2a0ee4ddd8 Refactor wal related naming and more (#13490)
Summary:
* Clarify in API comments which `log_` options in DBOptions relate to WALs, info log, and/or manifest files.
* Rename a bunch of "log" things to "wal" for clarity, especially in DBImpl. (More to go, especially some more challenging cases like `DBImpl::logs_`, but a step in the right direction IMHO)
* Simplify DBImpl ctor by moving constant initializers to field definitions.
* Use RelaxedAtomic for (renamed) `wals_total_size_`

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

Test Plan: existing tests

Reviewed By: cbi42

Differential Revision: D71939382

Pulled By: pdillinger

fbshipit-source-id: 852f4737eca83e6ad653010cc197ad1b6e6bae13
2025-04-11 10:08:29 -07:00
Changyu Bi 56359da691 Trigger memtable flush based on number of hidden entries scanned (#13523)
Summary:
Introduce a mutable CF option `memtable_op_scan_flush_trigger`. When a DB iterator scans this number of hidden entries (tombstones, overwritten puts) from the active memtable in a Seek() or Next() operation, it marks the memtable to be eligible for flush. Subsequent write operations will schedule the marked memtable for flush.

The main change is small and is in db_iter.cc. Some refactoring is done to consolidate and simplify creation of `ArenaWrappedDBIter` and `DBIter`.

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

Test Plan:
- new unit tests added.
- added `memtable_op_scan_flush_trigger` in crash test
- benchmark:
The following benchmark was done with a previous version of the PR where the option was `memtable_tombstone_scan_limit` and it concerns tombstone only. The results should still be applicable for the case when there's no overwritten puts.

Tests that when memtable has many tombstones, the option helps to improve scan performance:
```
TEST_TMPDIR=/dev/shm ./db_bench --benchmarks=seekrandomwhilewriting --expand_range_tombstones=true --writes_per_range_tombstone=1 --max_num_range_tombstones=10000000 --perf_level=2 --range_tombstone_width=100 --memtable_tombstone_scan_limit=

memtable_tombstone_scan_limit = 10000
seekrandomwhilewriting :      18.527 micros/op 53973 ops/sec 18.527 seconds 1000000 operations; (7348 of 1000000 found)
next_on_memtable_count = 122305248
grep "flush_started" /dev/shm/dbbench/LOG | wc
      8     200    2417

memtable_tombstone_scan_limit=200
seekrandomwhilewriting :       4.918 micros/op 203315 ops/sec 4.918 seconds 1000000 operations; (4510 of 1000000 found)
next_on_memtable_count = 1853167
grep "flush_started" /dev/shm/dbbench/LOG | wc
    184    4600   54121

When memtable_tombstone_scan_limit=200, more flush is trigged to drop tombstones sooner and improve scan performance.
```

Tests that the new option does not introduce noticeable regression:
```
TEST_TMPDIR=/dev/shm ./db_bench --benchmarks=seekrandomwhilewriting[-X5] --expand_range_tombstones=true --writes_per_range_tombstone=1 --max_num_range_tombstones=10000000 --perf_level=2 --range_tombstone_width=100 --seed=123

Main:
seekrandomwhilewriting [AVG 5 runs] : 46049 (± 4512) ops/sec
PR:
seekrandomwhilewriting [AVG 5 runs] : 46100 (± 4470) ops/sec

The results are noisy with this PR performing better and worse in different runs, with no noticeable regression.
```

Reviewed By: pdillinger

Differential Revision: D72596434

Pulled By: cbi42

fbshipit-source-id: 2d51a0221dc20dac844aeba2ad3999d075a4cf91
2025-04-10 17:53:33 -07:00
Yu Zhang 46c37a6327 Fix issue with reverse iteration with unprepared value (#13531)
Summary:
When ReadOptions.allow_unprepared_value is true, a `Iterator::PrepareValue()` call is needed to prepare the value after an entry is pinpointed, to only load the blob when it's actually needed. And it uses the `saved_key_.GetUserKey()` to prepare value.
https://github.com/facebook/rocksdb/blob/6d802639f7dc35bf765dbe1ed6b3942e4d76375d/db/db_iter.cc#L319

In the reverse iteration case, when the `FindValueForCurrentKeyUsingSeek()` path is used, `saved_key_` is only updated when `ReadOptions.iter_start_ts` is specified. This PR fixes it by updating `saved_key_` for the other case too.

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

Test Plan: The FIXME test that reproduce the bug is updated

Reviewed By: pdillinger

Differential Revision: D72681397

Pulled By: jowlyzhang

fbshipit-source-id: 6c239da53c9beed1560d30013474f2ba542b245c
2025-04-10 16:20:30 -07:00
anand76 f7764cb6b2 Remove fail_if_options_file_error DB option (#13504)
Summary:
The fail_if_options_file_error has been deprecated for more than a year. This PR removes it from the code base. https://github.com/facebook/rocksdb/issues/12056 fixed a bug that was blocking the option from removal. https://github.com/facebook/rocksdb/issues/12249 marked it as deprecated.

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

Reviewed By: hx235

Differential Revision: D72194063

Pulled By: anand1976

fbshipit-source-id: 0aa7cf56e60c48c7e7654743d3e64922ce65225d
2025-04-09 14:18:33 -07:00
Yu Zhang 6d802639f7 Fix a data race reported for secondary (#13529)
Summary:
Fix a reported data race, accessing `manifest_reader_` without locking `mutex_` could race with another `DBImpl::Secondary::TryCatchUpWithPrimary` thread that is updating to a new manifest in `ReactiveVersionSet::MaybeSwitchManifest`.

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

Test Plan: Existing tests

Reviewed By: hx235

Differential Revision: D72655645

Pulled By: jowlyzhang

fbshipit-source-id: 08599862346bb39a6872c3adfd7f0097fc633849
2025-04-08 15:16:55 -07:00
Yu Zhang 5e10baa412 Delete max_write_buffer_number_to_maintain (#13491)
Summary:
As titled. This option has been marked deprecated since introduction of a better option `max_write_buffer_size_to_maintain` and acts as its fallback since RocksDB 6.5.0 The internal user we know these options were created for migrated to `max_write_buffer_size_to_maintain` for a long time too.

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

Test Plan: existing tests

Reviewed By: cbi42

Differential Revision: D71984601

Pulled By: jowlyzhang

fbshipit-source-id: c264d4809e311f60fdbad817ebfade256db549b6
2025-04-07 21:44:36 -07:00
Hui Xiao 72571d09ad Clean up in repair, file ingestion and cf import (#13524)
Summary:
**Context/Summary:**
Rebased on https://github.com/facebook/rocksdb/pull/13522/files, this is to use the refactored function to calculate tail size from table property "tail_start_offset"

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

Test Plan: CI

Reviewed By: anand1976

Differential Revision: D72576262

Pulled By: hx235

fbshipit-source-id: 78c126bc64024c2341d183d6871e06d55fd27501
2025-04-07 12:50:56 -07:00
Hui Xiao 07b09c7548 Persist tail size of remote compaction output file to manifest (#13522)
Summary:
**Context/Summary:**

This is to fix a bug that tail size of remote compaction output SST file is not persisted to manifest in primary instance. This prevent us from using direct tail prefetch optimization each time opening this SST file.

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

Test Plan: Modify existing UT that failed before the fix

Reviewed By: anand1976

Differential Revision: D72479612

Pulled By: hx235

fbshipit-source-id: 1ba8aa66fac71b9196589f60076229c29a103706
2025-04-07 09:39:54 -07:00
Yu Zhang 4069afeede Add safeguarding from resurrected cutoff UDT from previous session (#13521)
Summary:
Public APIs like `DB::GetFullHistoryTsLow` and `DB::IncreaseFullHistoryTsLow` have such safeguarding, allowing them to only be invoked when user defined timestamp is enabled. This PR adds safeguarding into related internal APIs in `ColumnFamilyData` to properly handle the case when the UDT feature are toggled.

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

Test Plan: ./db_with_timestamp_basic_test --gtest_filter="*EnableDisableUDT*"

Reviewed By: cbi42

Differential Revision: D72475234

Pulled By: jowlyzhang

fbshipit-source-id: 194c07287e3100da95450b04c76552c9d4a86c2d
2025-04-04 17:13:56 -07:00
anand76 24e2b05e61 Multi scan API (#13473)
Summary:
A multi scan API for users to pass a set of scan ranges and have the table readers determine the optimal strategy for performing the scans. This might include coalescing of IOs across scans, for example. The requested scans should be in increasing key order. The scan start keys and other info is passed to NewMultiScanIterator, which in turn uses the newly added Prepare() interface in Iterator to update the iterator. The Prepare() takes a vector of ScanOptions, which contain the start keys and optional upper bounds, as well as user defined parameters in the property_bag taht are passed through as is to external table readers.

The initial implementation plumbs this through to the ExternalTableReader. This PR also fixes an issue of premature destruction of the external table iterator after the first scan of the multi-scan. The `LevelIterator` treats an invalid iterator as a potential end of file and destroys the table iterator in order to move to the next file. To prevent that, this PR defines the `NextAndGetResult` interface that the external table iterator must implement. The result returned by `NextAndGetResult` differentiates between iterator invalidation due to out of bound vs end of file.

Eventually, I envision the `MultiScanIterator` to be built on top of a producer-consumer queue like container, with RocksDB (producer) enqueueing keys and values into the container and the application (consumer) dequeueing them. Unlike a traditional producer consumer queue, there is no concurrency here. The results will be buffered in the container, and when the buffer is empty a new batch will be read from the child iterators. This will allow the virtual function call overhead to be amortized over many entries.

TODO (in future PRs):
1. Update the internal implementation of Prepare to trim the ScanOptions range based on the intersection with the table key range, taking into consideration unbounded scans and opaque user defined bounds.
2. Long term, take advantage of Prepare in BlockBasedTableIterator, atleast for the upper bound case.

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

Reviewed By: pdillinger

Differential Revision: D71447559

Pulled By: anand1976

fbshipit-source-id: 31668abb0c529aa1ac1738ae46c36cbddf9148f1
2025-04-02 16:07:56 -07:00
Hui Xiao 5735ff4e03 Update window build cmake to download newer snappy version and Java cmake_minimum_required (#13514)
Summary:
**Context/Summary:**

- This is an attempt to fix our [build-window-vs2022 failure](https://github.com/facebook/rocksdb/actions/runs/14215681026/job/39831770554?fbclid=IwZXh0bgNhZW0CMTAAAR2BQLjp8kC1u1yyvN1_S5qwmrHEZOfzxJdcbj2vq7mvwwq83n1cbkmiBCA_aem_ygYxQA5EUmxh2y4EjMlTfg) below. snappy-1.1.8's cmake_minimum_required  being less than 3.5 seems to trigger the complaint. Hopefully downloading the 1.2.2 which is the [first version starting to use higher cmake_minimum_required version](https://github.com/google/snappy/releases/tag/1.2.2) solves the failure.

```
    Directory: D:\a\rocksdb\rocksdb\thirdparty\snappy-1.1.8

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----            4/2/2025  9:02 AM                build
CMake Error at CMakeLists.txt:29 (cmake_minimum_required):
  Compatibility with CMake < 3.5 has been removed from CMake.

  Update the VERSION argument <min> value.  Or, use the <min>...<max> syntax
  to tell CMake that the project requires at least <min> but has been updated
  to work with policies introduced by <max> or earlier.

  Or, add -DCMAKE_POLICY_VERSION_MINIMUM=3.5 to try configuring anyway.
 ```
- The downloaded snappy do not include the content under nested repos Google Test and Google Benchmark. But snappy cmake by default will attempt to build them. Since we don't change snappy, we don't need building such development suit. This PR also disabled snappy cmake's attempt to build them.

- By running above changes, the same build [complained](https://github.com/facebook/rocksdb/actions/runs/14228883966/job/39874927730?pr=13514) about java cmakelists requiring too low cmake_minimum_required as well.  So this PR also upgraded its cmake_minimum_required to be 3.11 aligning with its warning message
```
if(${CMAKE_VERSION} VERSION_LESS "3.11.4")
    message("Please consider switching to CMake 3.11.4 or newer")
endif()
```

**Test plan**
Monitor build-window-vs2022 for this PR

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

Reviewed By: pdillinger

Differential Revision: D72333581

Pulled By: hx235

fbshipit-source-id: 1a9096738d39c8b1d270fe17fbd78c1ea4c4c45e
2025-04-02 15:46:02 -07:00
Hui Xiao 30e097e365 Disable 2pc TXN with WAL write injection in db stress; Re-enable track_and_verify_wal (#13508)
Summary:
**Context/Summary:**
Pessimistic transactions use 2PC and can't auto-recover from WAL write errors. This is because RocksDB cannot easily discard the corrupted WAL without risking the loss of uncommitted prepared data within the same WAL. Stress test does not support injecting errors that can' be auto-recovered for now. Therefore disabling WAL write error injection in stress tests to prevent crashing.

Previously track_and_verify_wal was disabled due to it caught those corrupted WAL. We can enable the feature now since there won't be such corrupted WAL.

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

Test Plan:
- Previous failed command pass now
```
python3 tools/db_crashtest.py --simple blackbox --interval=15  --WAL_size_limit_MB=0 --WAL_ttl_seconds=60 --acquire_snapshot_one_in=100 --adaptive_readahead=0 --adm_policy=1 --advise_random_on_open=1 --allow_data_in_errors=True --allow_fallocate=0 --allow_setting_blob_options_dynamically=1 --allow_unprepared_value=1 --async_io=1 --auto_readahead_size=1 --avoid_flush_during_recovery=0 --avoid_flush_during_shutdown=0 --avoid_unnecessary_blocking_io=0 --backup_max_size=104857600 --backup_one_in=100000 --batch_protection_bytes_per_key=8 --bgerror_resume_retry_interval=100 --blob_cache_size=1048576 --blob_compaction_readahead_size=4194304 --blob_compression_type=snappy --blob_file_size=1073741824 --blob_file_starting_level=0 --blob_garbage_collection_age_cutoff=1.0 --blob_garbage_collection_force_threshold=1.0 --block_align=1 --block_protection_bytes_per_key=8 --block_size=16384 --bloom_before_level=2147483646 --bloom_bits=19 --bottommost_compression_type=none --bottommost_file_compaction_delay=86400 --bytes_per_sync=262144 --cache_index_and_filter_blocks=0 --cache_index_and_filter_blocks_with_high_priority=0 --cache_size=8388608 --cache_type=auto_hyper_clock_cache --charge_compression_dictionary_building_buffer=1 --charge_file_metadata=0 --charge_filter_construction=1 --charge_table_reader=0 --check_multiget_consistency=0 --check_multiget_entity_consistency=0 --checkpoint_one_in=0 --checksum_type=kxxHash --clear_column_family_one_in=0 --commit_bypass_memtable_one_in=0 --compact_files_one_in=1000000 --compact_range_one_in=1000000 --compaction_pri=1 --compaction_readahead_size=0 --compaction_style=0 --compaction_ttl=10 --compress_format_version=1 --compressed_secondary_cache_size=16777216 --compression_checksum=0 --compression_max_dict_buffer_bytes=0 --compression_max_dict_bytes=0 --compression_parallel_threads=1 --compression_type=none --compression_use_zstd_dict_trainer=1 --compression_zstd_max_train_bytes=0 --continuous_verification_interval=0 --create_timestamped_snapshot_one_in=0 --daily_offpeak_time_utc= --data_block_index_type=1 --db_write_buffer_size=0 --decouple_partitioned_filters=0 --default_temperature=kHot --default_write_temperature=kCold --delete_obsolete_files_period_micros=30000000 --delpercent=0 --delrangepercent=0 --destroy_db_initially=0 --detect_filter_construct_corruption=0 --disable_file_deletions_one_in=10000 --disable_manual_compaction_one_in=10000 --disable_wal=0 --dump_malloc_stats=1 --enable_blob_files=1 --enable_blob_garbage_collection=1 --enable_checksum_handoff=1 --enable_compaction_filter=0 --enable_custom_split_merge=1 --enable_do_not_compress_roles=1 --enable_index_compression=0 --enable_memtable_insert_with_hint_prefix_extractor=0 --enable_pipelined_write=0 --enable_sst_partitioner_factory=0 --enable_thread_tracking=0 --enable_write_thread_adaptive_yield=1 --error_recovery_with_no_fault_injection=1 --exclude_wal_from_write_fault_injection=0 --fail_if_options_file_error=1 --fifo_allow_compaction=0 --file_checksum_impl=none --file_temperature_age_thresholds= --fill_cache=0 --flush_one_in=1000000 --format_version=5 --get_all_column_family_metadata_one_in=10000 --get_current_wal_file_one_in=0 --get_live_files_apis_one_in=1000000 --get_properties_of_all_tables_one_in=100000 --get_property_one_in=100000 --get_sorted_wal_files_one_in=0 --hard_pending_compaction_bytes_limit=274877906944 --high_pri_pool_ratio=0.5 --index_block_restart_interval=12 --index_shortening=1 --index_type=0 --ingest_external_file_one_in=0 --initial_auto_readahead_size=16384 --inplace_update_support=0 --iterpercent=0 --key_len_percent_dist=1,30,69 --key_may_exist_one_in=100 --last_level_temperature=kUnknown --level_compaction_dynamic_level_bytes=0 --lock_wal_one_in=1000000 --log_file_time_to_roll=0 --log_readahead_size=0 --long_running_snapshots=1 --low_pri_pool_ratio=0.5 --lowest_used_cache_tier=0 --manifest_preallocation_size=0 --mark_for_compaction_one_file_in=10 --max_auto_readahead_size=0 --max_background_compactions=20 --max_bytes_for_level_base=10485760 --max_key=25000000 --max_key_len=3 --max_log_file_size=1048576 --max_manifest_file_size=1073741824 --max_sequential_skip_in_iterations=2 --max_total_wal_size=0 --max_write_batch_group_size_bytes=64 --max_write_buffer_number=10 --max_write_buffer_size_to_maintain=0 --memtable_insert_hint_per_batch=0 --memtable_max_range_deletions=0 --memtable_prefix_bloom_size_ratio=0.1 --memtable_protection_bytes_per_key=4 --memtable_whole_key_filtering=1 --memtablerep=skip_list --metadata_charge_policy=0 --metadata_read_fault_one_in=0 --metadata_write_fault_one_in=128 --min_blob_size=16 --min_write_buffer_number_to_merge=1 --mmap_read=0 --mock_direct_io=True --nooverwritepercent=1 --num_file_reads_for_auto_readahead=2 --open_files=-1 --open_metadata_read_fault_one_in=8 --open_metadata_write_fault_one_in=8 --open_read_fault_one_in=0 --open_write_fault_one_in=16 --ops_per_thread=100000000 --optimize_filters_for_hits=1 --optimize_filters_for_memory=0 --optimize_multiget_for_io=0 --paranoid_file_checks=1 --paranoid_memory_checks=0 --partition_filters=0 --partition_pinning=0 --pause_background_one_in=10000 --periodic_compaction_seconds=0 --prefix_size=1 --prefixpercent=0 --prepopulate_blob_cache=1 --prepopulate_block_cache=0 --preserve_internal_time_seconds=60 --progress_reports=0 --promote_l0_one_in=0 --read_amp_bytes_per_bit=0 --read_fault_one_in=1000 --readahead_size=524288 --readpercent=0 --recycle_log_file_num=0 --reopen=0 --report_bg_io_stats=1 --reset_stats_one_in=1000000 --sample_for_compression=5 --secondary_cache_fault_one_in=32 --secondary_cache_uri= --set_options_one_in=2000 --skip_stats_update_on_db_open=0 --snapshot_hold_ops=100000 --soft_pending_compaction_bytes_limit=68719476736 --sqfc_name=bar --sqfc_version=1 --sst_file_manager_bytes_per_sec=104857600 --sst_file_manager_bytes_per_truncate=1048576 --stats_dump_period_sec=10 --stats_history_buffer_size=1048576 --strict_bytes_per_sync=0 --subcompactions=1 --sync=0 --table_cache_numshardbits=6 --target_file_size_base=524288 --target_file_size_multiplier=2 --test_batches_snapshots=0 --test_ingest_standalone_range_deletion_one_in=0 --top_level_index_pinning=2 --track_and_verify_wals=1 --two_write_queues=0 --txn_write_policy=1 --uncache_aggressiveness=12 --universal_max_read_amp=-1 --unordered_write=0 --unpartitioned_pinning=3 --use_adaptive_mutex=0 --use_adaptive_mutex_lru=1 --use_attribute_group=1 --use_blob_cache=0 --use_delta_encoding=0 --use_direct_io_for_flush_and_compaction=1 --use_direct_reads=0 --use_full_merge_v1=0 --use_get_entity=0 --use_merge=0 --use_multi_cf_iterator=0 --use_multi_get_entity=1 --use_multiget=0 --use_optimistic_txn=0 --use_put_entity_one_in=0 --use_shared_block_and_blob_cache=1 --use_sqfc_for_range_queries=0 --use_timed_put_one_in=0 --use_txn=1 --use_write_buffer_manager=0 --user_timestamp_size=0 --value_size_mult=32 --verification_only=0 --verify_checksum=1 --verify_checksum_one_in=1000000 --verify_compression=0 --verify_db_one_in=100000 --verify_file_checksums_one_in=0 --verify_iterator_with_expected_state_one_in=5 --verify_sst_unique_id_in_manifest=1 --wal_bytes_per_sync=0 --wal_compression=zstd --write_buffer_size=1048576 --write_dbid_to_manifest=1 --write_fault_one_in=5 --write_identity_file=0 --writepercent=100
```
- Rehearsal stress test 10x of our normal run shows no relevant errors to track_and_verify_wal

Reviewed By: pdillinger

Differential Revision: D72191287

Pulled By: hx235

fbshipit-source-id: 08d3fd52645ad526aec34842215c68b3ef06a9c9
2025-04-02 11:35:41 -07:00
Peter Dillinger b7a9d414c8 Fix WriteBatch atomicity and WAL recovery for some failures (#13489)
Summary:
Essentially fix https://github.com/facebook/rocksdb/issues/13429 by
* Avoiding publishing to readers a partial write batch written to memtable. Also clarify in DB::Write that WriteBatch is applied atomically, and improve some logging.
* When we know we have written a bad write batch to WAL due to memtable insert failure, make a good effort to roll it back to make the DB recoverable. (Not compatible with all options.)

Fixes https://github.com/facebook/rocksdb/issues/13429

Follow-up items:
* More rigorously test and fix the code paths and option combinations where these features could be useful.
* Allow default CF with disallow_memtable_writes (with caveat that violation stops writes on your open DB)

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

Test Plan: Updated existing test, manually verified the DB went into a "stopped" state at least in this example.

Reviewed By: jaykorean

Differential Revision: D71917670

Pulled By: pdillinger

fbshipit-source-id: c9b9dfc102817fc4c160a6c7170c04011c228aaf
2025-04-01 18:16:07 -07:00
Peter Dillinger be99011f08 More separation of txn_write_policy for crash tests (#13499)
Summary:
We are seeing some occasional failures with WRITE_(UN)PREPARED crash test runs, and it's alarming when these are grouped in with WRITE_COMMITTED, which AFAIK is the only one considered mature and mission-critical at this point.

* Mark WRITE_(UN)PREPARED as EXPERIMENTAL in the public APIs
* Separate out the `_with_txn` crash test jobs by write policy, now `_with_wc_txn`, `_with_wp_txn` and `_with_wup_txn` so that the major functional and maturity differences are better grouped.
* Add `_with_multiops_wup_txn` which was apparently missing
* Clean up db_crashtest.py for better consistency
  * Get rid of awkard "write_policy" parameter that could conflict with authoritative "txn_write_policy" parameter.
  * Similarly, move some multiops logic from different parameter sets to finalize_and_sanitize logic.

Immediate internal follow-up:
* Migrate from `_with_txn` which are now deprecated aliases of `_with_wc_txn` to more jobs with the new variants. And likely also add new multiops job.

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

Test Plan: manual runs of modified jobs, at least long enough to spot check things like txn_write_policy

Reviewed By: hx235

Differential Revision: D72015307

Pulled By: pdillinger

fbshipit-source-id: 06b99b2d1f15ac76fe7b8e22c93a51aaa2a42ecf
2025-04-01 14:17:37 -07:00
Hui Xiao 48eb646787 Mark MaxMemCompactionLevel() deprecated (#13503)
Summary:
**Context/Summary:**

MaxMemCompactionLevel() developed 10 years ago simply returns the level a memtable flushed to, which has historically been L0 and have no plan to change to something different for future. It is also not used in test or internally.

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

Test Plan: CI + fake release

Reviewed By: cbi42

Differential Revision: D72066092

Pulled By: hx235

fbshipit-source-id: 5ff5b16a6664ef3efabd3a6fbd8a2d0529b62460
2025-03-31 19:29:40 -07:00
Changyu Bi 325dcdf2e5 Deprecate ReadOptions::ignore_range_deletions and experimental::PromoteL0() (#13500)
Summary:
based on the option comment, `ignore_range_deletions` was added due to the overhead of range deletions in read path when a DB does not use DeleteRange(). The current implementation should not have a noticeable performance difference in this case.

`experimental::PromoteL0()` can be replaced by doing a manual compaction with proper CompactRangeOptions.

There are some internal use of these option and API so we will remove them later after the usages are updated.

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

Test Plan:
comment change only.
Performance: benchmark the performance difference with `ignore_range_deletions` and without (borrowed flag `universal_incremental` for this purpose), ran at the same time on the same machine.

- random point get:
    - ignore_range_deletions=false: 343078 ops/sec
    - ignore_range_deletions=true: 340219 ops/sec (0.8% slower)
```
(for I in $(seq 1 1); do TEST_TMPDIR=/dev/shm/t1 /data/users/changyubi/vscode-root/rocksdb/db_bench --benchmarks=fillseq,waitforcompaction,readrandom --write_buffer_size=67108864 --writes=1000000 --num=2000000 --reads=1000000  --seed=1723056275 --universal_incremental=false 2>&1 | grep "readrandom"; done;) | awk '{ t += $5; c++; print } END { print 1.0 * t / c }';
```

- sequential scan:
  - ignore_range_deletions=false: 5378104 ops/sec
  - ignore_range_deletions=true: 5393809 ops/sec (0.3% faster)
```
(for I in $(seq 1 10); do TEST_TMPDIR=/dev/shm/t1 /data/users/changyubi/vscode-root/rocksdb/db_bench --benchmarks=fillseq,waitforcompaction,readseq[-X10] --write_buffer_size=67108864 --writes=1000000 --num=2000000  --universal_incremental=true --seed=1723056275 2>1 | grep "\[AVG 10 runs\]"; done;) | awk '{ t += $6; c++; print; } END { printf "%.0f\n", 1.0 * t / c }';
```

The difference in ops/sec for the two benchmarks is likely noise.

Reviewed By: hx235

Differential Revision: D72069223

Pulled By: cbi42

fbshipit-source-id: ad82a051aa4682790d2178cd4fb2d1467397fbb5
2025-03-28 14:49:28 -07:00
prerit 743a02d6f6 Check for yields while waiting for lock in a loop (#13498)
Summary:
Acquiring a lock here can take a long time and cause a user mode scheduler to hold up, as it relies on explicit yielding. Hence, forcing a check here but ignoring any abort requests. Would rely on upstream to take action on aborts.

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

Reviewed By: pdillinger

Differential Revision: D71987173

Pulled By: jainpr

fbshipit-source-id: 4aec40bdf0bc657e29f72c306c576b3117f97a25
2025-03-27 15:10:55 -07:00
Hui Xiao 9072f5db09 Update for 10.1 release (#13485)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/13485

Reviewed By: jaykorean, pdillinger

Differential Revision: D71787995

Pulled By: hx235

fbshipit-source-id: 59b6ff7c824adbdef34b6ae12d7dbcc3e0852961
2025-03-25 14:55:07 -07:00
Peter Dillinger 49b0cb64df Fix uninitialized use in WBWIMemTable::Get (#13486)
Summary:
Based on passing address of uninit variable in ReadOnlyMemTable::Get() in memtable.h. The contract and other implementations suggest it is a pure out parameter that is always overwritten, so we initialize it in the function before checking its value in a loop

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

Test Plan: watch build-linux-valgrind in CI

Reviewed By: cbi42

Differential Revision: D71819843

Pulled By: pdillinger

fbshipit-source-id: 1e06f3ee6998099791af27de5b2872eb476ceb7c
2025-03-25 10:56:25 -07:00
Peter Dillinger 82794e0a4f Deprecate RangePtr, favor new RangeOpt and OptSlice (#13481)
Summary:
The new API in https://github.com/facebook/rocksdb/issues/13453 is awkward and precarious because of using RangePtr, which encodes optional keys using raw pointers to Slice. We could use `std::optional<Slice>` instead but that is unsatisfyingly a larger object with an inefficient size (typically 17 bytes).

Here I introduce a custom optional Slice type, `OptSlice`, that is the same size as a Slice, and use it in a number of places to clean up code and make some public APIs easier to work with. This includes

* `atomic_replace_range` (not yet released, OK to change)
* `GetAllKeyVersions()` which gets a behavior change because of its unusual handling of empty keys.
* `DeleteFilesInRanges()`
* TODO in follow-up: `CompactRange()`

Most of the diff is associated updates and refactorings. Also

* Move some relevant things out of db.h to keep it as tidy as possible.

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

Test Plan: tests updated

Reviewed By: hx235

Differential Revision: D71747774

Pulled By: pdillinger

fbshipit-source-id: b4c8519608d119b8bceca9bb0fd778608f62a141
2025-03-24 17:08:17 -07:00
Yu Zhang 934cf2d40d Implement the DB::GetPropertiesOfTablesForLevels API (#13469)
Summary:
As titled. This API returns the table properties of files per level. It can be handy for use cases that needed file's leveling info while retrieving TableProperties. We will use this API to later aggregate per level data write time info.

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

Test Plan: Added unit tests

Reviewed By: pdillinger

Differential Revision: D71353096

Pulled By: jowlyzhang

fbshipit-source-id: dc1fbb2c97e4365fc8d7241f9a59c65fbf4fb766
2025-03-21 17:23:01 -07:00
Yu Zhang 0b815cf3b3 Add a CompactionJobStats.num_input_files_trivially_moved field (#13479)
Summary:
This PR adds a new field `CompactionJobStats.num_input_files_trivially_moved` representing the number of files this compaction trivially moved. It should either equal to the total number of input files, or being 0.

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

Test Plan: Added tests

Reviewed By: hx235

Differential Revision: D71638796

Pulled By: jowlyzhang

fbshipit-source-id: 794c085408a0dc95f11874ca60fca3e6b5b92cba
2025-03-21 17:17:03 -07:00
Peter Dillinger 7f3ee34cdf Experimental ingestion option atomic_replace_range (#13453)
Summary:
Adding a new option (argument) for file ingestion `atomic_replace_range` which is intended to support a couple forms of "atomic replacement of a key range":
* (Experimental implementation here) With snapshot_consistency=false, the feature acts like an atomic DeleteFilesInRange prior to the ingestion, though requires no existing files to partially overlap the range. (Consider using SstPartitioner.) This is especially useful for "always compacted" workloads, perhaps along with CF option `disallow_memtable_writes` and ingestion option `fail_if_not_bottommost_level`. If both bounds are nullptr, the whole CF is replaced.
* (To implement in follow-up) With snapshot_consistency=true (and perhaps in some fallback cases from above such as partial overlap), a "giant tombstone file" as in https://github.com/facebook/rocksdb/issues/13078 is generated and ingested at the beginning of the list.

Because I see this as a more elaborate DeleteRange, I would naturally expect the upper bound/limit key to be exclusive, but it has been challenging getting that to work. The inclusive/exclusive handling is currently a documented bug for the experimental feature to sort out in follow-up work. (I would love to take advantage of proposed SliceBound, but that would be ambitious to adapt to DeleteRange. Even getting the "replace whole CF" variant of the functionality might be difficult to get worthing with DeleteRange underneath. Nevertheless, I feel it's best to consolidate these two forms of "atomic replacement" under variants of the same API.)

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

Test Plan:
Unit tests added / updated.

db_stress integration left as follow-up work (experimental feature, will be challenging)

Reviewed By: anand1976

Differential Revision: D71584295

Pulled By: pdillinger

fbshipit-source-id: 307abff426e4b7d0a340008918ebcddc896ef747
2025-03-21 15:55:41 -07:00
Maciej Szeszko d0374a0a72 Control SST write lifetime hints based on compaction style (#13472)
Summary:
This PR is a followup to https://github.com/facebook/rocksdb/pull/13461. We're introducing an experimental option / killswitch to control SST write lifetime hint calculation based on the selected compaction style. By default (and mostly for backwards compatibility reasons), we'll calculate the SST hints only for level compactions. With this change users have an option to configure SST lifetime hint policy in their environments to enable the calculations in the universal compaction mode as well. It's important to underline that as currently implemented, SST write lifetime hints are calculated in a static way and solely based on the level, which might not be suitable for non-uniform workloads with dynamic / high-variance lifespan of data within the same level. In those cases (or when the performance is not satisfactory), it's recommended to disable the hints by setting the set to empty. Please see the comment in `options.h` for more.

**NOTE:** We deliberately decided to introduce a new option to ensure no impact to external users running their RocksDB instances on local flash with the default `PosixWritableFile` file implementation.

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

Reviewed By: pdillinger, anand1976

Differential Revision: D71445488

Pulled By: mszeszko-meta

fbshipit-source-id: 57dc5e56662fa0b0fd686e183c0ec7090ff12d66
2025-03-21 13:10:43 -07:00
Jay Huh 12829883d7 Fix CompactionStats when max_sub_compaction > 1 (#13470)
Summary:
## Issue

Thanks to PRs https://github.com/facebook/rocksdb/issues/13455 and https://github.com/facebook/rocksdb/issues/13464 , we were able to find another issue with compaction stats.

When there are multiple sub-compactions and they are processed remotely, some compaction stats are not collected correctly.

Here's an example of how `num_input_records` can be double-counted during a compaction with multiple sub-compactions executed remotely. Please note that this problem is not limited to `num_input_records`.

Input File: 1 SST file with 100 keys.

- Key 1~50 are in one sub compaction
- Key 51~100 in another sub compaction

`UpdateOutputLevelCompactionStats()` currently retrieves the total number of entries from the input files and sets `num_input_records` in the internal_stats to 100. In `CompactionJob::Run()`, this method is called once after all sub-compactions have finished. However, during remote compaction, `UpdateOutputLevelCompactionStats()` is called for each offloaded sub-compaction on the remote side and then aggregated on the primary host. The internal_stats for the first sub-compaction will have 100 `num_input_records`, and the second sub-compaction will have another 100 `num_input_records`. We end up having 200 `num_input_records` in the aggregated internal_stats.

There was another issue that `num_input_record` was not properly excluding `num_input_range_del` in `UpdateCompactionJobStats()`. `job_stats_->num_input_record` originally has correct value set by compaction iterator, but then later overwritten in `UpdateCompactionJobStats()`. `UpdateCompactionJobStats()` was called during `CompactionJob::Install()`, so not caught by `VerifyInputRecordCount()`.

## Refactor and other changes before the fixes
* Renamed `UpdateOutputLevelCompactionStats()` to `BuildStatsFromInputTableProperties()` to make the function more descriptive. `BuildStatsFromInputTableProperties()` builds input stats by scanning through entries from TableProperties in the Input Files and it's at the top compaction level, not at the sub-compaction level. (It also updates a couple of non-input stats, `bytes_read_blob` and `num_dropped_records`, but will be refactored in a later PR.)
* `UpdateCompactionJobStats()` was moved from `CompactionJob::Install()` to `CompactionJob::Run()` and separated into `UpdateCompactionJobInputStats()` and `UpdateCompactionJobOutputStats()`.

## Fixes
* Remote Compaction no longer updates the subcompaction-job-level input stats from InputTableProperties to avoid double-counted stats in case of multiple sub-compactions. Subcompaction-job-level input stats are aggregated to the compaction-job-level input stats in the primary host after all sub-compactions are finished.
* Remote Compaction now only calls `UpdateCompactionJobOutputStats()` to update the job-level output stats by copying from internal stats.
* `UpdateCompactionJobInputStats()` now takes `num_input_range_del` and properly subtracts it from the input record count. `VerifyInputRecordCount()` expected `job_stats.num_input_records` to be equal to `internal_stats_.output_level_stats.num_input_records - num_input_range_del`. However, when updating the job-level stats, we were taking the entire `internal_stats_.output_level_stats.num_input_records` after verification.

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

Test Plan:
Local Compaction
```
./db_compaction_test -- --gtest_filter="*DBCompactionTest.VerifyRecordCount*"
```
Remote Compaction
```
./compaction_service_test --gtest_filter="*CompactionServiceTest.VerifyInputRecordCount*"
```

Reviewed By: pdillinger

Differential Revision: D71566149

Pulled By: jaykorean

fbshipit-source-id: c8aafcde701dec8901fd5e5a9ec186e26b896c19
2025-03-20 13:18:48 -07:00
Hui Xiao 2e175124d8 Rename Env::IOActivity::kReadManifest (#13471)
Summary:
Context/Summary: as mentioned in the [comment](https://github.com/facebook/rocksdb/pull/13178?fbclid=IwZXh0bgNhZW0CMTAAAR1nvz-1Ifh6Pm8PwFZbGHAxhLtwfi4W_XaSe-BqnBx3ICJOq-9DTdqFvs0_aem_ITO_0B6cca0kTViRmsAA8g#issuecomment-2702510373) , we want to rename this public name to align with the naming convention.

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

Test Plan:
- Compilation
- Manually check for no internal usage of this name. Hopefully it's good for OSS as well as this field is relatively new and the whole IOActivity is marked "EXPERIMENTAL"

Reviewed By: mszeszko-meta

Differential Revision: D71485300

Pulled By: hx235

fbshipit-source-id: 318c8b6c2a4d874f2f831e3ca690aa2fb8974c0f
2025-03-19 12:08:06 -07:00
Jay Huh 0a43d8a261 Verify compaction output record count (#13455)
Summary:
Continuing cbi42 's work in 602cc0f9a4be89020fb870dba2816f11dd515d16.

In this PR, we are adding record count verification for each compaction by comparing number of entries summed from Table Properties with the number of output records from the compaction stats.

If the count does not match, `Status::Corruption(msg)` is returned with detailed message including the actual number (from table property) and the expected number (from compaction stats)

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

Test Plan:
New UT added
```
./db_compaction_test -- --gtest_filter="*Verify*"
```

The check had to be disabled for some of the existing tests using MockTable/MockTableFactory, because TableProperties aren't populated properly for the MockTables.

Reviewed By: hx235

Differential Revision: D71235790

Pulled By: jaykorean

fbshipit-source-id: 3a86a878d13e79d948409d6a9843d1c992d2c98e
2025-03-18 18:40:33 -07:00
Jay Huh cc487ba367 Fix Compaction Stats for Remote Compaction and Tiered Storage (#13464)
Summary:
## Background

Compaction statistics are collected at various levels across different classes and structs.

* `InternalStats::CompactionStats`: Per-level Compaction Stats within a job (can be at subcompaction level which later get aggregated to the compaction level)
* `InternalStats::CompactionStatsFull`: Contains two per-level compaction stats - `output_level_stats` for primary output level stats and `proximal_level_stats` for proximal level stats. Proximal level statistics are only relevant when using Tiered Storage with the per-key placement feature enabled.
* `InternalStats::CompactionOutputsStats`: Simplified version of `InternalStats::CompactionStats`. Only has a subset of fields from `InternalStats::CompactionStats`
* `CompactionJobStats`: Job-level Compaction Stats. (can be at subcompaction level which later get aggregated to the compaction level)

Please note that some fields in Job-level stats are not in Per-level stats and they don't map 1-to-1 today.

## Issues

* In non-remote compactions, proximal level compaction statistics were not being aggregated into job-level statistics. Job level statistics were missing stats for proximal level for tiered storage compactions with per-key-replacement feature enabled.
* During remote compactions, proximal level compaction statistics were pre-aggregated into job-level statistics on the remote side. However, per-level compaction statistics were not part of the serialized compaction result, so that primary host lost that information and weren't able to populate `per_key_placement_comp_stats_` and `internal_stats_.proximal_level_stats` properly during the installation.
* `TieredCompactionTest` was only checking if (expected stats > 0 && actual stats > 0) instead actual value comparison

## Fixes

* Renamed `compaction_stats_` to `internal_stats_` for `InternalStats::CompactionStatsFull` in `CompactionJob` for better readability
* Removed the usage of `InternalStats::CompactionOutputsStats` and consolidated them to `InternalStats::CompactionStats`.
* Remote Compactions now include the internal stats in the serialized `CompactionServiceResult`. `output_level_stats` and `proximal_level_stats` get later propagated in sub_compact output stats accordingly.
* `CompactionJob::UpdateCompactionJobStats()` now takes `CompactionStatsFull` and aggregates the `proximal_level_stats` as well
* `TieredCompactionTest` is now doing the actual value comparisons for input/output file counts and record counts. Follow up is needed to do the same for the bytes read / written.

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

Test Plan:
Unit Tests updated to verify stats

```
./compaction_service_test
```
```
./tiered_compaction_test
```

Reviewed By: pdillinger

Differential Revision: D71220393

Pulled By: jaykorean

fbshipit-source-id: ad70bffd9614ced683f90c7570a17def9b5c8f3f
2025-03-18 16:28:18 -07:00
Yu Zhang 17ac19f2c4 Add a check during recovery for proper seqno advancement (#13465)
Summary:
This PR adds a check for an invariant of sequence number during recovery, that it should not be set backward. This is inspired by a recent SEV that is caused by a software bug. It is a relatively cheap and straightforward check that RocksDB can do to avoid silently opening the DB in a corrupted state.

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

Test Plan:
Existing tests should cover the case when the invariant is met

The corrupted state is manually tested using aforementioned bug.

Reviewed By: hx235

Differential Revision: D71226513

Pulled By: jowlyzhang

fbshipit-source-id: cd8056fa6653d44ceeb9ba9b4693ab0660a53b4e
2025-03-17 12:49:10 -07:00
Hui Xiao 24952ff088 Expose number of L0 files in the CF right before the compaction starts in CompactionJobInfo (#13462)
Summary:
**Context/Summary:**
For users who are interested in knowing how efficient their compaction in reducing L0 files or how bad their long-running compaction in "locking" L0 files, they now have a reference point "L0 files in the CF pre compaction" for their input compaction files.
- Compared to the existing stats or exposing in some other way, exposing this info in CompactionJobInfo allows users to compare it with other compaction data (e.g, compaction input num, compaction reason) of within **one** compaction (of per-compaction granularity).
- If this number is high while their "short-running" compaction has little L0 files input, then those compaction may have a room for improvement. Similar for those long-running compaction. This PR is to add a new field `CompactionJobInfo::num_l0_files_pre_compaction` for that.

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

Test Plan: - Piggyback on an existing test

Reviewed By: jaykorean

Differential Revision: D71124938

Pulled By: hx235

fbshipit-source-id: aa47c9c86c62d9425771b320f5636e50671fd289
2025-03-17 11:11:44 -07:00
Maciej Szeszko 6ac13a5f0a Expose WriteLifeTimeHint at the FileOptions level (#13461)
Summary:
The original implementation of NVMe write lifetime hints (https://github.com/facebook/rocksdb/pull/3095) assumed a flexible interface which decouples file creation from the explicit act of setting write lifetime hint (see `PosixWritableFile` for more context). However, there are existing file systems implementations (ex. Warm Storage) that require all the options (including file write lifetime hints) to be specified once at the time of the actual `FSWritableFile` object instantiation. We're extending the `FileOptions` with `Env::WriteLifeTimeHint` and patch existing callsites accordingly to enable one-shot metadata setup for those more constraint implementations.

NOTE: Today `CalculateSSTWriteHint` only sets write lifetime hint for Level compactions. We'll fill that gap in following PRs and add calculation for Universal Compactions which would unblock Zippy's use case.

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

Reviewed By: anand1976

Differential Revision: D71144645

Pulled By: mszeszko-meta

fbshipit-source-id: 6c09b62a360d48bd6e4fb08a1265bce2a49f3f4a
2025-03-14 21:43:50 -07:00
Peter Dillinger 0cc943c067 format_version < 2 unsupported for write, deprecated for read (#13463)
Summary:
In hopes of eventually removing some ugly and awkard code for compress_format_version < 2, users can no longer write files in that format and its read support is marked deprecated. For continuing to test that read support, there is a back door to writing the files in unit tests.

If format_version < 2 is specified, it is quietly sanitized to 2. (This is similar to other BlockBasedTableOptions.)

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

Test Plan: unit tests updated.

Reviewed By: hx235

Differential Revision: D71152916

Pulled By: pdillinger

fbshipit-source-id: 95be55e86f93f09fd898223578b9381385c3ccd8
2025-03-14 10:50:05 -07:00
Jay Huh ca7367a003 Replace penultimate naming with proximal (#13460)
Summary:
With generalized age-based tiering (work-in-progress), the "warm tier" data will no longer necessarily be placed in the second-to-last level (also known as the "penultimate level").

Also, the cold tier may no longer necessarily be at the last level, so we need to rename options like `preclude_last_level_seconds` to `preclude_cold_tier_seconds`, but renaming options is trickier because it can be a breaking change for consuming applications. We will do this later as a follow up.

**Minor fix included**: Fixed one `use-after-move` in CompactionPicker

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

Test Plan: CI

Reviewed By: pdillinger

Differential Revision: D71059486

Pulled By: jaykorean

fbshipit-source-id: fd360cdf719e015bf9f9e3f6f1663438226566a4
2025-03-12 18:24:28 -07:00
Jay Huh c5921df3d7 Add PerKeyPlacement support (#13459)
Summary:
This PR adds support for PerKeyPlacement in Remote Compaction.

The `seqno_to_time_mapping` is already available from the table properties of the input files. `preserve_internal_time_seconds` and `preclude_last_level_data_seconds` are directly read from the OPTIONS file upon db open in the remote worker. The necessary changes include:

- Add `is_penultimate_level_output` and `file_temperature` to the `CompactionServiceOutputFile`
- When building the output for the remote compaction, get the outputs for penultimate level and last level separately, serialize them with the two additional information added in this PR.
- When deserializing the result from the primary, SubcompactionState's `GetOutputs()` now takes `is_penultimate_level`. This allows us to determine which level to place the output file.
- Include stats from `compaction_stats.penultimate_level_stats` in the remote compaction result

# To Follow up
- Stats to be fixed. Stats are not being populated correctly for PerKeyPlacement even for non-remote compactions.
- Clean up / Reconcile the "penultimate" naming by replacing with "proximal"

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

Test Plan:
Updated the unit test

```
./compaction_service_test
```

Reviewed By: pdillinger

Differential Revision: D71007211

Pulled By: jaykorean

fbshipit-source-id: f926e56df17239875d849d46b8b940f8cd5f1825
2025-03-12 11:46:02 -07:00
Maciej Szeszko 8e16f8fecf Reduce db stress noise (#13447)
Summary:
[Experiment]

This PR is a followup to https://github.com/facebook/rocksdb/pull/13408. Thick bandaid of ignoring all injected read errors in context of periodic iterator auto refreshes in db stress proved to be effective. We confirmed our theory that errors are not a really a consequence / defect related to this new feature but rather due to subtle ways in which downstream code paths handle their respective IO failures. In this change we're replacing a thick 'ignore all IO read errors' bandaid in `no_batched_ops_stress` with a much smaller, targeted patches in obsolete files purge / delete codepaths, table block cache reader, table cache lookup to make sure we don't miss signal and ensure there's a single mechanism for ignoring error injection in db stress tests.

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

Reviewed By: hx235

Differential Revision: D70794787

Pulled By: mszeszko-meta

fbshipit-source-id: c5fcd4780d82357c407f53bf0bb22fc38f7bd277
2025-03-12 01:13:40 -07:00
Jay Huh 22ca6e5e68 Additional debug logging for InputFileCheck Failure (#13452)
Summary:
Add debug logging when the Wait() does not return `kSuccess` so that we can compare the version state that was printed by the logging added in https://github.com/facebook/rocksdb/issues/13427 upon InputFileCheck failure.

# Test Plan

CI + Tested with Temporary Change in Meta Internal Infra

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

Reviewed By: hx235

Differential Revision: D70898963

Pulled By: jaykorean

fbshipit-source-id: d591b82f2df173b5e01f6552230844ce95155256
2025-03-10 13:37:47 -07:00
Richard Barnes 60c266658d Use nullptr in infra_asic_fpga/ip/mtia/athena/main/models/cmodel/util/jsonUtils.cpp
Summary:
`nullptr` is preferable to `0` or `NULL`. Let's use it everywhere so we can enable `-Wzero-as-null-pointer-constant`.

 - If you approve of this diff, please use the "Accept & Ship" button :-)

Reviewed By: dtolnay

Differential Revision: D70818166

fbshipit-source-id: 4658fb004676fe2686249fdd8ecb322dec8aa63d
2025-03-09 11:18:56 -07:00
Peter Dillinger b9c7481fc2 Fix some secondary/read-only DB logic (#13441)
Summary:
Primarily, fix an issue from https://github.com/facebook/rocksdb/issues/13316 with opening secondary DB with preserve/preclude option (crash test disable in https://github.com/facebook/rocksdb/issues/13439). The issue comes down to mixed-up interpretations of "read_only" which should now be resolved. I've introduced the stronger notion of "unchanging" which means the VersionSet never sees any changes to the LSM tree, and the weaker notion of "read_only" which means LSM tree changes are not written through this VersionSet/etc. but can pick up externally written changes. In particular, ManifestTailer should use read_only=true (along with unchanging=false) for proper handling of preserve/preclude options.

A new assertion in VersionSet::CreateColumnFamily to help ensure sane usage of the two boolean flags is incompatible with the known wart of allowing CreateColumnFamily on a read-only DB. So to keep that assertion, I have fixed that issue by disallowing it. And this in turn required downstream clean-up in ldb, where I cleaned up some call sites as well.

Also, rename SanitizeOptions for ColumnFamilyOptions to SanitizeCfOptions, for ease of search etc.

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

Test Plan:
* Added preserve option to a test in db_secondary_test, which reproduced the failure seen in the crash test.
* Revert https://github.com/facebook/rocksdb/issues/13439 to re-enable crash test functionality
* Update some tests to deal with disallowing CF creation on read-only DB
* Add some testing around read-only DBs and CreateColumnFamily(ies)
* Resurrect a nearby test for read-only DB to be sure it doesn't write to the DB dir. New EnforcedReadOnlyReopen should probably be used in more places but didn't want to attempt a big migration here and now. (Suggested follow-up.)

Reviewed By: jowlyzhang

Differential Revision: D70808033

Pulled By: pdillinger

fbshipit-source-id: 486b4e9f9c9045150a0ebb9cb302753d03932a3f
2025-03-07 14:56:45 -08:00
Peter Dillinger 5d1c0a8832 Reformat assertion in TEST_VerifyNoObsoleteFilesCached (#13446)
Summary:
... for better automatic failure grouping

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

Test Plan: no production code change

Reviewed By: hx235

Differential Revision: D70789464

Pulled By: pdillinger

fbshipit-source-id: 68263f6ed666349d65b5f493865973a213f35ec9
2025-03-07 11:25:44 -08:00
Jay Huh d033c6a849 set ignore_unknown_options when parsing options (#13443)
Summary:
In case the primary host has a new option added which isn't available in the remote worker yet, the remote compaction currently fails. In most cases, these new options are not relevant to the remote compaction and the worker should be able to move on by ignoring it.

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

Test Plan: Verified internally in Meta Infra.

Reviewed By: anand1976

Differential Revision: D70744359

Pulled By: jaykorean

fbshipit-source-id: eb6a388c2358a7f8089f2e35a378b7017b9e03f3
2025-03-06 17:26:37 -08:00
Jay Huh 68b2d941be Introduce kAborted Status (#13438)
Summary:
If compaction job needs to be aborted inside `Schedule()` or `Wait()` today (e.g. Primary host is shutting down), the only two options are the following
- Handle it as failure by returning `CompactionServiceJobStatus::kFailure`
- Return `CompactionServiceJobStatus::kUseLocal` and let the compaction move on locally and eventually succeed or fail depending on the timing

In this PR, we are introducing a new status, `CompactionServiceJobStatus::kAborted`,  so that the implementation of `Schedule()` and `Wait()` can return it. Just like how `CompactionServiceJobStatus::kFailure` is handled, compaction will not move on and fail, but the status will be returned as `Status::Aborted()` instead of `Status::Incomplete()`

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

Test Plan:
Unit Test added
```
 ./compaction_service_test --gtest_filter="*CompactionServiceTest.AbortedWhileWait*"
```

Reviewed By: anand1976, hx235

Differential Revision: D70655355

Pulled By: jaykorean

fbshipit-source-id: 22614ce9c7455cda649b15465625edc93978fe11
2025-03-05 22:15:17 -08:00
Andrew Chang 8e6d431153 Add IOActivityToString helper method (#13440)
Summary:
I have a place I want to use this helper method inside the Sally codebase. I have this functionality in my Sally diff right now, but I think it is generic enough to warrant putting alongside `Env::PriorityToString`.

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

Test Plan: Just the compiler and CI checks are sufficient IMO.

Reviewed By: hx235

Differential Revision: D70664597

Pulled By: archang19

fbshipit-source-id: 341de6c6e311a3f421ad093c2c216e5caa5034dd
2025-03-05 19:07:01 -08:00
anand76 14c949df8b Initial implementation of ExternalTableBuilder (#13434)
Summary:
This PR adds the ability to use an ExternalTableBuilder through the SstFileWriter to create external tables. This is a counterpart to https://github.com/facebook/rocksdb/issues/13401 , which adds the ExternalTableReader. The support for external tables is confined to ingestion only DBs, with external table files ingested into the bottommost level only. https://github.com/facebook/rocksdb/issues/13431 enforces ingestion only DBs by adding a disallow_memtable_writes column family option.

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

Test Plan: New unit tests in table_test.cc

Reviewed By: pdillinger

Differential Revision: D70532054

Pulled By: anand1976

fbshipit-source-id: a837487eadfabed9627a0eceb403bfc5fc2c427c
2025-03-05 16:30:46 -08:00
anand76 f6bff87b92 Add opaque options in ReadOptions for external tables (#13436)
Summary:
Add an unordered_map of name/value pairs in ReadOptions::property_bag, similar to IOOptions::property_bag. It allows users to pass through some custom options to an external table.

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

Reviewed By: jaykorean

Differential Revision: D70649609

Pulled By: anand1976

fbshipit-source-id: 9b14806a9f3599b861827bd4ae6e948861edc51a
2025-03-05 16:25:41 -08:00
Peter Dillinger ec8f1452f5 Temp disable in crash test: secondary instance + seqno-time tracking (#13439)
Summary:
PR https://github.com/facebook/rocksdb/issues/13316 broke some crash test cases in DBImplSecondary, from combining test_secondary=1 and preserve_internal_time_seconds>0. Disabling that while investigating the fix.

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

Test Plan: manual blackbox_crash_test runs with forced test_secondary=1

Reviewed By: anand1976

Differential Revision: D70656373

Pulled By: pdillinger

fbshipit-source-id: fa2139e90bbe64ec8ebb062877d9337894ea3b43
2025-03-05 14:32:05 -08:00
Peter Dillinger 15873b1fdd New CF option disallow_memtable_writes (#13431)
Summary:
... to better support "ingestion only" column families such as those using an external file reader as in https://github.com/facebook/rocksdb/issues/13401.

It would be possible to implement this by getting rid of the memtable for that CF, but it quickly because clear that such an approach would need to update a lot of places to deal with such a possibility. And we already have logic to optimize reads when a memtable is empty. We put a vector memtable in place to minimize overheads of an empty memtable.

There are three layers of defense against writes to the memtable:
* WriteBatch ops to a disallowed CF will fail immediately, without waiting for Write(). For this check to work, we need a ColumnFamilyHandle and because of that, we don't support disallow_memtable_writes on the default column family.
* MemtableInserter will reject writes to disallowed CFs. This is needed to protect re-open with disallow when there are existing writes in a WAL.
* The placeholder memtable is marked immutable. This will cause an assertion failure on attempt to write, such as in case of bug or regression.

Suggested follow-up:
* Remove the limitation on using the option with the default column family, perhaps by solving https://github.com/facebook/rocksdb/issues/13429 more generally or perhaps with some specific check before the first memtable write of the batch (but potential CPU overhead for such a check - there's likely optimization opportunities around ColumnFamilyMemTables).

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

Test Plan:
unit tests added

Performance: A db_bench call designed to realistically focus on the CPU cost of writes:

```
./db_bench -db=/dev/shm/dbbench1 --benchmarks=fillrandom -num=10000000 -compaction_style=2 -fifo_compaction_max_table_files_size_mb=1000 -fifo_compaction_allow_compaction=0 -num_column_families=20 -disable_wal -write_buffer_size=1234000
```

Running before & after tests at the same time on the same machine, 40 iterations each, average ops/s, DEBUG_LEVEL=0, remove slowest run of each:
Before: 772466
After: 773785 (0.2% faster)

Likely within the noise, as if there was any change, we would expect a slight regression.

Reviewed By: anand1976

Differential Revision: D70495936

Pulled By: pdillinger

fbshipit-source-id: 306f7e737f87c1fbb52c5805f3cadb6e8ced9b40
2025-03-04 18:33:52 -08:00
Peter Dillinger da8eba8b49 Improve consistency of SeqnoToTime tracking in SuperVersion (#13316)
Summary:
This is an unexpectedly complex follow-up to https://github.com/facebook/rocksdb/issues/13269.

This change solves (and detects regressed) inconsistencies between whether a CF's SuperVersion is configured with a preserve/preclude option and whether it gets a usable SeqnoToTimeMapping. Operating with preserve/preclude and no usable mapping is degraded functionality we need to avoid. And no mapping is useful for actually disabling the feature (except with respect to existing SST files, but that's less of a concern for now).

The challenge is that how we maintain the DB's SeqnoToTimeMapping can depend on all the column families, and we don't want to iterate over all column families *for each column family* (e.g. on initially creating each). The existing code was a bit relaxed:
* On initially creating or re-configuring a CF, we might install an empty mapping, but soon thereafter (after releasing and re-acquiring the DB mutex) re-install another SuperVersion with a useful mapping.

The solution here is to refactor the logic so that there's a distinct but related workflow for (a) ensuring a quality set of mappings when we might only be considering a single CF (`EnsureSeqnoToTimeMapping()`), and (b) massaging that set of mappings to account for all CFs (`RegisterRecordSeqnoTimeWorker`) which doesn't need to re-install new SuperVersions because each CF already has good mappings and will get updated SuperVersions when the periodic task adds new mappings. This should eliminate the extra SuperVersion installs associated with preserve/preclude on CF creation or re-configure, making it the same as any other CF.

Some more details:
* Some refactorings such as removing new_seqno_to_time_mapping from SuperVersionContext. (Now use parameter instead of being stateful.)
* Propagate `read_only` aspect of DB to more places so that we can pro-actively disable preserve/preclude on read-only DBs, so that we don't run afoul of the assertion expecting SeqnoToTime entries.
* Introduce a utility struct `MinAndMaxPreserveSeconds` for aggregating preserve/preclude settings in a useful way, sometimes on one CF and sometimes across multiple CFs. Much cleaner! (IMHO)
* Introduce a function `InstallSuperVersionForConfigChange` that is a superset of `InstallSuperVersionAndScheduleWork` for when a CF is new or might have had a change to its mutable options.
* Eliminate redundant re-install SuperVersions of created "missing" CFs in DBImpl::Open.

Intended follow-up:
* Ensure each flush has an "upper bound" SeqnoToTime entry, which would resolve a FIXME in tiered_compaction_test, but causes enough test churn to deserve its own PR + investigation.

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

Test Plan:
This change is primarily validated by a new assertion in SuperVersion::Init to ensure consistency between (a) presence of any SeqnoToTime mappings in the SuperVersion and (b) preserve/preclude option being currently set.

One unit test update was needed because we now ensure at least one SeqnoToTime entry is created on any DB::Open with preserve/preclude, so that there is a lower bound time on all the future data writes. This required a small hack in associating the time with Seqno 1 instead of 0, which is reserved for "unspecified old."

Reviewed By: cbi42

Differential Revision: D70540638

Pulled By: pdillinger

fbshipit-source-id: bb419fdbeb5a1f115fc429c211f9b8efaf2f56d7
2025-03-04 17:44:01 -08:00
Nicolas De Carli 5f9b7ccce3 Add ROCKSDB_AUXV_GETAUXVAL_PRESENT flag to defs.bzl (#13435)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/13435

We've noticed the default CRC32c function gets executed when running on aarch64 cpus within our servers

Issue is that ROCKSDB_AUXV_GETAUXVAL_PRESENT evaluates to false

This fix enables the flag internally and reverts the previous fix, landed with D70423483

Reviewed By: pdillinger

Differential Revision: D70584250

fbshipit-source-id: 28e41316187c474fdfaf854f301ad14b6721fcad
2025-03-04 16:51:19 -08:00
Sean Ovens 0c7e5bd2f0 Shrink size of HashSkipList buckets from 56B to 48B (#13424)
Summary:
Previous order of fields in SkipList:

`const uint16_t kMaxHeight_;  // 2B`
`const uint16_t kBranching_;  // 2B`
`const uint32_t kScaledInverseBranching_;  // 4B`
`Comparator const compare_;  // 8B`
`Allocator* const allocator_;  // 8B`
`Node* const head_;  // 8B`
`std::atomic<int> max_height_;  // 4B`
`// 4B padding added automatically for alignment`
`Node** prev_;  // 8B`
`int32_t prev_height_;  // 4B`
`// 4B padding added automatically for alignment`

= 56B in total. By swapping prev_ and prev_height_, we get the following:

`const uint16_t kMaxHeight_;  // 2B`
`const uint16_t kBranching_;  // 2B`
`const uint32_t kScaledInverseBranching_;  // 4B`
`Comparator const compare_;  // 8B`
`Allocator* const allocator_;  // 8B`
`Node* const head_;  // 8B`
`std::atomic<int> max_height_;  // 4B`
`int32_t prev_height_;  // 4B`
`Node** prev_;  // 8B`

= 48B in total. So this change saves 8B per SkipList object. When allocated using AllocateAligned (as is the case for the [hash skiplist](https://github.com/facebook/rocksdb/blob/main/memtable/hash_skiplist_rep.cc#L243)) and assuming alignof(std::max_align_t) = 16, this change saves an additional 8B per SkipList object (so 16B in total).

Note: this does not affect the "skiplist" memtable, which internally uses InlineSkipList

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

Reviewed By: cbi42

Differential Revision: D70423252

Pulled By: pdillinger

fbshipit-source-id: 450dcc7f0e9e86cd3481f6930e83eea5fef78b97
2025-03-03 21:25:29 -08:00
Changyu Bi 7e272d2032 Update MultiGet to provide consistent CF view for kPersistedTier (#13433)
Summary:
when reading with ReadOptions::read_tier = kPersistedTier and with a snapshot, MultiGet allows the case where some CF is read before a flush and some CF is read after the flush. This is not desirable, especially when atomic_flush is enabled and users use MultiGet to do some consistency checks on the data in SST files. This PR updates the code path for SuperVersion acquisition to get a consistent view across when kPersistedTier is used.

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

Test Plan: a new unit test that could be flaky without this change.

Reviewed By: jaykorean

Differential Revision: D70509688

Pulled By: cbi42

fbshipit-source-id: 80de96f94407af9bb2062b6a185c61f65827c092
2025-03-03 15:21:10 -08:00
Nicolas De Carli 1d6c33d2a5 Enable hardware accelerated crc32c for ARM on Linux (#13432)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/13432

We've noticed the default CRC32c function gets executed when running on aarch64 cpus within our servers

Issue is that ROCKSDB_AUXV_GETAUXVAL_PRESENT evaluates to false

This fix allows the usage of hardware-accelerated crc32 within our fleet

Reviewed By: jaykorean

Differential Revision: D70423483

fbshipit-source-id: 601da3fbf156e3e40695eb76ee5d37f67f83d427
2025-03-02 08:05:21 -08:00
Peter Dillinger ebaeb03648 Write failure can be permanently fatal and break WriteBatch atomicity (#13428)
Summary:
This adds a test that attempts DeleteRange() with PlainTable (not supported) and shows that it not only puts the DB in failed write mode, it (a) breaks WriteBatch atomicity for readers, because they can see just part of a failed WriteBatch, and (b) makes the DB not recoverable (without manual intervention) if using WAL.

Note: WriteBatch atomicity is not clearly documented but indicated at the top of write_batch.h and the wiki page for Transactions, even without Transactions.

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

Test Plan: this is the test

Reviewed By: anand1976

Differential Revision: D70332226

Pulled By: pdillinger

fbshipit-source-id: 67bc4de68833a80578e48baa9d3a4f23f1600f3c
2025-02-27 11:37:56 -08:00
Jay Huh d1f383b8eb Add Logging for debugging InputFileCheck Failure (#13427)
Summary:
Add detailed log for debugging purpose

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

Test Plan: CI

Reviewed By: cbi42, hx235

Differential Revision: D70274613

Pulled By: jaykorean

fbshipit-source-id: de4bc61853136b923aa786717e7979be8886b9bd
2025-02-26 15:31:47 -08:00
Peter Dillinger 3af905aa68 Format compatibility test cover compressions, including mixed (#13414)
Summary:
The existing format compatibility test had limited coverage of compression options, particularly newer algorithms with and without dictionary compression. There are some subtleties that need to remain consistent, such as index blocks potentially being compressed but *not* using the file's dictionary if they are. This involves detecting (with a rough approximation) builds with the appropriate capabilities.

The other motivation for this change is testing some potentially useful reader-side functionality that has been in place for a long time but has not been exercised until now: mixing compressions in a single SST file. The block-based SST schema puts a compression marker on each block; arguably this is for distinguishing blocks compressed using the algorithm stored in compression_name table property from blocks left uncompressed, e.g. because they did not reach the threshold of useful compression ratio, but the marker can also distinguish compression algorithms / decompressors.

As we work toward customizable compression, it seems worth unlocking the capability to leverage the existing schema and SST reader-side support for mixing compression algorithms among the blocks of a file. Yes, a custom compression could implement its own dynamic algorithm chooser with its own tag on the compressed data (e.g. first byte), but that is slightly less storage efficient and doesn't support "vanilla" RocksDB builds reading files using a mix of built-in algorithms. As a hypothetical example, we might want to switch to lz4 on a machine that is under heavy CPU load and back to zstd when load is more normal. I dug up some data indicating ~30 seconds per output file in compaction, suggesting that file-level responsiveness might be too slow. This agility is perhaps more useful with disaggregated storage, where there is more flexibility in DB storage footprint and potentially more payoff in optimizing the *average* footprint.

In support of this direction, I have added a backdoor capability for debug builds of `ldb` to generate files with a mix of compression algorithms and incorporated this into the format compatibility test. All of the existing "forward compatible" versions (currently back to 8.6) are able to read the files generated with "mixed" compression. (NOTE: there's no easy way to patch a bunch of old versions to have them support generating mixed compression files, but going forward we can auto-detect builds with this "mixed" capability.) A subtle aspect of this support that is that for proper handling of decompression contexts and digested dictionaries, we need to set the `compression_name` table property to `zstd` if any blocks are zstd compressed. I'm expecting to add better info to SST files in follow-up, but this approach here gives us forward compatibility back to 8.6.

However, in the spirit of opening things up with what makes sense under the existing schema, we only support one compression dictionary per file. It will be used by any/all algorithms that support dictionary compression. This is not outrageous because it seems standard that a dictionary is *or can be* arbitrary data representative of what will be compressed. This means we would need a schema change to add dictionary compression support to an existing built-in compression algorithm (because otherwise old versions and new versions would disagree on whether the data dictionary is needed with that algorithm; this could take the form of a new built-in compression type, e.g. `kSnappyCompressionWithDict`; only snappy, bzip2, and windows-only xpress compression lack dictionary support currently).

Looking ahead to supporting custom compression, exposing a sizeable set of CompressionTypes to the user for custom handling essentially guarantees a path for the user to put *versioning* on their compression even if they neglect that initially, and without resorting to managing a bunch of distinct named entities. (I'm envisioning perhaps 64 or 127 CompressionTypes open to customization, enough for ~weekly new releases with more than a year of horizon on recycling.)

More details:
* Reduce the running time (CI cost) of the default format compatibility test by randomly sampling versions that aren't the oldest in a category. AFAIK, pretty much all regressions can be caught with the even more stripped-down SHORT_TEST.
* Configurable make parallelism with J environment variable
* Generate data files in a way that makes them much more eligible for index compression, e.g. bigger keys with less entropy
* Generate enough data files
* Remove 2.7.fb.branch from list because it shows an assertion violation when involving compression.
* Randomly choose a contiguous subset of the compression algorithms X {dictionary, no dictionary} configuration space when generating files, with a number of files > number of algorithms. This covers all the algorithms and both dictionary/no dictionary for each release (but not in all combinations).
* Have `ldb` fail if the specified compression type is not supported by the build.

Other future work needed:
* Blob files in format compatibility test, and support for mixed compression. NOTE: the blob file schema should naturally support mixing compression algorithms but the reader code does not because of an assertion that the block CompressionType (if not no compression) matches the whole file CompressionType. We might introduce a "various" CompressionType for this whole file marker in blob files.
* Do more to ensure certain features and code paths e.g. in the scripts are actually used in the compatibility test, so that they aren't accidentally neutralized.

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

Test Plan: Manual runs with some temporary instrumentation, also a recent revision of this change included a GitHub Actions run of the updated format compatible test: https://github.com/facebook/rocksdb/actions/runs/13463551149/job/37624205915?pr=13414

Reviewed By: hx235

Differential Revision: D70012056

Pulled By: pdillinger

fbshipit-source-id: 9ea5db76ba01a95338ed1a86b0edd71a469c4061
2025-02-25 00:12:34 -08:00
Changyu Bi 3740fccc4b Update for next release 10.0 (#13417)
Summary:
Updated version, HISTORY, compatibility script and folly hash for 10.0 release.

Included a HISTORY.md update backported from 10.0 branch: c1f63e16f0.

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

Test Plan: CI

Reviewed By: hx235

Differential Revision: D70029393

Pulled By: cbi42

fbshipit-source-id: f8276bb31cc69648b47e0cbcd728d2a33fbf531f
2025-02-24 13:28:20 -08:00
276 changed files with 12279 additions and 4742 deletions

No files matched your search

@@ -11,9 +11,9 @@ runs:
CMAKE_BIN: C:/Program Files/CMake/bin/cmake.exe
CTEST_BIN: C:/Program Files/CMake/bin/ctest.exe
JAVA_HOME: C:/Program Files/BellSoft/LibericaJDK-8
SNAPPY_HOME: ${{ github.workspace }}/thirdparty/snappy-1.1.8
SNAPPY_INCLUDE: ${{ github.workspace }}/thirdparty/snappy-1.1.8;${{ github.workspace }}/thirdparty/snappy-1.1.8/build
SNAPPY_LIB_DEBUG: ${{ github.workspace }}/thirdparty/snappy-1.1.8/build/Debug/snappy.lib
SNAPPY_HOME: ${{ github.workspace }}/thirdparty/snappy-1.2.2
SNAPPY_INCLUDE: ${{ github.workspace }}/thirdparty/snappy-1.2.2;${{ github.workspace }}/thirdparty/snappy-1.2.2/build
SNAPPY_LIB_DEBUG: ${{ github.workspace }}/thirdparty/snappy-1.2.2/build/Debug/snappy.lib
run: |-
# NOTE: if ... Exit $LASTEXITCODE lines needed to exit and report failure
echo ===================== Install Dependencies =====================
@@ -22,14 +22,14 @@ runs:
mkdir $Env:THIRDPARTY_HOME
cd $Env:THIRDPARTY_HOME
echo "Building Snappy dependency..."
curl -Lo snappy-1.1.8.zip https://github.com/google/snappy/archive/refs/tags/1.1.8.zip
curl -Lo snappy-1.2.2.zip https://github.com/google/snappy/archive/refs/tags/1.2.2.zip
if(!$?) { Exit $LASTEXITCODE }
unzip -q snappy-1.1.8.zip
unzip -q snappy-1.2.2.zip
if(!$?) { Exit $LASTEXITCODE }
cd snappy-1.1.8
cd snappy-1.2.2
mkdir build
cd build
& cmake -G "$Env:CMAKE_GENERATOR" ..
& cmake -G "$Env:CMAKE_GENERATOR" .. -DSNAPPY_BUILD_TESTS=OFF -DSNAPPY_BUILD_BENCHMARKS=OFF
if(!$?) { Exit $LASTEXITCODE }
msbuild Snappy.sln -maxCpuCount -property:Configuration=Debug -property:Platform=x64
if(!$?) { Exit $LASTEXITCODE }
@@ -38,7 +38,7 @@ runs:
$env:Path = $env:JAVA_HOME + ";" + $env:Path
mkdir build
cd build
& cmake -G "$Env:CMAKE_GENERATOR" -DCMAKE_BUILD_TYPE=Debug -DOPTDBG=1 -DPORTABLE="$Env:CMAKE_PORTABLE" -DSNAPPY=1 -DJNI=1 ..
& cmake -G "$Env:CMAKE_GENERATOR" -DCMAKE_BUILD_TYPE=Debug -DOPTDBG=1 -DPORTABLE="$Env:CMAKE_PORTABLE" -DSNAPPY=1 -DXPRESS=1 -DJNI=1 ..
if(!$?) { Exit $LASTEXITCODE }
cd ..
echo "Building with VS version: $Env:CMAKE_GENERATOR"
+75 -2
View File
@@ -1,11 +1,84 @@
# Rocksdb Change Log
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
## 10.0.1 (03/05/2025)
## 10.3.4 (06/17/2025)
### Public API Changes
* Change NewExternalTableFactory to return a unique_ptr instead of shared_ptr.
## 10.3.3 (06/04/2025)
### Bug Fixes
* Fix iterator operations returning NotImplemented status if disallow_memtable_writes and paranoid_memory_checks CF options are both set.
* Fix Windows-only XPRESS compression
* Fix newer gtest compatibility
## 10.3.2 (05/30/2025)
### Behavior Changes
* In Remote Compactions, instead of using FileSystem::GetFileSize() for each CompactionOutputFile, use the file size that is being tracked internally as part of the output file's metadata. FileSize is now part of CompactionServiceOutputFile and serialized in the CompactionServiceResult
## 10.3.1 (05/22/2025)
### New Features
* Add new experimental `TransactionOptions::large_txn_commit_optimize_byte_threshold` to enable optimizations for large transaction commit by transaction batch data size.
### Behavior Changes
* `TransactionOptions::large_txn_commit_optimize_threshold` now has default value 0 for disabled. `TransactionDBOptions::txn_commit_bypass_memtable_threshold` now has no effect on transactions.
## 10.3.0 (05/17/2025)
### New Features
* Add new experimental `CompactionOptionsFIFO::allow_trivial_copy_when_change_temperature` along with `CompactionOptionsFIFO::trivial_copy_buffer_size` to allow optimizing FIFO compactions with tiering when kChangeTemperature to move files from source tier FileSystem to another tier FileSystem via trivial and direct copying raw sst file instead of reading thru the content of the SST file then rebuilding the table files.
* Add a new field to Compaction Stats in LOG files for the pre-compression size written to each level.
* Add new experimental `TransactionOptions::large_txn_commit_optimize_threshold` to enable optimizations for large transaction commit with per transaction threshold. `TransactionDBOptions::txn_commit_bypass_memtable_threshold` is deprecated in favor of this transaction option.
* [internal team use only] Allow an application-defined `request_id` to be passed to RocksDB and propagated to the filesystem via IODebugContext
### Bug Fixes
* Fix a bug where transaction lock upgrade can incorrectly fail with a Deadlock status. This happens when a transaction has a non-zero timeout and tries to upgrade a shared lock that is also held by another transaction.
* Pass wrapped WritableFileWriter pointer to ExternalTableBuilder so that the file checksum can be correctly calculated and returned by SstFileWriter for external table files.
* Fix an infinite-loop bug in transaction locking. This can happen if a transaction reaches lock limit and its time out expires before it attempts to wait for it.
* Fixed a potential data race with `CompressionOptions::parallel_threads > 1` and a `TablePropertiesCollector` overriding `BlockAdd()`.
## 10.2.0 (04/21/2025)
### New Features
* Provide histogram stats `COMPACTION_PREFETCH_BYTES` to measure number of bytes for RocksDB's prefetching (as opposed to file
system's prefetch) on SST file during compaction read
* A new API DB::GetNewestUserDefinedTimestamp is added to return the newest user defined timestamp seen in a column family
* Introduce API `IngestWriteBatchWithIndex()` for ingesting updates into DB while bypassing memtable writes. This improves performance when writing a large write batch to the DB.
* Add a new CF option `memtable_op_scan_flush_trigger` that triggers a flush of the memtable if an iterator's Seek()/Next() scans over a certain number of invisible entries from the memtable.
### Public API Changes
* AdvancedColumnFamilyOptions.max_write_buffer_number_to_maintain is deleted. It's deprecated since introduction of a better option max_write_buffer_size_to_maintain since RocksDB 6.5.0.
* Deprecated API `DB::MaxMemCompactionLevel()`.
* Deprecated `ReadOptions::ignore_range_deletions`.
* Deprecated API `experimental::PromoteL0()`.
* Added arbitrary string map for additional options to be overriden for remote compactions
* The fail_if_options_file_error option in DBOptions has been removed. The behavior now is to always return failure in any API that fails to persist the OPTIONS file.
### Behavior Changes
* Make stats `PREFETCH_BYTES_USEFUL`, `PREFETCH_HITS`, `PREFETCH_BYTES` only account for prefetching during user initiated scan
### Bug Fixes
* Fix a bug in Posix file system that the FSWritableFile created via `FileSystem::ReopenWritableFile` internally does not track the correct file size.
* Fix a bug where tail size of remote compaction output is not persisted in primary db's manifest
## 10.1.0 (03/24/2025)
### New Features
* Added a new `DBOptions.calculate_sst_write_lifetime_hint_set` setting that allows to customize which compaction styles SST write lifetime hint calculation is allowed on. Today RocksDB supports only two modes `kCompactionStyleLevel` and `kCompactionStyleUniversal`.
* Add a new field `num_l0_files` in `CompactionJobInfo` about the number of L0 files in the CF right before and after the compaction
* Added per-key-placement feature in Remote Compaction
* Implemented API DB::GetPropertiesOfTablesByLevel that retrieves table properties for files in each LSM tree level
### Public API Changes
* `GetAllKeyVersions()` now interprets empty slices literally, as valid keys, and uses new `OptSlice` type default value for extreme upper and lower range limits.
* `DeleteFilesInRanges()` now takes `RangeOpt` which is based on `OptSlice`. The overload taking `RangePtr` is deprecated.
* Add an unordered map of name/value pairs, ReadOptions::property_bag, to pass opaque options through to an external table when creating an Iterator.
* Introduced CompactionServiceJobStatus::kAborted to allow handling aborted scenario in Schedule(), Wait() or OnInstallation() APIs in Remote Compactions.
* Added a column family option disallow_memtable_writes to safely fail any attempts to write to a non-default column family. This can be used for column families that are ingest only.
* format\_version < 2 in BlockBasedTableOptions is no longer supported for writing new files. Support for reading such files is deprecated and might be removed in the future. `CompressedSecondaryCacheOptions::compress_format_version == 1` is also deprecated.
### Behavior Changes
* `ldb` now returns an error if the specified `--compression_type` is not supported in the build.
* MultiGet with snapshot and ReadOptions::read_tier = kPersistedTier will now read a consistent view across CFs (instead of potentially reading some CF before and some CF after a flush).
* CreateColumnFamily() is no longer allowed on a read-only DB (OpenForReadOnly())
### Bug Fixes
* Fixed stats for Tiered Storage with preclude_last_level feature
## 10.0.0 (02/21/2025)
### New Features
+1 -1
View File
@@ -2489,7 +2489,7 @@ checkout_folly:
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 f1c1542fb071e9d88181279ec24043400222aef1
cd third-party/folly && git reset --hard 8e8186f67de7a23d3a07366946b1617343927d84
@# NOTE: this hack is required for clang in some cases
perl -pi -e 's/int rv = syscall/int rv = (int)syscall/' third-party/folly/folly/detail/Futex.cpp
@# NOTE: this hack is required for gcc in some cases
+7 -4
View File
@@ -118,6 +118,9 @@ fi
# fi
set -e
# Exclude third-party from formatting
EXCLUDE=':!third-party/'
uncommitted_code=`git diff HEAD`
# If there's no uncommitted changes, we assume user are doing post-commit
@@ -137,11 +140,11 @@ then
# should be relevant for formatting fixes.
FORMAT_UPSTREAM_MERGE_BASE="$(git merge-base "$FORMAT_UPSTREAM" HEAD)"
# Get the differences
diffs=$(git diff -U0 "$FORMAT_UPSTREAM_MERGE_BASE" | $CLANG_FORMAT_DIFF -p 1) || true
diffs=$(git diff -U0 "$FORMAT_UPSTREAM_MERGE_BASE" -- $EXCLUDE | $CLANG_FORMAT_DIFF -p 1) || true
echo "Checking format of changes not yet in $FORMAT_UPSTREAM..."
else
# Check the format of uncommitted lines,
diffs=$(git diff -U0 HEAD | $CLANG_FORMAT_DIFF -p 1) || true
diffs=$(git diff -U0 HEAD -- $EXCLUDE | $CLANG_FORMAT_DIFF -p 1) || true
echo "Checking format of uncommitted changes..."
fi
@@ -187,9 +190,9 @@ fi
# Do in-place format adjustment.
if [ -z "$uncommitted_code" ]
then
git diff -U0 "$FORMAT_UPSTREAM_MERGE_BASE" | $CLANG_FORMAT_DIFF -i -p 1
git diff -U0 "$FORMAT_UPSTREAM_MERGE_BASE" -- $EXCLUDE | $CLANG_FORMAT_DIFF -i -p 1
else
git diff -U0 HEAD | $CLANG_FORMAT_DIFF -i -p 1
git diff -U0 HEAD -- $EXCLUDE | $CLANG_FORMAT_DIFF -i -p 1
fi
echo "Files reformatted!"
+35 -32
View File
@@ -24,7 +24,14 @@ CompressedSecondaryCache::CompressedSecondaryCache(
cache_res_mgr_(std::make_shared<ConcurrentCacheReservationManager>(
std::make_shared<CacheReservationManagerImpl<CacheEntryRole::kMisc>>(
cache_))),
disable_cache_(opts.capacity == 0) {}
disable_cache_(opts.capacity == 0) {
auto mgr =
GetBuiltinCompressionManager(cache_options_.compress_format_version);
compressor_ = mgr->GetCompressor(cache_options_.compression_opts,
cache_options_.compression_type);
decompressor_ =
mgr->GetDecompressorOptimizeFor(cache_options_.compression_type);
}
CompressedSecondaryCache::~CompressedSecondaryCache() = default;
@@ -97,25 +104,24 @@ std::unique_ptr<SecondaryCacheResultHandle> CompressedSecondaryCache::Lookup(
kNoCompression, CacheTier::kVolatileTier,
create_context, allocator, &value, &charge);
} else {
UncompressionContext uncompression_context(
cache_options_.compression_type);
UncompressionInfo uncompression_info(uncompression_context,
UncompressionDict::GetEmptyDict(),
cache_options_.compression_type);
size_t uncompressed_size{0};
CacheAllocationPtr uncompressed =
UncompressData(uncompression_info, (char*)data_ptr,
handle_value_charge, &uncompressed_size,
cache_options_.compress_format_version, allocator);
if (!uncompressed) {
cache_->Release(lru_handle, /*erase_if_last_ref=*/true);
return nullptr;
// TODO: can we work some magic with create_cb, which might be based on
// custom compression, to decompress without an extra copy in create_cb?
Decompressor::Args args;
args.compressed_data = Slice(data_ptr, handle_value_charge);
args.compression_type = cache_options_.compression_type;
s = decompressor_->ExtractUncompressedSize(args);
assert(s.ok());
if (s.ok()) {
auto uncompressed = std::make_unique<char[]>(args.uncompressed_size);
s = decompressor_->DecompressBlock(args, uncompressed.get());
assert(s.ok());
if (s.ok()) {
s = helper->create_cb(
Slice(uncompressed.get(), args.uncompressed_size), kNoCompression,
CacheTier::kVolatileTier, create_context, allocator, &value,
&charge);
}
}
s = helper->create_cb(Slice(uncompressed.get(), uncompressed_size),
kNoCompression, CacheTier::kVolatileTier,
create_context, allocator, &value, &charge);
}
} else {
// The item was not compressed by us. Let the helper create_cb
@@ -198,20 +204,17 @@ Status CompressedSecondaryCache::InsertInternal(
type == kNoCompression &&
!cache_options_.do_not_compress_roles.Contains(helper->role)) {
PERF_COUNTER_ADD(compressed_sec_cache_uncompressed_bytes, data_size);
CompressionContext compression_context(cache_options_.compression_type,
cache_options_.compression_opts);
uint64_t sample_for_compression{0};
CompressionInfo compression_info(
cache_options_.compression_opts, compression_context,
CompressionDict::GetEmptyDict(), cache_options_.compression_type,
sample_for_compression);
bool success =
CompressData(val, compression_info,
cache_options_.compress_format_version, &compressed_val);
if (!success) {
return Status::Corruption("Error compressing value.");
CompressionType to_type = kNoCompression;
s = compressor_->CompressBlock(val, &compressed_val, &to_type,
nullptr /*working_area*/);
if (!s.ok()) {
return s;
}
// TODO: allow values not compressed when there's no size savings?
assert(to_type == cache_options_.compression_type);
if (to_type != cache_options_.compression_type) {
return Status::Corruption("Failed to compress value.");
}
val = Slice(compressed_val);
+2
View File
@@ -145,6 +145,8 @@ class CompressedSecondaryCache : public SecondaryCache {
const Cache::CacheItemHelper* GetHelper(bool enable_custom_split_merge) const;
std::shared_ptr<Cache> cache_;
CompressedSecondaryCacheOptions cache_options_;
std::unique_ptr<Compressor> compressor_;
std::shared_ptr<Decompressor> decompressor_;
mutable port::Mutex capacity_mutex_;
std::shared_ptr<ConcurrentCacheReservationManager> cache_res_mgr_;
bool disable_cache_;
+11 -1
View File
@@ -121,7 +121,17 @@ CacheWithSecondaryAdapter::~CacheWithSecondaryAdapter() {
assert(s.ok());
assert(placeholder_usage_ == 0);
assert(reserved_usage_ == 0);
assert(pri_cache_res_->GetTotalMemoryUsed() == sec_capacity);
bool pri_cache_res_mismatch =
pri_cache_res_->GetTotalMemoryUsed() != sec_capacity;
if (pri_cache_res_mismatch) {
fprintf(stderr,
"~CacheWithSecondaryAdapter: Primary cache reservation: "
"%zu, Secondary cache capacity: %zu, "
"Secondary cache reserved: %zu\n",
pri_cache_res_->GetTotalMemoryUsed(), sec_capacity,
sec_reserved_);
assert(pri_cache_res_mismatch);
}
}
#endif // NDEBUG
}
+61 -15
View File
@@ -11,18 +11,29 @@ CRASHTEST_MAKE=$(MAKE) -f crash_test.mk
CRASHTEST_PY=$(PYTHON) -u tools/db_crashtest.py --stress_cmd=$(DB_STRESS_CMD) --cleanup_cmd='$(DB_CLEANUP_CMD)'
.PHONY: crash_test crash_test_with_atomic_flush crash_test_with_txn \
crash_test_with_wc_txn crash_test_with_wp_txn crash_test_with_wup_txn \
crash_test_with_best_efforts_recovery crash_test_with_ts \
crash_test_with_multiops_wc_txn \
crash_test_with_multiops_wp_txn \
crash_test_with_multiops_wup_txn \
crash_test_with_optimistic_txn \
crash_test_with_tiered_storage \
blackbox_crash_test blackbox_crash_test_with_atomic_flush \
blackbox_crash_test_with_wc_txn blackbox_crash_test_with_wp_txn \
blackbox_crash_test_with_wup_txn \
blackbox_crash_test_with_txn blackbox_crash_test_with_ts \
blackbox_crash_test_with_best_efforts_recovery \
whitebox_crash_test whitebox_crash_test_with_atomic_flush \
whitebox_crash_test_with_txn whitebox_crash_test_with_ts \
blackbox_crash_test_with_multiops_wc_txn \
blackbox_crash_test_with_multiops_wp_txn \
crash_test_with_tiered_storage blackbox_crash_test_with_tiered_storage \
whitebox_crash_test_with_tiered_storage \
whitebox_crash_test_with_optimistic_txn \
blackbox_crash_test_with_multiops_wup_txn \
blackbox_crash_test_with_optimistic_txn \
blackbox_crash_test_with_tiered_storage \
whitebox_crash_test whitebox_crash_test_with_atomic_flush \
whitebox_crash_test_with_wc_txn whitebox_crash_test_with_wp_txn \
whitebox_crash_test_with_wup_txn \
whitebox_crash_test_with_txn whitebox_crash_test_with_ts \
whitebox_crash_test_with_optimistic_txn \
whitebox_crash_test_with_tiered_storage \
crash_test: $(DB_STRESS_CMD)
# Do not parallelize
@@ -34,10 +45,20 @@ crash_test_with_atomic_flush: $(DB_STRESS_CMD)
$(CRASHTEST_MAKE) whitebox_crash_test_with_atomic_flush
$(CRASHTEST_MAKE) blackbox_crash_test_with_atomic_flush
crash_test_with_txn: $(DB_STRESS_CMD)
crash_test_with_wc_txn: $(DB_STRESS_CMD)
# Do not parallelize
$(CRASHTEST_MAKE) whitebox_crash_test_with_txn
$(CRASHTEST_MAKE) blackbox_crash_test_with_txn
$(CRASHTEST_MAKE) whitebox_crash_test_with_wc_txn
$(CRASHTEST_MAKE) blackbox_crash_test_with_wc_txn
crash_test_with_wp_txn: $(DB_STRESS_CMD)
# Do not parallelize
$(CRASHTEST_MAKE) whitebox_crash_test_with_wp_txn
$(CRASHTEST_MAKE) blackbox_crash_test_with_wp_txn
crash_test_with_wup_txn: $(DB_STRESS_CMD)
# Do not parallelize
$(CRASHTEST_MAKE) whitebox_crash_test_with_wup_txn
$(CRASHTEST_MAKE) blackbox_crash_test_with_wup_txn
crash_test_with_optimistic_txn: $(DB_STRESS_CMD)
# Do not parallelize
@@ -62,6 +83,9 @@ crash_test_with_multiops_wc_txn: $(DB_STRESS_CMD)
crash_test_with_multiops_wp_txn: $(DB_STRESS_CMD)
$(CRASHTEST_MAKE) blackbox_crash_test_with_multiops_wp_txn
crash_test_with_multiops_wup_txn: $(DB_STRESS_CMD)
$(CRASHTEST_MAKE) blackbox_crash_test_with_multiops_wup_txn
blackbox_crash_test: $(DB_STRESS_CMD)
$(CRASHTEST_PY) --simple blackbox $(CRASH_TEST_EXT_ARGS)
$(CRASHTEST_PY) blackbox $(CRASH_TEST_EXT_ARGS)
@@ -69,8 +93,14 @@ blackbox_crash_test: $(DB_STRESS_CMD)
blackbox_crash_test_with_atomic_flush: $(DB_STRESS_CMD)
$(CRASHTEST_PY) --cf_consistency blackbox $(CRASH_TEST_EXT_ARGS)
blackbox_crash_test_with_txn: $(DB_STRESS_CMD)
$(CRASHTEST_PY) --txn blackbox $(CRASH_TEST_EXT_ARGS)
blackbox_crash_test_with_wc_txn: $(DB_STRESS_CMD)
$(CRASHTEST_PY) --txn blackbox --txn_write_policy 0 $(CRASH_TEST_EXT_ARGS)
blackbox_crash_test_with_wp_txn: $(DB_STRESS_CMD)
$(CRASHTEST_PY) --txn blackbox --txn_write_policy 1 $(CRASH_TEST_EXT_ARGS)
blackbox_crash_test_with_wup_txn: $(DB_STRESS_CMD)
$(CRASHTEST_PY) --txn blackbox --txn_write_policy 2 $(CRASH_TEST_EXT_ARGS)
blackbox_crash_test_with_best_efforts_recovery: $(DB_STRESS_CMD)
$(CRASHTEST_PY) --test_best_efforts_recovery blackbox $(CRASH_TEST_EXT_ARGS)
@@ -79,10 +109,13 @@ blackbox_crash_test_with_ts: $(DB_STRESS_CMD)
$(CRASHTEST_PY) --enable_ts blackbox $(CRASH_TEST_EXT_ARGS)
blackbox_crash_test_with_multiops_wc_txn: $(DB_STRESS_CMD)
$(CRASHTEST_PY) --test_multiops_txn --write_policy write_committed blackbox $(CRASH_TEST_EXT_ARGS)
$(CRASHTEST_PY) --test_multiops_txn --txn_write_policy 0 blackbox $(CRASH_TEST_EXT_ARGS)
blackbox_crash_test_with_multiops_wp_txn: $(DB_STRESS_CMD)
$(CRASHTEST_PY) --test_multiops_txn --write_policy write_prepared blackbox $(CRASH_TEST_EXT_ARGS)
$(CRASHTEST_PY) --test_multiops_txn --txn_write_policy 1 blackbox $(CRASH_TEST_EXT_ARGS)
blackbox_crash_test_with_multiops_wup_txn: $(DB_STRESS_CMD)
$(CRASHTEST_PY) --test_multiops_txn --txn_write_policy 2 blackbox $(CRASH_TEST_EXT_ARGS)
blackbox_crash_test_with_tiered_storage: $(DB_STRESS_CMD)
$(CRASHTEST_PY) --test_tiered_storage blackbox $(CRASH_TEST_EXT_ARGS)
@@ -104,9 +137,17 @@ whitebox_crash_test_with_atomic_flush: $(DB_STRESS_CMD)
$(CRASHTEST_PY) --cf_consistency whitebox --random_kill_odd \
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
whitebox_crash_test_with_txn: $(DB_STRESS_CMD)
$(CRASHTEST_PY) --txn whitebox --random_kill_odd \
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
whitebox_crash_test_with_wc_txn: $(DB_STRESS_CMD)
$(CRASHTEST_PY) --txn whitebox --txn_write_policy 0 \
--random_kill_odd $(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
whitebox_crash_test_with_wp_txn: $(DB_STRESS_CMD)
$(CRASHTEST_PY) --txn whitebox --txn_write_policy 1 \
--random_kill_odd $(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
whitebox_crash_test_with_wup_txn: $(DB_STRESS_CMD)
$(CRASHTEST_PY) --txn whitebox --txn_write_policy 2 \
--random_kill_odd $(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
whitebox_crash_test_with_ts: $(DB_STRESS_CMD)
$(CRASHTEST_PY) --enable_ts whitebox --random_kill_odd \
@@ -119,3 +160,8 @@ whitebox_crash_test_with_tiered_storage: $(DB_STRESS_CMD)
whitebox_crash_test_with_optimistic_txn: $(DB_STRESS_CMD)
$(CRASHTEST_PY) --optimistic_txn whitebox --random_kill_odd \
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
# Old names DEPRECATED
crash_test_with_txn: crash_test_with_wc_txn
whitebox_crash_test_with_txn: whitebox_crash_test_with_wc_txn
blackbox_crash_test_with_txn: blackbox_crash_test_with_wc_txn
+27 -23
View File
@@ -42,9 +42,9 @@ Status ArenaWrappedDBIter::GetProperty(std::string prop_name,
void ArenaWrappedDBIter::Init(
Env* env, const ReadOptions& read_options, const ImmutableOptions& ioptions,
const MutableCFOptions& mutable_cf_options, const Version* version,
const SequenceNumber& sequence, uint64_t max_sequential_skip_in_iteration,
uint64_t version_number, ReadCallback* read_callback,
ColumnFamilyHandleImpl* cfh, bool expose_blob_index, bool allow_refresh) {
const SequenceNumber& sequence, uint64_t version_number,
ReadCallback* read_callback, ColumnFamilyHandleImpl* cfh,
bool expose_blob_index, bool allow_refresh, ReadOnlyMemTable* active_mem) {
read_options_ = read_options;
if (!CheckFSFeatureSupport(env->GetFileSystem().get(),
FSSupportedOps::kAsyncIO)) {
@@ -52,15 +52,14 @@ void ArenaWrappedDBIter::Init(
}
read_options_.total_order_seek |= ioptions.prefix_seek_opt_in_only;
auto mem = arena_.AllocateAligned(sizeof(DBIter));
db_iter_ = new (mem) DBIter(env, read_options_, ioptions, mutable_cf_options,
ioptions.user_comparator,
/* iter */ nullptr, version, sequence, true,
max_sequential_skip_in_iteration, read_callback,
cfh, expose_blob_index);
db_iter_ = DBIter::NewIter(
env, read_options_, ioptions, mutable_cf_options,
ioptions.user_comparator, /*internal_iter=*/nullptr, version, sequence,
read_callback, active_mem, cfh, expose_blob_index, &arena_);
sv_number_ = version_number;
allow_refresh_ = allow_refresh;
allow_mark_memtable_for_flush_ = active_mem;
memtable_range_tombstone_iter_ = nullptr;
}
@@ -166,9 +165,8 @@ void ArenaWrappedDBIter::DoRefresh(const Snapshot* snapshot,
read_callback_->Refresh(read_seq);
}
Init(env, read_options_, cfd->ioptions(), sv->mutable_cf_options, sv->current,
read_seq, sv->mutable_cf_options.max_sequential_skip_in_iterations,
sv->version_number, read_callback_, cfh_, expose_blob_index_,
allow_refresh_);
read_seq, sv->version_number, read_callback_, cfh_, expose_blob_index_,
allow_refresh_, allow_mark_memtable_for_flush_ ? sv->mem : nullptr);
InternalIterator* internal_iter = db_impl->NewInternalIterator(
read_options_, cfd, sv, &arena_, read_seq,
@@ -253,20 +251,26 @@ Status ArenaWrappedDBIter::Refresh(const Snapshot* snapshot) {
}
ArenaWrappedDBIter* NewArenaWrappedDbIterator(
Env* env, const ReadOptions& read_options, const ImmutableOptions& ioptions,
const MutableCFOptions& mutable_cf_options, const Version* version,
const SequenceNumber& sequence, uint64_t max_sequential_skip_in_iterations,
uint64_t version_number, ReadCallback* read_callback,
ColumnFamilyHandleImpl* cfh, bool expose_blob_index, bool allow_refresh) {
ArenaWrappedDBIter* iter = new ArenaWrappedDBIter();
iter->Init(env, read_options, ioptions, mutable_cf_options, version, sequence,
max_sequential_skip_in_iterations, version_number, read_callback,
cfh, expose_blob_index, allow_refresh);
Env* env, const ReadOptions& read_options, ColumnFamilyHandleImpl* cfh,
SuperVersion* sv, const SequenceNumber& sequence,
ReadCallback* read_callback, DBImpl* db_impl, bool expose_blob_index,
bool allow_refresh, bool allow_mark_memtable_for_flush) {
ArenaWrappedDBIter* db_iter = new ArenaWrappedDBIter();
db_iter->Init(env, read_options, cfh->cfd()->ioptions(),
sv->mutable_cf_options, sv->current, sequence,
sv->version_number, read_callback, cfh, expose_blob_index,
allow_refresh,
allow_mark_memtable_for_flush ? sv->mem : nullptr);
if (cfh != nullptr && allow_refresh) {
iter->StoreRefreshInfo(cfh, read_callback, expose_blob_index);
db_iter->StoreRefreshInfo(cfh, read_callback, expose_blob_index);
}
return iter;
InternalIterator* internal_iter = db_impl->NewInternalIterator(
db_iter->GetReadOptions(), cfh->cfd(), sv, db_iter->GetArena(), sequence,
/*allow_unprepared_value=*/true, db_iter);
db_iter->SetIterUnderDBIter(internal_iter);
return db_iter;
}
} // namespace ROCKSDB_NAMESPACE
+14 -13
View File
@@ -19,7 +19,6 @@
#include "options/cf_options.h"
#include "rocksdb/db.h"
#include "rocksdb/iterator.h"
#include "util/autovector.h"
namespace ROCKSDB_NAMESPACE {
@@ -99,13 +98,19 @@ class ArenaWrappedDBIter : public Iterator {
bool PrepareValue() override { return db_iter_->PrepareValue(); }
void Prepare(const std::vector<ScanOptions>& scan_opts) override {
db_iter_->Prepare(scan_opts);
}
// FIXME: we could just pass SV in for mutable cf option, version and version
// number, but this is used by SstFileReader which does not have a SV.
void Init(Env* env, const ReadOptions& read_options,
const ImmutableOptions& ioptions,
const MutableCFOptions& mutable_cf_options, const Version* version,
const SequenceNumber& sequence,
uint64_t max_sequential_skip_in_iterations, uint64_t version_number,
const SequenceNumber& sequence, uint64_t version_number,
ReadCallback* read_callback, ColumnFamilyHandleImpl* cfh,
bool expose_blob_index, bool allow_refresh);
bool expose_blob_index, bool allow_refresh,
ReadOnlyMemTable* active_mem);
// Store some parameters so we can refresh the iterator at a later point
// with these same params
@@ -128,20 +133,16 @@ class ArenaWrappedDBIter : public Iterator {
ReadCallback* read_callback_;
bool expose_blob_index_ = false;
bool allow_refresh_ = true;
bool allow_mark_memtable_for_flush_ = true;
// If this is nullptr, it means the mutable memtable does not contain range
// tombstone when added under this DBIter.
std::unique_ptr<TruncatedRangeDelIterator>* memtable_range_tombstone_iter_ =
nullptr;
};
// Generate the arena wrapped iterator class.
// `cfh` is used for reneweal. If left null, renewal will not
// be supported.
ArenaWrappedDBIter* NewArenaWrappedDbIterator(
Env* env, const ReadOptions& read_options, const ImmutableOptions& ioptions,
const MutableCFOptions& mutable_cf_options, const Version* version,
const SequenceNumber& sequence, uint64_t max_sequential_skip_in_iterations,
uint64_t version_number, ReadCallback* read_callback,
ColumnFamilyHandleImpl* cfh = nullptr, bool expose_blob_index = false,
bool allow_refresh = true);
Env* env, const ReadOptions& read_options, ColumnFamilyHandleImpl* cfh,
SuperVersion* sv, const SequenceNumber& sequence,
ReadCallback* read_callback, DBImpl* db_impl, bool expose_blob_index,
bool allow_refresh, bool allow_mark_memtable_for_flush);
} // namespace ROCKSDB_NAMESPACE
+10 -7
View File
@@ -188,10 +188,12 @@ Status BlobFileBuilder::OpenBlobFileIfNeeded() {
}
std::unique_ptr<FSWritableFile> file;
FileOptions fo_copy;
{
assert(file_options_);
Status s = NewWritableFile(fs_, blob_file_path, &file, *file_options_);
fo_copy = *file_options_;
fo_copy.write_hint = write_hint_;
Status s = NewWritableFile(fs_, blob_file_path, &file, fo_copy);
TEST_SYNC_POINT_CALLBACK(
"BlobFileBuilder::OpenBlobFileIfNeeded:NewWritableFile", &s);
@@ -209,7 +211,9 @@ Status BlobFileBuilder::OpenBlobFileIfNeeded() {
assert(file);
file->SetIOPriority(write_options_->rate_limiter_priority);
file->SetWriteLifeTimeHint(write_hint_);
// Subsequent attempts to override the hint via SetWriteLifeTimeHint
// with the very same value will be ignored by the fs.
file->SetWriteLifeTimeHint(fo_copy.write_hint);
FileTypeSet tmp_set = immutable_options_->checksum_handoff_file_types;
Statistics* const statistics = immutable_options_->stats;
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
@@ -263,10 +267,9 @@ Status BlobFileBuilder::CompressBlobIfNeeded(
// TODO: allow user CompressionOptions, including max_compressed_bytes_per_kb
CompressionOptions opts;
CompressionContext context(blob_compression_type_, opts);
constexpr uint64_t sample_for_compression = 0;
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
blob_compression_type_, sample_for_compression);
blob_compression_type_);
constexpr uint32_t compression_format_version = 2;
@@ -275,8 +278,8 @@ Status BlobFileBuilder::CompressBlobIfNeeded(
{
StopWatch stop_watch(immutable_options_->clock, immutable_options_->stats,
BLOB_DB_COMPRESSION_MICROS);
success =
CompressData(*blob, info, compression_format_version, compressed_blob);
success = OLD_CompressData(*blob, info, compression_format_version,
compressed_blob);
}
if (!success) {
+1 -2
View File
@@ -405,10 +405,9 @@ TEST_F(BlobFileBuilderTest, Compression) {
CompressionOptions opts;
CompressionContext context(kSnappyCompression, opts);
constexpr uint64_t sample_for_compression = 0;
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
kSnappyCompression, sample_for_compression);
kSnappyCompression);
std::string compressed_value;
ASSERT_TRUE(Snappy_Compress(info, uncompressed_value.data(),
+1
View File
@@ -6,6 +6,7 @@
#pragma once
#include <cassert>
#include <cstdint>
#include <iosfwd>
#include <memory>
#include <string>
+12 -9
View File
@@ -250,7 +250,8 @@ Status BlobFileReader::ReadFromFile(const RandomAccessFileReader* file_reader,
Status s;
IOOptions io_options;
s = file_reader->PrepareIOOptions(read_options, io_options);
IODebugContext dbg;
s = file_reader->PrepareIOOptions(read_options, io_options, &dbg);
if (!s.ok()) {
return s;
}
@@ -259,13 +260,13 @@ Status BlobFileReader::ReadFromFile(const RandomAccessFileReader* file_reader,
constexpr char* scratch = nullptr;
s = file_reader->Read(io_options, read_offset, read_size, slice, scratch,
aligned_buf);
aligned_buf, &dbg);
} else {
buf->reset(new char[read_size]);
constexpr AlignedBuf* aligned_scratch = nullptr;
s = file_reader->Read(io_options, read_offset, read_size, slice, buf->get(),
aligned_scratch);
aligned_scratch, &dbg);
}
if (!s.ok()) {
@@ -334,7 +335,8 @@ Status BlobFileReader::GetBlob(
constexpr bool for_compaction = true;
IOOptions io_options;
s = file_reader_->PrepareIOOptions(read_options, io_options);
IODebugContext dbg;
s = file_reader_->PrepareIOOptions(read_options, io_options, &dbg);
if (!s.ok()) {
return s;
}
@@ -463,10 +465,11 @@ void BlobFileReader::MultiGetBlob(
PERF_COUNTER_ADD(blob_read_count, num_blobs);
PERF_COUNTER_ADD(blob_read_byte, total_len);
IOOptions opts;
s = file_reader_->PrepareIOOptions(read_options, opts);
IODebugContext dbg;
s = file_reader_->PrepareIOOptions(read_options, opts, &dbg);
if (s.ok()) {
s = file_reader_->MultiRead(opts, read_reqs.data(), read_reqs.size(),
direct_io ? &aligned_buf : nullptr);
direct_io ? &aligned_buf : nullptr, &dbg);
}
if (!s.ok()) {
for (auto& req : read_reqs) {
@@ -602,9 +605,9 @@ Status BlobFileReader::UncompressBlobIfNeeded(
{
PERF_TIMER_GUARD(blob_decompress_time);
StopWatch stop_watch(clock, statistics, BLOB_DB_DECOMPRESSION_MICROS);
output = UncompressData(info, value_slice.data(), value_slice.size(),
&uncompressed_size, compression_format_version,
allocator);
output = OLD_UncompressData(info, value_slice.data(), value_slice.size(),
&uncompressed_size, compression_format_version,
allocator);
}
TEST_SYNC_POINT_CALLBACK(
+3 -4
View File
@@ -75,15 +75,14 @@ void WriteBlobFile(const ImmutableOptions& immutable_options,
} else {
CompressionOptions opts;
CompressionContext context(compression, opts);
constexpr uint64_t sample_for_compression = 0;
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
compression, sample_for_compression);
compression);
constexpr uint32_t compression_format_version = 2;
for (size_t i = 0; i < num; ++i) {
ASSERT_TRUE(CompressData(blobs[i], info, compression_format_version,
&compressed_blobs[i]));
ASSERT_TRUE(OLD_CompressData(blobs[i], info, compression_format_version,
&compressed_blobs[i]));
blobs_to_write[i] = compressed_blobs[i];
blob_sizes[i] = compressed_blobs[i].size();
}
+3 -4
View File
@@ -77,15 +77,14 @@ void WriteBlobFile(const ImmutableOptions& immutable_options,
} else {
CompressionOptions opts;
CompressionContext context(compression, opts);
constexpr uint64_t sample_for_compression = 0;
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
compression, sample_for_compression);
compression);
constexpr uint32_t compression_format_version = 2;
for (size_t i = 0; i < num; ++i) {
ASSERT_TRUE(CompressData(blobs[i], info, compression_format_version,
&compressed_blobs[i]));
ASSERT_TRUE(OLD_CompressData(blobs[i], info, compression_format_version,
&compressed_blobs[i]));
blobs_to_write[i] = compressed_blobs[i];
blob_sizes[i] = compressed_blobs[i].size();
}
+20 -6
View File
@@ -74,8 +74,8 @@ Status BuildTable(
EventLogger* event_logger, int job_id, TableProperties* table_properties,
Env::WriteLifeTimeHint write_hint, const std::string* full_history_ts_low,
BlobFileCompletionCallback* blob_callback, Version* version,
uint64_t* num_input_entries, uint64_t* memtable_payload_bytes,
uint64_t* memtable_garbage_bytes) {
uint64_t* memtable_payload_bytes, uint64_t* memtable_garbage_bytes,
InternalStats::CompactionStats* flush_stats) {
assert((tboptions.column_family_id ==
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily) ==
tboptions.column_family_name.empty());
@@ -145,7 +145,9 @@ Status BuildTable(
bool use_direct_writes = file_options.use_direct_writes;
TEST_SYNC_POINT_CALLBACK("BuildTable:create_file", &use_direct_writes);
#endif // !NDEBUG
IOStatus io_s = NewWritableFile(fs, fname, &file, file_options);
FileOptions fo_copy = file_options;
fo_copy.write_hint = write_hint;
IOStatus io_s = NewWritableFile(fs, fname, &file, fo_copy);
assert(s.ok());
s = io_s;
if (io_status->ok()) {
@@ -163,7 +165,9 @@ Status BuildTable(
table_file_created = true;
FileTypeSet tmp_set = ioptions.checksum_handoff_file_types;
file->SetIOPriority(tboptions.write_options.rate_limiter_priority);
file->SetWriteLifeTimeHint(write_hint);
// Subsequent attempts to override the hint via SetWriteLifeTimeHint
// with the very same value will be ignored by the fs.
file->SetWriteLifeTimeHint(fo_copy.write_hint);
file_writer.reset(new WritableFileWriter(
std::move(file), fname, file_options, ioptions.clock, io_tracer,
ioptions.stats, Histograms::SST_WRITE_MICROS, ioptions.listeners,
@@ -249,6 +253,10 @@ Status BuildTable(
}
builder->Add(key_after_flush, value_after_flush);
if (flush_stats) {
flush_stats->num_output_records++;
}
s = meta->UpdateBoundaries(key_after_flush, value_after_flush,
ikey.sequence, ikey.type);
if (!s.ok()) {
@@ -280,6 +288,9 @@ Status BuildTable(
auto tombstone = range_del_it->Tombstone();
std::pair<InternalKey, Slice> kv = tombstone.Serialize();
builder->Add(kv.first.Encode(), kv.second);
if (flush_stats) {
flush_stats->num_output_records++;
}
InternalKey tombstone_end = tombstone.SerializeEndKey();
meta->UpdateBoundariesForRange(kv.first, tombstone_end, tombstone.seq_,
tboptions.internal_comparator);
@@ -301,9 +312,9 @@ Status BuildTable(
TEST_SYNC_POINT("BuildTable:BeforeFinishBuildTable");
const bool empty = builder->IsEmpty();
if (num_input_entries != nullptr) {
if (flush_stats) {
assert(c_iter.HasNumInputEntryScanned());
*num_input_entries =
flush_stats->num_input_records =
c_iter.NumInputEntryScanned() + num_unfragmented_tombstones;
}
if (!s.ok() || empty) {
@@ -330,6 +341,9 @@ Status BuildTable(
}
if (s.ok() && !empty) {
if (flush_stats) {
flush_stats->bytes_written_pre_comp = builder->PreCompressionSize();
}
uint64_t file_size = builder->FileSize();
meta->fd.file_size = file_size;
meta->tail_size = builder->GetTailSize();
+5 -4
View File
@@ -10,6 +10,7 @@
#include <utility>
#include <vector>
#include "db/internal_stats.h"
#include "db/range_tombstone_fragmenter.h"
#include "db/seqno_to_time_mapping.h"
#include "db/table_properties_collector.h"
@@ -34,7 +35,6 @@ class SnapshotChecker;
class TableCache;
class TableBuilder;
class WritableFileWriter;
class InternalStats;
class BlobFileCompletionCallback;
// Convenience function for NewTableBuilder on the embedded table_factory.
@@ -49,6 +49,7 @@ TableBuilder* NewTableBuilder(const TableBuilderOptions& tboptions,
//
// @param column_family_name Name of the column family that is also identified
// by column_family_id, or empty string if unknown.
// @param flush_stats treat flush as level 0 compaction in internal stats
Status BuildTable(
const std::string& dbname, VersionSet* versions,
const ImmutableDBOptions& db_options, const TableBuilderOptions& tboptions,
@@ -69,8 +70,8 @@ Status BuildTable(
Env::WriteLifeTimeHint write_hint = Env::WLTH_NOT_SET,
const std::string* full_history_ts_low = nullptr,
BlobFileCompletionCallback* blob_callback = nullptr,
Version* version = nullptr, uint64_t* num_input_entries = nullptr,
uint64_t* memtable_payload_bytes = nullptr,
uint64_t* memtable_garbage_bytes = nullptr);
Version* version = nullptr, uint64_t* memtable_payload_bytes = nullptr,
uint64_t* memtable_garbage_bytes = nullptr,
InternalStats::CompactionStats* flush_stats = nullptr);
} // namespace ROCKSDB_NAMESPACE
+516 -10
View File
@@ -24,12 +24,14 @@
#include "rocksdb/experimental.h"
#include "rocksdb/filter_policy.h"
#include "rocksdb/iterator.h"
#include "rocksdb/listener.h"
#include "rocksdb/memtablerep.h"
#include "rocksdb/merge_operator.h"
#include "rocksdb/options.h"
#include "rocksdb/perf_context.h"
#include "rocksdb/rate_limiter.h"
#include "rocksdb/slice_transform.h"
#include "rocksdb/sst_file_manager.h"
#include "rocksdb/statistics.h"
#include "rocksdb/status.h"
#include "rocksdb/table.h"
@@ -49,6 +51,7 @@
#include "util/stderr_logger.h"
#include "utilities/merge_operators.h"
using ROCKSDB_NAMESPACE::BackgroundErrorReason;
using ROCKSDB_NAMESPACE::BackupEngine;
using ROCKSDB_NAMESPACE::BackupEngineOptions;
using ROCKSDB_NAMESPACE::BackupID;
@@ -65,7 +68,9 @@ using ROCKSDB_NAMESPACE::ColumnFamilyMetaData;
using ROCKSDB_NAMESPACE::ColumnFamilyOptions;
using ROCKSDB_NAMESPACE::CompactionFilter;
using ROCKSDB_NAMESPACE::CompactionFilterFactory;
using ROCKSDB_NAMESPACE::CompactionJobInfo;
using ROCKSDB_NAMESPACE::CompactionOptionsFIFO;
using ROCKSDB_NAMESPACE::CompactionReason;
using ROCKSDB_NAMESPACE::CompactRangeOptions;
using ROCKSDB_NAMESPACE::Comparator;
using ROCKSDB_NAMESPACE::CompressionType;
@@ -76,8 +81,11 @@ using ROCKSDB_NAMESPACE::DBOptions;
using ROCKSDB_NAMESPACE::DbPath;
using ROCKSDB_NAMESPACE::Env;
using ROCKSDB_NAMESPACE::EnvOptions;
using ROCKSDB_NAMESPACE::EventListener;
using ROCKSDB_NAMESPACE::ExternalFileIngestionInfo;
using ROCKSDB_NAMESPACE::FileLock;
using ROCKSDB_NAMESPACE::FilterPolicy;
using ROCKSDB_NAMESPACE::FlushJobInfo;
using ROCKSDB_NAMESPACE::FlushOptions;
using ROCKSDB_NAMESPACE::HistogramData;
using ROCKSDB_NAMESPACE::HyperClockCacheOptions;
@@ -90,6 +98,7 @@ using ROCKSDB_NAMESPACE::Logger;
using ROCKSDB_NAMESPACE::LRUCacheOptions;
using ROCKSDB_NAMESPACE::MemoryAllocator;
using ROCKSDB_NAMESPACE::MemoryUtil;
using ROCKSDB_NAMESPACE::MemTableInfo;
using ROCKSDB_NAMESPACE::MergeOperator;
using ROCKSDB_NAMESPACE::NewBloomFilterPolicy;
using ROCKSDB_NAMESPACE::NewCompactOnDeletionCollectorFactory;
@@ -113,10 +122,12 @@ using ROCKSDB_NAMESPACE::Slice;
using ROCKSDB_NAMESPACE::SliceParts;
using ROCKSDB_NAMESPACE::SliceTransform;
using ROCKSDB_NAMESPACE::Snapshot;
using ROCKSDB_NAMESPACE::SstFileManager;
using ROCKSDB_NAMESPACE::SstFileMetaData;
using ROCKSDB_NAMESPACE::SstFileWriter;
using ROCKSDB_NAMESPACE::Status;
using ROCKSDB_NAMESPACE::StderrLogger;
using ROCKSDB_NAMESPACE::SubcompactionJobInfo;
using ROCKSDB_NAMESPACE::TablePropertiesCollectorFactory;
using ROCKSDB_NAMESPACE::Transaction;
using ROCKSDB_NAMESPACE::TransactionDB;
@@ -130,6 +141,8 @@ using ROCKSDB_NAMESPACE::WriteBatch;
using ROCKSDB_NAMESPACE::WriteBatchWithIndex;
using ROCKSDB_NAMESPACE::WriteBufferManager;
using ROCKSDB_NAMESPACE::WriteOptions;
using ROCKSDB_NAMESPACE::WriteStallCondition;
using ROCKSDB_NAMESPACE::WriteStallInfo;
using std::unordered_set;
using std::vector;
@@ -139,6 +152,9 @@ extern "C" {
struct rocksdb_t {
DB* rep;
};
struct rocksdb_status_ptr_t {
Status* rep;
};
struct rocksdb_backup_engine_t {
BackupEngine* rep;
};
@@ -226,6 +242,9 @@ struct rocksdb_cache_t {
struct rocksdb_write_buffer_manager_t {
std::shared_ptr<WriteBufferManager> rep;
};
struct rocksdb_sst_file_manager_t {
std::shared_ptr<SstFileManager> rep;
};
struct rocksdb_livefiles_t {
std::vector<LiveFileMetaData> rep;
};
@@ -292,6 +311,28 @@ struct rocksdb_compactionfiltercontext_t {
CompactionFilter::Context rep;
};
struct rocksdb_flushjobinfo_t {
FlushJobInfo rep;
};
struct rocksdb_writestallcondition_t {
WriteStallCondition rep;
};
struct rocksdb_writestallinfo_t {
WriteStallInfo rep;
};
struct rocksdb_memtableinfo_t {
MemTableInfo rep;
};
struct rocksdb_compactionjobinfo_t {
CompactionJobInfo rep;
};
struct rocksdb_subcompactionjobinfo_t {
SubcompactionJobInfo rep;
};
struct rocksdb_externalfileingestioninfo_t {
ExternalFileIngestionInfo rep;
};
struct rocksdb_statistics_histogram_data_t {
rocksdb_statistics_histogram_data_t() : rep() {}
HistogramData rep;
@@ -2627,6 +2668,16 @@ rocksdb_iterator_t* rocksdb_writebatch_wi_create_iterator_with_base(
return result;
}
rocksdb_iterator_t* rocksdb_writebatch_wi_create_iterator_with_base_readopts(
rocksdb_writebatch_wi_t* wbwi, rocksdb_iterator_t* base_iterator,
const rocksdb_readoptions_t* options) {
rocksdb_iterator_t* result = new rocksdb_iterator_t;
result->rep =
wbwi->rep->NewIteratorWithBase(base_iterator->rep, &options->rep);
delete base_iterator;
return result;
}
rocksdb_iterator_t* rocksdb_writebatch_wi_create_iterator_with_base_cf(
rocksdb_writebatch_wi_t* wbwi, rocksdb_iterator_t* base_iterator,
rocksdb_column_family_handle_t* column_family) {
@@ -2637,6 +2688,17 @@ rocksdb_iterator_t* rocksdb_writebatch_wi_create_iterator_with_base_cf(
return result;
}
rocksdb_iterator_t* rocksdb_writebatch_wi_create_iterator_with_base_cf_readopts(
rocksdb_writebatch_wi_t* wbwi, rocksdb_iterator_t* base_iterator,
rocksdb_column_family_handle_t* column_family,
const rocksdb_readoptions_t* options) {
rocksdb_iterator_t* result = new rocksdb_iterator_t;
result->rep = wbwi->rep->NewIteratorWithBase(
column_family->rep, base_iterator->rep, &options->rep);
delete base_iterator;
return result;
}
char* rocksdb_writebatch_wi_get_from_batch(rocksdb_writebatch_wi_t* wbwi,
const rocksdb_options_t* options,
const char* key, size_t keylen,
@@ -2696,6 +2758,23 @@ char* rocksdb_writebatch_wi_get_from_batch_and_db(
return result;
}
rocksdb_pinnableslice_t* rocksdb_writebatch_wi_get_pinned_from_batch_and_db(
rocksdb_writebatch_wi_t* wbwi, rocksdb_t* db,
const rocksdb_readoptions_t* options, const char* key, size_t keylen,
char** errptr) {
rocksdb_pinnableslice_t* v = new (rocksdb_pinnableslice_t);
Status s = wbwi->rep->GetFromBatchAndDB(db->rep, options->rep,
Slice(key, keylen), &v->rep);
if (!s.ok()) {
delete (v);
if (!s.IsNotFound()) {
SaveError(errptr, s);
}
return nullptr;
}
return v;
}
char* rocksdb_writebatch_wi_get_from_batch_and_db_cf(
rocksdb_writebatch_wi_t* wbwi, rocksdb_t* db,
const rocksdb_readoptions_t* options,
@@ -2717,6 +2796,24 @@ char* rocksdb_writebatch_wi_get_from_batch_and_db_cf(
return result;
}
rocksdb_pinnableslice_t* rocksdb_writebatch_wi_get_pinned_from_batch_and_db_cf(
rocksdb_writebatch_wi_t* wbwi, rocksdb_t* db,
const rocksdb_readoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, char** errptr) {
rocksdb_pinnableslice_t* v = new (rocksdb_pinnableslice_t);
Status s = wbwi->rep->GetFromBatchAndDB(
db->rep, options->rep, column_family->rep, Slice(key, keylen), &v->rep);
if (!s.ok()) {
delete (v);
if (!s.IsNotFound()) {
SaveError(errptr, s);
}
return nullptr;
}
return v;
}
void rocksdb_write_writebatch_wi(rocksdb_t* db,
const rocksdb_writeoptions_t* options,
rocksdb_writebatch_wi_t* wbwi, char** errptr) {
@@ -2930,6 +3027,352 @@ void rocksdb_block_based_options_set_unpartitioned_pinning_tier(
static_cast<ROCKSDB_NAMESPACE::PinningTier>(v);
}
/* FlushJobInfo */
const char* rocksdb_flushjobinfo_cf_name(const rocksdb_flushjobinfo_t* info,
size_t* size) {
*size = info->rep.cf_name.size();
return info->rep.cf_name.data();
}
const char* rocksdb_flushjobinfo_file_path(const rocksdb_flushjobinfo_t* info,
size_t* size) {
*size = info->rep.file_path.size();
return info->rep.file_path.data();
}
unsigned char rocksdb_flushjobinfo_triggered_writes_slowdown(
const rocksdb_flushjobinfo_t* info) {
return info->rep.triggered_writes_slowdown;
}
unsigned char rocksdb_flushjobinfo_triggered_writes_stop(
const rocksdb_flushjobinfo_t* info) {
return info->rep.triggered_writes_stop;
}
uint64_t rocksdb_flushjobinfo_largest_seqno(
const rocksdb_flushjobinfo_t* info) {
return info->rep.largest_seqno;
}
uint64_t rocksdb_flushjobinfo_smallest_seqno(
const rocksdb_flushjobinfo_t* info) {
return info->rep.smallest_seqno;
}
void rocksdb_reset_status(rocksdb_status_ptr_t* status_ptr) {
auto ptr = status_ptr->rep;
*ptr = Status::OK();
}
/* CompactionJobInfo */
void rocksdb_compactionjobinfo_status(const rocksdb_compactionjobinfo_t* info,
char** errptr) {
SaveError(errptr, info->rep.status);
}
const char* rocksdb_compactionjobinfo_cf_name(
const rocksdb_compactionjobinfo_t* info, size_t* size) {
*size = info->rep.cf_name.size();
return info->rep.cf_name.data();
}
size_t rocksdb_compactionjobinfo_input_files_count(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.input_files.size();
}
const char* rocksdb_compactionjobinfo_input_file_at(
const rocksdb_compactionjobinfo_t* info, size_t pos, size_t* size) {
assert(info != nullptr);
assert(pos < info->rep.input_files.size());
const std::string& path = info->rep.input_files[pos];
*size = path.size();
return path.data();
}
size_t rocksdb_compactionjobinfo_output_files_count(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.output_files.size();
}
const char* rocksdb_compactionjobinfo_output_file_at(
const rocksdb_compactionjobinfo_t* info, size_t pos, size_t* size) {
assert(info != nullptr);
assert(pos < info->rep.output_files.size());
const std::string& path = info->rep.output_files[pos];
*size = path.size();
return path.data();
}
uint64_t rocksdb_compactionjobinfo_elapsed_micros(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.stats.elapsed_micros;
}
uint64_t rocksdb_compactionjobinfo_num_corrupt_keys(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.stats.num_corrupt_keys;
}
int rocksdb_compactionjobinfo_base_input_level(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.base_input_level;
}
int rocksdb_compactionjobinfo_output_level(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.output_level;
}
size_t rocksdb_compactionjobinfo_num_input_files(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.stats.num_input_files;
}
size_t rocksdb_compactionjobinfo_num_input_files_at_output_level(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.stats.num_input_files_at_output_level;
}
uint64_t rocksdb_compactionjobinfo_input_records(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.stats.num_input_records;
}
uint64_t rocksdb_compactionjobinfo_output_records(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.stats.num_output_records;
}
uint64_t rocksdb_compactionjobinfo_total_input_bytes(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.stats.total_input_bytes;
}
uint64_t rocksdb_compactionjobinfo_total_output_bytes(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.stats.total_output_bytes;
}
uint32_t rocksdb_compactionjobinfo_compaction_reason(
const rocksdb_compactionjobinfo_t* info) {
return static_cast<uint32_t>(info->rep.compaction_reason);
}
/* SubcompactionJobInfo */
void rocksdb_subcompactionjobinfo_status(
const rocksdb_subcompactionjobinfo_t* info, char** errptr) {
SaveError(errptr, info->rep.status);
}
const char* rocksdb_subcompactionjobinfo_cf_name(
const rocksdb_subcompactionjobinfo_t* info, size_t* size) {
*size = info->rep.cf_name.size();
return info->rep.cf_name.data();
}
uint64_t rocksdb_subcompactionjobinfo_thread_id(
const rocksdb_subcompactionjobinfo_t* info) {
return info->rep.thread_id;
}
int rocksdb_subcompactionjobinfo_base_input_level(
const rocksdb_subcompactionjobinfo_t* info) {
return info->rep.base_input_level;
}
int rocksdb_subcompactionjobinfo_output_level(
const rocksdb_subcompactionjobinfo_t* info) {
return info->rep.output_level;
}
/* ExternalFileIngestionInfo */
const char* rocksdb_externalfileingestioninfo_cf_name(
const rocksdb_externalfileingestioninfo_t* info, size_t* size) {
*size = info->rep.cf_name.size();
return info->rep.cf_name.data();
}
const char* rocksdb_externalfileingestioninfo_internal_file_path(
const rocksdb_externalfileingestioninfo_t* info, size_t* size) {
*size = info->rep.internal_file_path.size();
return info->rep.internal_file_path.data();
}
/* External write stall info */
extern ROCKSDB_LIBRARY_API const char* rocksdb_writestallinfo_cf_name(
const rocksdb_writestallinfo_t* info, size_t* size) {
*size = info->rep.cf_name.size();
return info->rep.cf_name.data();
}
const rocksdb_writestallcondition_t* rocksdb_writestallinfo_cur(
const rocksdb_writestallinfo_t* info) {
return reinterpret_cast<const rocksdb_writestallcondition_t*>(
&info->rep.condition.cur);
}
const rocksdb_writestallcondition_t* rocksdb_writestallinfo_prev(
const rocksdb_writestallinfo_t* info) {
return reinterpret_cast<const rocksdb_writestallcondition_t*>(
&info->rep.condition.prev);
}
const char* rocksdb_memtableinfo_cf_name(const rocksdb_memtableinfo_t* info,
size_t* size) {
*size = info->rep.cf_name.size();
return info->rep.cf_name.data();
}
uint64_t rocksdb_memtableinfo_first_seqno(const rocksdb_memtableinfo_t* info) {
return info->rep.first_seqno;
}
uint64_t rocksdb_memtableinfo_earliest_seqno(
const rocksdb_memtableinfo_t* info) {
return info->rep.earliest_seqno;
}
uint64_t rocksdb_memtableinfo_num_entries(const rocksdb_memtableinfo_t* info) {
return info->rep.num_entries;
}
uint64_t rocksdb_memtableinfo_num_deletes(const rocksdb_memtableinfo_t* info) {
return info->rep.num_deletes;
}
/* event listener */
struct rocksdb_eventlistener_t : public EventListener {
void* state_{};
void (*destructor_)(void*){};
void (*on_flush_begin)(void*, rocksdb_t*, const rocksdb_flushjobinfo_t*){};
void (*on_flush_completed)(void*, rocksdb_t*,
const rocksdb_flushjobinfo_t*){};
void (*on_compaction_begin)(void*, rocksdb_t*,
const rocksdb_compactionjobinfo_t*){};
void (*on_compaction_completed)(void*, rocksdb_t*,
const rocksdb_compactionjobinfo_t*){};
void (*on_subcompaction_begin)(void*,
const rocksdb_subcompactionjobinfo_t*){};
void (*on_subcompaction_completed)(void*,
const rocksdb_subcompactionjobinfo_t*){};
void (*on_external_file_ingested)(
void*, rocksdb_t*, const rocksdb_externalfileingestioninfo_t*){};
void (*on_background_error)(void*, uint32_t, rocksdb_status_ptr_t*){};
void (*on_stall_conditions_changed)(void*, const rocksdb_writestallinfo_t*){};
void (*on_memtable_sealed)(void*, const rocksdb_memtableinfo_t*){};
rocksdb_eventlistener_t() = default;
rocksdb_eventlistener_t(const rocksdb_eventlistener_t&) = delete;
rocksdb_eventlistener_t& operator=(const rocksdb_eventlistener_t&) = delete;
rocksdb_eventlistener_t(rocksdb_eventlistener_t&&) = delete;
rocksdb_eventlistener_t& operator=(rocksdb_eventlistener_t&&) = delete;
void OnFlushBegin(DB* db, const FlushJobInfo& info) override {
rocksdb_t c_db = {db};
on_flush_begin(state_, &c_db,
reinterpret_cast<const rocksdb_flushjobinfo_t*>(&info));
}
void OnFlushCompleted(DB* db, const FlushJobInfo& info) override {
rocksdb_t c_db = {db};
on_flush_completed(state_, &c_db,
reinterpret_cast<const rocksdb_flushjobinfo_t*>(&info));
}
void OnCompactionBegin(DB* db, const CompactionJobInfo& info) override {
rocksdb_t c_db = {db};
on_compaction_begin(
state_, &c_db,
reinterpret_cast<const rocksdb_compactionjobinfo_t*>(&info));
}
void OnCompactionCompleted(DB* db, const CompactionJobInfo& info) override {
rocksdb_t c_db = {db};
on_compaction_completed(
state_, &c_db,
reinterpret_cast<const rocksdb_compactionjobinfo_t*>(&info));
}
void OnSubcompactionBegin(const SubcompactionJobInfo& info) override {
on_subcompaction_begin(
state_, reinterpret_cast<const rocksdb_subcompactionjobinfo_t*>(&info));
}
void OnSubcompactionCompleted(const SubcompactionJobInfo& info) override {
on_subcompaction_completed(
state_, reinterpret_cast<const rocksdb_subcompactionjobinfo_t*>(&info));
}
void OnExternalFileIngested(DB* db,
const ExternalFileIngestionInfo& info) override {
rocksdb_t c_db = {db};
on_external_file_ingested(
state_, &c_db,
reinterpret_cast<const rocksdb_externalfileingestioninfo_t*>(&info));
}
void OnBackgroundError(BackgroundErrorReason reason,
Status* status) override {
rocksdb_status_ptr_t* s = new rocksdb_status_ptr_t;
s->rep = status;
on_background_error(state_, static_cast<uint32_t>(reason), s);
delete s;
}
void OnStallConditionsChanged(const WriteStallInfo& info) override {
on_stall_conditions_changed(
state_, reinterpret_cast<const rocksdb_writestallinfo_t*>(&info));
}
void OnMemTableSealed(const MemTableInfo& info) override {
on_memtable_sealed(state_,
reinterpret_cast<const rocksdb_memtableinfo_t*>(&info));
}
~rocksdb_eventlistener_t() override { destructor_(state_); }
};
rocksdb_eventlistener_t* rocksdb_eventlistener_create(
void* state_, void (*destructor_)(void*), on_flush_begin_cb on_flush_begin,
on_flush_completed_cb on_flush_completed,
on_compaction_begin_cb on_compaction_begin,
on_compaction_completed_cb on_compaction_completed,
on_subcompaction_begin_cb on_subcompaction_begin,
on_subcompaction_completed_cb on_subcompaction_completed,
on_external_file_ingested_cb on_external_file_ingested,
on_background_error_cb on_background_error,
on_stall_conditions_changed_cb on_stall_conditions_changed,
on_memtable_sealed_cb on_memtable_sealed) {
rocksdb_eventlistener_t* et = new rocksdb_eventlistener_t;
et->state_ = state_;
et->destructor_ = destructor_;
et->on_flush_begin = on_flush_begin;
et->on_flush_completed = on_flush_completed;
et->on_compaction_begin = on_compaction_begin;
et->on_compaction_completed = on_compaction_completed;
et->on_subcompaction_begin = on_subcompaction_begin;
et->on_subcompaction_completed = on_subcompaction_completed;
et->on_external_file_ingested = on_external_file_ingested;
et->on_background_error = on_background_error;
et->on_stall_conditions_changed = on_stall_conditions_changed;
et->on_memtable_sealed = on_memtable_sealed;
return et;
}
void rocksdb_eventlistener_destroy(rocksdb_eventlistener_t* t) { delete t; }
void rocksdb_options_add_eventlistener(rocksdb_options_t* opt,
rocksdb_eventlistener_t* t) {
opt->rep.listeners.emplace_back(std::shared_ptr<EventListener>(t));
}
rocksdb_cuckoo_table_options_t* rocksdb_cuckoo_options_create() {
return new rocksdb_cuckoo_table_options_t;
}
@@ -3295,6 +3738,16 @@ uint64_t rocksdb_options_get_periodic_compaction_seconds(
return opt->rep.periodic_compaction_seconds;
}
void rocksdb_options_set_memtable_op_scan_flush_trigger(rocksdb_options_t* opt,
uint32_t n) {
opt->rep.memtable_op_scan_flush_trigger = n;
}
uint32_t rocksdb_options_get_memtable_op_scan_flush_trigger(
rocksdb_options_t* opt) {
return opt->rep.memtable_op_scan_flush_trigger;
}
void rocksdb_options_enable_statistics(rocksdb_options_t* opt) {
opt->rep.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
}
@@ -3804,16 +4257,6 @@ int rocksdb_options_get_min_write_buffer_number_to_merge(
return opt->rep.min_write_buffer_number_to_merge;
}
void rocksdb_options_set_max_write_buffer_number_to_maintain(
rocksdb_options_t* opt, int n) {
opt->rep.max_write_buffer_number_to_maintain = n;
}
int rocksdb_options_get_max_write_buffer_number_to_maintain(
rocksdb_options_t* opt) {
return opt->rep.max_write_buffer_number_to_maintain;
}
void rocksdb_options_set_max_write_buffer_size_to_maintain(
rocksdb_options_t* opt, int64_t n) {
opt->rep.max_write_buffer_size_to_maintain = n;
@@ -4332,6 +4775,8 @@ uint64_t rocksdb_perfcontext_metric(rocksdb_perfcontext_t* context,
return rep->internal_recent_skipped_count;
case rocksdb_internal_merge_count:
return rep->internal_merge_count;
case rocksdb_internal_merge_point_lookup_count:
return rep->internal_merge_point_lookup_count;
case rocksdb_get_snapshot_time:
return rep->get_snapshot_time;
case rocksdb_get_from_memtable_time:
@@ -5239,6 +5684,67 @@ ROCKSDB_LIBRARY_API void rocksdb_write_buffer_manager_set_allow_stall(
wbm->rep->SetAllowStall(new_allow_stall);
}
rocksdb_sst_file_manager_t* rocksdb_sst_file_manager_create(
rocksdb_env_t* env) {
rocksdb_sst_file_manager_t* sfm = new rocksdb_sst_file_manager_t;
sfm->rep.reset(ROCKSDB_NAMESPACE::NewSstFileManager(env->rep));
return sfm;
}
void rocksdb_sst_file_manager_destroy(rocksdb_sst_file_manager_t* sfm) {
delete sfm;
}
void rocksdb_sst_file_manager_set_max_allowed_space_usage(
rocksdb_sst_file_manager_t* sfm, uint64_t max_allowed_space) {
sfm->rep->SetMaxAllowedSpaceUsage(max_allowed_space);
}
void rocksdb_sst_file_manager_set_compaction_buffer_size(
rocksdb_sst_file_manager_t* sfm, uint64_t compaction_buffer_size) {
sfm->rep->SetCompactionBufferSize(compaction_buffer_size);
}
bool rocksdb_sst_file_manager_is_max_allowed_space_reached(
rocksdb_sst_file_manager_t* sfm) {
return sfm->rep->IsMaxAllowedSpaceReached();
}
bool rocksdb_sst_file_manager_is_max_allowed_space_reached_including_compactions(
rocksdb_sst_file_manager_t* sfm) {
return sfm->rep->IsMaxAllowedSpaceReachedIncludingCompactions();
}
uint64_t rocksdb_sst_file_manager_get_total_size(
rocksdb_sst_file_manager_t* sfm) {
return sfm->rep->GetTotalSize();
}
int64_t rocksdb_sst_file_manager_get_delete_rate_bytes_per_second(
rocksdb_sst_file_manager_t* sfm) {
return sfm->rep->GetDeleteRateBytesPerSecond();
}
void rocksdb_sst_file_manager_set_delete_rate_bytes_per_second(
rocksdb_sst_file_manager_t* sfm, int64_t delete_rate) {
return sfm->rep->SetDeleteRateBytesPerSecond(delete_rate);
}
double rocksdb_sst_file_manager_get_max_trash_db_ratio(
rocksdb_sst_file_manager_t* sfm) {
return sfm->rep->GetMaxTrashDBRatio();
}
void rocksdb_sst_file_manager_set_max_trash_db_ratio(
rocksdb_sst_file_manager_t* sfm, double ratio) {
return sfm->rep->SetMaxTrashDBRatio(ratio);
}
uint64_t rocksdb_sst_file_manager_get_total_trash_size(
rocksdb_sst_file_manager_t* sfm) {
return sfm->rep->GetTotalTrashSize();
}
rocksdb_dbpath_t* rocksdb_dbpath_create(const char* path,
uint64_t target_size) {
rocksdb_dbpath_t* result = new rocksdb_dbpath_t;
+114 -12
View File
@@ -103,6 +103,12 @@ static void CheckValue(char* err, const char* expected, char** actual,
Free(actual);
}
static void CheckPinnedValue(char* err, const char* expected,
const char** actual, size_t actual_length) {
CheckNoError(err);
CheckEqual(expected, *actual, actual_length);
}
static void CheckGet(rocksdb_t* db, const rocksdb_readoptions_t* options,
const char* key, const char* expected) {
char* err = NULL;
@@ -1245,6 +1251,8 @@ int main(int argc, char** argv) {
CheckCondition(count == 3);
size_t size;
char* value;
const char* pinned_value;
rocksdb_pinnableslice_t* p;
value = rocksdb_writebatch_wi_get_from_batch(wbi, options, "box", 3, &size,
&err);
CheckValue(err, "c", &value, size);
@@ -1254,9 +1262,19 @@ int main(int argc, char** argv) {
value = rocksdb_writebatch_wi_get_from_batch_and_db(wbi, db, roptions,
"foo", 3, &size, &err);
CheckValue(err, "hello", &value, size);
p = rocksdb_writebatch_wi_get_pinned_from_batch_and_db(wbi, db, roptions,
"foo", 3, &err);
pinned_value = rocksdb_pinnableslice_value(p, &size);
CheckPinnedValue(err, "hello", &pinned_value, size);
rocksdb_pinnableslice_destroy(p);
value = rocksdb_writebatch_wi_get_from_batch_and_db(wbi, db, roptions,
"box", 3, &size, &err);
CheckValue(err, "c", &value, size);
p = rocksdb_writebatch_wi_get_pinned_from_batch_and_db(wbi, db, roptions,
"box", 3, &err);
pinned_value = rocksdb_pinnableslice_value(p, &size);
CheckPinnedValue(err, "c", &pinned_value, size);
rocksdb_pinnableslice_destroy(p);
rocksdb_write_writebatch_wi(db, woptions, wbi, &err);
CheckNoError(err);
CheckGet(db, roptions, "foo", "hello");
@@ -1362,6 +1380,46 @@ int main(int argc, char** argv) {
rocksdb_writebatch_wi_destroy(wbi);
}
StartPhase("wbwi_iter_readoptions");
{
rocksdb_readoptions_t* iter_roptions = rocksdb_readoptions_create();
rocksdb_readoptions_set_iterate_lower_bound(iter_roptions, "boy", 3);
rocksdb_readoptions_set_iterate_upper_bound(iter_roptions, "fool", 4);
rocksdb_iterator_t* base_iter = rocksdb_create_iterator(db, iter_roptions);
rocksdb_writebatch_wi_t* wbi = rocksdb_writebatch_wi_create(0, 1);
rocksdb_writebatch_wi_put(wbi, "bar", 3, "b",
1); // should get filtered out
rocksdb_writebatch_wi_put(wbi, "cat", 3, "miau", 4);
rocksdb_writebatch_wi_put(wbi, "gnu", 3, "muh",
3); // should get filtered out
rocksdb_iterator_t* iter =
rocksdb_writebatch_wi_create_iterator_with_base_readopts(wbi, base_iter,
iter_roptions);
CheckCondition(!rocksdb_iter_valid(iter));
rocksdb_iter_seek_to_first(iter);
CheckCondition(rocksdb_iter_valid(iter));
CheckIter(iter, "cat", "miau");
rocksdb_iter_next(iter);
CheckIter(iter, "foo", "hello");
rocksdb_iter_prev(iter);
CheckIter(iter, "cat", "miau");
rocksdb_iter_prev(iter);
CheckCondition(!rocksdb_iter_valid(iter));
rocksdb_iter_seek_to_last(iter);
CheckIter(iter, "foo", "hello");
rocksdb_iter_seek(iter, "b", 1);
CheckIter(iter, "cat", "miau");
rocksdb_iter_seek_for_prev(iter, "d", 1);
CheckIter(iter, "cat", "miau");
rocksdb_iter_seek_for_prev(iter, "fool", 3);
CheckIter(iter, "foo", "hello");
rocksdb_iter_get_error(iter, &err);
CheckNoError(err);
rocksdb_iter_destroy(iter);
rocksdb_writebatch_wi_destroy(wbi);
rocksdb_readoptions_destroy(iter_roptions);
}
StartPhase("multiget");
{
const char* keys[3] = {"box", "foo", "notfound"};
@@ -1792,6 +1850,35 @@ int main(int argc, char** argv) {
rocksdb_flush_wal(db, 1, &err);
CheckNoError(err);
// 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",
3); // should be filtered out
rocksdb_writebatch_wi_put_cf(wbwi, handles[1], "buffy", 5, "charmed", 7);
rocksdb_writebatch_wi_put_cf(wbwi, handles[1], "bus", 3, "yellow",
6); // should be filtered out
rocksdb_readoptions_t* iter_roptions = rocksdb_readoptions_create();
rocksdb_readoptions_set_iterate_lower_bound(iter_roptions, "bu", 2);
rocksdb_readoptions_set_iterate_upper_bound(iter_roptions, "buffz", 5);
rocksdb_iterator_t* base_iter =
rocksdb_create_iterator_cf(db, iter_roptions, handles[1]);
rocksdb_iterator_t* wbwi_iter =
rocksdb_writebatch_wi_create_iterator_with_base_cf_readopts(
wbwi, base_iter, handles[1], iter_roptions);
CheckCondition(!rocksdb_iter_valid(wbwi_iter));
rocksdb_iter_seek_to_first(wbwi_iter);
CheckCondition(rocksdb_iter_valid(wbwi_iter));
CheckIter(wbwi_iter, "buff", "rocksdb");
rocksdb_iter_next(wbwi_iter);
CheckIter(wbwi_iter, "buffy", "charmed");
rocksdb_iter_next(wbwi_iter);
CheckCondition(!rocksdb_iter_valid(wbwi_iter));
rocksdb_iter_destroy(wbwi_iter);
rocksdb_writebatch_wi_destroy(wbwi);
rocksdb_readoptions_destroy(iter_roptions);
const char* keys[3] = {"box", "box", "barfooxx"};
const rocksdb_column_family_handle_t* get_handles[3] = {
handles[0], handles[1], handles[1]};
@@ -2129,6 +2216,10 @@ int main(int argc, char** argv) {
CheckCondition(100000 ==
rocksdb_options_get_periodic_compaction_seconds(o));
rocksdb_options_set_memtable_op_scan_flush_trigger(o, 100);
CheckCondition(100 ==
rocksdb_options_get_memtable_op_scan_flush_trigger(o));
rocksdb_options_set_ttl(o, 5000);
CheckCondition(5000 == rocksdb_options_get_ttl(o));
@@ -2146,10 +2237,6 @@ int main(int argc, char** argv) {
CheckCondition(23 ==
rocksdb_options_get_min_write_buffer_number_to_merge(o));
rocksdb_options_set_max_write_buffer_number_to_maintain(o, 64);
CheckCondition(64 ==
rocksdb_options_get_max_write_buffer_number_to_maintain(o));
rocksdb_options_set_max_write_buffer_size_to_maintain(o, 50000);
CheckCondition(50000 ==
rocksdb_options_get_max_write_buffer_size_to_maintain(o));
@@ -2407,8 +2494,6 @@ int main(int argc, char** argv) {
CheckCondition(97 == rocksdb_options_get_max_write_buffer_number(copy));
CheckCondition(23 ==
rocksdb_options_get_min_write_buffer_number_to_merge(copy));
CheckCondition(
64 == rocksdb_options_get_max_write_buffer_number_to_maintain(copy));
CheckCondition(50000 ==
rocksdb_options_get_max_write_buffer_size_to_maintain(copy));
CheckCondition(1 == rocksdb_options_get_enable_pipelined_write(copy));
@@ -2572,6 +2657,12 @@ int main(int argc, char** argv) {
CheckCondition(100000 ==
rocksdb_options_get_periodic_compaction_seconds(o));
rocksdb_options_set_memtable_op_scan_flush_trigger(copy, 800);
CheckCondition(800 ==
rocksdb_options_get_memtable_op_scan_flush_trigger(copy));
CheckCondition(100 ==
rocksdb_options_get_memtable_op_scan_flush_trigger(o));
rocksdb_options_set_ttl(copy, 8000);
CheckCondition(8000 == rocksdb_options_get_ttl(copy));
CheckCondition(5000 == rocksdb_options_get_ttl(o));
@@ -2596,12 +2687,6 @@ int main(int argc, char** argv) {
CheckCondition(23 ==
rocksdb_options_get_min_write_buffer_number_to_merge(o));
rocksdb_options_set_max_write_buffer_number_to_maintain(copy, 128);
CheckCondition(
128 == rocksdb_options_get_max_write_buffer_number_to_maintain(copy));
CheckCondition(64 ==
rocksdb_options_get_max_write_buffer_number_to_maintain(o));
rocksdb_options_set_max_write_buffer_size_to_maintain(copy, 9000);
CheckCondition(9000 ==
rocksdb_options_get_max_write_buffer_size_to_maintain(copy));
@@ -4052,6 +4137,23 @@ int main(int argc, char** argv) {
rocksdb_cache_destroy(lru);
}
StartPhase("sst_file_manager");
{
rocksdb_sst_file_manager_t* sst_file_manager;
sst_file_manager = rocksdb_sst_file_manager_create(env);
rocksdb_sst_file_manager_set_delete_rate_bytes_per_second(sst_file_manager,
1);
rocksdb_sst_file_manager_set_max_trash_db_ratio(sst_file_manager, 0.75);
CheckCondition(1 ==
rocksdb_sst_file_manager_get_delete_rate_bytes_per_second(
sst_file_manager));
CheckCondition(0.75 == rocksdb_sst_file_manager_get_max_trash_db_ratio(
sst_file_manager));
rocksdb_sst_file_manager_destroy(sst_file_manager);
}
StartPhase("cancel_all_background_work");
rocksdb_cancel_all_background_work(db, 1);
+51 -27
View File
@@ -200,8 +200,9 @@ const uint64_t kDefaultTtl = 0xfffffffffffffffe;
const uint64_t kDefaultPeriodicCompSecs = 0xfffffffffffffffe;
} // anonymous namespace
ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
const ColumnFamilyOptions& src) {
ColumnFamilyOptions SanitizeCfOptions(const ImmutableDBOptions& db_options,
bool read_only,
const ColumnFamilyOptions& src) {
ColumnFamilyOptions result = src;
size_t clamp_max = std::conditional<
sizeof(size_t) == 4, std::integral_constant<size_t, 0xffffffff>,
@@ -261,15 +262,10 @@ ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
if (result.max_write_buffer_number < 2) {
result.max_write_buffer_number = 2;
}
// fall back max_write_buffer_number_to_maintain if
// max_write_buffer_size_to_maintain is not set
if (result.max_write_buffer_size_to_maintain < 0) {
result.max_write_buffer_size_to_maintain =
result.max_write_buffer_number *
static_cast<int64_t>(result.write_buffer_size);
} else if (result.max_write_buffer_size_to_maintain == 0 &&
result.max_write_buffer_number_to_maintain < 0) {
result.max_write_buffer_number_to_maintain = result.max_write_buffer_number;
}
// bloom filter size shouldn't exceed 1/4 of memtable size.
if (result.memtable_prefix_bloom_size_ratio > 0.25) {
@@ -440,6 +436,25 @@ ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
result.periodic_compaction_seconds = 0;
}
if (read_only && (result.preserve_internal_time_seconds > 0 ||
result.preclude_last_level_data_seconds > 0)) {
// With no writes coming in, we don't need periodic SeqnoToTime entries.
// Existing SST files may or may not have that info associated with them.
ROCKS_LOG_WARN(
db_options.info_log.get(),
"preserve_internal_time_seconds and preclude_last_level_data_seconds "
"are ignored in read-only DB");
result.preserve_internal_time_seconds = 0;
result.preclude_last_level_data_seconds = 0;
}
if (read_only && result.memtable_op_scan_flush_trigger != 0) {
ROCKS_LOG_WARN(db_options.info_log.get(),
"option memtable_op_scan_flush_trigger is sanitized to "
"0(disabled) for read only DB.");
result.memtable_op_scan_flush_trigger = 0;
}
return result;
}
@@ -497,6 +512,17 @@ void SuperVersion::Init(
imm->Ref();
current->Ref();
refs.store(1, std::memory_order_relaxed);
// There should be at least one mapping entry iff time tracking is enabled.
#ifndef NDEBUG
MinAndMaxPreserveSeconds preserve_info{mutable_cf_options};
if (preserve_info.IsEnabled()) {
assert(seqno_to_time_mapping);
assert(!seqno_to_time_mapping->Empty());
} else {
assert(seqno_to_time_mapping == nullptr);
}
#endif // NDEBUG
}
namespace {
@@ -535,7 +561,7 @@ ColumnFamilyData::ColumnFamilyData(
const FileOptions* file_options, ColumnFamilySet* column_family_set,
BlockCacheTracer* const block_cache_tracer,
const std::shared_ptr<IOTracer>& io_tracer, const std::string& db_id,
const std::string& db_session_id)
const std::string& db_session_id, bool read_only)
: id_(id),
name_(name),
dummy_versions_(_dummy_versions),
@@ -545,7 +571,7 @@ ColumnFamilyData::ColumnFamilyData(
dropped_(false),
flush_skip_reschedule_(false),
internal_comparator_(cf_options.comparator),
initial_cf_options_(SanitizeOptions(db_options, cf_options)),
initial_cf_options_(SanitizeCfOptions(db_options, read_only, cf_options)),
ioptions_(db_options, initial_cf_options_),
mutable_cf_options_(initial_cf_options_),
is_delete_range_supported_(
@@ -553,7 +579,6 @@ ColumnFamilyData::ColumnFamilyData(
write_buffer_manager_(write_buffer_manager),
mem_(nullptr),
imm_(ioptions_.min_write_buffer_number_to_merge,
ioptions_.max_write_buffer_number_to_maintain,
ioptions_.max_write_buffer_size_to_maintain),
super_version_(nullptr),
super_version_number_(0),
@@ -1344,20 +1369,17 @@ bool ColumnFamilyData::ReturnThreadLocalSuperVersion(SuperVersion* sv) {
return false;
}
void ColumnFamilyData::InstallSuperVersion(SuperVersionContext* sv_context,
InstrumentedMutex* db_mutex) {
db_mutex->AssertHeld();
return InstallSuperVersion(sv_context, mutable_cf_options_);
}
void ColumnFamilyData::InstallSuperVersion(
SuperVersionContext* sv_context,
const MutableCFOptions& mutable_cf_options) {
SuperVersionContext* sv_context, InstrumentedMutex* db_mutex,
std::optional<std::shared_ptr<SeqnoToTimeMapping>>
new_seqno_to_time_mapping) {
db_mutex->AssertHeld();
SuperVersion* new_superversion = sv_context->new_superversion.release();
new_superversion->mutable_cf_options = mutable_cf_options;
new_superversion->mutable_cf_options = GetLatestMutableCFOptions();
new_superversion->Init(this, mem_, imm_.current(), current_,
sv_context->new_seqno_to_time_mapping
? std::move(sv_context->new_seqno_to_time_mapping)
new_seqno_to_time_mapping.has_value()
? std::move(new_seqno_to_time_mapping.value())
: super_version_
? super_version_->ShareSeqnoToTimeMapping()
: nullptr);
@@ -1370,7 +1392,7 @@ void ColumnFamilyData::InstallSuperVersion(
// currently RecalculateWriteStallConditions() treats it as further slowing
// down is needed.
super_version_->write_stall_condition =
RecalculateWriteStallConditions(mutable_cf_options);
RecalculateWriteStallConditions(new_superversion->mutable_cf_options);
} else {
super_version_->write_stall_condition =
old_superversion->write_stall_condition;
@@ -1383,8 +1405,9 @@ void ColumnFamilyData::InstallSuperVersion(
ResetThreadLocalSuperVersions();
if (old_superversion->mutable_cf_options.write_buffer_size !=
mutable_cf_options.write_buffer_size) {
mem_->UpdateWriteBufferSize(mutable_cf_options.write_buffer_size);
new_superversion->mutable_cf_options.write_buffer_size) {
mem_->UpdateWriteBufferSize(
new_superversion->mutable_cf_options.write_buffer_size);
}
if (old_superversion->write_stall_condition !=
new_superversion->write_stall_condition) {
@@ -1685,7 +1708,8 @@ ColumnFamilySet::ColumnFamilySet(const std::string& dbname,
dummy_cfd_(new ColumnFamilyData(
ColumnFamilyData::kDummyColumnFamilyDataId, "", nullptr, nullptr,
nullptr, ColumnFamilyOptions(), *db_options, &file_options_, nullptr,
block_cache_tracer, io_tracer, db_id, db_session_id)),
block_cache_tracer, io_tracer, db_id, db_session_id,
/*read_only*/ true)),
default_cfd_cache_(nullptr),
db_name_(dbname),
db_options_(db_options),
@@ -1757,12 +1781,12 @@ size_t ColumnFamilySet::NumberOfColumnFamilies() const {
// under a DB mutex AND write thread
ColumnFamilyData* ColumnFamilySet::CreateColumnFamily(
const std::string& name, uint32_t id, Version* dummy_versions,
const ColumnFamilyOptions& options) {
const ColumnFamilyOptions& options, bool read_only) {
assert(column_families_.find(name) == column_families_.end());
ColumnFamilyData* new_cfd = new ColumnFamilyData(
id, name, dummy_versions, table_cache_, write_buffer_manager_, options,
*db_options_, &file_options_, this, block_cache_tracer_, io_tracer_,
db_id_, db_session_id_);
db_id_, db_session_id_, read_only);
column_families_.insert({name, id});
column_family_data_.insert({id, new_cfd});
auto ucmp = new_cfd->user_comparator();
+32 -25
View File
@@ -281,8 +281,9 @@ Status CheckConcurrentWritesSupported(const ColumnFamilyOptions& cf_options);
Status CheckCFPathsSupported(const DBOptions& db_options,
const ColumnFamilyOptions& cf_options);
ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
const ColumnFamilyOptions& src);
ColumnFamilyOptions SanitizeCfOptions(const ImmutableDBOptions& db_options,
bool read_only,
const ColumnFamilyOptions& src);
// Wrap user defined table properties collector factories `from cf_options`
// into internal ones in internal_tbl_prop_coll_factories. Add a system internal
// one too.
@@ -384,10 +385,10 @@ class ColumnFamilyData {
Version* dummy_versions() { return dummy_versions_; }
Version* current() { return current_; } // REQUIRE: DB mutex held
void SetCurrent(Version* _current); // REQUIRE: DB mutex held
uint64_t GetNumLiveVersions() const; // REQUIRE: DB mutex held
uint64_t GetTotalSstFilesSize() const; // REQUIRE: DB mutex held
uint64_t GetLiveSstFilesSize() const; // REQUIRE: DB mutex held
uint64_t GetTotalBlobFileSize() const; // REQUIRE: DB mutex held
uint64_t GetNumLiveVersions() const; // REQUIRE: DB mutex held
uint64_t GetTotalSstFilesSize() const; // REQUIRE: DB mutex held
uint64_t GetLiveSstFilesSize() const; // REQUIRE: DB mutex held
uint64_t GetTotalBlobFileSize() const; // REQUIRE: DB mutex held
// REQUIRE: DB mutex held
void SetMemtable(MemTable* new_mem) {
AssignMemtableID(new_mem);
@@ -490,15 +491,11 @@ class ColumnFamilyData {
uint64_t GetSuperVersionNumberRelaxed() const {
return super_version_number_.load(std::memory_order_relaxed);
}
// will return a pointer to SuperVersion* if previous SuperVersion
// if its reference count is zero and needs deletion or nullptr if not
// As argument takes a pointer to allocated SuperVersion to enable
// the clients to allocate SuperVersion outside of mutex.
// IMPORTANT: Only call this from DBImpl::InstallSuperVersion()
// Only intended for use by DBImpl::InstallSuperVersion() and variants
void InstallSuperVersion(SuperVersionContext* sv_context,
const MutableCFOptions& mutable_cf_options);
void InstallSuperVersion(SuperVersionContext* sv_context,
InstrumentedMutex* db_mutex);
InstrumentedMutex* db_mutex,
std::optional<std::shared_ptr<SeqnoToTimeMapping>>
new_seqno_to_time_mapping = {});
void ResetThreadLocalSuperVersions();
@@ -540,6 +537,12 @@ class ColumnFamilyData {
assert(!ts_low.empty());
const Comparator* ucmp = user_comparator();
assert(ucmp);
// Guard against resurrected full_history_ts_low persisted in MANIFEST
// from previous DB sessions. This could happen if UDT was enabled and then
// disabled.
if (ucmp->timestamp_size() == 0) {
return;
}
if (full_history_ts_low_.empty() ||
ucmp->CompareTimestamp(ts_low, full_history_ts_low_) > 0) {
full_history_ts_low_ = std::move(ts_low);
@@ -547,6 +550,11 @@ class ColumnFamilyData {
}
const std::string& GetFullHistoryTsLow() const {
const Comparator* ucmp = user_comparator();
assert(ucmp);
if (ucmp->timestamp_size() == 0) {
assert(full_history_ts_low_.empty());
}
return full_history_ts_low_;
}
@@ -593,16 +601,14 @@ class ColumnFamilyData {
private:
friend class ColumnFamilySet;
ColumnFamilyData(uint32_t id, const std::string& name,
Version* dummy_versions, Cache* table_cache,
WriteBufferManager* write_buffer_manager,
const ColumnFamilyOptions& options,
const ImmutableDBOptions& db_options,
const FileOptions* file_options,
ColumnFamilySet* column_family_set,
BlockCacheTracer* const block_cache_tracer,
const std::shared_ptr<IOTracer>& io_tracer,
const std::string& db_id, const std::string& db_session_id);
ColumnFamilyData(
uint32_t id, const std::string& name, Version* dummy_versions,
Cache* table_cache, WriteBufferManager* write_buffer_manager,
const ColumnFamilyOptions& options, const ImmutableDBOptions& db_options,
const FileOptions* file_options, ColumnFamilySet* column_family_set,
BlockCacheTracer* const block_cache_tracer,
const std::shared_ptr<IOTracer>& io_tracer, const std::string& db_id,
const std::string& db_session_id, bool read_only);
std::vector<std::string> GetDbPaths() const;
@@ -764,7 +770,8 @@ class ColumnFamilySet {
ColumnFamilyData* CreateColumnFamily(const std::string& name, uint32_t id,
Version* dummy_version,
const ColumnFamilyOptions& options);
const ColumnFamilyOptions& options,
bool read_only);
const UnorderedMap<uint32_t, size_t>& GetRunningColumnFamiliesTimestampSize()
const {
+6 -6
View File
@@ -72,7 +72,6 @@ class ColumnFamilyTestBase : public testing::Test {
env_->skip_fsync_ = true;
dbname_ = test::PerThreadDBPath("column_family_test");
db_options_.create_if_missing = true;
db_options_.fail_if_options_file_error = true;
db_options_.env = env_;
}
@@ -271,7 +270,8 @@ class ColumnFamilyTestBase : public testing::Test {
// them.
ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(
ConfigOptions(), desc.options,
SanitizeOptions(dbfull()->immutable_db_options(), current_cf_opt)));
SanitizeCfOptions(dbfull()->immutable_db_options(),
/*read_only*/ false, current_cf_opt)));
cfi++;
}
}
@@ -2175,7 +2175,7 @@ TEST_P(ColumnFamilyTest, FlushStaleColumnFamilies) {
ASSERT_TRUE(has_cf2_sst);
ASSERT_OK(Flush(0));
ASSERT_EQ(0, dbfull()->TEST_total_log_size());
ASSERT_EQ(0, dbfull()->TEST_wals_total_size());
Close();
}
@@ -2232,7 +2232,7 @@ TEST_P(ColumnFamilyTest, CreateMissingColumnFamilies) {
ASSERT_EQ(my_fs->options_files_created.load(), 2);
}
TEST_P(ColumnFamilyTest, SanitizeOptions) {
TEST_P(ColumnFamilyTest, SanitizeCfOptions) {
DBOptions db_options;
for (int s = kCompactionStyleLevel; s <= kCompactionStyleUniversal; ++s) {
for (int l = 0; l <= 2; l++) {
@@ -2248,8 +2248,8 @@ TEST_P(ColumnFamilyTest, SanitizeOptions) {
original.write_buffer_size =
l * 4 * 1024 * 1024 + i * 1024 * 1024 + j * 1024 + k;
ColumnFamilyOptions result =
SanitizeOptions(ImmutableDBOptions(db_options), original);
ColumnFamilyOptions result = SanitizeCfOptions(
ImmutableDBOptions(db_options), /*read_only*/ false, original);
ASSERT_TRUE(result.level0_stop_writes_trigger >=
result.level0_slowdown_writes_trigger);
ASSERT_TRUE(result.level0_slowdown_writes_trigger >=
+50 -54
View File
@@ -338,16 +338,16 @@ Compaction::Compaction(
_blob_garbage_collection_age_cutoff > 1
? mutable_cf_options().blob_garbage_collection_age_cutoff
: _blob_garbage_collection_age_cutoff),
penultimate_level_(
// For simplicity, we don't support the concept of "penultimate level"
proximal_level_(
// For simplicity, we don't support the concept of "proximal level"
// with `CompactionReason::kExternalSstIngestion` and
// `CompactionReason::kRefitLevel`
_compaction_reason == CompactionReason::kExternalSstIngestion ||
_compaction_reason == CompactionReason::kRefitLevel
? Compaction::kInvalidLevel
: EvaluatePenultimateLevel(vstorage, mutable_cf_options_,
immutable_options_, start_level_,
output_level_)) {
: EvaluateProximalLevel(vstorage, mutable_cf_options_,
immutable_options_, start_level_,
output_level_)) {
MarkFilesBeingCompacted(true);
if (is_manual_compaction_) {
compaction_reason_ = CompactionReason::kManualCompaction;
@@ -405,10 +405,10 @@ Compaction::Compaction(
}
}
PopulatePenultimateLevelOutputRange();
PopulateProximalLevelOutputRange();
}
void Compaction::PopulatePenultimateLevelOutputRange() {
void Compaction::PopulateProximalLevelOutputRange() {
if (!SupportsPerKeyPlacement()) {
assert(keep_in_last_level_through_seqno_ == kMaxSequenceNumber);
return;
@@ -417,46 +417,42 @@ void Compaction::PopulatePenultimateLevelOutputRange() {
// exclude the last level, the range of all input levels is the safe range
// of keys that can be moved up.
int exclude_level = number_levels_ - 1;
penultimate_output_range_type_ = PenultimateOutputRangeType::kNonLastRange;
proximal_output_range_type_ = ProximalOutputRangeType::kNonLastRange;
// For universal compaction, the penultimate_output_range could be extended if
// all penultimate level files are included in the compaction (which includes
// the case that the penultimate level is empty).
// For universal compaction, the proximal_output_range could be extended if
// all proximal level files are included in the compaction (which includes
// the case that the proximal level is empty).
if (immutable_options_.compaction_style == kCompactionStyleUniversal) {
exclude_level = kInvalidLevel;
penultimate_output_range_type_ = PenultimateOutputRangeType::kFullRange;
std::set<uint64_t> penultimate_inputs;
proximal_output_range_type_ = ProximalOutputRangeType::kFullRange;
std::set<uint64_t> proximal_inputs;
for (const auto& input_lvl : inputs_) {
if (input_lvl.level == penultimate_level_) {
if (input_lvl.level == proximal_level_) {
for (const auto& file : input_lvl.files) {
penultimate_inputs.emplace(file->fd.GetNumber());
proximal_inputs.emplace(file->fd.GetNumber());
}
}
}
auto penultimate_files = input_vstorage_->LevelFiles(penultimate_level_);
for (const auto& file : penultimate_files) {
if (penultimate_inputs.find(file->fd.GetNumber()) ==
penultimate_inputs.end()) {
auto proximal_files = input_vstorage_->LevelFiles(proximal_level_);
for (const auto& file : proximal_files) {
if (proximal_inputs.find(file->fd.GetNumber()) == proximal_inputs.end()) {
exclude_level = number_levels_ - 1;
penultimate_output_range_type_ =
PenultimateOutputRangeType::kNonLastRange;
proximal_output_range_type_ = ProximalOutputRangeType::kNonLastRange;
break;
}
}
}
// FIXME: should make use of `penultimate_output_range_type_`.
// FIXME: should make use of `proximal_output_range_type_`.
// FIXME: when last level's input range does not overlap with
// penultimate level, and penultimate level input is empty,
// this call will not set penultimate_level_smallest_ or
// penultimate_level_largest_. No keys will be compacted up.
GetBoundaryInternalKeys(input_vstorage_, inputs_,
&penultimate_level_smallest_,
&penultimate_level_largest_, exclude_level);
// proximal level, and proximal level input is empty,
// this call will not set proximal_level_smallest_ or
// proximal_level_largest_. No keys will be compacted up.
GetBoundaryInternalKeys(input_vstorage_, inputs_, &proximal_level_smallest_,
&proximal_level_largest_, exclude_level);
if (penultimate_output_range_type_ !=
PenultimateOutputRangeType::kFullRange) {
// If not full range in penultimate level, must keep everything already
if (proximal_output_range_type_ != ProximalOutputRangeType::kFullRange) {
// If not full range in proximal level, must keep everything already
// in the last level there, because moving it back up might cause
// overlap/placement issues that are difficult to resolve properly in the
// presence of range deletes
@@ -486,23 +482,23 @@ Compaction::~Compaction() {
}
bool Compaction::SupportsPerKeyPlacement() const {
return penultimate_level_ != kInvalidLevel;
return proximal_level_ != kInvalidLevel;
}
int Compaction::GetPenultimateLevel() const { return penultimate_level_; }
int Compaction::GetProximalLevel() const { return proximal_level_; }
// smallest_key and largest_key include timestamps if user-defined timestamp is
// enabled.
bool Compaction::OverlapPenultimateLevelOutputRange(
bool Compaction::OverlapProximalLevelOutputRange(
const Slice& smallest_key, const Slice& largest_key) const {
if (!SupportsPerKeyPlacement()) {
return false;
}
// See FIXME in Compaction::PopulatePenultimateLevelOutputRange().
// See FIXME in Compaction::PopulateProximalLevelOutputRange().
// We do not compact any key up in this case.
if (penultimate_level_smallest_.size() == 0 ||
penultimate_level_largest_.size() == 0) {
if (proximal_level_smallest_.size() == 0 ||
proximal_level_largest_.size() == 0) {
return false;
}
@@ -510,13 +506,13 @@ bool Compaction::OverlapPenultimateLevelOutputRange(
input_vstorage_->InternalComparator()->user_comparator();
return ucmp->CompareWithoutTimestamp(
smallest_key, penultimate_level_largest_.user_key()) <= 0 &&
smallest_key, proximal_level_largest_.user_key()) <= 0 &&
ucmp->CompareWithoutTimestamp(
largest_key, penultimate_level_smallest_.user_key()) >= 0;
largest_key, proximal_level_smallest_.user_key()) >= 0;
}
// key includes timestamp if user-defined timestamp is enabled.
void Compaction::TEST_AssertWithinPenultimateLevelOutputRange(
void Compaction::TEST_AssertWithinProximalLevelOutputRange(
const Slice& user_key, bool expect_failure) const {
#ifdef NDEBUG
(void)user_key;
@@ -524,15 +520,15 @@ void Compaction::TEST_AssertWithinPenultimateLevelOutputRange(
#else
assert(SupportsPerKeyPlacement());
assert(penultimate_level_smallest_.size() > 0);
assert(penultimate_level_largest_.size() > 0);
assert(proximal_level_smallest_.size() > 0);
assert(proximal_level_largest_.size() > 0);
auto* cmp = input_vstorage_->user_comparator();
// op_type of a key can change during compaction, e.g. Merge -> Put.
if (!(cmp->Compare(user_key, penultimate_level_smallest_.user_key()) >= 0)) {
if (!(cmp->Compare(user_key, proximal_level_smallest_.user_key()) >= 0)) {
assert(expect_failure);
} else if (!(cmp->Compare(user_key, penultimate_level_largest_.user_key()) <=
} else if (!(cmp->Compare(user_key, proximal_level_largest_.user_key()) <=
0)) {
assert(expect_failure);
} else {
@@ -1018,7 +1014,7 @@ uint64_t Compaction::MinInputFileEpochNumber() const {
return min_epoch_number;
}
int Compaction::EvaluatePenultimateLevel(
int Compaction::EvaluateProximalLevel(
const VersionStorageInfo* vstorage,
const MutableCFOptions& mutable_cf_options,
const ImmutableOptions& immutable_options, const int start_level,
@@ -1033,21 +1029,21 @@ int Compaction::EvaluatePenultimateLevel(
return kInvalidLevel;
}
int penultimate_level = output_level - 1;
assert(penultimate_level < immutable_options.num_levels);
if (penultimate_level <= 0) {
int proximal_level = output_level - 1;
assert(proximal_level < immutable_options.num_levels);
if (proximal_level <= 0) {
return kInvalidLevel;
}
// If the penultimate level is not within input level -> output level range
// check if the penultimate output level is empty, if it's empty, it could
// also be locked for the penultimate output.
// If the proximal level is not within input level -> output level range
// check if the proximal output level is empty, if it's empty, it could
// also be locked for the proximal output.
// TODO: ideally, it only needs to check if there's a file within the
// compaction output key range. For simplicity, it just check if there's any
// file on the penultimate level.
// file on the proximal level.
if (start_level == immutable_options.num_levels - 1 &&
(immutable_options.compaction_style != kCompactionStyleUniversal ||
!vstorage->LevelFiles(penultimate_level).empty())) {
!vstorage->LevelFiles(proximal_level).empty())) {
return kInvalidLevel;
}
@@ -1061,7 +1057,7 @@ int Compaction::EvaluatePenultimateLevel(
return kInvalidLevel;
}
return penultimate_level;
return proximal_level;
}
void Compaction::FilterInputsForCompactionIterator() {
+50 -43
View File
@@ -102,13 +102,13 @@ class Compaction {
BlobGarbageCollectionPolicy::kUseDefault,
double blob_garbage_collection_age_cutoff = -1);
// The type of the penultimate level output range
enum class PenultimateOutputRangeType : int {
kNotSupported, // it cannot output to the penultimate level
kFullRange, // any data could be output to the penultimate level
// The type of the proximal level output range
enum class ProximalOutputRangeType : int {
kNotSupported, // it cannot output to the proximal level
kFullRange, // any data could be output to the proximal level
kNonLastRange, // only the keys within non_last_level compaction inputs can
// be outputted to the penultimate level
kDisabled, // no data can be outputted to the penultimate level
// be outputted to the proximal level
kDisabled, // no data can be outputted to the proximal level
};
// No copying allowed
@@ -283,6 +283,13 @@ class Compaction {
// are non-overlapping and can be trivially moved.
bool is_trivial_move() const { return is_trivial_move_; }
bool is_trivial_copy_compaction() const {
return immutable_options_.compaction_style == kCompactionStyleFIFO &&
compaction_reason_ == CompactionReason::kChangeTemperature &&
mutable_cf_options_.compaction_options_fifo
.allow_trivial_copy_when_change_temperature;
}
// How many total levels are there?
int number_levels() const { return number_levels_; }
@@ -370,29 +377,29 @@ class Compaction {
Slice GetLargestUserKey() const { return largest_user_key_; }
PenultimateOutputRangeType GetPenultimateOutputRangeType() const {
return penultimate_output_range_type_;
ProximalOutputRangeType GetProximalOutputRangeType() const {
return proximal_output_range_type_;
}
// Return true if the compaction supports per_key_placement
bool SupportsPerKeyPlacement() const;
// Get per_key_placement penultimate output level, which is `last_level - 1`
// Get per_key_placement proximal output level, which is `last_level - 1`
// if per_key_placement feature is supported. Otherwise, return -1.
int GetPenultimateLevel() const;
int GetProximalLevel() const;
// Return true if the given range is overlap with penultimate level output
// Return true if the given range is overlap with proximal level output
// range.
// Both smallest_key and largest_key include timestamps if user-defined
// timestamp is enabled.
bool OverlapPenultimateLevelOutputRange(const Slice& smallest_key,
const Slice& largest_key) const;
bool OverlapProximalLevelOutputRange(const Slice& smallest_key,
const Slice& largest_key) const;
// For testing purposes, check that a key is within penultimate level
// For testing purposes, check that a key is within proximal level
// output range for per_key_placement feature, which is safe to place the key
// to the penultimate level. Different compaction strategies have different
// to the proximal level. Different compaction strategies have different
// rules. `user_key` includes timestamp if user-defined timestamp is enabled.
void TEST_AssertWithinPenultimateLevelOutputRange(
void TEST_AssertWithinProximalLevelOutputRange(
const Slice& user_key, bool expect_failure = false) const;
CompactionReason compaction_reason() const { return compaction_reason_; }
@@ -441,20 +448,20 @@ class Compaction {
static constexpr int kInvalidLevel = -1;
// Evaluate penultimate output level. If the compaction supports
// per_key_placement feature, it returns the penultimate level number.
// Evaluate proximal output level. If the compaction supports
// per_key_placement feature, it returns the proximal level number.
// Otherwise, it's set to kInvalidLevel (-1), which means
// output_to_penultimate_level is not supported.
// Note: even the penultimate level output is supported (PenultimateLevel !=
// output_to_proximal_level is not supported.
// Note: even the proximal level output is supported (ProximalLevel !=
// kInvalidLevel), some key range maybe unsafe to be outputted to the
// penultimate level. The safe key range is populated by
// `PopulatePenultimateLevelOutputRange()`.
// Which could potentially disable all penultimate level output.
static int EvaluatePenultimateLevel(
const VersionStorageInfo* vstorage,
const MutableCFOptions& mutable_cf_options,
const ImmutableOptions& immutable_options, const int start_level,
const int output_level);
// proximal level. The safe key range is populated by
// `PopulateProximalLevelOutputRange()`.
// Which could potentially disable all proximal level output.
static int EvaluateProximalLevel(const VersionStorageInfo* vstorage,
const MutableCFOptions& mutable_cf_options,
const ImmutableOptions& immutable_options,
const int start_level,
const int output_level);
// If some data cannot be safely migrated "up" the LSM tree due to a change
// in the preclude_last_level_data_seconds setting, this indicates a sequence
@@ -482,10 +489,10 @@ class Compaction {
InternalKey* smallest_key, InternalKey* largest_key,
int exclude_level = -1);
// populate penultimate level output range, which will be used to determine if
// a key is safe to output to the penultimate level (details see
// `Compaction::WithinPenultimateLevelOutputRange()`.
void PopulatePenultimateLevelOutputRange();
// populate proximal level output range, which will be used to determine if
// a key is safe to output to the proximal level (details see
// `Compaction::WithinProximalLevelOutputRange()`.
void PopulateProximalLevelOutputRange();
// If oldest snapshot is specified at Compaction construction time, we have
// an opportunity to optimize inputs for compaction iterator for this case:
@@ -616,20 +623,20 @@ class Compaction {
// only set when per_key_placement feature is enabled, -1 (kInvalidLevel)
// means not supported.
const int penultimate_level_;
const int proximal_level_;
// Key range for penultimate level output
// Key range for proximal level output
// includes timestamp if user-defined timestamp is enabled.
// penultimate_output_range_type_ shows the range type
InternalKey penultimate_level_smallest_;
InternalKey penultimate_level_largest_;
PenultimateOutputRangeType penultimate_output_range_type_ =
PenultimateOutputRangeType::kNotSupported;
// proximal_output_range_type_ shows the range type
InternalKey proximal_level_smallest_;
InternalKey proximal_level_largest_;
ProximalOutputRangeType proximal_output_range_type_ =
ProximalOutputRangeType::kNotSupported;
};
#ifndef NDEBUG
// Helper struct only for tests, which contains the data to decide if a key
// should be output to the penultimate level.
// should be output to the proximal level.
// TODO: remove this when the public feature knob is available
struct PerKeyPlacementContext {
const int level;
@@ -637,16 +644,16 @@ struct PerKeyPlacementContext {
const Slice value;
const SequenceNumber seq_num;
bool& output_to_penultimate_level;
bool& output_to_proximal_level;
PerKeyPlacementContext(int _level, Slice _key, Slice _value,
SequenceNumber _seq_num,
bool& _output_to_penultimate_level)
bool& _output_to_proximal_level)
: level(_level),
key(_key),
value(_value),
seq_num(_seq_num),
output_to_penultimate_level(_output_to_penultimate_level) {}
output_to_proximal_level(_output_to_proximal_level) {}
};
#endif /* !NDEBUG */
+278 -162
View File
@@ -109,16 +109,16 @@ const char* GetCompactionReasonString(CompactionReason compaction_reason) {
}
}
const char* GetCompactionPenultimateOutputRangeTypeString(
Compaction::PenultimateOutputRangeType range_type) {
const char* GetCompactionProximalOutputRangeTypeString(
Compaction::ProximalOutputRangeType range_type) {
switch (range_type) {
case Compaction::PenultimateOutputRangeType::kNotSupported:
case Compaction::ProximalOutputRangeType::kNotSupported:
return "NotSupported";
case Compaction::PenultimateOutputRangeType::kFullRange:
case Compaction::ProximalOutputRangeType::kFullRange:
return "FullRange";
case Compaction::PenultimateOutputRangeType::kNonLastRange:
case Compaction::ProximalOutputRangeType::kNonLastRange:
return "NonLastRange";
case Compaction::PenultimateOutputRangeType::kDisabled:
case Compaction::ProximalOutputRangeType::kDisabled:
return "Disabled";
default:
assert(false);
@@ -147,7 +147,7 @@ CompactionJob::CompactionJob(
BlobFileCompletionCallback* blob_callback, int* bg_compaction_scheduled,
int* bg_bottom_compaction_scheduled)
: compact_(new CompactionState(compaction)),
compaction_stats_(compaction->compaction_reason(), 1),
internal_stats_(compaction->compaction_reason(), 1),
db_options_(db_options),
mutable_db_options_copy_(mutable_db_options),
log_buffer_(log_buffer),
@@ -155,7 +155,7 @@ CompactionJob::CompactionJob(
stats_(stats),
bottommost_level_(false),
write_hint_(Env::WLTH_NOT_SET),
compaction_job_stats_(compaction_job_stats),
job_stats_(compaction_job_stats),
job_id_(job_id),
dbname_(dbname),
db_id_(db_id),
@@ -191,7 +191,7 @@ CompactionJob::CompactionJob(
extra_num_subcompaction_threads_reserved_(0),
bg_compaction_scheduled_(bg_compaction_scheduled),
bg_bottom_compaction_scheduled_(bg_bottom_compaction_scheduled) {
assert(compaction_job_stats_ != nullptr);
assert(job_stats_ != nullptr);
assert(log_buffer_ != nullptr);
const auto* cfd = compact_->compaction->column_family_data();
@@ -240,9 +240,8 @@ void CompactionJob::ReportStartedCompaction(Compaction* compaction) {
// to ensure GetThreadList() can always show them all together.
ThreadStatusUtil::SetThreadOperation(ThreadStatus::OP_COMPACTION);
compaction_job_stats_->is_manual_compaction =
compaction->is_manual_compaction();
compaction_job_stats_->is_full_compaction = compaction->is_full_compaction();
job_stats_->is_manual_compaction = compaction->is_manual_compaction();
job_stats_->is_full_compaction = compaction->is_full_compaction();
}
void CompactionJob::Prepare(
@@ -260,7 +259,8 @@ void CompactionJob::Prepare(
assert(storage_info);
assert(storage_info->NumLevelFiles(compact_->compaction->level()) > 0);
write_hint_ = storage_info->CalculateSSTWriteHint(c->output_level());
write_hint_ = storage_info->CalculateSSTWriteHint(
c->output_level(), db_options_.calculate_sst_write_lifetime_hint_set);
bottommost_level_ = c->bottommost_level();
if (!known_single_subcompact.has_value() && c->ShouldFormSubcompactions()) {
@@ -301,8 +301,7 @@ void CompactionJob::Prepare(
SequenceNumber preserve_time_min_seqno = kMaxSequenceNumber;
SequenceNumber preclude_last_level_min_seqno = kMaxSequenceNumber;
uint64_t preserve_time_duration =
std::max(c->mutable_cf_options().preserve_internal_time_seconds,
c->mutable_cf_options().preclude_last_level_data_seconds);
MinAndMaxPreserveSeconds(c->mutable_cf_options()).max_preserve_seconds;
if (preserve_time_duration > 0) {
const ReadOptions read_options(Env::IOActivity::kCompaction);
@@ -379,8 +378,8 @@ void CompactionJob::Prepare(
}
// Now combine what we would like to preclude from last level with what we
// can safely support without dangerously moving data back up the LSM tree,
// to get the final seqno threshold for penultimate vs. last. In particular,
// when the reserved output key range for the penultimate level does not
// to get the final seqno threshold for proximal vs. last. In particular,
// when the reserved output key range for the proximal level does not
// include the entire last level input key range, we need to keep entries
// already in the last level there. (Even allowing within-range entries to
// move back up could cause problems with range tombstones. Perhaps it
@@ -389,8 +388,8 @@ void CompactionJob::Prepare(
// tracking and complexity to CompactionIterator that is probably not
// worthwhile overall. Correctness is also more clear when splitting by
// seqno threshold.)
penultimate_after_seqno_ = std::max(preclude_last_level_min_seqno,
c->GetKeepInLastLevelThroughSeqno());
proximal_after_seqno_ = std::max(preclude_last_level_min_seqno,
c->GetKeepInLastLevelThroughSeqno());
options_file_number_ = versions_->options_file_number();
}
@@ -696,17 +695,17 @@ Status CompactionJob::Run() {
thread.join();
}
compaction_stats_.SetMicros(db_options_.clock->NowMicros() - start_micros);
internal_stats_.SetMicros(db_options_.clock->NowMicros() - start_micros);
for (auto& state : compact_->sub_compact_states) {
compaction_stats_.AddCpuMicros(state.compaction_job_stats.cpu_micros);
internal_stats_.AddCpuMicros(state.compaction_job_stats.cpu_micros);
state.RemoveLastEmptyOutput();
}
RecordTimeToHistogram(stats_, COMPACTION_TIME,
compaction_stats_.stats.micros);
internal_stats_.output_level_stats.micros);
RecordTimeToHistogram(stats_, COMPACTION_CPU_TIME,
compaction_stats_.stats.cpu_micros);
internal_stats_.output_level_stats.cpu_micros);
TEST_SYNC_POINT("CompactionJob::Run:BeforeVerify");
@@ -841,7 +840,6 @@ Status CompactionJob::Run() {
TEST_SYNC_POINT("CompactionJob::ReleaseSubcompactionResources:0");
TEST_SYNC_POINT("CompactionJob::ReleaseSubcompactionResources:1");
TablePropertiesCollection tp;
for (const auto& state : compact_->sub_compact_states) {
for (const auto& output : state.GetOutputs()) {
auto fn =
@@ -856,46 +854,67 @@ Status CompactionJob::Run() {
// compaction_service is set. We now know whether each sub_compaction was
// done remotely or not. Reset is_remote_compaction back to false and allow
// AggregateCompactionStats() to set the right value.
compaction_job_stats_->is_remote_compaction = false;
job_stats_->is_remote_compaction = false;
// Finish up all bookkeeping to unify the subcompaction results.
compact_->AggregateCompactionStats(compaction_stats_, *compaction_job_stats_);
compact_->AggregateCompactionStats(internal_stats_, *job_stats_);
uint64_t num_input_range_del = 0;
bool ok = UpdateCompactionStats(&num_input_range_del);
// (Sub)compactions returned ok, do sanity check on the number of input keys.
if (status.ok() && ok && compaction_job_stats_->has_num_input_records) {
size_t ts_sz = compact_->compaction->column_family_data()
->user_comparator()
->timestamp_size();
// When trim_ts_ is non-empty, CompactionIterator takes
// HistoryTrimmingIterator as input iterator and sees a trimmed view of
// input keys. So the number of keys it processed is not suitable for
// verification here.
// TODO: support verification when trim_ts_ is non-empty.
if (!(ts_sz > 0 && !trim_ts_.empty())) {
assert(compaction_stats_.stats.num_input_records > 0);
// TODO: verify the number of range deletion entries.
uint64_t expected =
compaction_stats_.stats.num_input_records - num_input_range_del;
uint64_t actual = compaction_job_stats_->num_input_records;
if (expected != actual) {
char scratch[2345];
compact_->compaction->Summary(scratch, sizeof(scratch));
std::string msg =
"Compaction number of input keys does not match "
"number of keys processed. Expected " +
std::to_string(expected) + " but processed " +
std::to_string(actual) + ". Compaction summary: " + scratch;
bool ok = BuildStatsFromInputTableProperties(&num_input_range_del);
// (Sub)compactions returned ok, do sanity check on the number of input
// keys.
if (status.ok() && ok) {
if (job_stats_->has_num_input_records) {
status = VerifyInputRecordCount(num_input_range_del);
if (!status.ok()) {
ROCKS_LOG_WARN(
db_options_.info_log, "[%s] [JOB %d] Compaction with status: %s",
compact_->compaction->column_family_data()->GetName().c_str(),
job_context_->job_id, msg.c_str());
if (db_options_.compaction_verify_record_count) {
status = Status::Corruption(msg);
}
job_context_->job_id, status.ToString().c_str());
}
}
UpdateCompactionJobInputStats(internal_stats_, num_input_range_del);
}
UpdateCompactionJobOutputStats(internal_stats_);
// Verify number of output records
// Only verify on table with format collects table properties
const auto& mutable_cf_options = compact_->compaction->mutable_cf_options();
if (status.ok() &&
(mutable_cf_options.table_factory->IsInstanceOf(
TableFactory::kBlockBasedTableName()) ||
mutable_cf_options.table_factory->IsInstanceOf(
TableFactory::kPlainTableName())) &&
db_options_.compaction_verify_record_count) {
uint64_t total_output_num = 0;
for (const auto& state : compact_->sub_compact_states) {
for (const auto& output : state.GetOutputs()) {
total_output_num += output.table_properties->num_entries -
output.table_properties->num_range_deletions;
}
}
uint64_t expected = internal_stats_.output_level_stats.num_output_records;
if (internal_stats_.has_proximal_level_output) {
expected += internal_stats_.proximal_level_stats.num_output_records;
}
if (expected != total_output_num) {
char scratch[2345];
compact_->compaction->Summary(scratch, sizeof(scratch));
std::string msg =
"Number of keys in compaction output SST files does not match "
"number of keys added. Expected " +
std::to_string(expected) + " but there are " +
std::to_string(total_output_num) +
" in output SST files. Compaction summary: " + scratch;
ROCKS_LOG_WARN(
db_options_.info_log, "[%s] [JOB %d] Compaction with status: %s",
compact_->compaction->column_family_data()->GetName().c_str(),
job_context_->job_id, msg.c_str());
status = Status::Corruption(msg);
}
}
RecordCompactionIOStats();
LogFlush(db_options_.info_log);
TEST_SYNC_POINT("CompactionJob::Run():End");
@@ -917,7 +936,7 @@ Status CompactionJob::Install(bool* compaction_released) {
int output_level = compact_->compaction->output_level();
cfd->internal_stats()->AddCompactionStats(output_level, thread_pri_,
compaction_stats_);
internal_stats_);
if (status.ok()) {
status = InstallCompactionResults(compaction_released);
@@ -928,7 +947,7 @@ Status CompactionJob::Install(bool* compaction_released) {
VersionStorageInfo::LevelSummaryStorage tmp;
auto vstorage = cfd->current()->storage_info();
const auto& stats = compaction_stats_.stats;
const auto& stats = internal_stats_.output_level_stats;
double read_write_amp = 0.0;
double write_amp = 0.0;
@@ -994,19 +1013,20 @@ Status CompactionJob::Install(bool* compaction_released) {
blob_files.back()->GetBlobFileNumber());
}
if (compaction_stats_.has_penultimate_level_output) {
ROCKS_LOG_BUFFER(
log_buffer_,
"[%s] has Penultimate Level output: %" PRIu64
", level %d, number of files: %" PRIu64 ", number of records: %" PRIu64,
column_family_name.c_str(),
compaction_stats_.penultimate_level_stats.bytes_written,
compact_->compaction->GetPenultimateLevel(),
compaction_stats_.penultimate_level_stats.num_output_files,
compaction_stats_.penultimate_level_stats.num_output_records);
if (internal_stats_.has_proximal_level_output) {
ROCKS_LOG_BUFFER(log_buffer_,
"[%s] has Proximal Level output: %" PRIu64
", level %d, number of files: %" PRIu64
", number of records: %" PRIu64,
column_family_name.c_str(),
internal_stats_.proximal_level_stats.bytes_written,
compact_->compaction->GetProximalLevel(),
internal_stats_.proximal_level_stats.num_output_files,
internal_stats_.proximal_level_stats.num_output_records);
}
UpdateCompactionJobStats(stats);
TEST_SYNC_POINT_CALLBACK(
"CompactionJob::Install:AfterUpdateCompactionJobStats", job_stats_);
auto stream = event_logger_->LogToBuffer(log_buffer_, 8192);
stream << "job" << job_id_ << "event" << "compaction_finished"
@@ -1028,17 +1048,16 @@ Status CompactionJob::Install(bool* compaction_released) {
<< CompressionTypeToString(compact_->compaction->output_compression());
stream << "num_single_delete_mismatches"
<< compaction_job_stats_->num_single_del_mismatch;
<< job_stats_->num_single_del_mismatch;
stream << "num_single_delete_fallthrough"
<< compaction_job_stats_->num_single_del_fallthru;
<< job_stats_->num_single_del_fallthru;
if (measure_io_stats_) {
stream << "file_write_nanos" << compaction_job_stats_->file_write_nanos;
stream << "file_range_sync_nanos"
<< compaction_job_stats_->file_range_sync_nanos;
stream << "file_fsync_nanos" << compaction_job_stats_->file_fsync_nanos;
stream << "file_write_nanos" << job_stats_->file_write_nanos;
stream << "file_range_sync_nanos" << job_stats_->file_range_sync_nanos;
stream << "file_fsync_nanos" << job_stats_->file_fsync_nanos;
stream << "file_prepare_write_nanos"
<< compaction_job_stats_->file_prepare_write_nanos;
<< job_stats_->file_prepare_write_nanos;
}
stream << "lsm_state";
@@ -1056,16 +1075,16 @@ Status CompactionJob::Install(bool* compaction_released) {
stream << "blob_file_tail" << blob_files.back()->GetBlobFileNumber();
}
if (compaction_stats_.has_penultimate_level_output) {
if (internal_stats_.has_proximal_level_output) {
InternalStats::CompactionStats& pl_stats =
compaction_stats_.penultimate_level_stats;
stream << "penultimate_level_num_output_files" << pl_stats.num_output_files;
stream << "penultimate_level_bytes_written" << pl_stats.bytes_written;
stream << "penultimate_level_num_output_records"
internal_stats_.proximal_level_stats;
stream << "proximal_level_num_output_files" << pl_stats.num_output_files;
stream << "proximal_level_bytes_written" << pl_stats.bytes_written;
stream << "proximal_level_num_output_records"
<< pl_stats.num_output_records;
stream << "penultimate_level_num_output_files_blob"
stream << "proximal_level_num_output_files_blob"
<< pl_stats.num_output_files_blob;
stream << "penultimate_level_bytes_written_blob"
stream << "proximal_level_bytes_written_blob"
<< pl_stats.bytes_written_blob;
}
@@ -1313,7 +1332,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
std::vector<std::string> blob_file_paths;
// TODO: BlobDB to support output_to_penultimate_level compaction, which needs
// TODO: BlobDB to support output_to_proximal_level compaction, which needs
// 2 builders, so may need to move to `CompactionOutputs`
std::unique_ptr<BlobFileBuilder> blob_file_builder(
(mutable_cf_options.enable_blob_files &&
@@ -1398,30 +1417,30 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
}
const auto& ikey = c_iter->ikey();
bool use_penultimate_output = ikey.sequence > penultimate_after_seqno_;
bool use_proximal_output = ikey.sequence > proximal_after_seqno_;
#ifndef NDEBUG
if (sub_compact->compaction->SupportsPerKeyPlacement()) {
// Could be overridden by unittest
PerKeyPlacementContext context(sub_compact->compaction->output_level(),
ikey.user_key, c_iter->value(),
ikey.sequence, use_penultimate_output);
ikey.sequence, use_proximal_output);
TEST_SYNC_POINT_CALLBACK("CompactionIterator::PrepareOutput.context",
&context);
if (use_penultimate_output) {
// Verify that entries sent to the penultimate level are within the
if (use_proximal_output) {
// Verify that entries sent to the proximal level are within the
// allowed range (because the input key range of the last level could
// be larger than the allowed output key range of the penultimate
// be larger than the allowed output key range of the proximal
// level). This check uses user keys (ignores sequence numbers) because
// compaction boundaries are a "clean cut" between user keys (see
// CompactionPicker::ExpandInputsToCleanCut()), which is especially
// important when preferred sequence numbers has been swapped in for
// kTypeValuePreferredSeqno / TimedPut.
sub_compact->compaction->TEST_AssertWithinPenultimateLevelOutputRange(
sub_compact->compaction->TEST_AssertWithinProximalLevelOutputRange(
c_iter->user_key());
}
} else {
assert(penultimate_after_seqno_ == kMaxSequenceNumber);
assert(!use_penultimate_output);
assert(proximal_after_seqno_ == kMaxSequenceNumber);
assert(!use_proximal_output);
}
#endif // NDEBUG
@@ -1430,7 +1449,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
// 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_penultimate_output,
status = sub_compact->AddToOutput(*c_iter, use_proximal_output,
open_file_func, close_file_func);
if (!status.ok()) {
break;
@@ -1642,15 +1661,15 @@ Status CompactionJob::FinishCompactionOutputFile(
std::pair<SequenceNumber, SequenceNumber> keep_seqno_range{
0, kMaxSequenceNumber};
if (sub_compact->compaction->SupportsPerKeyPlacement()) {
if (outputs.IsPenultimateLevel()) {
keep_seqno_range.first = penultimate_after_seqno_;
if (outputs.IsProximalLevel()) {
keep_seqno_range.first = proximal_after_seqno_;
} else {
keep_seqno_range.second = penultimate_after_seqno_;
keep_seqno_range.second = proximal_after_seqno_;
}
}
CompactionIterationStats range_del_out_stats;
// NOTE1: Use `bottommost_level_ = true` for both bottommost and
// output_to_penultimate_level compaction here, as it's only used to decide
// output_to_proximal_level compaction here, as it's only used to decide
// if range dels could be dropped. (Logically, we are taking a single sorted
// run returned from CompactionIterator and physically splitting it between
// two output levels.)
@@ -1813,22 +1832,22 @@ Status CompactionJob::InstallCompactionResults(bool* compaction_released) {
{
Compaction::InputLevelSummaryBuffer inputs_summary;
if (compaction_stats_.has_penultimate_level_output) {
if (internal_stats_.has_proximal_level_output) {
ROCKS_LOG_BUFFER(
log_buffer_,
"[%s] [JOB %d] Compacted %s => output_to_penultimate_level: %" PRIu64
"[%s] [JOB %d] Compacted %s => output_to_proximal_level: %" PRIu64
" bytes + last: %" PRIu64 " bytes. Total: %" PRIu64 " bytes",
compaction->column_family_data()->GetName().c_str(), job_id_,
compaction->InputLevelSummary(&inputs_summary),
compaction_stats_.penultimate_level_stats.bytes_written,
compaction_stats_.stats.bytes_written,
compaction_stats_.TotalBytesWritten());
internal_stats_.proximal_level_stats.bytes_written,
internal_stats_.output_level_stats.bytes_written,
internal_stats_.TotalBytesWritten());
} else {
ROCKS_LOG_BUFFER(log_buffer_,
"[%s] [JOB %d] Compacted %s => %" PRIu64 " bytes",
compaction->column_family_data()->GetName().c_str(),
job_id_, compaction->InputLevelSummary(&inputs_summary),
compaction_stats_.TotalBytesWritten());
internal_stats_.TotalBytesWritten());
}
}
@@ -1947,11 +1966,11 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
// Here last_level_temperature supersedes default_write_temperature, when
// enabled and applicable
if (last_level_temp != Temperature::kUnknown &&
sub_compact->compaction->is_last_level() &&
!outputs.IsPenultimateLevel()) {
sub_compact->compaction->is_last_level() && !outputs.IsProximalLevel()) {
temperature = last_level_temp;
}
fo_copy.temperature = temperature;
fo_copy.write_hint = write_hint_;
Status s;
IOStatus io_s = NewWritableFile(fs_.get(), fname, &writable_file, fo_copy);
@@ -2037,7 +2056,9 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
}
writable_file->SetIOPriority(GetRateLimiterPriority());
writable_file->SetWriteLifeTimeHint(write_hint_);
// Subsequent attempts to override the hint via SetWriteLifeTimeHint
// with the very same value will be ignored by the fs.
writable_file->SetWriteLifeTimeHint(fo_copy.write_hint);
FileTypeSet tmp_set = db_options_.checksum_handoff_file_types;
writable_file->SetPreallocationBlockSize(static_cast<size_t>(
sub_compact->compaction->OutputFilePreallocationSize()));
@@ -2062,7 +2083,7 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
bottommost_level_, TableFileCreationReason::kCompaction,
0 /* oldest_key_time */, current_time, db_id_, db_session_id_,
sub_compact->compaction->max_output_file_size(), file_number,
penultimate_after_seqno_ /*last_level_inclusive_max_seqno_threshold*/);
proximal_after_seqno_ /*last_level_inclusive_max_seqno_threshold*/);
outputs.NewBuilder(tboptions);
@@ -2086,12 +2107,13 @@ void CopyPrefix(const Slice& src, size_t prefix_length, std::string* dst) {
}
} // namespace
bool CompactionJob::UpdateCompactionStats(uint64_t* num_input_range_del) {
bool CompactionJob::BuildStatsFromInputTableProperties(
uint64_t* num_input_range_del) {
assert(compact_);
Compaction* compaction = compact_->compaction;
compaction_stats_.stats.num_input_files_in_non_output_levels = 0;
compaction_stats_.stats.num_input_files_in_output_level = 0;
internal_stats_.output_level_stats.num_input_files_in_non_output_levels = 0;
internal_stats_.output_level_stats.num_input_files_in_output_level = 0;
bool has_error = false;
const ReadOptions read_options(Env::IOActivity::kCompaction);
@@ -2103,13 +2125,14 @@ bool CompactionJob::UpdateCompactionStats(uint64_t* num_input_range_del) {
size_t num_input_files = flevel->num_files;
uint64_t* bytes_read;
if (compaction->level(input_level) != compaction->output_level()) {
compaction_stats_.stats.num_input_files_in_non_output_levels +=
internal_stats_.output_level_stats.num_input_files_in_non_output_levels +=
static_cast<int>(num_input_files);
bytes_read = &compaction_stats_.stats.bytes_read_non_output_levels;
bytes_read =
&internal_stats_.output_level_stats.bytes_read_non_output_levels;
} else {
compaction_stats_.stats.num_input_files_in_output_level +=
internal_stats_.output_level_stats.num_input_files_in_output_level +=
static_cast<int>(num_input_files);
bytes_read = &compaction_stats_.stats.bytes_read_output_level;
bytes_read = &internal_stats_.output_level_stats.bytes_read_output_level;
}
for (size_t i = 0; i < num_input_files; ++i) {
const FileMetaData* file_meta = flevel->files[i].file_metadata;
@@ -2129,7 +2152,8 @@ bool CompactionJob::UpdateCompactionStats(uint64_t* num_input_range_del) {
has_error = true;
}
}
compaction_stats_.stats.num_input_records += file_input_entries;
internal_stats_.output_level_stats.num_input_records +=
file_input_entries;
if (num_input_range_del) {
*num_input_range_del += file_num_range_del;
}
@@ -2140,62 +2164,122 @@ bool CompactionJob::UpdateCompactionStats(uint64_t* num_input_range_del) {
size_t num_filtered_input_files = filtered_flevel.size();
uint64_t* bytes_skipped;
if (compaction->level(input_level) != compaction->output_level()) {
compaction_stats_.stats.num_filtered_input_files_in_non_output_levels +=
internal_stats_.output_level_stats
.num_filtered_input_files_in_non_output_levels +=
static_cast<int>(num_filtered_input_files);
bytes_skipped = &compaction_stats_.stats.bytes_skipped_non_output_levels;
bytes_skipped =
&internal_stats_.output_level_stats.bytes_skipped_non_output_levels;
} else {
compaction_stats_.stats.num_filtered_input_files_in_output_level +=
internal_stats_.output_level_stats
.num_filtered_input_files_in_output_level +=
static_cast<int>(num_filtered_input_files);
bytes_skipped = &compaction_stats_.stats.bytes_skipped_output_level;
bytes_skipped =
&internal_stats_.output_level_stats.bytes_skipped_output_level;
}
for (const FileMetaData* filtered_file_meta : filtered_flevel) {
*bytes_skipped += filtered_file_meta->fd.GetFileSize();
}
}
assert(compaction_job_stats_);
compaction_stats_.stats.bytes_read_blob =
compaction_job_stats_->total_blob_bytes_read;
compaction_stats_.stats.num_dropped_records =
compaction_stats_.DroppedRecords();
// TODO - find a better place to set these two
assert(job_stats_);
internal_stats_.output_level_stats.bytes_read_blob =
job_stats_->total_blob_bytes_read;
internal_stats_.output_level_stats.num_dropped_records =
internal_stats_.DroppedRecords();
return !has_error;
}
void CompactionJob::UpdateCompactionJobStats(
const InternalStats::CompactionStats& stats) const {
compaction_job_stats_->elapsed_micros = stats.micros;
void CompactionJob::UpdateCompactionJobInputStats(
const InternalStats::CompactionStatsFull& internal_stats,
uint64_t num_input_range_del) const {
assert(job_stats_);
// input information
compaction_job_stats_->total_input_bytes =
stats.bytes_read_non_output_levels + stats.bytes_read_output_level;
compaction_job_stats_->num_input_records = stats.num_input_records;
compaction_job_stats_->num_input_files =
stats.num_input_files_in_non_output_levels +
stats.num_input_files_in_output_level;
compaction_job_stats_->num_input_files_at_output_level =
stats.num_input_files_in_output_level;
compaction_job_stats_->num_filtered_input_files =
stats.num_filtered_input_files_in_non_output_levels +
stats.num_filtered_input_files_in_output_level;
compaction_job_stats_->num_filtered_input_files_at_output_level =
stats.num_filtered_input_files_in_output_level;
compaction_job_stats_->total_skipped_input_bytes =
stats.bytes_skipped_non_output_levels + stats.bytes_skipped_output_level;
job_stats_->total_input_bytes =
internal_stats.output_level_stats.bytes_read_non_output_levels +
internal_stats.output_level_stats.bytes_read_output_level;
job_stats_->num_input_records =
internal_stats.output_level_stats.num_input_records - num_input_range_del;
job_stats_->num_input_files =
internal_stats.output_level_stats.num_input_files_in_non_output_levels +
internal_stats.output_level_stats.num_input_files_in_output_level;
job_stats_->num_input_files_at_output_level =
internal_stats.output_level_stats.num_input_files_in_output_level;
job_stats_->num_filtered_input_files =
internal_stats.output_level_stats
.num_filtered_input_files_in_non_output_levels +
internal_stats.output_level_stats
.num_filtered_input_files_in_output_level;
job_stats_->num_filtered_input_files_at_output_level =
internal_stats.output_level_stats
.num_filtered_input_files_in_output_level;
job_stats_->total_skipped_input_bytes =
internal_stats.output_level_stats.bytes_skipped_non_output_levels +
internal_stats.output_level_stats.bytes_skipped_output_level;
if (internal_stats.has_proximal_level_output) {
job_stats_->total_input_bytes +=
internal_stats.proximal_level_stats.bytes_read_non_output_levels +
internal_stats.proximal_level_stats.bytes_read_output_level;
job_stats_->num_input_records +=
internal_stats.proximal_level_stats.num_input_records;
job_stats_->num_input_files +=
internal_stats.proximal_level_stats
.num_input_files_in_non_output_levels +
internal_stats.proximal_level_stats.num_input_files_in_output_level;
job_stats_->num_input_files_at_output_level +=
internal_stats.proximal_level_stats.num_input_files_in_output_level;
job_stats_->num_filtered_input_files +=
internal_stats.proximal_level_stats
.num_filtered_input_files_in_non_output_levels +
internal_stats.proximal_level_stats
.num_filtered_input_files_in_output_level;
job_stats_->num_filtered_input_files_at_output_level +=
internal_stats.proximal_level_stats
.num_filtered_input_files_in_output_level;
job_stats_->total_skipped_input_bytes +=
internal_stats.proximal_level_stats.bytes_skipped_non_output_levels +
internal_stats.proximal_level_stats.bytes_skipped_output_level;
}
}
void CompactionJob::UpdateCompactionJobOutputStats(
const InternalStats::CompactionStatsFull& internal_stats) const {
assert(job_stats_);
job_stats_->elapsed_micros = internal_stats.output_level_stats.micros;
job_stats_->cpu_micros = internal_stats.output_level_stats.cpu_micros;
// output information
compaction_job_stats_->total_output_bytes = stats.bytes_written;
compaction_job_stats_->total_output_bytes_blob = stats.bytes_written_blob;
compaction_job_stats_->num_output_records = stats.num_output_records;
compaction_job_stats_->num_output_files = stats.num_output_files;
compaction_job_stats_->num_output_files_blob = stats.num_output_files_blob;
job_stats_->total_output_bytes =
internal_stats.output_level_stats.bytes_written;
job_stats_->total_output_bytes_blob =
internal_stats.output_level_stats.bytes_written_blob;
job_stats_->num_output_records =
internal_stats.output_level_stats.num_output_records;
job_stats_->num_output_files =
internal_stats.output_level_stats.num_output_files;
job_stats_->num_output_files_blob =
internal_stats.output_level_stats.num_output_files_blob;
if (stats.num_output_files > 0) {
if (internal_stats.has_proximal_level_output) {
job_stats_->total_output_bytes +=
internal_stats.proximal_level_stats.bytes_written;
job_stats_->total_output_bytes_blob +=
internal_stats.proximal_level_stats.bytes_written_blob;
job_stats_->num_output_records +=
internal_stats.proximal_level_stats.num_output_records;
job_stats_->num_output_files +=
internal_stats.proximal_level_stats.num_output_files;
job_stats_->num_output_files_blob +=
internal_stats.proximal_level_stats.num_output_files_blob;
}
if (job_stats_->num_output_files > 0) {
CopyPrefix(compact_->SmallestUserKey(),
CompactionJobStats::kMaxPrefixLength,
&compaction_job_stats_->smallest_output_key_prefix);
&job_stats_->smallest_output_key_prefix);
CopyPrefix(compact_->LargestUserKey(), CompactionJobStats::kMaxPrefixLength,
&compaction_job_stats_->largest_output_key_prefix);
&job_stats_->largest_output_key_prefix);
}
}
@@ -2216,8 +2300,8 @@ void CompactionJob::LogCompaction() {
cfd->GetName().c_str(), scratch);
// build event logger report
auto stream = event_logger_->Log();
stream << "job" << job_id_ << "event" << "compaction_started"
<< "compaction_reason"
stream << "job" << job_id_ << "event" << "compaction_started" << "cf_name"
<< cfd->GetName() << "compaction_reason"
<< GetCompactionReasonString(compaction->compaction_reason());
for (size_t i = 0; i < compaction->num_input_levels(); ++i) {
stream << ("files_L" + std::to_string(compaction->level(i)));
@@ -2233,19 +2317,19 @@ void CompactionJob::LogCompaction() {
? int64_t{-1} // Use -1 for "none"
: static_cast<int64_t>(existing_snapshots_[0]));
if (compaction->SupportsPerKeyPlacement()) {
stream << "prenultimate_after_seqno" << penultimate_after_seqno_;
stream << "proximal_after_seqno" << proximal_after_seqno_;
stream << "preserve_seqno_after" << preserve_seqno_after_;
stream << "penultimate_output_level" << compaction->GetPenultimateLevel();
stream << "penultimate_output_range"
<< GetCompactionPenultimateOutputRangeTypeString(
compaction->GetPenultimateOutputRangeType());
stream << "proximal_output_level" << compaction->GetProximalLevel();
stream << "proximal_output_range"
<< GetCompactionProximalOutputRangeTypeString(
compaction->GetProximalOutputRangeType());
if (compaction->GetPenultimateOutputRangeType() ==
Compaction::PenultimateOutputRangeType::kDisabled) {
if (compaction->GetProximalOutputRangeType() ==
Compaction::ProximalOutputRangeType::kDisabled) {
ROCKS_LOG_WARN(
db_options_.info_log,
"[%s] [JOB %d] Penultimate level output is disabled, likely "
"because of the range conflict in the penultimate level",
"[%s] [JOB %d] Proximal level output is disabled, likely "
"because of the range conflict in the proximal level",
cfd->GetName().c_str(), job_id_);
}
}
@@ -2270,4 +2354,36 @@ Env::IOPriority CompactionJob::GetRateLimiterPriority() {
return Env::IO_LOW;
}
Status CompactionJob::VerifyInputRecordCount(
uint64_t num_input_range_del) const {
size_t ts_sz = compact_->compaction->column_family_data()
->user_comparator()
->timestamp_size();
// When trim_ts_ is non-empty, CompactionIterator takes
// HistoryTrimmingIterator as input iterator and sees a trimmed view of
// input keys. So the number of keys it processed is not suitable for
// verification here.
// TODO: support verification when trim_ts_ is non-empty.
if (!(ts_sz > 0 && !trim_ts_.empty())) {
assert(internal_stats_.output_level_stats.num_input_records > 0);
// TODO: verify the number of range deletion entries.
uint64_t expected = internal_stats_.output_level_stats.num_input_records -
num_input_range_del;
uint64_t actual = job_stats_->num_input_records;
if (expected != actual) {
char scratch[2345];
compact_->compaction->Summary(scratch, sizeof(scratch));
std::string msg =
"Compaction number of input keys does not match "
"number of keys processed. Expected " +
std::to_string(expected) + " but processed " +
std::to_string(actual) + ". Compaction summary: " + scratch;
if (db_options_.compaction_verify_record_count) {
return Status::Corruption(msg);
}
}
}
return Status::OK();
}
} // namespace ROCKSDB_NAMESPACE
+73 -46
View File
@@ -67,7 +67,7 @@ class SubcompactionState;
// if needed.
//
// CompactionJob has 2 main stats:
// 1. CompactionJobStats compaction_job_stats_
// 1. CompactionJobStats job_stats_
// CompactionJobStats is a public data structure which is part of Compaction
// event listener that rocksdb share the job stats with the user.
// Internally it's an aggregation of all the compaction_job_stats from each
@@ -81,7 +81,7 @@ class SubcompactionState;
// +------------------------+ |
// | CompactionJob | | +------------------------+
// | | | | SubcompactionState |
// | compaction_job_stats +-----+ | |
// | job_stats +-----+ | |
// | | +--------->| compaction_job_stats |
// | | | | |
// +------------------------+ | +------------------------+
@@ -98,16 +98,13 @@ class SubcompactionState;
// +--------->+ |
// +------------------------+
//
// 2. CompactionStatsFull compaction_stats_
// 2. CompactionStatsFull internal_stats_
// `CompactionStatsFull` is an internal stats about the compaction, which
// is eventually sent to `ColumnFamilyData::internal_stats_` and used for
// logging and public metrics.
// Internally, it's an aggregation of stats_ from each `SubcompactionState`.
// It has 2 parts, normal stats about the main compaction information and
// the penultimate level output stats.
// `SubcompactionState` maintains the CompactionOutputs for normal output and
// the penultimate level output if exists, the per_level stats is
// stored with the outputs.
// It has 2 parts, ordinary output level stats and the proximal level output
// stats.
// +---------------------------+
// | SubcompactionState |
// | |
@@ -119,15 +116,15 @@ class SubcompactionState;
// | | |
// | | +----------------------+ |
// +--------------------------------+ | | | CompactionOutputs | |
// | CompactionJob | | | | (penultimate_level) | |
// | CompactionJob | | | | (proximal_level) | |
// | | +--------->| stats_ | |
// | compaction_stats_ | | | | +----------------------+ |
// | internal_stats_ | | | | +----------------------+ |
// | +-------------------------+ | | | | |
// | |stats (normal) |------|----+ +---------------------------+
// | |output_level_stats |------|----+ +---------------------------+
// | +-------------------------+ | | |
// | | | |
// | +-------------------------+ | | | +---------------------------+
// | |penultimate_level_stats +------+ | | SubcompactionState |
// | |proximal_level_stats |------+ | | SubcompactionState |
// | +-------------------------+ | | | | |
// | | | | | +----------------------+ |
// | | | | | | CompactionOutputs | |
@@ -137,7 +134,7 @@ class SubcompactionState;
// | | |
// | | +----------------------+ |
// | | | CompactionOutputs | |
// | | | (penultimate_level) | |
// | | | (proximal_level) | |
// +--------->| stats_ | |
// | +----------------------+ |
// | |
@@ -199,23 +196,9 @@ class CompactionJob {
IOStatus io_status() const { return io_status_; }
protected:
// Update the following stats in compaction_stats_.stats
// - num_input_files_in_non_output_levels
// - num_input_files_in_output_level
// - bytes_read_non_output_levels
// - bytes_read_output_level
// - num_input_records
// - bytes_read_blob
// - num_dropped_records
//
// @param num_input_range_del if non-null, will be set to the number of range
// deletion entries in this compaction input.
//
// Returns true iff compaction_stats_.stats.num_input_records and
// num_input_range_del are calculated successfully.
bool UpdateCompactionStats(uint64_t* num_input_range_del = nullptr);
virtual void UpdateCompactionJobStats(
const InternalStats::CompactionStats& stats) const;
void UpdateCompactionJobOutputStats(
const InternalStats::CompactionStatsFull& internal_stats) const;
void LogCompaction();
virtual void RecordCompactionIOStats();
void CleanupCompaction();
@@ -224,7 +207,7 @@ class CompactionJob {
void ProcessKeyValueCompaction(SubcompactionState* sub_compact);
CompactionState* compact_;
InternalStats::CompactionStatsFull compaction_stats_;
InternalStats::CompactionStatsFull internal_stats_;
const ImmutableDBOptions& db_options_;
const MutableDBOptions mutable_db_options_copy_;
LogBuffer* log_buffer_;
@@ -237,11 +220,37 @@ class CompactionJob {
IOStatus io_status_;
CompactionJobStats* compaction_job_stats_;
CompactionJobStats* job_stats_;
private:
friend class CompactionJobTestBase;
// Collect the following stats from Input Table Properties
// - num_input_files_in_non_output_levels
// - num_input_files_in_output_level
// - bytes_read_non_output_levels
// - bytes_read_output_level
// - num_input_records
// - bytes_read_blob
// - num_dropped_records
// and set them in internal_stats_.output_level_stats
//
// @param num_input_range_del if non-null, will be set to the number of range
// deletion entries in this compaction input.
//
// Returns true iff internal_stats_.output_level_stats.num_input_records and
// num_input_range_del are calculated successfully.
//
// This should be called only once for compactions (not per subcompaction)
bool BuildStatsFromInputTableProperties(
uint64_t* num_input_range_del = nullptr);
void UpdateCompactionJobInputStats(
const InternalStats::CompactionStatsFull& internal_stats,
uint64_t num_input_range_del) const;
Status VerifyInputRecordCount(uint64_t num_input_range_del) const;
// Generates a histogram representing potential divisions of key ranges from
// the input. It adds the starting and/or ending keys of certain input files
// to the working set and then finds the approximate size of data in between
@@ -363,8 +372,8 @@ class CompactionJob {
// Minimal sequence number to preclude the data from the last level. If the
// key has bigger (newer) sequence number than this, it will be precluded from
// the last level (output to penultimate level).
SequenceNumber penultimate_after_seqno_ = kMaxSequenceNumber;
// the last level (output to proximal level).
SequenceNumber proximal_after_seqno_ = kMaxSequenceNumber;
// Options File Number used for Remote Compaction
// Setting this requires DBMutex.
@@ -418,8 +427,9 @@ struct CompactionServiceInput {
// CompactionServiceOutputFile is the metadata for the output SST file
struct CompactionServiceOutputFile {
std::string file_name;
SequenceNumber smallest_seqno;
SequenceNumber largest_seqno;
uint64_t file_size{};
SequenceNumber smallest_seqno{};
SequenceNumber largest_seqno{};
std::string smallest_internal_key;
std::string largest_internal_key;
uint64_t oldest_ancester_time = kUnknownOldestAncesterTime;
@@ -427,21 +437,26 @@ struct CompactionServiceOutputFile {
uint64_t epoch_number = kUnknownEpochNumber;
std::string file_checksum = kUnknownFileChecksum;
std::string file_checksum_func_name = kUnknownFileChecksumFuncName;
uint64_t paranoid_hash;
uint64_t paranoid_hash{};
bool marked_for_compaction;
UniqueId64x2 unique_id{};
TableProperties table_properties;
bool is_proximal_level_output;
Temperature file_temperature = Temperature::kUnknown;
CompactionServiceOutputFile() = default;
CompactionServiceOutputFile(
const std::string& name, SequenceNumber smallest, SequenceNumber largest,
std::string _smallest_internal_key, std::string _largest_internal_key,
uint64_t _oldest_ancester_time, uint64_t _file_creation_time,
uint64_t _epoch_number, const std::string& _file_checksum,
const std::string& name, uint64_t size, SequenceNumber smallest,
SequenceNumber largest, std::string _smallest_internal_key,
std::string _largest_internal_key, uint64_t _oldest_ancester_time,
uint64_t _file_creation_time, uint64_t _epoch_number,
const std::string& _file_checksum,
const std::string& _file_checksum_func_name, uint64_t _paranoid_hash,
bool _marked_for_compaction, UniqueId64x2 _unique_id,
const TableProperties& _table_properties)
const TableProperties& _table_properties, bool _is_proximal_level_output,
Temperature _file_temperature)
: file_name(name),
file_size(size),
smallest_seqno(smallest),
largest_seqno(largest),
smallest_internal_key(std::move(_smallest_internal_key)),
@@ -454,7 +469,9 @@ struct CompactionServiceOutputFile {
paranoid_hash(_paranoid_hash),
marked_for_compaction(_marked_for_compaction),
unique_id(std::move(_unique_id)),
table_properties(_table_properties) {}
table_properties(_table_properties),
is_proximal_level_output(_is_proximal_level_output),
file_temperature(_file_temperature) {}
};
// CompactionServiceResult contains the compaction result from a different db
@@ -470,8 +487,21 @@ struct CompactionServiceResult {
uint64_t bytes_read = 0;
uint64_t bytes_written = 0;
// Job-level Compaction Stats.
//
// NOTE: Job level stats cannot be rebuilt from scratch by simply aggregating
// per-level stats due to some fields populated directly during compaction
// (e.g. RecordDroppedKeys()). This is why we need both job-level stats and
// per-level in the serialized result. If rebuilding job-level stats from
// per-level stats become possible in the future, consider deprecating this
// field.
CompactionJobStats stats;
// Per-level Compaction Stats for both output_level_stats and
// proximal_level_stats
InternalStats::CompactionStatsFull internal_stats;
// serialization interface to read and write the object
static Status Read(const std::string& data_str, CompactionServiceResult* obj);
Status Write(std::string* output);
@@ -517,9 +547,6 @@ class CompactionServiceCompactionJob : private CompactionJob {
protected:
void RecordCompactionIOStats() override;
void UpdateCompactionJobStats(
const InternalStats::CompactionStats& stats) const override;
private:
// Get table file name in output_path
std::string GetTableFileName(uint64_t file_number) override;
+9 -6
View File
@@ -1474,7 +1474,7 @@ TEST_F(CompactionJobTest, OldestBlobFileNumber) {
/* expected_oldest_blob_file_numbers */ {19});
}
TEST_F(CompactionJobTest, VerifyPenultimateLevelOutput) {
TEST_F(CompactionJobTest, VerifyProximalLevelOutput) {
cf_options_.last_level_temperature = Temperature::kCold;
SyncPoint::GetInstance()->SetCallBack(
"Compaction::SupportsPerKeyPlacement:Enabled", [&](void* arg) {
@@ -1487,8 +1487,7 @@ TEST_F(CompactionJobTest, VerifyPenultimateLevelOutput) {
SyncPoint::GetInstance()->SetCallBack(
"CompactionIterator::PrepareOutput.context", [&](void* arg) {
auto context = static_cast<PerKeyPlacementContext*>(arg);
context->output_to_penultimate_level =
context->seq_num > latest_cold_seq;
context->output_to_proximal_level = context->seq_num > latest_cold_seq;
});
SyncPoint::GetInstance()->EnableProcessing();
@@ -1534,11 +1533,11 @@ TEST_F(CompactionJobTest, VerifyPenultimateLevelOutput) {
/*verify_func=*/[&](Compaction& comp) {
for (char c = 'a'; c <= 'z'; c++) {
if (c == 'a') {
comp.TEST_AssertWithinPenultimateLevelOutputRange(
comp.TEST_AssertWithinProximalLevelOutputRange(
"a", true /*expect_failure*/);
} else {
std::string c_str{c};
comp.TEST_AssertWithinPenultimateLevelOutputRange(c_str);
comp.TEST_AssertWithinProximalLevelOutputRange(c_str);
}
}
});
@@ -1670,6 +1669,7 @@ TEST_F(CompactionJobTest, ResultSerialization) {
UniqueId64x2 id{rnd64.Uniform(UINT64_MAX), rnd64.Uniform(UINT64_MAX)};
result.output_files.emplace_back(
rnd.RandomString(rnd.Uniform(kStrMaxLen)) /* file_name */,
rnd64.Uniform(UINT64_MAX) /* file_size */,
rnd64.Uniform(UINT64_MAX) /* smallest_seqno */,
rnd64.Uniform(UINT64_MAX) /* largest_seqno */,
rnd.RandomBinaryString(
@@ -1682,7 +1682,8 @@ TEST_F(CompactionJobTest, ResultSerialization) {
file_checksum /* file_checksum */,
file_checksum_func_name /* file_checksum_func_name */,
rnd64.Uniform(UINT64_MAX) /* paranoid_hash */,
rnd.OneIn(2) /* marked_for_compaction */, id /* unique_id */, tp);
rnd.OneIn(2) /* marked_for_compaction */, id /* unique_id */, tp,
false /* is_proximal_level_output */, Temperature::kHot);
}
result.output_level = rnd.Uniform(10);
result.output_path = rnd.RandomString(rnd.Uniform(kStrMaxLen));
@@ -1736,6 +1737,8 @@ TEST_F(CompactionJobTest, ResultSerialization) {
ASSERT_EQ(deserialized_tmp.output_files[0].file_checksum, file_checksum);
ASSERT_EQ(deserialized_tmp.output_files[0].file_checksum_func_name,
file_checksum_func_name);
ASSERT_EQ(deserialized_tmp.output_files[0].file_temperature,
Temperature::kHot);
}
// Test unknown field
+4 -3
View File
@@ -54,7 +54,8 @@ Status CompactionOutputs::Finish(
}
current_output().finished = true;
stats_.bytes_written += current_bytes;
stats_.num_output_files = outputs_.size();
stats_.bytes_written_pre_comp += builder_->PreCompressionSize();
stats_.num_output_files = static_cast<int>(outputs_.size());
return s;
}
@@ -792,8 +793,8 @@ void CompactionOutputs::FillFilesToCutForTtl() {
}
CompactionOutputs::CompactionOutputs(const Compaction* compaction,
const bool is_penultimate_level)
: compaction_(compaction), is_penultimate_level_(is_penultimate_level) {
const bool is_proximal_level)
: compaction_(compaction), is_proximal_level_(is_proximal_level) {
partitioner_ = compaction->output_level() == 0
? nullptr
: compaction->CreateSstPartitioner();
+20 -21
View File
@@ -30,29 +30,32 @@ class CompactionOutputs {
// compaction output file
struct Output {
Output(FileMetaData&& _meta, const InternalKeyComparator& _icmp,
bool _enable_hash, bool _finished, uint64_t precalculated_hash)
bool _enable_hash, bool _finished, uint64_t precalculated_hash,
bool _is_proximal_level)
: meta(std::move(_meta)),
validator(_icmp, _enable_hash, precalculated_hash),
finished(_finished) {}
finished(_finished),
is_proximal_level(_is_proximal_level) {}
FileMetaData meta;
OutputValidator validator;
bool finished;
bool is_proximal_level;
std::shared_ptr<const TableProperties> table_properties;
};
CompactionOutputs() = delete;
explicit CompactionOutputs(const Compaction* compaction,
const bool is_penultimate_level);
const bool is_proximal_level);
bool IsPenultimateLevel() const { return is_penultimate_level_; }
bool IsProximalLevel() const { return is_proximal_level_; }
// Add generated output to the list
void AddOutput(FileMetaData&& meta, const InternalKeyComparator& icmp,
bool enable_hash, bool finished = false,
uint64_t precalculated_hash = 0) {
outputs_.emplace_back(std::move(meta), icmp, enable_hash, finished,
precalculated_hash);
precalculated_hash, is_proximal_level_);
}
// Set new table builder for the current output
@@ -63,34 +66,29 @@ class CompactionOutputs {
file_writer_.reset(writer);
}
// TODO: Remove it when remote compaction support tiered compaction
void AddBytesWritten(uint64_t bytes) { stats_.bytes_written += bytes; }
void SetNumOutputRecords(uint64_t num) { stats_.num_output_records = num; }
void SetNumOutputFiles(uint64_t num) { stats_.num_output_files = num; }
// TODO: Move the BlobDB builder into CompactionOutputs
const std::vector<BlobFileAddition>& GetBlobFileAdditions() const {
if (is_penultimate_level_) {
if (is_proximal_level_) {
assert(blob_file_additions_.empty());
}
return blob_file_additions_;
}
std::vector<BlobFileAddition>* GetBlobFileAdditionsPtr() {
assert(!is_penultimate_level_);
assert(!is_proximal_level_);
return &blob_file_additions_;
}
bool HasBlobFileAdditions() const { return !blob_file_additions_.empty(); }
BlobGarbageMeter* CreateBlobGarbageMeter() {
assert(!is_penultimate_level_);
assert(!is_proximal_level_);
blob_garbage_meter_ = std::make_unique<BlobGarbageMeter>();
return blob_garbage_meter_.get();
}
BlobGarbageMeter* GetBlobGarbageMeter() const {
if (is_penultimate_level_) {
if (is_proximal_level_) {
// blobdb doesn't support per_key_placement yet
assert(blob_garbage_meter_ == nullptr);
return nullptr;
@@ -99,8 +97,9 @@ class CompactionOutputs {
}
void UpdateBlobStats() {
assert(!is_penultimate_level_);
stats_.num_output_files_blob = blob_file_additions_.size();
assert(!is_proximal_level_);
stats_.num_output_files_blob =
static_cast<int>(blob_file_additions_.size());
for (const auto& blob : blob_file_additions_) {
stats_.bytes_written_blob += blob.GetTotalBlobBytes();
}
@@ -304,12 +303,12 @@ class CompactionOutputs {
std::vector<BlobFileAddition> blob_file_additions_;
std::unique_ptr<BlobGarbageMeter> blob_garbage_meter_;
// Basic compaction output stats for this level's outputs
InternalStats::CompactionOutputsStats stats_;
// Per level's output stat
InternalStats::CompactionStats stats_;
// indicate if this CompactionOutputs obj for penultimate_level, should always
// indicate if this CompactionOutputs obj for proximal_level, should always
// be false if per_key_placement feature is not enabled.
const bool is_penultimate_level_;
const bool is_proximal_level_;
// partitioner information
std::string last_key_for_partitioner_;
@@ -363,7 +362,7 @@ class CompactionOutputs {
std::vector<size_t> level_ptrs_;
};
// helper struct to concatenate the last level and penultimate level outputs
// helper struct to concatenate the last level and proximal level outputs
// which could be replaced by std::ranges::join_view() in c++20
struct OutputIterator {
public:
+19 -19
View File
@@ -272,8 +272,8 @@ bool CompactionPicker::RangeOverlapWithCompaction(
return true;
}
if (c->SupportsPerKeyPlacement()) {
if (c->OverlapPenultimateLevelOutputRange(smallest_user_key,
largest_user_key)) {
if (c->OverlapProximalLevelOutputRange(smallest_user_key,
largest_user_key)) {
return true;
}
}
@@ -284,7 +284,7 @@ bool CompactionPicker::RangeOverlapWithCompaction(
bool CompactionPicker::FilesRangeOverlapWithCompaction(
const std::vector<CompactionInputFiles>& inputs, int level,
int penultimate_level) const {
int proximal_level) const {
bool is_empty = true;
for (auto& in : inputs) {
if (!in.empty()) {
@@ -301,18 +301,18 @@ bool CompactionPicker::FilesRangeOverlapWithCompaction(
// files cannot be overlapped in the order of L0 files.
InternalKey smallest, largest;
GetRange(inputs, &smallest, &largest, Compaction::kInvalidLevel);
if (penultimate_level != Compaction::kInvalidLevel) {
if (proximal_level != Compaction::kInvalidLevel) {
if (ioptions_.compaction_style == kCompactionStyleUniversal) {
if (RangeOverlapWithCompaction(smallest.user_key(), largest.user_key(),
penultimate_level)) {
proximal_level)) {
return true;
}
} else {
InternalKey penultimate_smallest, penultimate_largest;
GetRange(inputs, &penultimate_smallest, &penultimate_largest, level);
if (RangeOverlapWithCompaction(penultimate_smallest.user_key(),
penultimate_largest.user_key(),
penultimate_level)) {
InternalKey proximal_smallest, proximal_largest;
GetRange(inputs, &proximal_smallest, &proximal_largest, level);
if (RangeOverlapWithCompaction(proximal_smallest.user_key(),
proximal_largest.user_key(),
proximal_level)) {
return true;
}
}
@@ -353,7 +353,7 @@ Compaction* CompactionPicker::CompactFiles(
}
assert(output_level == 0 || !FilesRangeOverlapWithCompaction(
input_files, output_level,
Compaction::EvaluatePenultimateLevel(
Compaction::EvaluateProximalLevel(
vstorage, mutable_cf_options, ioptions_,
start_level, output_level)));
#endif /* !NDEBUG */
@@ -659,9 +659,9 @@ Compaction* CompactionPicker::CompactRange(
// overlaping outputs in the same level.
if (FilesRangeOverlapWithCompaction(
inputs, output_level,
Compaction::EvaluatePenultimateLevel(vstorage, mutable_cf_options,
ioptions_, start_level,
output_level))) {
Compaction::EvaluateProximalLevel(vstorage, mutable_cf_options,
ioptions_, start_level,
output_level))) {
// This compaction output could potentially conflict with the output
// of a currently running compaction, we cannot run it.
*manual_conflict = true;
@@ -848,9 +848,9 @@ Compaction* CompactionPicker::CompactRange(
// overlaping outputs in the same level.
if (FilesRangeOverlapWithCompaction(
compaction_inputs, output_level,
Compaction::EvaluatePenultimateLevel(vstorage, mutable_cf_options,
ioptions_, input_level,
output_level))) {
Compaction::EvaluateProximalLevel(vstorage, mutable_cf_options,
ioptions_, input_level,
output_level))) {
// This compaction output could potentially conflict with the output
// of a currently running compaction, we cannot run it.
*manual_conflict = true;
@@ -1137,7 +1137,7 @@ Status CompactionPicker::SanitizeAndConvertCompactionInputFiles(
if (output_level != 0 &&
FilesRangeOverlapWithCompaction(
*converted_input_files, output_level,
Compaction::EvaluatePenultimateLevel(
Compaction::EvaluateProximalLevel(
version->storage_info(), version->GetMutableCFOptions(),
ioptions_, (*converted_input_files)[0].level, output_level))) {
return Status::Aborted(
@@ -1154,7 +1154,7 @@ void CompactionPicker::RegisterCompaction(Compaction* c) {
assert(ioptions_.compaction_style != kCompactionStyleLevel ||
c->output_level() == 0 ||
!FilesRangeOverlapWithCompaction(*c->inputs(), c->output_level(),
c->GetPenultimateLevel()));
c->GetProximalLevel()));
// CompactionReason::kExternalSstIngestion's start level is just a placeholder
// number without actual meaning as file ingestion technically does not have
// an input level like other compactions
+7 -1
View File
@@ -138,6 +138,12 @@ class CompactionPicker {
return !level0_compactions_in_progress_.empty();
}
// Is any compaction in progress
bool IsCompactionInProgress() const {
return !(level0_compactions_in_progress_.empty() &&
compactions_in_progress_.empty());
}
// Return true if the passed key range overlap with a compaction output
// that is currently running.
bool RangeOverlapWithCompaction(const Slice& smallest_user_key,
@@ -190,7 +196,7 @@ class CompactionPicker {
// key range of a currently running compaction.
bool FilesRangeOverlapWithCompaction(
const std::vector<CompactionInputFiles>& inputs, int level,
int penultimate_level) const;
int proximal_level) const;
bool SetupOtherInputs(const std::string& cf_name,
const MutableCFOptions& mutable_cf_options,
+9 -9
View File
@@ -414,9 +414,9 @@ void LevelCompactionBuilder::SetupOtherFilesWithRoundRobinExpansion() {
&tmp_start_level_inputs) ||
compaction_picker_->FilesRangeOverlapWithCompaction(
{tmp_start_level_inputs}, output_level_,
Compaction::EvaluatePenultimateLevel(vstorage_, mutable_cf_options_,
ioptions_, start_level_,
output_level_))) {
Compaction::EvaluateProximalLevel(vstorage_, mutable_cf_options_,
ioptions_, start_level_,
output_level_))) {
// Constraint 1a
tmp_start_level_inputs.clear();
return;
@@ -490,9 +490,9 @@ bool LevelCompactionBuilder::SetupOtherInputsIfNeeded() {
// We need to disallow this from happening.
if (compaction_picker_->FilesRangeOverlapWithCompaction(
compaction_inputs_, output_level_,
Compaction::EvaluatePenultimateLevel(vstorage_, mutable_cf_options_,
ioptions_, start_level_,
output_level_))) {
Compaction::EvaluateProximalLevel(vstorage_, mutable_cf_options_,
ioptions_, start_level_,
output_level_))) {
// This compaction output could potentially conflict with the output
// of a currently running compaction, we cannot run it.
return false;
@@ -846,9 +846,9 @@ bool LevelCompactionBuilder::PickFileToCompact() {
&start_level_inputs_) ||
compaction_picker_->FilesRangeOverlapWithCompaction(
{start_level_inputs_}, output_level_,
Compaction::EvaluatePenultimateLevel(vstorage_, mutable_cf_options_,
ioptions_, start_level_,
output_level_))) {
Compaction::EvaluateProximalLevel(vstorage_, mutable_cf_options_,
ioptions_, start_level_,
output_level_))) {
// A locked (pending compaction) input-level file was pulled in due to
// user-key overlap.
start_level_inputs_.clear();
+51 -34
View File
@@ -1134,10 +1134,15 @@ TEST_F(CompactionPickerTest, FIFOToCold1) {
fifo_options_.max_table_files_size = kMaxSize;
fifo_options_.file_temperature_age_thresholds = {
{Temperature::kCold, kColdThreshold}};
fifo_options_.allow_trivial_copy_when_change_temperature = true;
fifo_options_.trivial_copy_buffer_size = 16 * 1024 * 1024;
mutable_cf_options_.compaction_options_fifo = fifo_options_;
mutable_cf_options_.level0_file_num_compaction_trigger = 100;
mutable_cf_options_.max_compaction_bytes = kFileSize * 100;
FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_);
auto copiedIOptions = ioptions_;
copiedIOptions.compaction_style = kCompactionStyleFIFO;
FIFOCompactionPicker fifo_compaction_picker(copiedIOptions, &icmp_);
int64_t current_time = 0;
ASSERT_OK(Env::Default()->GetCurrentTime(&current_time));
@@ -1186,7 +1191,10 @@ TEST_F(CompactionPickerTest, FIFOToColdMaxCompactionSize) {
mutable_cf_options_.compaction_options_fifo = fifo_options_;
mutable_cf_options_.level0_file_num_compaction_trigger = 100;
mutable_cf_options_.max_compaction_bytes = kFileSize * 9;
FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_);
auto copiedIOptions = ioptions_;
copiedIOptions.compaction_style = kCompactionStyleFIFO;
FIFOCompactionPicker fifo_compaction_picker(copiedIOptions, &icmp_);
int64_t current_time = 0;
ASSERT_OK(Env::Default()->GetCurrentTime(&current_time));
@@ -1253,7 +1261,10 @@ TEST_F(CompactionPickerTest, FIFOToColdWithExistingCold) {
mutable_cf_options_.compaction_options_fifo = fifo_options_;
mutable_cf_options_.level0_file_num_compaction_trigger = 100;
mutable_cf_options_.max_compaction_bytes = kFileSize * 100;
FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_);
auto copiedIOptions = ioptions_;
copiedIOptions.compaction_style = kCompactionStyleFIFO;
FIFOCompactionPicker fifo_compaction_picker(copiedIOptions, &icmp_);
int64_t current_time = 0;
ASSERT_OK(Env::Default()->GetCurrentTime(&current_time));
@@ -1318,7 +1329,10 @@ TEST_F(CompactionPickerTest, FIFOToColdWithHotBetweenCold) {
mutable_cf_options_.compaction_options_fifo = fifo_options_;
mutable_cf_options_.level0_file_num_compaction_trigger = 100;
mutable_cf_options_.max_compaction_bytes = kFileSize * 100;
FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_);
auto copiedIOptions = ioptions_;
copiedIOptions.compaction_style = kCompactionStyleFIFO;
FIFOCompactionPicker fifo_compaction_picker(copiedIOptions, &icmp_);
int64_t current_time = 0;
ASSERT_OK(Env::Default()->GetCurrentTime(&current_time));
@@ -1385,7 +1399,10 @@ TEST_F(CompactionPickerTest, FIFOToHotAndWarm) {
mutable_cf_options_.compaction_options_fifo = fifo_options_;
mutable_cf_options_.level0_file_num_compaction_trigger = 100;
mutable_cf_options_.max_compaction_bytes = kFileSize * 100;
FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_);
auto copiedIOptions = ioptions_;
copiedIOptions.compaction_style = kCompactionStyleFIFO;
FIFOCompactionPicker fifo_compaction_picker(copiedIOptions, &icmp_);
int64_t current_time = 0;
ASSERT_OK(Env::Default()->GetCurrentTime(&current_time));
@@ -3677,7 +3694,7 @@ TEST_F(CompactionPickerTest, UniversalSizeRatioTierCompactionLastLevel) {
const uint64_t kFileSize = 100000;
const int kNumLevels = 7;
const int kLastLevel = kNumLevels - 1;
const int kPenultimateLevel = kLastLevel - 1;
const int kProximalLevel = kLastLevel - 1;
ioptions_.compaction_style = kCompactionStyleUniversal;
mutable_cf_options_.preclude_last_level_data_seconds = 1000;
@@ -3702,14 +3719,14 @@ TEST_F(CompactionPickerTest, UniversalSizeRatioTierCompactionLastLevel) {
// Here to make sure it's size ratio compaction instead of size amp
ASSERT_EQ(compaction->compaction_reason(),
CompactionReason::kUniversalSizeRatio);
ASSERT_EQ(compaction->output_level(), kPenultimateLevel - 1);
ASSERT_EQ(compaction->output_level(), kProximalLevel - 1);
ASSERT_EQ(compaction->input_levels(0)->num_files, 2);
ASSERT_EQ(compaction->input_levels(5)->num_files, 0);
ASSERT_EQ(compaction->input_levels(6)->num_files, 0);
}
TEST_F(CompactionPickerTest, UniversalSizeAmpTierCompactionNotSuport) {
// Tiered compaction only support level_num > 2 (otherwise the penultimate
// Tiered compaction only support level_num > 2 (otherwise the proximal
// level is going to be level 0, which may make thing more complicated), so
// when there's only 2 level, still treating level 1 as the last level for
// size amp compaction
@@ -3753,7 +3770,7 @@ TEST_F(CompactionPickerTest, UniversalSizeAmpTierCompactionLastLevel) {
const uint64_t kFileSize = 100000;
const int kNumLevels = 7;
const int kLastLevel = kNumLevels - 1;
const int kPenultimateLevel = kLastLevel - 1;
const int kProximalLevel = kLastLevel - 1;
ioptions_.compaction_style = kCompactionStyleUniversal;
mutable_cf_options_.preclude_last_level_data_seconds = 1000;
@@ -3775,10 +3792,10 @@ TEST_F(CompactionPickerTest, UniversalSizeAmpTierCompactionLastLevel) {
vstorage_.get(), &log_buffer_));
// It's a Size Amp compaction, but doesn't include the last level file and
// output to the penultimate level.
// output to the proximal level.
ASSERT_EQ(compaction->compaction_reason(),
CompactionReason::kUniversalSizeAmplification);
ASSERT_EQ(compaction->output_level(), kPenultimateLevel);
ASSERT_EQ(compaction->output_level(), kProximalLevel);
ASSERT_EQ(compaction->input_levels(0)->num_files, 2);
ASSERT_EQ(compaction->input_levels(5)->num_files, 1);
ASSERT_EQ(compaction->input_levels(6)->num_files, 0);
@@ -3940,7 +3957,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest, OverlapWithNormalCompaction) {
ASSERT_EQ(enable_per_key_placement_,
level_compaction_picker.FilesRangeOverlapWithCompaction(
input_files, 6,
Compaction::EvaluatePenultimateLevel(
Compaction::EvaluateProximalLevel(
vstorage_.get(), mutable_cf_options_, ioptions_, 0, 6)));
}
@@ -4028,7 +4045,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
ASSERT_EQ(enable_per_key_placement_,
universal_compaction_picker.FilesRangeOverlapWithCompaction(
input_files, 6,
Compaction::EvaluatePenultimateLevel(
Compaction::EvaluateProximalLevel(
vstorage_.get(), mutable_cf_options_, ioptions_, 0, 6)));
}
@@ -4076,9 +4093,9 @@ TEST_P(PerKeyPlacementCompactionPickerTest, NormalCompactionOverlapUniversal) {
input_files, 5, Compaction::kInvalidLevel));
}
TEST_P(PerKeyPlacementCompactionPickerTest, PenultimateOverlapUniversal) {
TEST_P(PerKeyPlacementCompactionPickerTest, ProximalOverlapUniversal) {
// This test is make sure the Tiered compaction would lock whole range of
// both output level and penultimate level
// both output level and proximal level
if (enable_per_key_placement_) {
mutable_cf_options_.preclude_last_level_data_seconds = 10000;
}
@@ -4098,7 +4115,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest, PenultimateOverlapUniversal) {
UpdateVersionStorageInfo();
// the existing compaction is the 1st L4 file + L6 file
// then compaction of the 2nd L4 file to L5 (penultimate level) is overlapped
// then compaction of the 2nd L4 file to L5 (proximal level) is overlapped
// when the tiered compaction feature is on.
CompactionOptions comp_options;
std::unordered_set<uint64_t> input_set;
@@ -4187,9 +4204,9 @@ TEST_P(PerKeyPlacementCompactionPickerTest, LastLevelOnlyOverlapUniversal) {
}
TEST_P(PerKeyPlacementCompactionPickerTest,
LastLevelOnlyFailPenultimateUniversal) {
LastLevelOnlyFailProximalUniversal) {
// This is to test last_level only compaction still unable to do the
// penultimate level compaction if there's already a file in the penultimate
// proximal level compaction if there's already a file in the proximal
// level.
// This should rarely happen in universal compaction, as the non-empty L5
// should be included in the compaction.
@@ -4222,9 +4239,9 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
mutable_db_options_, 0));
ASSERT_TRUE(comp1);
ASSERT_EQ(comp1->GetPenultimateLevel(), Compaction::kInvalidLevel);
ASSERT_EQ(comp1->GetProximalLevel(), Compaction::kInvalidLevel);
// As comp1 cannot be output to the penultimate level, compacting file 40 to
// As comp1 cannot be output to the proximal level, compacting file 40 to
// L5 is always safe.
input_set.clear();
input_files.clear();
@@ -4239,14 +4256,14 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
comp_options, input_files, 5, vstorage_.get(), mutable_cf_options_,
mutable_db_options_, 0));
ASSERT_TRUE(comp2);
ASSERT_EQ(Compaction::kInvalidLevel, comp2->GetPenultimateLevel());
ASSERT_EQ(Compaction::kInvalidLevel, comp2->GetProximalLevel());
}
TEST_P(PerKeyPlacementCompactionPickerTest,
LastLevelOnlyConflictWithOngoingUniversal) {
// This is to test last_level only compaction still unable to do the
// penultimate level compaction if there's already an ongoing compaction to
// the penultimate level
// proximal level compaction if there's already an ongoing compaction to
// the proximal level
if (enable_per_key_placement_) {
mutable_cf_options_.preclude_last_level_data_seconds = 10000;
}
@@ -4265,7 +4282,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
Add(6, 60U, "101", "351", 60000000U);
UpdateVersionStorageInfo();
// create an ongoing compaction to L5 (penultimate level)
// create an ongoing compaction to L5 (proximal level)
CompactionOptions comp_options;
std::unordered_set<uint64_t> input_set;
input_set.insert(40);
@@ -4278,7 +4295,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
mutable_db_options_, 0));
ASSERT_TRUE(comp1);
ASSERT_EQ(comp1->GetPenultimateLevel(), Compaction::kInvalidLevel);
ASSERT_EQ(comp1->GetProximalLevel(), Compaction::kInvalidLevel);
input_set.clear();
input_files.clear();
@@ -4289,7 +4306,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
ASSERT_EQ(enable_per_key_placement_,
universal_compaction_picker.FilesRangeOverlapWithCompaction(
input_files, 6,
Compaction::EvaluatePenultimateLevel(
Compaction::EvaluateProximalLevel(
vstorage_.get(), mutable_cf_options_, ioptions_, 6, 6)));
if (!enable_per_key_placement_) {
@@ -4297,7 +4314,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
mutable_db_options_, 0));
ASSERT_TRUE(comp2);
ASSERT_EQ(Compaction::kInvalidLevel, comp2->GetPenultimateLevel());
ASSERT_EQ(Compaction::kInvalidLevel, comp2->GetProximalLevel());
}
}
@@ -4306,7 +4323,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
// This is similar to `LastLevelOnlyConflictWithOngoingUniversal`, the only
// change is the ongoing compaction to L5 has no overlap with the last level
// compaction, so it's safe to move data from the last level to the
// penultimate level.
// proximal level.
if (enable_per_key_placement_) {
mutable_cf_options_.preclude_last_level_data_seconds = 10000;
}
@@ -4325,7 +4342,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
Add(6, 60U, "101", "351", 60000000U);
UpdateVersionStorageInfo();
// create an ongoing compaction to L5 (penultimate level)
// create an ongoing compaction to L5 (proximal level)
CompactionOptions comp_options;
std::unordered_set<uint64_t> input_set;
input_set.insert(42);
@@ -4338,7 +4355,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
mutable_db_options_, 0));
ASSERT_TRUE(comp1);
ASSERT_EQ(comp1->GetPenultimateLevel(), Compaction::kInvalidLevel);
ASSERT_EQ(comp1->GetProximalLevel(), Compaction::kInvalidLevel);
input_set.clear();
input_files.clear();
@@ -4349,8 +4366,8 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
// always safe to move data up
ASSERT_FALSE(universal_compaction_picker.FilesRangeOverlapWithCompaction(
input_files, 6,
Compaction::EvaluatePenultimateLevel(vstorage_.get(), mutable_cf_options_,
ioptions_, 6, 6)));
Compaction::EvaluateProximalLevel(vstorage_.get(), mutable_cf_options_,
ioptions_, 6, 6)));
// 2 compactions can be run in parallel
std::unique_ptr<Compaction> comp2(universal_compaction_picker.CompactFiles(
@@ -4358,9 +4375,9 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
mutable_db_options_, 0));
ASSERT_TRUE(comp2);
if (enable_per_key_placement_) {
ASSERT_NE(Compaction::kInvalidLevel, comp2->GetPenultimateLevel());
ASSERT_NE(Compaction::kInvalidLevel, comp2->GetProximalLevel());
} else {
ASSERT_EQ(Compaction::kInvalidLevel, comp2->GetPenultimateLevel());
ASSERT_EQ(Compaction::kInvalidLevel, comp2->GetProximalLevel());
}
}
+14 -27
View File
@@ -288,7 +288,9 @@ class UniversalCompactionBuilder {
// and the index of the file in that level
struct InputFileInfo {
InputFileInfo() : f(nullptr), level(0), index(0) {}
InputFileInfo() : InputFileInfo(nullptr, 0, 0) {}
InputFileInfo(FileMetaData* file_meta, size_t l, size_t i)
: f(file_meta), level(l), index(i) {}
FileMetaData* f;
size_t level;
@@ -321,22 +323,14 @@ SmallestKeyHeap create_level_heap(Compaction* c, const Comparator* ucmp) {
SmallestKeyHeap smallest_key_priority_q =
SmallestKeyHeap(SmallestKeyHeapComparator(ucmp));
InputFileInfo input_file;
for (size_t l = 0; l < c->num_input_levels(); l++) {
if (c->num_input_files(l) != 0) {
if (l == 0 && c->start_level() == 0) {
for (size_t i = 0; i < c->num_input_files(0); i++) {
input_file.f = c->input(0, i);
input_file.level = 0;
input_file.index = i;
smallest_key_priority_q.push(std::move(input_file));
smallest_key_priority_q.emplace(c->input(0, i), 0, i);
}
} else {
input_file.f = c->input(l, 0);
input_file.level = l;
input_file.index = 0;
smallest_key_priority_q.push(std::move(input_file));
smallest_key_priority_q.emplace(c->input(l, 0), l, 0);
}
}
}
@@ -374,7 +368,7 @@ bool UniversalCompactionBuilder::IsInputFilesNonOverlapping(Compaction* c) {
auto comparator = icmp_->user_comparator();
int first_iter = 1;
InputFileInfo prev, curr, next;
InputFileInfo prev, curr;
SmallestKeyHeap smallest_key_priority_q =
create_level_heap(c, icmp_->user_comparator());
@@ -397,17 +391,10 @@ bool UniversalCompactionBuilder::IsInputFilesNonOverlapping(Compaction* c) {
prev = curr;
}
next.f = nullptr;
if (c->level(curr.level) != 0 &&
curr.index < c->num_input_files(curr.level) - 1) {
next.f = c->input(curr.level, curr.index + 1);
next.level = curr.level;
next.index = curr.index + 1;
}
if (next.f) {
smallest_key_priority_q.push(std::move(next));
smallest_key_priority_q.emplace(c->input(curr.level, curr.index + 1),
curr.level, curr.index + 1);
}
}
return true;
@@ -996,7 +983,7 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
if (output_level != 0 && picker_->FilesRangeOverlapWithCompaction(
inputs, output_level,
Compaction::EvaluatePenultimateLevel(
Compaction::EvaluateProximalLevel(
vstorage_, mutable_cf_options_, ioptions_,
start_level, output_level))) {
return nullptr;
@@ -1345,7 +1332,7 @@ Compaction* UniversalCompactionBuilder::PickIncrementalForReduceSizeAmp(
// intra L0 compactions outputs could have overlap
if (output_level != 0 && picker_->FilesRangeOverlapWithCompaction(
inputs, output_level,
Compaction::EvaluatePenultimateLevel(
Compaction::EvaluateProximalLevel(
vstorage_, mutable_cf_options_, ioptions_,
start_level, output_level))) {
return nullptr;
@@ -1486,9 +1473,9 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
}
if (picker_->FilesRangeOverlapWithCompaction(
inputs, output_level,
Compaction::EvaluatePenultimateLevel(
vstorage_, mutable_cf_options_, ioptions_, start_level,
output_level))) {
Compaction::EvaluateProximalLevel(vstorage_, mutable_cf_options_,
ioptions_, start_level,
output_level))) {
return nullptr;
}
@@ -1590,7 +1577,7 @@ Compaction* UniversalCompactionBuilder::PickCompactionWithSortedRunRange(
// intra L0 compactions outputs could have overlap
if (output_level != 0 && picker_->FilesRangeOverlapWithCompaction(
inputs, output_level,
Compaction::EvaluatePenultimateLevel(
Compaction::EvaluateProximalLevel(
vstorage_, mutable_cf_options_, ioptions_,
start_level, output_level))) {
return nullptr;
+226 -55
View File
@@ -74,10 +74,14 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
compaction->column_family_data()->GetName().c_str(), job_id_,
compaction_input.output_level, input_files_oss.str().c_str());
CompactionServiceJobInfo info(
dbname_, db_id_, db_session_id_, GetCompactionId(sub_compact),
dbname_, db_id_, db_session_id_,
compaction->column_family_data()->GetID(),
compaction->column_family_data()->GetName(), GetCompactionId(sub_compact),
thread_pri_, compaction->compaction_reason(),
compaction->is_full_compaction(), compaction->is_manual_compaction(),
compaction->bottommost_level());
compaction->bottommost_level(), compaction->start_level(),
compaction->output_level());
CompactionServiceScheduleResponse response =
db_options_.compaction_service->Schedule(info, compaction_input_binary);
switch (response.status) {
@@ -110,6 +114,9 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
break;
}
std::string debug_str_before_wait =
compaction->input_version()->DebugString(/*hex=*/true);
ROCKS_LOG_INFO(db_options_.info_log,
"[%s] [JOB %d] Waiting for remote compaction...",
compaction->column_family_data()->GetName().c_str(), job_id_);
@@ -118,6 +125,17 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
db_options_.compaction_service->Wait(response.scheduled_job_id,
&compaction_result_binary);
if (compaction_status != CompactionServiceJobStatus::kSuccess) {
ROCKS_LOG_ERROR(
db_options_.info_log,
"[%s] [JOB %d] Wait() status is not kSuccess. "
"\nDebugString Before Wait():\n%s"
"\nDebugString After Wait():\n%s",
compaction->column_family_data()->GetName().c_str(), job_id_,
debug_str_before_wait.c_str(),
compaction->input_version()->DebugString(/*hex=*/true).c_str());
}
if (compaction_status == CompactionServiceJobStatus::kUseLocal) {
ROCKS_LOG_INFO(
db_options_.info_log,
@@ -203,18 +221,24 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
}
FileMetaData meta;
uint64_t file_size;
// FIXME: file_size should be part of CompactionServiceOutputFile so that
// we don't get DB corruption if the full file size has not been propagated
// back to the caller through the file system (which could have metadata
// lag or caching bugs).
s = fs_->GetFileSize(tgt_file, IOOptions(), &file_size, nullptr);
uint64_t file_size = file.file_size;
// TODO - Clean this up in the next release.
// For backward compatibility - in case the remote worker does not populate
// the file_size yet. If missing, continue to populate this from the file
// system.
if (file_size == 0) {
s = fs_->GetFileSize(tgt_file, IOOptions(), &file_size, nullptr);
}
if (!s.ok()) {
sub_compact->status = s;
db_options_.compaction_service->OnInstallation(
response.scheduled_job_id, CompactionServiceJobStatus::kFailure);
return CompactionServiceJobStatus::kFailure;
}
assert(file_size > 0);
meta.fd = FileDescriptor(file_num, compaction->output_path_id(), file_size,
file.smallest_seqno, file.largest_seqno);
meta.smallest.DecodeFrom(file.smallest_internal_key);
@@ -226,19 +250,35 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
meta.file_checksum_func_name = file.file_checksum_func_name;
meta.marked_for_compaction = file.marked_for_compaction;
meta.unique_id = file.unique_id;
meta.temperature = file.file_temperature;
meta.tail_size =
FileMetaData::CalculateTailSize(file_size, file.table_properties);
auto cfd = compaction->column_family_data();
sub_compact->Current().AddOutput(std::move(meta),
cfd->internal_comparator(), false, true,
file.paranoid_hash);
sub_compact->Current().UpdateTableProperties(file.table_properties);
CompactionOutputs* compaction_outputs =
sub_compact->Outputs(file.is_proximal_level_output);
assert(compaction_outputs);
compaction_outputs->AddOutput(std::move(meta), cfd->internal_comparator(),
false, true, file.paranoid_hash);
compaction_outputs->UpdateTableProperties(file.table_properties);
}
// Set per-level stats
auto compaction_output_stats =
sub_compact->OutputStats(false /* is_proximal_level */);
assert(compaction_output_stats);
compaction_output_stats->Add(
compaction_result.internal_stats.output_level_stats);
if (compaction->SupportsPerKeyPlacement()) {
compaction_output_stats =
sub_compact->OutputStats(true /* is_proximal_level */);
assert(compaction_output_stats);
compaction_output_stats->Add(
compaction_result.internal_stats.proximal_level_stats);
}
// Set job stats
sub_compact->compaction_job_stats = compaction_result.stats;
sub_compact->Current().SetNumOutputRecords(
compaction_result.stats.num_output_records);
sub_compact->Current().SetNumOutputFiles(
compaction_result.stats.num_output_files);
sub_compact->Current().AddBytesWritten(compaction_result.bytes_written);
RecordTick(stats_, REMOTE_COMPACT_READ_BYTES, compaction_result.bytes_read);
RecordTick(stats_, REMOTE_COMPACT_WRITE_BYTES,
compaction_result.bytes_written);
@@ -258,18 +298,6 @@ void CompactionServiceCompactionJob::RecordCompactionIOStats() {
CompactionJob::RecordCompactionIOStats();
}
void CompactionServiceCompactionJob::UpdateCompactionJobStats(
const InternalStats::CompactionStats& stats) const {
compaction_job_stats_->elapsed_micros = stats.micros;
// output information only in remote compaction
compaction_job_stats_->total_output_bytes = stats.bytes_written;
compaction_job_stats_->total_output_bytes_blob = stats.bytes_written_blob;
compaction_job_stats_->num_output_records = stats.num_output_records;
compaction_job_stats_->num_output_files = stats.num_output_files;
compaction_job_stats_->num_output_files_blob = stats.num_output_files_blob;
}
CompactionServiceCompactionJob::CompactionServiceCompactionJob(
int job_id, Compaction* compaction, const ImmutableDBOptions& db_options,
const MutableDBOptions& mutable_db_options, const FileOptions& file_options,
@@ -331,15 +359,14 @@ Status CompactionServiceCompactionJob::Run() {
ProcessKeyValueCompaction(sub_compact);
compaction_stats_.stats.micros =
db_options_.clock->NowMicros() - start_micros;
compaction_stats_.stats.cpu_micros =
sub_compact->compaction_job_stats.cpu_micros;
uint64_t elapsed_micros = db_options_.clock->NowMicros() - start_micros;
internal_stats_.SetMicros(elapsed_micros);
internal_stats_.AddCpuMicros(elapsed_micros);
RecordTimeToHistogram(stats_, COMPACTION_TIME,
compaction_stats_.stats.micros);
internal_stats_.output_level_stats.micros);
RecordTimeToHistogram(stats_, COMPACTION_CPU_TIME,
compaction_stats_.stats.cpu_micros);
internal_stats_.output_level_stats.cpu_micros);
Status status = sub_compact->status;
IOStatus io_s = sub_compact->io_status;
@@ -369,38 +396,45 @@ Status CompactionServiceCompactionJob::Run() {
// Build Compaction Job Stats
// 1. Aggregate CompactionOutputStats into Internal Compaction Stats
// (compaction_stats_) and aggregate Compaction Job Stats
// (compaction_job_stats_) from the sub compactions
compact_->AggregateCompactionStats(compaction_stats_, *compaction_job_stats_);
// 1. Aggregate internal stats and job stats for all subcompactions
// internal stats: sub_compact.proximal_level_outputs_.stats and
// sub_compact.compaction_outputs_.stats into
// internal_stats_.output_level_stats and
// internal_stats_.proximal_level_stats
// job-level stats: sub_compact.compaction_job_stats into compact.job_stats_
//
// For remote compaction, there's only one subcompaction.
compact_->AggregateCompactionStats(internal_stats_, *job_stats_);
// 2. Update the Output information in the Compaction Job Stats with
// aggregated Internal Compaction Stats.
UpdateCompactionJobStats(compaction_stats_.stats);
// 3. Set fields that are not propagated as part of aggregations above
// 2. Update job-level output stats with the aggregated internal_stats_
// Please note that input stats will be updated by primary host when all
// subcompactions are finished
UpdateCompactionJobOutputStats(internal_stats_);
// and set fields that are not propagated as part of the update
compaction_result_->stats.is_manual_compaction = c->is_manual_compaction();
compaction_result_->stats.is_full_compaction = c->is_full_compaction();
compaction_result_->stats.is_remote_compaction = true;
// 4. Update IO Stats that are not part of the aggregations above (bytes_read,
// bytes_written)
// 3. Update IO Stats that are not part of the the update above
// (bytes_read, bytes_written)
RecordCompactionIOStats();
// Build Output
compaction_result_->internal_stats = internal_stats_;
compaction_result_->output_level = compact_->compaction->output_level();
compaction_result_->output_path = output_path_;
if (status.ok()) {
for (const auto& output_file : sub_compact->GetOutputs()) {
auto& meta = output_file.meta;
compaction_result_->output_files.emplace_back(
MakeTableFileName(meta.fd.GetNumber()), meta.fd.smallest_seqno,
meta.fd.largest_seqno, meta.smallest.Encode().ToString(),
meta.largest.Encode().ToString(), meta.oldest_ancester_time,
meta.file_creation_time, meta.epoch_number, meta.file_checksum,
meta.file_checksum_func_name, output_file.validator.GetHash(),
meta.marked_for_compaction, meta.unique_id,
*output_file.table_properties);
MakeTableFileName(meta.fd.GetNumber()), meta.fd.GetFileSize(),
meta.fd.smallest_seqno, meta.fd.largest_seqno,
meta.smallest.Encode().ToString(), meta.largest.Encode().ToString(),
meta.oldest_ancester_time, meta.file_creation_time, meta.epoch_number,
meta.file_checksum, meta.file_checksum_func_name,
output_file.validator.GetHash(), meta.marked_for_compaction,
meta.unique_id, *output_file.table_properties,
output_file.is_proximal_level, meta.temperature);
}
}
@@ -500,6 +534,10 @@ static std::unordered_map<std::string, OptionTypeInfo>
{offsetof(struct CompactionServiceOutputFile, file_name),
OptionType::kEncodedString, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"file_size",
{offsetof(struct CompactionServiceOutputFile, file_size),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"smallest_seqno",
{offsetof(struct CompactionServiceOutputFile, smallest_seqno),
OptionType::kUInt64T, OptionVerificationType::kNormal,
@@ -572,7 +610,16 @@ static std::unordered_map<std::string, OptionTypeInfo>
const auto this_one = static_cast<const TableProperties*>(addr1);
const auto that_one = static_cast<const TableProperties*>(addr2);
return this_one->AreEqual(opts, that_one, mismatch);
}}}};
}}},
{"is_proximal_level_output",
{offsetof(struct CompactionServiceOutputFile,
is_proximal_level_output),
OptionType::kBoolean, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"file_temperature",
{offsetof(struct CompactionServiceOutputFile, file_temperature),
OptionType::kTemperature, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}}};
static std::unordered_map<std::string, OptionTypeInfo>
compaction_job_stats_type_info = {
@@ -697,6 +744,125 @@ static std::unordered_map<std::string, OptionTypeInfo>
OptionTypeFlags::kNone}},
};
static std::unordered_map<std::string, OptionTypeInfo>
compaction_stats_type_info = {
{"micros",
{offsetof(struct InternalStats::CompactionStats, micros),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"cpu_micros",
{offsetof(struct InternalStats::CompactionStats, cpu_micros),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"bytes_read_non_output_levels",
{offsetof(struct InternalStats::CompactionStats,
bytes_read_non_output_levels),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"bytes_read_output_level",
{offsetof(struct InternalStats::CompactionStats,
bytes_read_output_level),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"bytes_skipped_non_output_levels",
{offsetof(struct InternalStats::CompactionStats,
bytes_skipped_non_output_levels),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"bytes_skipped_output_level",
{offsetof(struct InternalStats::CompactionStats,
bytes_skipped_output_level),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"bytes_read_blob",
{offsetof(struct InternalStats::CompactionStats, bytes_read_blob),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"bytes_written",
{offsetof(struct InternalStats::CompactionStats, bytes_written),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"bytes_written_blob",
{offsetof(struct InternalStats::CompactionStats, bytes_written_blob),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"bytes_moved",
{offsetof(struct InternalStats::CompactionStats, bytes_moved),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"num_input_files_in_non_output_levels",
{offsetof(struct InternalStats::CompactionStats,
num_input_files_in_non_output_levels),
OptionType::kInt, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"num_input_files_in_output_level",
{offsetof(struct InternalStats::CompactionStats,
num_input_files_in_output_level),
OptionType::kInt, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"num_filtered_input_files_in_non_output_levels",
{offsetof(struct InternalStats::CompactionStats,
num_filtered_input_files_in_non_output_levels),
OptionType::kInt, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"num_filtered_input_files_in_output_level",
{offsetof(struct InternalStats::CompactionStats,
num_filtered_input_files_in_output_level),
OptionType::kInt, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"num_output_files",
{offsetof(struct InternalStats::CompactionStats, num_output_files),
OptionType::kInt, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"num_output_files_blob",
{offsetof(struct InternalStats::CompactionStats,
num_output_files_blob),
OptionType::kInt, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"num_input_records",
{offsetof(struct InternalStats::CompactionStats, num_input_records),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"num_dropped_records",
{offsetof(struct InternalStats::CompactionStats, num_dropped_records),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"num_output_records",
{offsetof(struct InternalStats::CompactionStats, num_output_records),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"count",
{offsetof(struct InternalStats::CompactionStats, count),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"counts", OptionTypeInfo::Array<
int, static_cast<int>(CompactionReason::kNumOfReasons)>(
offsetof(struct InternalStats::CompactionStats, counts),
OptionVerificationType::kNormal, OptionTypeFlags::kNone,
{0, OptionType::kInt})},
};
static std::unordered_map<std::string, OptionTypeInfo>
compaction_internal_stats_type_info = {
{"output_level_stats",
OptionTypeInfo::Struct(
"output_level_stats", &compaction_stats_type_info,
offsetof(struct InternalStats::CompactionStatsFull,
output_level_stats),
OptionVerificationType::kNormal, OptionTypeFlags::kNone)},
{"has_proximal_level_output",
{offsetof(struct InternalStats::CompactionStatsFull,
has_proximal_level_output),
OptionType::kBoolean, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"proximal_level_stats",
OptionTypeInfo::Struct(
"proximal_level_stats", &compaction_stats_type_info,
offsetof(struct InternalStats::CompactionStatsFull,
proximal_level_stats),
OptionVerificationType::kNormal, OptionTypeFlags::kNone)},
};
namespace {
// this is a helper struct to serialize and deserialize class Status, because
// Status's members are not public.
@@ -803,6 +969,11 @@ static std::unordered_map<std::string, OptionTypeInfo> cs_result_type_info = {
"stats", &compaction_job_stats_type_info,
offsetof(struct CompactionServiceResult, stats),
OptionVerificationType::kNormal, OptionTypeFlags::kNone)},
{"internal_stats",
OptionTypeInfo::Struct(
"internal_stats", &compaction_internal_stats_type_info,
offsetof(struct CompactionServiceResult, internal_stats),
OptionVerificationType::kNormal, OptionTypeFlags::kNone)},
};
Status CompactionServiceInput::Read(const std::string& data_str,
+152 -27
View File
@@ -21,10 +21,10 @@ class MyTestCompactionService : public CompactionService {
: db_path_(std::move(db_path)),
options_(options),
statistics_(statistics),
start_info_("na", "na", "na", 0, Env::TOTAL, CompactionReason::kUnknown,
false, false, false),
wait_info_("na", "na", "na", 0, Env::TOTAL, CompactionReason::kUnknown,
false, false, false),
start_info_("na", "na", "na", 0, "na", 0, Env::TOTAL,
CompactionReason::kUnknown, false, false, false, -1, -1),
wait_info_("na", "na", "na", 0, "na", 0, Env::TOTAL,
CompactionReason::kUnknown, false, false, false, -1, -1),
listeners_(listeners),
table_properties_collector_factories_(
std::move(table_properties_collector_factories)) {}
@@ -84,6 +84,7 @@ class MyTestCompactionService : public CompactionService {
options_override.table_factory = options_.table_factory;
options_override.sst_partitioner_factory = options_.sst_partitioner_factory;
options_override.statistics = statistics_;
options_override.info_log = options_.info_log;
if (!listeners_.empty()) {
options_override.listeners = listeners_;
}
@@ -277,8 +278,17 @@ TEST_F(CompactionServiceTest, BasicCompactions) {
Statistics* primary_statistics = GetPrimaryStatistics();
Statistics* compactor_statistics = GetCompactorStatistics();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"BlockBasedTable::PrefetchTail::TaiSizeNotRecorded",
[&](void* /* arg */) {
// Trigger assertion to verify precise tail prefetch size calculation
assert(false);
});
SyncPoint::GetInstance()->EnableProcessing();
GenerateTestData();
ASSERT_OK(dbfull()->TEST_WaitForCompact());
SyncPoint::GetInstance()->DisableProcessing();
VerifyTestData();
auto my_cs = GetCompactionService();
@@ -357,11 +367,12 @@ TEST_F(CompactionServiceTest, BasicCompactions) {
} else {
ASSERT_OK(result.status);
}
ASSERT_GE(result.stats.elapsed_micros, 1);
ASSERT_GE(result.stats.cpu_micros, 1);
ASSERT_GE(result.internal_stats.output_level_stats.micros, 1);
ASSERT_GE(result.internal_stats.output_level_stats.cpu_micros, 1);
ASSERT_EQ(20, result.stats.num_output_records);
ASSERT_EQ(result.output_files.size(), result.stats.num_output_files);
ASSERT_EQ(20, result.internal_stats.output_level_stats.num_output_records);
ASSERT_EQ(result.output_files.size(),
result.internal_stats.output_level_stats.num_output_files);
uint64_t total_size = 0;
for (auto output_file : result.output_files) {
@@ -372,13 +383,14 @@ TEST_F(CompactionServiceTest, BasicCompactions) {
ASSERT_GT(file_size, 0);
total_size += file_size;
}
ASSERT_EQ(total_size, result.stats.total_output_bytes);
ASSERT_EQ(total_size, result.internal_stats.TotalBytesWritten());
ASSERT_TRUE(result.stats.is_remote_compaction);
ASSERT_TRUE(result.stats.is_manual_compaction);
ASSERT_FALSE(result.stats.is_full_compaction);
Close();
SyncPoint::GetInstance()->DisableProcessing();
}
TEST_F(CompactionServiceTest, ManualCompaction) {
@@ -422,6 +434,30 @@ TEST_F(CompactionServiceTest, ManualCompaction) {
ASSERT_OK(result.status);
ASSERT_TRUE(result.stats.is_manual_compaction);
ASSERT_TRUE(result.stats.is_remote_compaction);
auto info = my_cs->GetCompactionInfoForStart();
ASSERT_EQ(0, info.cf_id);
ASSERT_EQ(kDefaultColumnFamilyName, info.cf_name);
info = my_cs->GetCompactionInfoForWait();
ASSERT_EQ(0, info.cf_id);
ASSERT_EQ(kDefaultColumnFamilyName, info.cf_name);
// Test non-default CF
ASSERT_OK(
db_->CompactRange(CompactRangeOptions(), handles_[1], nullptr, nullptr));
my_cs->GetResult(&result);
ASSERT_OK(result.status);
ASSERT_TRUE(result.stats.is_manual_compaction);
ASSERT_TRUE(result.stats.is_remote_compaction);
info = my_cs->GetCompactionInfoForStart();
ASSERT_EQ(handles_[1]->GetID(), info.cf_id);
ASSERT_EQ(handles_[1]->GetName(), info.cf_name);
info = my_cs->GetCompactionInfoForWait();
ASSERT_EQ(handles_[1]->GetID(), info.cf_id);
ASSERT_EQ(handles_[1]->GetName(), info.cf_name);
}
TEST_F(CompactionServiceTest, CompactionOutputFileIOError) {
@@ -716,6 +752,46 @@ TEST_F(CompactionServiceTest, VerifyStatsLocalFallback) {
VerifyTestData();
}
TEST_F(CompactionServiceTest, VerifyInputRecordCount) {
Options options = CurrentOptions();
options.disable_auto_compactions = true;
ReopenWithCompactionService(&options);
GenerateTestData();
auto my_cs = GetCompactionService();
std::string start_str = Key(15);
std::string end_str = Key(45);
Slice start(start_str);
Slice end(end_str);
uint64_t comp_num = my_cs->GetCompactionNum();
// Only iterator through 10 keys and force compaction to finish.
int num_iter = 0;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"CompactionJob::ProcessKeyValueCompaction()::stop", [&](void* stop_ptr) {
num_iter++;
if (num_iter == 10) {
*(bool*)stop_ptr = true;
}
});
SyncPoint::GetInstance()->EnableProcessing();
// CompactRange() should fail
Status s = db_->CompactRange(CompactRangeOptions(), &start, &end);
ASSERT_NOK(s);
ASSERT_TRUE(s.IsCorruption());
const char* expected_message =
"Compaction number of input keys does not match number of keys "
"processed.";
ASSERT_TRUE(std::strstr(s.getState(), expected_message));
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
}
TEST_F(CompactionServiceTest, CorruptedOutput) {
Options options = CurrentOptions();
options.disable_auto_compactions = true;
@@ -849,6 +925,12 @@ TEST_F(CompactionServiceTest, TruncatedOutput) {
Slice end(end_str);
uint64_t comp_num = my_cs->GetCompactionNum();
// Skip calculating tail size to avoid crashing due to truncated file size
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"FileMetaData::CalculateTailSize", [&](void* arg) {
bool* skip = static_cast<bool*>(arg);
*skip = true;
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"CompactionServiceCompactionJob::Run:0", [&](void* arg) {
CompactionServiceResult* compaction_result =
@@ -865,7 +947,7 @@ TEST_F(CompactionServiceTest, TruncatedOutput) {
ASSERT_OK(s);
ASSERT_GT(file_size, 0);
ASSERT_OK(test::TruncateFile(env_, file_name, file_size / 2));
ASSERT_OK(test::TruncateFile(env_, file_name, file_size / 4));
}
});
SyncPoint::GetInstance()->EnableProcessing();
@@ -1188,34 +1270,52 @@ TEST_F(CompactionServiceTest, PrecludeLastLevel) {
for (int i = 0; i < kNumTrigger; i++) {
for (int j = 0; j < kNumKeys; j++) {
// FIXME: need to assign outputs to levels to allow overlapping ranges:
// ASSERT_OK(Put(Key(j * kNumTrigger + i), "v" + std::to_string(i)));
// instead of this (too easy):
ASSERT_OK(Put(Key(i * kNumKeys + j), "v" + std::to_string(i)));
ASSERT_OK(Put(Key(j * kNumTrigger + i), "v" + std::to_string(i)));
}
ASSERT_OK(Flush());
}
ASSERT_OK(dbfull()->TEST_WaitForCompact());
// Data split between penultimate (kUnknown) and last (kCold) levels
// FIXME: need to assign outputs to levels to get this:
// ASSERT_EQ("0,0,0,0,0,1,1", FilesPerLevel());
// ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
// ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
// instead of this (WRONG but currently expected):
ASSERT_EQ("0,0,0,0,0,0,2", FilesPerLevel());
// Check manifest temperatures
// Data split between proximal (kUnknown) and last (kCold) levels
ASSERT_EQ("0,0,0,0,0,1,1", FilesPerLevel());
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
// TODO: Check FileSystem temperatures with FileTemperatureTestFS
for (int i = 0; i < kNumTrigger; i++) {
for (int j = 0; j < kNumKeys; j++) {
// FIXME
// ASSERT_EQ(Get(Key(j * kNumTrigger + i)), "v" + std::to_string(i));
ASSERT_EQ(Get(Key(i * kNumKeys + j)), "v" + std::to_string(i));
ASSERT_EQ(Get(Key(j * kNumTrigger + i)), "v" + std::to_string(i));
}
}
// Verify Output Stats
auto my_cs = GetCompactionService();
{
CompactionServiceResult result;
my_cs->GetResult(&result);
ASSERT_OK(result.status);
ASSERT_GT(result.internal_stats.output_level_stats.cpu_micros, 0);
ASSERT_GT(result.internal_stats.output_level_stats.micros, 0);
ASSERT_EQ(result.internal_stats.output_level_stats.num_output_records +
result.internal_stats.proximal_level_stats.num_output_records,
kNumTrigger * kNumKeys);
ASSERT_EQ(result.internal_stats.output_level_stats.num_output_files +
result.internal_stats.proximal_level_stats.num_output_files,
2);
CompactionServiceJobInfo info = my_cs->GetCompactionInfoForStart();
ASSERT_EQ(0, info.base_input_level);
ASSERT_EQ(kNumLevels - 1, info.output_level);
}
SyncPoint::GetInstance()->DisableProcessing();
// Disable Preclude feature and run full compaction to the bottommost level
{
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
CompactionServiceJobInfo info = my_cs->GetCompactionInfoForStart();
ASSERT_EQ(kNumLevels - 2, info.base_input_level);
ASSERT_EQ(kNumLevels - 1, info.output_level);
}
}
TEST_F(CompactionServiceTest, ConcurrentCompaction) {
@@ -1285,12 +1385,17 @@ TEST_F(CompactionServiceTest, CompactionInfo) {
ASSERT_EQ(true, info.is_manual_compaction);
ASSERT_EQ(false, info.is_full_compaction);
ASSERT_EQ(true, info.bottommost_level);
ASSERT_EQ(1, info.base_input_level);
ASSERT_EQ(2, info.output_level);
info = my_cs->GetCompactionInfoForWait();
ASSERT_EQ(Env::USER, info.priority);
ASSERT_EQ(CompactionReason::kManualCompaction, info.compaction_reason);
ASSERT_EQ(true, info.is_manual_compaction);
ASSERT_EQ(false, info.is_full_compaction);
ASSERT_EQ(true, info.bottommost_level);
ASSERT_EQ(1, info.base_input_level);
ASSERT_EQ(2, info.output_level);
ASSERT_EQ(kDefaultColumnFamilyName, info.cf_name);
// Test priority BOTTOM
env_->SetBackgroundThreads(1, Env::BOTTOM);
@@ -1322,18 +1427,24 @@ TEST_F(CompactionServiceTest, CompactionInfo) {
ASSERT_EQ(false, info.is_full_compaction);
ASSERT_EQ(true, info.bottommost_level);
ASSERT_EQ(Env::BOTTOM, info.priority);
ASSERT_EQ(0, info.base_input_level);
ASSERT_EQ(db_->NumberLevels() - 1, info.output_level);
info = my_cs->GetCompactionInfoForWait();
ASSERT_EQ(Env::BOTTOM, info.priority);
ASSERT_EQ(CompactionReason::kLevelL0FilesNum, info.compaction_reason);
ASSERT_EQ(false, info.is_manual_compaction);
ASSERT_EQ(false, info.is_full_compaction);
ASSERT_EQ(true, info.bottommost_level);
ASSERT_EQ(0, info.base_input_level);
ASSERT_EQ(db_->NumberLevels() - 1, info.output_level);
// Test Non-Bottommost Level
options.num_levels = 4;
ReopenWithCompactionService(&options);
my_cs =
static_cast_with_check<MyTestCompactionService>(GetCompactionService());
int compaction_num = my_cs->GetCompactionNum();
ASSERT_EQ(0, compaction_num);
for (int i = 0; i < options.level0_file_num_compaction_trigger; i++) {
for (int j = 0; j < 10; j++) {
@@ -1342,16 +1453,22 @@ TEST_F(CompactionServiceTest, CompactionInfo) {
}
ASSERT_OK(Flush());
}
ASSERT_OK(dbfull()->TEST_WaitForCompact());
// This is trivial move. Done locally.
ASSERT_EQ(0, my_cs->GetCompactionNum());
info = my_cs->GetCompactionInfoForStart();
ASSERT_EQ(false, info.is_manual_compaction);
ASSERT_EQ(false, info.is_full_compaction);
ASSERT_EQ(false, info.bottommost_level);
ASSERT_EQ(-1, info.base_input_level);
ASSERT_EQ(-1, info.output_level);
info = my_cs->GetCompactionInfoForWait();
ASSERT_EQ(false, info.is_manual_compaction);
ASSERT_EQ(false, info.is_full_compaction);
ASSERT_EQ(false, info.bottommost_level);
ASSERT_EQ(-1, info.base_input_level);
ASSERT_EQ(-1, info.output_level);
// Test Full Compaction + Bottommost Level
options.num_levels = 6;
@@ -1366,7 +1483,10 @@ TEST_F(CompactionServiceTest, CompactionInfo) {
}
ASSERT_OK(Flush());
}
MoveFilesToLevel(options.num_levels - 1);
// Force final level compaction
// base_input_level == output_level == last_level
CompactRangeOptions cro;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
@@ -1378,10 +1498,15 @@ TEST_F(CompactionServiceTest, CompactionInfo) {
ASSERT_EQ(true, info.bottommost_level);
ASSERT_EQ(CompactionReason::kManualCompaction, info.compaction_reason);
info = my_cs->GetCompactionInfoForWait();
ASSERT_EQ(options.num_levels - 1, info.base_input_level);
ASSERT_EQ(options.num_levels - 1, info.output_level);
ASSERT_EQ(true, info.is_manual_compaction);
ASSERT_EQ(true, info.is_full_compaction);
ASSERT_EQ(true, info.bottommost_level);
ASSERT_EQ(CompactionReason::kManualCompaction, info.compaction_reason);
ASSERT_EQ(options.num_levels - 1, info.base_input_level);
ASSERT_EQ(options.num_levels - 1, info.output_level);
ASSERT_EQ("0,0,0,0,0,1", FilesPerLevel());
}
TEST_F(CompactionServiceTest, FallbackLocalAuto) {
+4 -4
View File
@@ -36,11 +36,11 @@ Slice CompactionState::LargestUserKey() {
}
void CompactionState::AggregateCompactionStats(
InternalStats::CompactionStatsFull& compaction_stats,
CompactionJobStats& compaction_job_stats) {
InternalStats::CompactionStatsFull& internal_stats,
CompactionJobStats& job_stats) {
for (const auto& sc : sub_compact_states) {
sc.AggregateCompactionOutputStats(compaction_stats);
compaction_job_stats.Add(sc.compaction_job_stats);
sc.AggregateCompactionOutputStats(internal_stats);
job_stats.Add(sc.compaction_job_stats);
}
}
} // namespace ROCKSDB_NAMESPACE
+2 -2
View File
@@ -29,8 +29,8 @@ class CompactionState {
Status status;
void AggregateCompactionStats(
InternalStats::CompactionStatsFull& compaction_stats,
CompactionJobStats& compaction_job_stats);
InternalStats::CompactionStatsFull& internal_stats,
CompactionJobStats& job_stats);
explicit CompactionState(Compaction* c) : compaction(c) {}
+17 -18
View File
@@ -14,33 +14,32 @@
namespace ROCKSDB_NAMESPACE {
void SubcompactionState::AggregateCompactionOutputStats(
InternalStats::CompactionStatsFull& compaction_stats) const {
InternalStats::CompactionStatsFull& internal_stats) const {
// Outputs should be closed. By extension, any files created just for
// range deletes have already been written also.
assert(compaction_outputs_.HasBuilder() == false);
assert(penultimate_level_outputs_.HasBuilder() == false);
assert(proximal_level_outputs_.HasBuilder() == false);
// FIXME: These stats currently include abandonned output files
// assert(compaction_outputs_.stats_.num_output_files ==
// compaction_outputs_.outputs_.size());
// assert(penultimate_level_outputs_.stats_.num_output_files ==
// penultimate_level_outputs_.outputs_.size());
// assert(proximal_level_outputs_.stats_.num_output_files ==
// proximal_level_outputs_.outputs_.size());
compaction_stats.stats.Add(compaction_outputs_.stats_);
if (penultimate_level_outputs_.HasOutput()) {
compaction_stats.has_penultimate_level_output = true;
compaction_stats.penultimate_level_stats.Add(
penultimate_level_outputs_.stats_);
internal_stats.output_level_stats.Add(compaction_outputs_.stats_);
if (proximal_level_outputs_.HasOutput()) {
internal_stats.has_proximal_level_output = true;
internal_stats.proximal_level_stats.Add(proximal_level_outputs_.stats_);
}
}
OutputIterator SubcompactionState::GetOutputs() const {
return OutputIterator(penultimate_level_outputs_.outputs_,
return OutputIterator(proximal_level_outputs_.outputs_,
compaction_outputs_.outputs_);
}
void SubcompactionState::Cleanup(Cache* cache) {
penultimate_level_outputs_.Cleanup();
proximal_level_outputs_.Cleanup();
compaction_outputs_.Cleanup();
if (!status.ok()) {
@@ -63,9 +62,9 @@ void SubcompactionState::Cleanup(Cache* cache) {
}
Slice SubcompactionState::SmallestUserKey() const {
if (penultimate_level_outputs_.HasOutput()) {
if (proximal_level_outputs_.HasOutput()) {
Slice a = compaction_outputs_.SmallestUserKey();
Slice b = penultimate_level_outputs_.SmallestUserKey();
Slice b = proximal_level_outputs_.SmallestUserKey();
if (a.empty()) {
return b;
}
@@ -85,9 +84,9 @@ Slice SubcompactionState::SmallestUserKey() const {
}
Slice SubcompactionState::LargestUserKey() const {
if (penultimate_level_outputs_.HasOutput()) {
if (proximal_level_outputs_.HasOutput()) {
Slice a = compaction_outputs_.LargestUserKey();
Slice b = penultimate_level_outputs_.LargestUserKey();
Slice b = proximal_level_outputs_.LargestUserKey();
if (a.empty()) {
return b;
}
@@ -107,12 +106,12 @@ Slice SubcompactionState::LargestUserKey() const {
}
Status SubcompactionState::AddToOutput(
const CompactionIterator& iter, bool use_penultimate_output,
const CompactionIterator& iter, bool use_proximal_output,
const CompactionFileOpenFunc& open_file_func,
const CompactionFileCloseFunc& close_file_func) {
// update target output
current_outputs_ = use_penultimate_output ? &penultimate_level_outputs_
: &compaction_outputs_;
current_outputs_ =
use_proximal_output ? &proximal_level_outputs_ : &compaction_outputs_;
return current_outputs_->AddToOutput(iter, open_file_func, close_file_func);
}
+43 -26
View File
@@ -26,13 +26,13 @@ namespace ROCKSDB_NAMESPACE {
// Maintains state and outputs for each sub-compaction
// It contains 2 `CompactionOutputs`:
// 1. one for the normal output files
// 2. another for the penultimate level outputs
// 2. another for the proximal level outputs
// a `current` pointer maintains the current output group, when calling
// `AddToOutput()`, it checks the output of the current compaction_iterator key
// and point `current` to the target output group. By default, it just points to
// normal compaction_outputs, if the compaction_iterator key should be placed on
// the penultimate level, `current` is changed to point to
// `penultimate_level_outputs`.
// the proximal level, `current` is changed to point to
// `proximal_level_outputs`.
// The later operations uses `Current()` to get the target group.
//
// +----------+ +-----------------------------+ +---------+
@@ -43,7 +43,7 @@ namespace ROCKSDB_NAMESPACE {
// | | ... |
// |
// | +-----------------------------+ +---------+
// +-------------> | penultimate_level_outputs |----->| output |
// +-------------> | proximal_level_outputs |----->| output |
// +-----------------------------+ +---------+
// | ... |
@@ -78,7 +78,7 @@ class SubcompactionState {
Slice LargestUserKey() const;
// Get all outputs from the subcompaction. For per_key_placement compaction,
// it returns both the last level outputs and penultimate level outputs.
// it returns both the last level outputs and proximal level outputs.
OutputIterator GetOutputs() const;
// Assign range dels aggregator. The various tombstones will potentially
@@ -92,7 +92,7 @@ class SubcompactionState {
void RemoveLastEmptyOutput() {
compaction_outputs_.RemoveLastEmptyOutput();
penultimate_level_outputs_.RemoveLastEmptyOutput();
proximal_level_outputs_.RemoveLastEmptyOutput();
}
void BuildSubcompactionJobInfo(
@@ -119,14 +119,14 @@ class SubcompactionState {
start(_start),
end(_end),
sub_job_id(_sub_job_id),
compaction_outputs_(c, /*is_penultimate_level=*/false),
penultimate_level_outputs_(c, /*is_penultimate_level=*/true) {
compaction_outputs_(c, /*is_proximal_level=*/false),
proximal_level_outputs_(c, /*is_proximal_level=*/true) {
assert(compaction != nullptr);
// Set output split key (used for RoundRobin feature) only for normal
// compaction_outputs, output to penultimate_level feature doesn't support
// compaction_outputs, output to proximal_level feature doesn't support
// RoundRobin feature (and may never going to be supported, because for
// RoundRobin, the data time is mostly naturally sorted, no need to have
// per-key placement with output_to_penultimate_level).
// per-key placement with output_to_proximal_level).
compaction_outputs_.SetOutputSlitKey(start, end);
}
@@ -141,18 +141,17 @@ class SubcompactionState {
compaction_job_stats(std::move(state.compaction_job_stats)),
sub_job_id(state.sub_job_id),
compaction_outputs_(std::move(state.compaction_outputs_)),
penultimate_level_outputs_(std::move(state.penultimate_level_outputs_)),
proximal_level_outputs_(std::move(state.proximal_level_outputs_)),
range_del_agg_(std::move(state.range_del_agg_)) {
current_outputs_ =
state.current_outputs_ == &state.penultimate_level_outputs_
? &penultimate_level_outputs_
: &compaction_outputs_;
current_outputs_ = state.current_outputs_ == &state.proximal_level_outputs_
? &proximal_level_outputs_
: &compaction_outputs_;
}
// Add all the new files from this compaction to version_edit
void AddOutputsEdit(VersionEdit* out_edit) const {
for (const auto& file : penultimate_level_outputs_.outputs_) {
out_edit->AddFile(compaction->GetPenultimateLevel(), file.meta);
for (const auto& file : proximal_level_outputs_.outputs_) {
out_edit->AddFile(compaction->GetProximalLevel(), file.meta);
}
for (const auto& file : compaction_outputs_.outputs_) {
out_edit->AddFile(compaction->output_level(), file.meta);
@@ -162,13 +161,32 @@ class SubcompactionState {
void Cleanup(Cache* cache);
void AggregateCompactionOutputStats(
InternalStats::CompactionStatsFull& compaction_stats) const;
InternalStats::CompactionStatsFull& internal_stats) const;
CompactionOutputs& Current() const {
assert(current_outputs_);
return *current_outputs_;
}
CompactionOutputs* Outputs(bool is_proximal_level) {
assert(compaction);
if (is_proximal_level) {
assert(compaction->SupportsPerKeyPlacement());
return &proximal_level_outputs_;
}
return &compaction_outputs_;
}
// Per-level stats for the output
InternalStats::CompactionStats* OutputStats(bool is_proximal_level) {
assert(compaction);
if (is_proximal_level) {
assert(compaction->SupportsPerKeyPlacement());
return &proximal_level_outputs_.stats_;
}
return &compaction_outputs_.stats_;
}
CompactionRangeDelAggregator* RangeDelAgg() const {
return range_del_agg_.get();
}
@@ -179,12 +197,11 @@ class SubcompactionState {
}
// Add compaction_iterator key/value to the `Current` output group.
Status AddToOutput(const CompactionIterator& iter,
bool use_penultimate_output,
Status AddToOutput(const CompactionIterator& iter, bool use_proximal_output,
const CompactionFileOpenFunc& open_file_func,
const CompactionFileCloseFunc& close_file_func);
// Close all compaction output files, both output_to_penultimate_level outputs
// Close all compaction output files, both output_to_proximal_level outputs
// and normal outputs.
Status CloseCompactionFiles(const Status& curr_status,
const CompactionFileOpenFunc& open_file_func,
@@ -195,11 +212,11 @@ class SubcompactionState {
// CloseOutput() may open new compaction output files.
Status s = curr_status;
if (per_key) {
s = penultimate_level_outputs_.CloseOutput(
s, range_del_agg_.get(), open_file_func, close_file_func);
s = proximal_level_outputs_.CloseOutput(s, range_del_agg_.get(),
open_file_func, close_file_func);
} else {
assert(penultimate_level_outputs_.HasBuilder() == false);
assert(penultimate_level_outputs_.HasOutput() == false);
assert(proximal_level_outputs_.HasBuilder() == false);
assert(proximal_level_outputs_.HasOutput() == false);
}
s = compaction_outputs_.CloseOutput(s, range_del_agg_.get(), open_file_func,
close_file_func);
@@ -209,7 +226,7 @@ class SubcompactionState {
private:
// State kept for output being generated
CompactionOutputs compaction_outputs_;
CompactionOutputs penultimate_level_outputs_;
CompactionOutputs proximal_level_outputs_;
CompactionOutputs* current_outputs_ = &compaction_outputs_;
std::unique_ptr<CompactionRangeDelAggregator> range_del_agg_;
};
+415 -189
View File
@@ -33,42 +33,13 @@ ConfigOptions GetStrictConfigOptions() {
class TieredCompactionTest : public DBTestBase {
public:
TieredCompactionTest()
: DBTestBase("tiered_compaction_test", /*env_do_fsync=*/true),
kBasicCompStats(CompactionReason::kUniversalSizeAmplification, 1),
kBasicPerKeyPlacementCompStats(
CompactionReason::kUniversalSizeAmplification, 1),
kBasicFlushStats(CompactionReason::kFlush, 1) {
kBasicCompStats.micros = kHasValue;
kBasicCompStats.cpu_micros = kHasValue;
kBasicCompStats.bytes_read_non_output_levels = kHasValue;
kBasicCompStats.num_input_files_in_non_output_levels = kHasValue;
kBasicCompStats.num_input_records = kHasValue;
kBasicCompStats.num_dropped_records = kHasValue;
kBasicPerLevelStats.num_output_records = kHasValue;
kBasicPerLevelStats.bytes_written = kHasValue;
kBasicPerLevelStats.num_output_files = kHasValue;
kBasicPerKeyPlacementCompStats.micros = kHasValue;
kBasicPerKeyPlacementCompStats.cpu_micros = kHasValue;
kBasicPerKeyPlacementCompStats.Add(kBasicPerLevelStats);
kBasicFlushStats.micros = kHasValue;
kBasicFlushStats.cpu_micros = kHasValue;
kBasicFlushStats.bytes_written = kHasValue;
kBasicFlushStats.num_output_files = kHasValue;
}
: DBTestBase("tiered_compaction_test", /*env_do_fsync=*/true) {}
protected:
static constexpr uint8_t kHasValue = 1;
InternalStats::CompactionStats kBasicCompStats;
InternalStats::CompactionStats kBasicPerKeyPlacementCompStats;
InternalStats::CompactionOutputsStats kBasicPerLevelStats;
InternalStats::CompactionStats kBasicFlushStats;
std::atomic_bool enable_per_key_placement = true;
CompactionJobStats job_stats;
void SetUp() override {
SyncPoint::GetInstance()->SetCallBack(
"Compaction::SupportsPerKeyPlacement:Enabled", [&](void* arg) {
@@ -108,21 +79,36 @@ class TieredCompactionTest : public DBTestBase {
// Verify the compaction stats, the stats are roughly compared
void VerifyCompactionStats(
const std::vector<InternalStats::CompactionStats>& expect_stats,
const InternalStats::CompactionStats& expect_pl_stats) {
const std::vector<InternalStats::CompactionStats>& expected_stats,
const InternalStats::CompactionStats& expected_pl_stats,
size_t output_level, uint64_t num_input_range_del = 0) {
const std::vector<InternalStats::CompactionStats>& stats =
GetCompactionStats();
const size_t kLevels = expect_stats.size();
const size_t kLevels = expected_stats.size();
ASSERT_EQ(kLevels, stats.size());
ASSERT_TRUE(output_level < kLevels);
for (auto it = stats.begin(), expect = expect_stats.begin();
it != stats.end(); it++, expect++) {
VerifyCompactionStats(*it, *expect);
for (size_t level = 0; level < kLevels; level++) {
VerifyCompactionStats(stats[level], expected_stats[level]);
}
const InternalStats::CompactionStats& pl_stats =
GetPerKeyPlacementCompactionStats();
VerifyCompactionStats(pl_stats, expect_pl_stats);
VerifyCompactionStats(pl_stats, expected_pl_stats);
const auto& output_level_stats = stats[output_level];
CompactionJobStats expected_job_stats;
expected_job_stats.cpu_micros = output_level_stats.cpu_micros;
expected_job_stats.num_input_files =
output_level_stats.num_input_files_in_output_level +
output_level_stats.num_input_files_in_non_output_levels;
expected_job_stats.num_input_records =
output_level_stats.num_input_records - num_input_range_del;
expected_job_stats.num_output_files =
output_level_stats.num_output_files + pl_stats.num_output_files;
expected_job_stats.num_output_records =
output_level_stats.num_output_records + pl_stats.num_output_records;
VerifyCompactionJobStats(job_stats, expected_job_stats);
}
void ResetAllStats(std::vector<InternalStats::CompactionStats>& stats,
@@ -139,42 +125,52 @@ class TieredCompactionTest : public DBTestBase {
}
private:
void CompareStats(uint64_t val, uint64_t expect) {
if (expect > 0) {
ASSERT_TRUE(val > 0);
} else {
ASSERT_EQ(val, 0);
}
}
void VerifyCompactionStats(
const InternalStats::CompactionStats& stats,
const InternalStats::CompactionStats& expect_stats) {
CompareStats(stats.micros, expect_stats.micros);
CompareStats(stats.cpu_micros, expect_stats.cpu_micros);
CompareStats(stats.bytes_read_non_output_levels,
expect_stats.bytes_read_non_output_levels);
CompareStats(stats.bytes_read_output_level,
expect_stats.bytes_read_output_level);
CompareStats(stats.bytes_read_blob, expect_stats.bytes_read_blob);
CompareStats(stats.bytes_written, expect_stats.bytes_written);
CompareStats(stats.bytes_moved, expect_stats.bytes_moved);
CompareStats(stats.num_input_files_in_non_output_levels,
expect_stats.num_input_files_in_non_output_levels);
CompareStats(stats.num_input_files_in_output_level,
expect_stats.num_input_files_in_output_level);
CompareStats(stats.num_output_files, expect_stats.num_output_files);
CompareStats(stats.num_output_files_blob,
expect_stats.num_output_files_blob);
CompareStats(stats.num_input_records, expect_stats.num_input_records);
CompareStats(stats.num_dropped_records, expect_stats.num_dropped_records);
CompareStats(stats.num_output_records, expect_stats.num_output_records);
ASSERT_EQ(stats.micros > 0, expect_stats.micros > 0);
ASSERT_EQ(stats.cpu_micros > 0, expect_stats.cpu_micros > 0);
// Hard to get consistent byte sizes of SST files.
// Use ASSERT_NEAR for comparison
ASSERT_NEAR(stats.bytes_read_non_output_levels * 1.0f,
expect_stats.bytes_read_non_output_levels * 1.0f,
stats.bytes_read_non_output_levels * 0.5f);
ASSERT_NEAR(stats.bytes_read_output_level * 1.0f,
expect_stats.bytes_read_output_level * 1.0f,
stats.bytes_read_output_level * 0.5f);
ASSERT_NEAR(stats.bytes_read_blob * 1.0f,
expect_stats.bytes_read_blob * 1.0f,
stats.bytes_read_blob * 0.5f);
ASSERT_NEAR(stats.bytes_written * 1.0f, expect_stats.bytes_written * 1.0f,
stats.bytes_written * 0.5f);
ASSERT_EQ(stats.bytes_moved, expect_stats.bytes_moved);
ASSERT_EQ(stats.num_input_files_in_non_output_levels,
expect_stats.num_input_files_in_non_output_levels);
ASSERT_EQ(stats.num_input_files_in_output_level,
expect_stats.num_input_files_in_output_level);
ASSERT_EQ(stats.num_output_files, expect_stats.num_output_files);
ASSERT_EQ(stats.num_output_files_blob, expect_stats.num_output_files_blob);
ASSERT_EQ(stats.num_input_records, expect_stats.num_input_records);
ASSERT_EQ(stats.num_dropped_records, expect_stats.num_dropped_records);
ASSERT_EQ(stats.num_output_records, expect_stats.num_output_records);
ASSERT_EQ(stats.count, expect_stats.count);
for (int i = 0; i < static_cast<int>(CompactionReason::kNumOfReasons);
i++) {
ASSERT_EQ(stats.counts[i], expect_stats.counts[i]);
}
}
void VerifyCompactionJobStats(const CompactionJobStats& stats,
const CompactionJobStats& expected_stats) {
ASSERT_EQ(stats.cpu_micros, expected_stats.cpu_micros);
ASSERT_EQ(stats.num_input_files, expected_stats.num_input_files);
ASSERT_EQ(stats.num_input_records, expected_stats.num_input_records);
ASSERT_EQ(job_stats.num_output_files, expected_stats.num_output_files);
ASSERT_EQ(job_stats.num_output_records, expected_stats.num_output_records);
}
};
TEST_F(TieredCompactionTest, SequenceBasedTieredStorageUniversal) {
@@ -199,19 +195,39 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageUniversal) {
[&](void* arg) {
*static_cast<SequenceNumber*>(arg) = latest_cold_seq.load();
});
SyncPoint::GetInstance()->SetCallBack(
"CompactionJob::Install:AfterUpdateCompactionJobStats", [&](void* arg) {
job_stats.Reset();
job_stats.Add(*(static_cast<CompactionJobStats*>(arg)));
});
SyncPoint::GetInstance()->EnableProcessing();
std::vector<InternalStats::CompactionStats> expect_stats(kNumLevels);
InternalStats::CompactionStats& last_stats = expect_stats[kLastLevel];
InternalStats::CompactionStats expect_pl_stats;
// Put keys in the following way to create overlaps
// First file from 0 ~ 99
// Second file from 10 ~ 109
// ...
size_t bytes_per_file = 1952;
uint64_t total_input_key_count = kNumTrigger * kNumKeys;
uint64_t total_output_key_count = 130; // 0 ~ 129
for (int i = 0; i < kNumTrigger; i++) {
for (int j = 0; j < kNumKeys; j++) {
ASSERT_OK(Put(Key(i * 10 + j), "value" + std::to_string(i)));
}
ASSERT_OK(Flush());
seq_history.emplace_back(dbfull()->GetLatestSequenceNumber());
expect_stats[0].Add(kBasicFlushStats);
InternalStats::CompactionStats flush_stats(CompactionReason::kFlush, 1);
flush_stats.cpu_micros = 1;
flush_stats.micros = 1;
flush_stats.bytes_written = bytes_per_file;
flush_stats.num_output_files = 1;
flush_stats.num_input_records = kNumKeys;
flush_stats.num_output_records = kNumKeys;
expect_stats[0].Add(flush_stats);
}
ASSERT_OK(dbfull()->TEST_WaitForCompact());
@@ -221,32 +237,97 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageUniversal) {
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
// basic compaction stats are still counted to the last level
expect_stats[kLastLevel].Add(kBasicCompStats);
expect_pl_stats.Add(kBasicPerKeyPlacementCompStats);
uint64_t bytes_written_penultimate_level =
GetPerKeyPlacementCompactionStats().bytes_written;
VerifyCompactionStats(expect_stats, expect_pl_stats);
// TODO - Use designated initializer when c++20 support is required
{
InternalStats::CompactionStats last_level_compaction_stats(
CompactionReason::kUniversalSizeAmplification, 1);
last_level_compaction_stats.cpu_micros = 1;
last_level_compaction_stats.micros = 1;
last_level_compaction_stats.bytes_written = 0;
last_level_compaction_stats.bytes_read_non_output_levels =
bytes_per_file * kNumTrigger;
last_level_compaction_stats.num_input_files_in_non_output_levels =
kNumTrigger;
last_level_compaction_stats.num_input_records = total_input_key_count;
last_level_compaction_stats.num_dropped_records =
total_input_key_count - total_output_key_count;
last_level_compaction_stats.num_output_records = 0;
last_level_compaction_stats.num_output_files = 0;
expect_stats[kLastLevel].Add(last_level_compaction_stats);
}
{
InternalStats::CompactionStats penultimate_level_compaction_stats(
CompactionReason::kUniversalSizeAmplification, 1);
penultimate_level_compaction_stats.cpu_micros = 1;
penultimate_level_compaction_stats.micros = 1;
penultimate_level_compaction_stats.bytes_written =
bytes_written_penultimate_level;
penultimate_level_compaction_stats.num_output_files = 1;
penultimate_level_compaction_stats.num_output_records =
total_output_key_count;
expect_pl_stats.Add(penultimate_level_compaction_stats);
}
VerifyCompactionStats(expect_stats, expect_pl_stats, kLastLevel);
ResetAllStats(expect_stats, expect_pl_stats);
// move forward the cold_seq to split the file into 2 levels, so should have
// both the last level stats and the output_to_penultimate_level stats
// both the last level stats and the penultimate level stats
latest_cold_seq = seq_history[0];
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ASSERT_EQ("0,0,0,0,0,1,1", FilesPerLevel());
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
last_stats.Add(kBasicCompStats);
last_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
last_stats.Add(kBasicPerLevelStats);
last_stats.num_dropped_records = 0;
expect_pl_stats.Add(kBasicPerKeyPlacementCompStats);
expect_pl_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
VerifyCompactionStats(expect_stats, expect_pl_stats);
// Now update the input count to be the total count from the previous
total_input_key_count = total_output_key_count;
uint64_t moved_to_last_level_key_count = 10;
// delete all cold data, so all data will be on penultimate level
// bytes read in non output = bytes written in penultimate level from previous
uint64_t bytes_read_in_non_output_level = bytes_written_penultimate_level;
uint64_t bytes_written_output_level =
GetCompactionStats()[kLastLevel].bytes_written;
// Now get the new bytes written in penultimate level
bytes_written_penultimate_level =
GetPerKeyPlacementCompactionStats().bytes_written;
{
InternalStats::CompactionStats last_level_compaction_stats(
CompactionReason::kManualCompaction, 1);
last_level_compaction_stats.cpu_micros = 1;
last_level_compaction_stats.micros = 1;
last_level_compaction_stats.bytes_written = bytes_written_output_level;
last_level_compaction_stats.bytes_read_non_output_levels =
bytes_read_in_non_output_level;
last_level_compaction_stats.num_input_files_in_non_output_levels = 1;
last_level_compaction_stats.num_input_records = total_input_key_count;
last_level_compaction_stats.num_dropped_records =
total_input_key_count - total_output_key_count;
last_level_compaction_stats.num_output_records =
moved_to_last_level_key_count;
last_level_compaction_stats.num_output_files = 1;
expect_stats[kLastLevel].Add(last_level_compaction_stats);
}
{
InternalStats::CompactionStats penultimate_level_compaction_stats(
CompactionReason::kManualCompaction, 1);
penultimate_level_compaction_stats.cpu_micros = 1;
penultimate_level_compaction_stats.micros = 1;
penultimate_level_compaction_stats.bytes_written =
bytes_written_penultimate_level;
penultimate_level_compaction_stats.num_output_files = 1;
penultimate_level_compaction_stats.num_output_records =
total_output_key_count - moved_to_last_level_key_count;
expect_pl_stats.Add(penultimate_level_compaction_stats);
}
VerifyCompactionStats(expect_stats, expect_pl_stats, kLastLevel);
// delete all cold data, so all data will be on proximal level
for (int i = 0; i < 10; i++) {
ASSERT_OK(Delete(Key(i)));
}
@@ -255,17 +336,54 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageUniversal) {
ResetAllStats(expect_stats, expect_pl_stats);
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ASSERT_EQ("0,0,0,0,0,1", FilesPerLevel());
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
last_stats.Add(kBasicCompStats);
last_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
last_stats.bytes_read_output_level = kHasValue;
last_stats.num_input_files_in_output_level = kHasValue;
expect_pl_stats.Add(kBasicPerKeyPlacementCompStats);
expect_pl_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
VerifyCompactionStats(expect_stats, expect_pl_stats);
// 10 tombstones added
total_input_key_count = total_input_key_count + 10;
total_output_key_count = total_output_key_count - 10;
auto last_level_stats = GetCompactionStats()[kLastLevel];
bytes_written_penultimate_level =
GetPerKeyPlacementCompactionStats().bytes_written;
ASSERT_LT(bytes_written_penultimate_level,
last_level_stats.bytes_read_non_output_levels +
last_level_stats.bytes_read_output_level);
{
InternalStats::CompactionStats last_level_compaction_stats(
CompactionReason::kManualCompaction, 1);
last_level_compaction_stats.cpu_micros = 1;
last_level_compaction_stats.micros = 1;
last_level_compaction_stats.bytes_written = 0;
last_level_compaction_stats.bytes_read_non_output_levels =
last_level_stats.bytes_read_non_output_levels;
last_level_compaction_stats.bytes_read_output_level =
last_level_stats.bytes_read_output_level;
last_level_compaction_stats.num_input_files_in_non_output_levels = 2;
last_level_compaction_stats.num_input_files_in_output_level = 1;
last_level_compaction_stats.num_input_records = total_input_key_count;
last_level_compaction_stats.num_dropped_records =
total_input_key_count - total_output_key_count;
last_level_compaction_stats.num_output_records = 0;
last_level_compaction_stats.num_output_files = 0;
expect_stats[kLastLevel].Add(last_level_compaction_stats);
}
{
InternalStats::CompactionStats penultimate_level_compaction_stats(
CompactionReason::kManualCompaction, 1);
penultimate_level_compaction_stats.cpu_micros = 1;
penultimate_level_compaction_stats.micros = 1;
penultimate_level_compaction_stats.bytes_written =
bytes_written_penultimate_level;
penultimate_level_compaction_stats.num_output_files = 1;
penultimate_level_compaction_stats.num_output_records =
total_output_key_count;
expect_pl_stats.Add(penultimate_level_compaction_stats);
}
VerifyCompactionStats(expect_stats, expect_pl_stats, kLastLevel);
// move forward the cold_seq again with range delete, take a snapshot to keep
// the range dels in both cold and hot SSTs
@@ -275,6 +393,7 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageUniversal) {
ASSERT_OK(
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), start, end));
ASSERT_OK(Flush());
uint64_t num_input_range_del = 1;
ResetAllStats(expect_stats, expect_pl_stats);
@@ -283,12 +402,49 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageUniversal) {
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
last_stats.Add(kBasicCompStats);
last_stats.Add(kBasicPerLevelStats);
last_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
expect_pl_stats.Add(kBasicPerKeyPlacementCompStats);
expect_pl_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
VerifyCompactionStats(expect_stats, expect_pl_stats);
// Previous output + one delete range
total_input_key_count = total_output_key_count + num_input_range_del;
moved_to_last_level_key_count = 20;
last_level_stats = GetCompactionStats()[kLastLevel];
bytes_written_penultimate_level =
GetPerKeyPlacementCompactionStats().bytes_written;
// Expected to write more in last level
ASSERT_GT(bytes_written_penultimate_level, last_level_stats.bytes_written);
{
InternalStats::CompactionStats last_level_compaction_stats(
CompactionReason::kManualCompaction, 1);
last_level_compaction_stats.cpu_micros = 1;
last_level_compaction_stats.micros = 1;
last_level_compaction_stats.bytes_written = last_level_stats.bytes_written;
last_level_compaction_stats.bytes_read_non_output_levels =
last_level_stats.bytes_read_non_output_levels;
last_level_compaction_stats.bytes_read_output_level = 0;
last_level_compaction_stats.num_input_files_in_non_output_levels = 2;
last_level_compaction_stats.num_input_files_in_output_level = 0;
last_level_compaction_stats.num_input_records = total_input_key_count;
last_level_compaction_stats.num_dropped_records =
num_input_range_del; // delete range tombstone
last_level_compaction_stats.num_output_records =
moved_to_last_level_key_count;
last_level_compaction_stats.num_output_files = 1;
expect_stats[kLastLevel].Add(last_level_compaction_stats);
}
{
InternalStats::CompactionStats penultimate_level_compaction_stats(
CompactionReason::kManualCompaction, 1);
penultimate_level_compaction_stats.cpu_micros = 1;
penultimate_level_compaction_stats.micros = 1;
penultimate_level_compaction_stats.bytes_written =
bytes_written_penultimate_level;
penultimate_level_compaction_stats.num_output_files = 1;
penultimate_level_compaction_stats.num_output_records =
total_input_key_count - moved_to_last_level_key_count -
num_input_range_del;
expect_pl_stats.Add(penultimate_level_compaction_stats);
}
VerifyCompactionStats(expect_stats, expect_pl_stats, kLastLevel,
num_input_range_del);
// verify data
std::string value;
@@ -341,11 +497,11 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageUniversal) {
// This test was essentially for a hacked-up version on future functionality.
// It can be resurrected if/when a form of range-based tiering is properly
// implemented.
// TODO - Add stats verification when adding this test back
TEST_F(TieredCompactionTest, DISABLED_RangeBasedTieredStorageUniversal) {
const int kNumTrigger = 4;
const int kNumLevels = 7;
const int kNumKeys = 100;
const int kLastLevel = kNumLevels - 1;
auto options = CurrentOptions();
options.compaction_style = kCompactionStyleUniversal;
@@ -364,14 +520,13 @@ TEST_F(TieredCompactionTest, DISABLED_RangeBasedTieredStorageUniversal) {
"CompactionIterator::PrepareOutput.context", [&](void* arg) {
auto context = static_cast<PerKeyPlacementContext*>(arg);
MutexLock l(&mutex);
context->output_to_penultimate_level =
context->output_to_proximal_level =
cmp->Compare(context->key, hot_start) >= 0 &&
cmp->Compare(context->key, hot_end) < 0;
});
SyncPoint::GetInstance()->EnableProcessing();
std::vector<InternalStats::CompactionStats> expect_stats(kNumLevels);
InternalStats::CompactionStats& last_stats = expect_stats[kLastLevel];
InternalStats::CompactionStats expect_pl_stats;
for (int i = 0; i < kNumTrigger; i++) {
@@ -379,21 +534,15 @@ TEST_F(TieredCompactionTest, DISABLED_RangeBasedTieredStorageUniversal) {
ASSERT_OK(Put(Key(j), "value" + std::to_string(j)));
}
ASSERT_OK(Flush());
expect_stats[0].Add(kBasicFlushStats);
}
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ASSERT_EQ("0,0,0,0,0,1,1", FilesPerLevel());
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
last_stats.Add(kBasicCompStats);
last_stats.Add(kBasicPerLevelStats);
expect_pl_stats.Add(kBasicPerKeyPlacementCompStats);
VerifyCompactionStats(expect_stats, expect_pl_stats);
ResetAllStats(expect_stats, expect_pl_stats);
// change to all cold, no output_to_penultimate_level output
// change to all cold, no output_to_proximal_level output
{
MutexLock l(&mutex);
hot_start = Key(100);
@@ -404,14 +553,6 @@ TEST_F(TieredCompactionTest, DISABLED_RangeBasedTieredStorageUniversal) {
ASSERT_EQ(GetSstSizeHelper(Temperature::kUnknown), 0);
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
last_stats.Add(kBasicCompStats);
last_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
last_stats.Add(kBasicPerLevelStats);
last_stats.num_dropped_records = 0;
last_stats.bytes_read_output_level = kHasValue;
last_stats.num_input_files_in_output_level = kHasValue;
VerifyCompactionStats(expect_stats, expect_pl_stats);
// change to all hot, universal compaction support moving data to up level if
// it's within compaction level range.
{
@@ -421,7 +562,7 @@ TEST_F(TieredCompactionTest, DISABLED_RangeBasedTieredStorageUniversal) {
}
// No data is moved from cold tier to hot tier because no input files from L5
// or higher, it's not safe to move data to output_to_penultimate_level level.
// or higher, it's not safe to move data to output_to_proximal_level level.
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_EQ("0,0,0,0,0,1", FilesPerLevel());
@@ -567,7 +708,7 @@ TEST_F(TieredCompactionTest, LevelColdRangeDelete) {
// 20->30 will be marked as cold data, but it cannot be placed to cold tier
// (bottommost) otherwise, it will be "deleted" by the range del in
// output_to_penultimate_level level verify that these data will be able to
// output_to_proximal_level level verify that these data will be able to
// queried
for (int i = 20; i < 30; i++) {
ASSERT_OK(Put(Key(i), "value" + std::to_string(i)));
@@ -677,17 +818,17 @@ TEST_F(TieredCompactionTest, LevelOutofBoundaryRangeDelete) {
std::vector<std::vector<FileMetaData>> level_to_files;
dbfull()->TEST_GetFilesMetaData(dbfull()->DefaultColumnFamily(),
&level_to_files);
// range tombstone is in the penultimate level
const int penultimate_level = kNumLevels - 2;
ASSERT_EQ(level_to_files[penultimate_level].size(), 1);
ASSERT_EQ(level_to_files[penultimate_level][0].num_entries, 1);
ASSERT_EQ(level_to_files[penultimate_level][0].num_deletions, 1);
ASSERT_EQ(level_to_files[penultimate_level][0].temperature,
// range tombstone is in the proximal level
const int proximal_level = kNumLevels - 2;
ASSERT_EQ(level_to_files[proximal_level].size(), 1);
ASSERT_EQ(level_to_files[proximal_level][0].num_entries, 1);
ASSERT_EQ(level_to_files[proximal_level][0].num_deletions, 1);
ASSERT_EQ(level_to_files[proximal_level][0].temperature,
Temperature::kUnknown);
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
ASSERT_EQ("0,1,10",
FilesPerLevel()); // one file is at the penultimate level which
FilesPerLevel()); // one file is at the proximal level which
// only contains a range delete
// Add 2 hot keys, each is a new SST, they will be placed in the same level as
@@ -701,7 +842,7 @@ TEST_F(TieredCompactionTest, LevelOutofBoundaryRangeDelete) {
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
ASSERT_EQ("0,2,10",
FilesPerLevel()); // one file is at the penultimate level
FilesPerLevel()); // one file is at the proximal level
// which only contains a range delete
std::vector<LiveFileMetaData> live_file_meta;
db_->GetLiveFilesMetaData(&live_file_meta);
@@ -711,7 +852,7 @@ TEST_F(TieredCompactionTest, LevelOutofBoundaryRangeDelete) {
if (meta.num_deletions > 0) {
// found SST with del, which has 2 entries, one for data one for range del
ASSERT_EQ(meta.level,
kNumLevels - 2); // output to penultimate level
kNumLevels - 2); // output to proximal level
ASSERT_EQ(meta.num_entries, 2);
ASSERT_EQ(meta.num_deletions, 1);
found_sst_with_del = true;
@@ -722,7 +863,7 @@ TEST_F(TieredCompactionTest, LevelOutofBoundaryRangeDelete) {
// release the first snapshot and compact, which should compact the range del
// but new inserted key `0` and `6` are still hot data which will be placed on
// the penultimate level
// the proximal level
db_->ReleaseSnapshot(snap);
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
ASSERT_EQ("0,2,7", FilesPerLevel());
@@ -738,7 +879,7 @@ TEST_F(TieredCompactionTest, LevelOutofBoundaryRangeDelete) {
ASSERT_FALSE(found_sst_with_del);
// Now make all data cold, key 0 will be moved to the last level, but key 6 is
// still in snap2, so it will be kept at the penultimate level
// still in snap2, so it will be kept at the proximal level
latest_cold_seq = dbfull()->GetLatestSequenceNumber();
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
ASSERT_EQ("0,1,8", FilesPerLevel());
@@ -783,7 +924,7 @@ TEST_F(TieredCompactionTest, UniversalRangeDelete) {
}
ASSERT_OK(Flush());
// compact to the penultimate level with 10 files
// compact to the proximal level with 10 files
CompactRangeOptions cro;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
@@ -810,7 +951,7 @@ TEST_F(TieredCompactionTest, UniversalRangeDelete) {
ASSERT_EQ("0,0,0,0,0,0,8", FilesPerLevel());
// range del with snapshot should be preserved in the penultimate level
// range del with snapshot should be preserved in the proximal level
auto snap = db_->GetSnapshot();
start = Key(6);
@@ -841,7 +982,7 @@ TEST_F(TieredCompactionTest, UniversalRangeDelete) {
if (meta.num_deletions > 0) {
// found SST with del, which has 2 entries, one for data one for range del
ASSERT_EQ(meta.level,
kNumLevels - 2); // output_to_penultimate_level level
kNumLevels - 2); // output_to_proximal_level level
ASSERT_EQ(meta.num_entries, 2);
ASSERT_EQ(meta.num_deletions, 1);
found_sst_with_del = true;
@@ -890,6 +1031,8 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageLevel) {
const int kNumKeys = 100;
const int kLastLevel = kNumLevels - 1;
int output_level = 0;
auto options = CurrentOptions();
SetColdTemperature(options);
options.level0_file_num_compaction_trigger = kNumTrigger;
@@ -906,18 +1049,42 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageLevel) {
[&](void* arg) {
*static_cast<SequenceNumber*>(arg) = latest_cold_seq.load();
});
SyncPoint::GetInstance()->SetCallBack(
"CompactionJob::Install:AfterUpdateCompactionJobStats", [&](void* arg) {
job_stats.Reset();
job_stats.Add(*(static_cast<CompactionJobStats*>(arg)));
});
SyncPoint::GetInstance()->SetCallBack(
"CompactionJob::ProcessKeyValueCompaction()::Processing", [&](void* arg) {
auto compaction = static_cast<Compaction*>(arg);
output_level = compaction->output_level();
});
SyncPoint::GetInstance()->EnableProcessing();
std::vector<InternalStats::CompactionStats> expect_stats(kNumLevels);
InternalStats::CompactionStats& last_stats = expect_stats[kLastLevel];
InternalStats::CompactionStats expect_pl_stats;
// Put keys in the following way to create overlaps
// First file from 0 ~ 99
// Second file from 10 ~ 109
// ...
size_t bytes_per_file = 1952;
uint64_t total_input_key_count = kNumTrigger * kNumKeys;
uint64_t total_output_key_count = 130; // 0 ~ 129
for (int i = 0; i < kNumTrigger; i++) {
for (int j = 0; j < kNumKeys; j++) {
ASSERT_OK(Put(Key(i * 10 + j), "value" + std::to_string(i)));
}
ASSERT_OK(Flush());
expect_stats[0].Add(kBasicFlushStats);
InternalStats::CompactionStats flush_stats(CompactionReason::kFlush, 1);
flush_stats.cpu_micros = 1;
flush_stats.micros = 1;
flush_stats.bytes_written = bytes_per_file;
flush_stats.num_output_files = 1;
flush_stats.num_input_records = kNumKeys;
flush_stats.num_output_records = kNumKeys;
expect_stats[0].Add(flush_stats);
}
ASSERT_OK(dbfull()->TEST_WaitForCompact());
@@ -926,10 +1093,30 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageLevel) {
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
expect_stats[1].Add(kBasicCompStats);
expect_stats[1].Add(kBasicPerLevelStats);
expect_stats[1].ResetCompactionReason(CompactionReason::kLevelL0FilesNum);
VerifyCompactionStats(expect_stats, expect_pl_stats);
uint64_t bytes_written_output_level =
GetCompactionStats()[output_level].bytes_written;
ASSERT_GT(bytes_written_output_level, 0);
{
InternalStats::CompactionStats output_level_compaction_stats(
CompactionReason::kLevelL0FilesNum, 1);
output_level_compaction_stats.cpu_micros = 1;
output_level_compaction_stats.micros = 1;
output_level_compaction_stats.bytes_written = bytes_written_output_level;
output_level_compaction_stats.bytes_read_non_output_levels =
bytes_per_file * kNumTrigger;
output_level_compaction_stats.bytes_read_output_level = 0;
output_level_compaction_stats.num_input_files_in_non_output_levels =
kNumTrigger;
output_level_compaction_stats.num_input_files_in_output_level = 0;
output_level_compaction_stats.num_input_records = total_input_key_count;
output_level_compaction_stats.num_dropped_records =
total_input_key_count - total_output_key_count;
output_level_compaction_stats.num_output_records = total_output_key_count;
output_level_compaction_stats.num_output_files = 1;
expect_stats[output_level].Add(output_level_compaction_stats);
}
VerifyCompactionStats(expect_stats, expect_pl_stats, output_level);
// move all data to the last level
MoveFilesToLevel(kLastLevel);
@@ -944,15 +1131,26 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageLevel) {
ASSERT_EQ(GetSstSizeHelper(Temperature::kUnknown), 0);
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
last_stats.Add(kBasicCompStats);
last_stats.Add(kBasicPerLevelStats);
last_stats.num_dropped_records = 0;
last_stats.bytes_read_non_output_levels = 0;
last_stats.num_input_files_in_non_output_levels = 0;
last_stats.bytes_read_output_level = kHasValue;
last_stats.num_input_files_in_output_level = kHasValue;
last_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
VerifyCompactionStats(expect_stats, expect_pl_stats);
total_input_key_count = total_output_key_count;
{
InternalStats::CompactionStats output_level_compaction_stats(
CompactionReason::kManualCompaction, 1);
output_level_compaction_stats.cpu_micros = 1;
output_level_compaction_stats.micros = 1;
output_level_compaction_stats.bytes_written = bytes_written_output_level;
output_level_compaction_stats.bytes_read_non_output_levels = 0;
output_level_compaction_stats.bytes_read_output_level =
bytes_written_output_level;
output_level_compaction_stats.num_input_files_in_non_output_levels = 0;
output_level_compaction_stats.num_input_files_in_output_level = 1;
output_level_compaction_stats.num_input_records = total_input_key_count;
output_level_compaction_stats.num_dropped_records =
total_input_key_count - total_output_key_count;
output_level_compaction_stats.num_output_records = total_output_key_count;
output_level_compaction_stats.num_output_files = 1;
expect_stats[output_level].Add(output_level_compaction_stats);
}
VerifyCompactionStats(expect_stats, expect_pl_stats, output_level);
// Add new data, which is all hot and overriding all existing data
latest_cold_seq = dbfull()->GetLatestSequenceNumber();
@@ -976,17 +1174,47 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageLevel) {
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
uint64_t bytes_written_in_proximal_level =
GetPerKeyPlacementCompactionStats().bytes_written;
for (int level = 2; level < kNumLevels - 1; level++) {
expect_stats[level].bytes_moved = kHasValue;
expect_stats[level].bytes_moved = bytes_written_in_proximal_level;
}
last_stats.Add(kBasicCompStats);
last_stats.bytes_read_output_level = kHasValue;
last_stats.num_input_files_in_output_level = kHasValue;
last_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
expect_pl_stats.Add(kBasicPerKeyPlacementCompStats);
expect_pl_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
VerifyCompactionStats(expect_stats, expect_pl_stats);
// Another set of 130 keys + from the previous
total_input_key_count = total_output_key_count + 130;
// Merged into 130
total_output_key_count = 130;
{
InternalStats::CompactionStats output_level_compaction_stats(
CompactionReason::kManualCompaction, 1);
output_level_compaction_stats.cpu_micros = 1;
output_level_compaction_stats.micros = 1;
output_level_compaction_stats.bytes_written = 0;
output_level_compaction_stats.bytes_read_non_output_levels =
bytes_written_in_proximal_level;
output_level_compaction_stats.bytes_read_output_level =
bytes_written_output_level;
output_level_compaction_stats.num_input_files_in_non_output_levels = 1;
output_level_compaction_stats.num_input_files_in_output_level = 1;
output_level_compaction_stats.num_input_records = total_input_key_count;
output_level_compaction_stats.num_dropped_records =
total_input_key_count - total_output_key_count;
output_level_compaction_stats.num_output_records = 0;
output_level_compaction_stats.num_output_files = 0;
expect_stats[output_level].Add(output_level_compaction_stats);
}
{
InternalStats::CompactionStats proximal_level_compaction_stats(
CompactionReason::kManualCompaction, 1);
expect_pl_stats.cpu_micros = 1;
expect_pl_stats.micros = 1;
expect_pl_stats.bytes_written = bytes_written_in_proximal_level;
expect_pl_stats.num_output_files = 1;
expect_pl_stats.num_output_records = total_output_key_count;
expect_pl_stats.Add(proximal_level_compaction_stats);
}
VerifyCompactionStats(expect_stats, expect_pl_stats, output_level);
// move forward the cold_seq, try to split the data into cold and hot, but in
// this case it's unsafe to split the data
@@ -1138,7 +1366,7 @@ TEST_F(TieredCompactionTest, DISABLED_RangeBasedTieredStorageLevel) {
"CompactionIterator::PrepareOutput.context", [&](void* arg) {
auto context = static_cast<PerKeyPlacementContext*>(arg);
MutexLock l(&mutex);
context->output_to_penultimate_level =
context->output_to_proximal_level =
cmp->Compare(context->key, hot_start) >= 0 &&
cmp->Compare(context->key, hot_end) < 0;
});
@@ -1221,10 +1449,10 @@ TEST_F(TieredCompactionTest, DISABLED_RangeBasedTieredStorageLevel) {
options.statistics->getTickerCount(COMPACTION_RANGE_DEL_DROP_OBSOLETE),
1);
// Tests that we only compact keys up to penultimate level
// that are within penultimate level input's internal key range.
// UPDATE: this functionality has changed. With penultimate-enabled
// compaction, the expanded potential output range in the penultimate
// Tests that we only compact keys up to proximal level
// that are within proximal level input's internal key range.
// UPDATE: this functionality has changed. With proximal-enabled
// compaction, the expanded potential output range in the proximal
// level is reserved so should be safe to use.
{
MutexLock l(&mutex);
@@ -1376,7 +1604,7 @@ TEST_P(PrecludeLastLevelTest, MigrationFromPreserveTimeManualCompaction) {
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
// all data is moved up to the penultimate level
// all data is moved up to the proximal level
ASSERT_EQ("0,0,0,0,0,1", FilesPerLevel());
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
@@ -1448,7 +1676,7 @@ TEST_P(PrecludeLastLevelTest, MigrationFromPreserveTimeAutoCompaction) {
ASSERT_OK(dbfull()->TEST_WaitForCompact());
}
// all data is moved up to the penultimate level
// all data is moved up to the proximal level
ASSERT_EQ("0,0,0,0,0,1", FilesPerLevel());
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
@@ -1489,8 +1717,7 @@ TEST_P(PrecludeLastLevelTest, MigrationFromPreserveTimePartial) {
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
std::vector<KeyVersion> key_versions;
ASSERT_OK(GetAllKeyVersions(db_, Slice(), Slice(),
std::numeric_limits<size_t>::max(),
ASSERT_OK(GetAllKeyVersions(db_, {}, {}, std::numeric_limits<size_t>::max(),
&key_versions));
// make sure there're more than 300 keys and first 100 keys are having seqno
@@ -1579,9 +1806,9 @@ TEST_P(PrecludeLastLevelTest, SmallPrecludeTime) {
}
TEST_P(PrecludeLastLevelTest, CheckInternalKeyRange) {
// When compacting keys from the last level to penultimate level,
// output to penultimate level should be within internal key range
// of input files from penultimate level.
// When compacting keys from the last level to proximal level,
// output to proximal level should be within internal key range
// of input files from proximal level.
// Set up:
// L5:
// File 1: DeleteRange[1, 3)@4, File 2: [3@5, 100@6]
@@ -1719,8 +1946,8 @@ TEST_P(PrecludeWithCompactStyleTest, RangeTombstoneSnapshotMigrateFromLast) {
ApplyConfigChange(&options, {{"preclude_last_level_data_seconds", "10000"}});
// To exercise the WithinPenultimateLevelOutputRange feature, we want files
// around the middle file to be compacted on the penultimate level
// To exercise the WithinProximalLevelOutputRange feature, we want files
// around the middle file to be compacted on the proximal level
ASSERT_OK(Put(Key(0), "val0"));
ASSERT_OK(Flush());
ASSERT_OK(Put(Key(3), "val3"));
@@ -1777,9 +2004,9 @@ TEST_P(PrecludeWithCompactStyleTest, RangeTombstoneSnapshotMigrateFromLast) {
EXPECT_EQ("0,0,0,0,0,3,1", FilesPerLevel());
VerifyLogicalState(__LINE__);
// Compact everything, but some data still goes to both penultimate and last
// Compact everything, but some data still goes to both proximal and last
// levels. A full-range compaction should be safe to "migrate" data from the
// last level to penultimate (because of preclude setting change).
// last level to proximal (because of preclude setting change).
ASSERT_OK(CompactRange({}, {}, {}));
EXPECT_EQ("0,0,0,0,0,1,1", FilesPerLevel());
VerifyLogicalState(__LINE__);
@@ -1898,7 +2125,7 @@ TEST_P(TimedPutPrecludeLastLevelTest, InterleavedTimedPutAndPut) {
Close();
}
TEST_P(TimedPutPrecludeLastLevelTest, PreserveTimedPutOnPenultimateLevel) {
TEST_P(TimedPutPrecludeLastLevelTest, PreserveTimedPutOnProximalLevel) {
Options options = CurrentOptions();
options.compaction_style = kCompactionStyleUniversal;
options.disable_auto_compactions = true;
@@ -1924,14 +2151,14 @@ TEST_P(TimedPutPrecludeLastLevelTest, PreserveTimedPutOnPenultimateLevel) {
ASSERT_OK(TimedPut(0, Key(2), "v2", kMockStartTime - 1 * 24 * 60 * 60, wo));
ASSERT_OK(Flush());
// Should still be in penultimate level.
// Should still be in proximal level.
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_EQ("0,0,0,0,0,1", FilesPerLevel());
ASSERT_GT(GetSstSizeHelper(Temperature::kHot), 0);
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
// Wait one more day and release snapshot. Data's preferred seqno should be
// swapped in, but data should still stay in penultimate level. SST file's
// swapped in, but data should still stay in proximal level. SST file's
// seqno to time mapping should continue to cover preferred seqno after
// compaction.
db_->ReleaseSnapshot(snap1);
@@ -2079,8 +2306,7 @@ TEST_P(PrecludeLastLevelTest, LastLevelOnlyCompactionPartial) {
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
std::vector<KeyVersion> key_versions;
ASSERT_OK(GetAllKeyVersions(db_, Slice(), Slice(),
std::numeric_limits<size_t>::max(),
ASSERT_OK(GetAllKeyVersions(db_, {}, {}, std::numeric_limits<size_t>::max(),
&key_versions));
// make sure there're more than 300 keys and first 100 keys are having seqno
@@ -2253,13 +2479,13 @@ TEST_P(PrecludeLastLevelOptionalTest, LastLevelOnlyCompactionNoPreclude) {
Close();
}
TEST_P(PrecludeLastLevelOptionalTest, PeriodicCompactionToPenultimateLevel) {
TEST_P(PrecludeLastLevelOptionalTest, PeriodicCompactionToProximalLevel) {
// Test the last level only periodic compaction should also be blocked by an
// ongoing compaction in penultimate level if tiered compaction is enabled
// ongoing compaction in proximal level if tiered compaction is enabled
// otherwise, the periodic compaction should just run for the last level.
const int kNumTrigger = 4;
const int kNumLevels = 7;
const int kPenultimateLevel = kNumLevels - 2;
const int kProximalLevel = kNumLevels - 2;
const int kKeyPerSec = 1;
const int kNumKeys = 100;
@@ -2301,13 +2527,13 @@ TEST_P(PrecludeLastLevelOptionalTest, PeriodicCompactionToPenultimateLevel) {
SyncPoint::GetInstance()->SetCallBack(
"CompactionJob::ProcessKeyValueCompaction()::Processing", [&](void* arg) {
auto compaction = static_cast<Compaction*>(arg);
if (compaction->output_level() == kPenultimateLevel) {
if (compaction->output_level() == kProximalLevel) {
is_size_ratio_compaction_running = true;
TEST_SYNC_POINT(
"PrecludeLastLevelTest::PeriodicCompactionToPenultimateLevel:"
"PrecludeLastLevelTest::PeriodicCompactionToProximalLevel:"
"SizeRatioCompaction1");
TEST_SYNC_POINT(
"PrecludeLastLevelTest::PeriodicCompactionToPenultimateLevel:"
"PrecludeLastLevelTest::PeriodicCompactionToProximalLevel:"
"SizeRatioCompaction2");
is_size_ratio_compaction_running = false;
}
@@ -2329,17 +2555,17 @@ TEST_P(PrecludeLastLevelOptionalTest, PeriodicCompactionToPenultimateLevel) {
verified_last_level_compaction = true;
}
TEST_SYNC_POINT(
"PrecludeLastLevelTest::PeriodicCompactionToPenultimateLevel:"
"PrecludeLastLevelTest::PeriodicCompactionToProximalLevel:"
"AutoCompactionPicked");
});
SyncPoint::GetInstance()->LoadDependency({
{"PrecludeLastLevelTest::PeriodicCompactionToPenultimateLevel:"
{"PrecludeLastLevelTest::PeriodicCompactionToProximalLevel:"
"SizeRatioCompaction1",
"PrecludeLastLevelTest::PeriodicCompactionToPenultimateLevel:DoneWrite"},
{"PrecludeLastLevelTest::PeriodicCompactionToPenultimateLevel:"
"PrecludeLastLevelTest::PeriodicCompactionToProximalLevel:DoneWrite"},
{"PrecludeLastLevelTest::PeriodicCompactionToProximalLevel:"
"AutoCompactionPicked",
"PrecludeLastLevelTest::PeriodicCompactionToPenultimateLevel:"
"PrecludeLastLevelTest::PeriodicCompactionToProximalLevel:"
"SizeRatioCompaction2"},
});
@@ -2356,11 +2582,11 @@ TEST_P(PrecludeLastLevelOptionalTest, PeriodicCompactionToPenultimateLevel) {
}
TEST_SYNC_POINT(
"PrecludeLastLevelTest::PeriodicCompactionToPenultimateLevel:DoneWrite");
"PrecludeLastLevelTest::PeriodicCompactionToProximalLevel:DoneWrite");
// wait for periodic compaction time and flush to trigger the periodic
// compaction, which should be blocked by ongoing compaction in the
// penultimate level
// proximal level
mock_clock_->MockSleepForSeconds(10000);
for (int i = 0; i < 3 * kNumKeys; i++) {
ASSERT_OK(Put(Key(i), rnd.RandomString(10)));
@@ -2423,7 +2649,7 @@ class ThreeRangesPartitionerFactory : public SstPartitionerFactory {
}
};
TEST_P(PrecludeLastLevelTest, PartialPenultimateLevelCompaction) {
TEST_P(PrecludeLastLevelTest, PartialProximalLevelCompaction) {
const int kNumTrigger = 4;
const int kNumLevels = 7;
const int kKeyPerSec = 10;
@@ -2593,8 +2819,8 @@ TEST_P(PrecludeLastLevelTest, RangeDelsCauseFileEndpointsToOverlap) {
"UniversalCompactionBuilder::PickCompaction:Return", [&](void* arg) {
auto compaction = static_cast<Compaction*>(arg);
if (compaction->SupportsPerKeyPlacement()) {
ASSERT_EQ(compaction->GetPenultimateOutputRangeType(),
Compaction::PenultimateOutputRangeType::kNonLastRange);
ASSERT_EQ(compaction->GetProximalOutputRangeType(),
Compaction::ProximalOutputRangeType::kNonLastRange);
per_key_comp_num++;
}
});
@@ -2650,7 +2876,7 @@ TEST_P(PrecludeLastLevelTest, RangeDelsCauseFileEndpointsToOverlap) {
ASSERT_EQ(3, per_key_comp_num);
verify_db();
// Finish off the penultimate level.
// Finish off the proximal level.
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
ASSERT_EQ("0,0,0,0,0,0,3", FilesPerLevel());
verify_db();
+11
View File
@@ -26,6 +26,17 @@ Status DeleteFilesInRange(DB* db, ColumnFamilyHandle* column_family,
Status DeleteFilesInRanges(DB* db, ColumnFamilyHandle* column_family,
const RangePtr* ranges, size_t n, bool include_end) {
std::vector<RangeOpt> range_opts(n);
for (size_t i = 0; i < n; ++i) {
range_opts[i] = {OptSlice::CopyFromPtr(ranges[i].start),
OptSlice::CopyFromPtr(ranges[i].limit)};
}
return DeleteFilesInRanges(db, column_family, range_opts.data(), n,
include_end);
}
Status DeleteFilesInRanges(DB* db, ColumnFamilyHandle* column_family,
const RangeOpt* ranges, size_t n, bool include_end) {
return (static_cast_with_check<DBImpl>(db->GetRootDB()))
->DeleteFilesInRanges(column_family, ranges, n, include_end);
}
-1
View File
@@ -863,7 +863,6 @@ TEST_F(CorruptionTest, ParanoidFileChecksOnCompact) {
ASSERT_OK(DB::Open(options, dbname_, &db_));
assert(db_ != nullptr); // suppress false clang-analyze report
Build(100, 2);
// ASSERT_OK(db_->Flush(FlushOptions()));
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
ASSERT_OK(dbi->TEST_FlushMemTable());
mock->SetCorruptionMode(mode);
+104 -14
View File
@@ -161,6 +161,7 @@ TEST_F(DBBasicTest, UniqueSession) {
ASSERT_EQ(sid2, sid3);
DestroyAndReopen(options);
CreateAndReopenWithCF({"goku"}, options);
ASSERT_OK(db_->GetDbSessionId(sid1));
ASSERT_OK(Put("bar", "e1"));
@@ -179,6 +180,7 @@ TEST_F(DBBasicTest, UniqueSession) {
TEST_F(DBBasicTest, ReadOnlyDB) {
ASSERT_OK(Put("foo", "v1"));
ASSERT_OK(Put("bar", "v2"));
ASSERT_OK(Flush());
ASSERT_OK(Put("foo", "v3"));
Close();
@@ -208,10 +210,11 @@ TEST_F(DBBasicTest, ReadOnlyDB) {
auto options = CurrentOptions();
assert(options.env == env_);
ASSERT_OK(ReadOnlyReopen(options));
ASSERT_OK(EnforcedReadOnlyReopen(options));
ASSERT_EQ("v3", Get("foo"));
ASSERT_EQ("v2", Get("bar"));
verify_all_iters();
ASSERT_EQ(Flush().code(), Status::Code::kNotSupported);
Close();
// Reopen and flush memtable.
@@ -219,26 +222,38 @@ TEST_F(DBBasicTest, ReadOnlyDB) {
ASSERT_OK(Flush());
Close();
// Now check keys in read only mode.
ASSERT_OK(ReadOnlyReopen(options));
ASSERT_OK(EnforcedReadOnlyReopen(options));
ASSERT_EQ("v3", Get("foo"));
ASSERT_EQ("v2", Get("bar"));
verify_all_iters();
ASSERT_TRUE(db_->SyncWAL().IsNotSupported());
ASSERT_EQ(db_->SyncWAL().code(), Status::Code::kNotSupported);
// More ops that should fail
std::vector<ColumnFamilyHandle*> cfhs{{}};
ASSERT_EQ(db_->CreateColumnFamily(options, "blah", &cfhs[0]).code(),
Status::Code::kNotSupported);
ASSERT_EQ(db_->CreateColumnFamilies(options, {"blah"}, &cfhs).code(),
Status::Code::kNotSupported);
std::vector<ColumnFamilyDescriptor> cfds;
cfds.push_back({"blah", options});
ASSERT_EQ(db_->CreateColumnFamilies(cfds, &cfhs).code(),
Status::Code::kNotSupported);
}
// TODO akanksha: Update the test to check that combination
// does not actually write to FS (use open read-only with
// CompositeEnvWrapper+ReadOnlyFileSystem).
TEST_F(DBBasicTest, DISABLED_ReadOnlyDBWithWriteDBIdToManifestSet) {
TEST_F(DBBasicTest, ReadOnlyDBWithWriteDBIdToManifestSet) {
auto options = CurrentOptions();
options.write_dbid_to_manifest = false;
DestroyAndReopen(options);
ASSERT_OK(Put("foo", "v1"));
ASSERT_OK(Put("bar", "v2"));
ASSERT_OK(Put("foo", "v3"));
Close();
auto options = CurrentOptions();
options.write_dbid_to_manifest = true;
assert(options.env == env_);
ASSERT_OK(ReadOnlyReopen(options));
ASSERT_OK(EnforcedReadOnlyReopen(options));
std::string db_id1;
ASSERT_OK(db_->GetDbIdentity(db_id1));
ASSERT_EQ("v3", Get("foo"));
@@ -258,7 +273,7 @@ TEST_F(DBBasicTest, DISABLED_ReadOnlyDBWithWriteDBIdToManifestSet) {
ASSERT_OK(Flush());
Close();
// Now check keys in read only mode.
ASSERT_OK(ReadOnlyReopen(options));
ASSERT_OK(EnforcedReadOnlyReopen(options));
ASSERT_EQ("v3", Get("foo"));
ASSERT_EQ("v2", Get("bar"));
ASSERT_TRUE(db_->SyncWAL().IsNotSupported());
@@ -3273,8 +3288,7 @@ TEST_F(DBBasicTest, GetAllKeyVersions) {
ASSERT_OK(Delete(std::to_string(i)));
}
std::vector<KeyVersion> key_versions;
ASSERT_OK(GetAllKeyVersions(db_, Slice(), Slice(),
std::numeric_limits<size_t>::max(),
ASSERT_OK(GetAllKeyVersions(db_, {}, {}, std::numeric_limits<size_t>::max(),
&key_versions));
ASSERT_EQ(kNumInserts + kNumDeletes + kNumUpdates, key_versions.size());
for (size_t i = 0; i < kNumInserts + kNumDeletes + kNumUpdates; i++) {
@@ -3284,7 +3298,7 @@ TEST_F(DBBasicTest, GetAllKeyVersions) {
ASSERT_EQ(key_versions[i].GetTypeName(), "TypeValue");
}
}
ASSERT_OK(GetAllKeyVersions(db_, handles_[0], Slice(), Slice(),
ASSERT_OK(GetAllKeyVersions(db_, handles_[0], {}, {},
std::numeric_limits<size_t>::max(),
&key_versions));
ASSERT_EQ(kNumInserts + kNumDeletes + kNumUpdates, key_versions.size());
@@ -3299,10 +3313,17 @@ TEST_F(DBBasicTest, GetAllKeyVersions) {
for (size_t i = 0; i + 1 != kNumDeletes; ++i) {
ASSERT_OK(Delete(1, std::to_string(i)));
}
ASSERT_OK(GetAllKeyVersions(db_, handles_[1], Slice(), Slice(),
ASSERT_OK(GetAllKeyVersions(db_, handles_[1], {}, {},
std::numeric_limits<size_t>::max(),
&key_versions));
ASSERT_EQ(kNumInserts + kNumDeletes + kNumUpdates - 3, key_versions.size());
// Change from historical behavior: empty key is now interpreted literally as
// a legal key (rather than as a "not present" key)
ASSERT_OK(GetAllKeyVersions(db_, handles_[1], Slice(), Slice(),
std::numeric_limits<size_t>::max(),
&key_versions));
ASSERT_EQ(key_versions.size(), 0);
}
TEST_F(DBBasicTest, ValueTypeString) {
@@ -3354,6 +3375,69 @@ TEST_F(DBBasicTest, MultiGetIOBufferOverrun) {
keys.data(), values.data(), statuses.data(), true);
}
TEST_F(DBBasicTest, MultiGetWithSnapshotsAndPersistedTier) {
Options options = CurrentOptions();
options.create_if_missing = true;
options.atomic_flush = true;
DestroyAndReopen(options);
CreateAndReopenWithCF({"cf1", "cf2"}, options);
// Insert initial data
ASSERT_OK(Put(0, "key1", "value1_cf0"));
ASSERT_OK(Put(1, "key1", "value1_cf1"));
ASSERT_OK(Put(2, "key1", "value1_cf2"));
ASSERT_OK(Flush({0, 1, 2}));
for (auto cf : {0, 1, 2}) {
ASSERT_EQ(1, NumTableFilesAtLevel(0, cf));
}
ASSERT_OK(Put(0, "key1", "value2_cf0"));
ASSERT_OK(Put(1, "key1", "value2_cf1"));
ASSERT_OK(Put(2, "key1", "value2_cf2"));
// Prepare for concurrent atomic flush
std::atomic<bool> flush_done(false);
std::thread flush_thread([&]() {
ASSERT_OK(Flush({0, 1, 2}));
flush_done.store(true);
});
// Perform MultiGet with snapshot and read_tier = kPersistentTier
ReadOptions ro;
const Snapshot* snapshot = db_->GetSnapshot();
ro.snapshot = snapshot;
ro.read_tier = kPersistedTier;
std::string k = "key1";
std::vector<Slice> keys(3, Slice(k));
std::vector<Status> statuses(keys.size());
std::vector<ColumnFamilyHandle*> cfs(keys.size());
std::vector<Slice> new_keys(keys.size());
std::vector<PinnableSlice> pin_values(keys.size());
for (size_t i = 0; i < keys.size(); ++i) {
cfs[i] = handles_[i];
}
db_->MultiGet(ro, cfs.size(), cfs.data(), keys.data(), pin_values.data(),
statuses.data());
for (const auto& s : statuses) {
ASSERT_OK(s);
}
if (pin_values[0] == "value1_cf0") {
// Check if the first value matches expected value
ASSERT_EQ(pin_values[1], "value1_cf1");
ASSERT_EQ(pin_values[2], "value1_cf2");
} else {
// If first value doesn't match, check if we got the updated values
ASSERT_EQ(pin_values[0], "value2_cf0");
ASSERT_EQ(pin_values[1], "value2_cf1");
ASSERT_EQ(pin_values[2], "value2_cf2");
}
flush_thread.join();
db_->ReleaseSnapshot(snapshot);
}
TEST_F(DBBasicTest, IncrementalRecoveryNoCorrupt) {
Options options = CurrentOptions();
DestroyAndReopen(options);
@@ -5002,6 +5086,7 @@ TEST_F(DBBasicTest, DisallowMemtableWrite) {
options_allow.create_if_missing = true;
Options options_disallow = options_allow;
options_disallow.disallow_memtable_writes = true;
options_disallow.paranoid_memory_checks = true;
DestroyAndReopen(options_allow);
// CFs allowing and disallowing memtable write
@@ -5041,6 +5126,11 @@ TEST_F(DBBasicTest, DisallowMemtableWrite) {
EXPECT_EQ(Get(2, "b2"), "2");
EXPECT_EQ(Get(3, "b3"), "NOT_FOUND");
std::unique_ptr<Iterator> iter(
dbfull()->NewIterator(ReadOptions(), handles_[3]));
iter->Seek("a3");
ASSERT_OK(iter->status());
iter.reset();
// When the DB is re-opened with WAL entries for a CF that is newly setting
// disallow_memtable_writes, we detect that and fail the open gracefully.
ASSERT_EQ(TryReopenWithColumnFamilies(
+5
View File
@@ -506,6 +506,8 @@ TEST_P(DBBlockCacheTest1, WarmCacheWithBlocksDuringFlush) {
table_options.prepopulate_block_cache =
BlockBasedTableOptions::PrepopulateBlockCache::kFlushOnly;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
// Include a compression dictionary block
options.compression_opts.max_dict_bytes = 123;
DestroyAndReopen(options);
std::string value(kValueSize, 'a');
@@ -537,6 +539,9 @@ TEST_P(DBBlockCacheTest1, WarmCacheWithBlocksDuringFlush) {
options.statistics->getTickerCount(BLOCK_CACHE_FILTER_HIT));
}
ASSERT_EQ(0, options.statistics->getTickerCount(BLOCK_CACHE_FILTER_MISS));
// Including compression dict
ASSERT_EQ(0, options.statistics->getTickerCount(BLOCK_CACHE_MISS));
}
// Verify compaction not counted
+290 -105
View File
@@ -127,6 +127,19 @@ class DBCompactionTestWithParam
exclusive_manual_compaction_ = std::get<1>(GetParam());
}
class TrivialMoveEventListener : public EventListener {
public:
explicit TrivialMoveEventListener(size_t expected_trivially_moved_files)
: expected_trivially_moved_files_(expected_trivially_moved_files) {}
void OnCompactionBegin(DB* /*db*/, const CompactionJobInfo& ci) override {
ASSERT_EQ(ci.stats.num_input_files_trivially_moved,
expected_trivially_moved_files_);
}
private:
size_t expected_trivially_moved_files_ = 0;
};
// Required if inheriting from testing::WithParamInterface<>
static void SetUpTestCase() {}
static void TearDownTestCase() {}
@@ -1301,6 +1314,9 @@ TEST_P(DBCompactionTestWithParam, TrivialMoveOneFile) {
Options options = CurrentOptions();
options.write_buffer_size = 100000000;
TrivialMoveEventListener* trivial_move_listener =
new TrivialMoveEventListener(1 /*expected_trivially_moved_files*/);
options.listeners.emplace_back(trivial_move_listener);
options.max_subcompactions = max_subcompactions_;
DestroyAndReopen(options);
@@ -1361,6 +1377,10 @@ TEST_P(DBCompactionTestWithParam, TrivialMoveNonOverlappingFiles) {
Options options = CurrentOptions();
options.disable_auto_compactions = true;
// 8 is number of `ranges` that each is a non overlapping file.
TrivialMoveEventListener* trivial_move_listener =
new TrivialMoveEventListener(8 /*expected_trivially_moved_files*/);
options.listeners.emplace_back(trivial_move_listener);
options.write_buffer_size = 10 * 1024 * 1024;
options.max_subcompactions = max_subcompactions_;
@@ -1408,6 +1428,11 @@ TEST_P(DBCompactionTestWithParam, TrivialMoveNonOverlappingFiles) {
trivial_move = 0;
non_trivial_move = 0;
values.clear();
options.listeners.clear();
// Same ranges of files, but now overlapping, trivial move not applicable.
TrivialMoveEventListener* trivial_move_listener2 =
new TrivialMoveEventListener(0 /*expected_trivially_moved_files*/);
options.listeners.emplace_back(trivial_move_listener2);
DestroyAndReopen(options);
// Same ranges as above but overlapping
ranges = {
@@ -1455,6 +1480,11 @@ TEST_P(DBCompactionTestWithParam, TrivialMoveTargetLevel) {
Options options = CurrentOptions();
options.disable_auto_compactions = true;
// Two non overlapping files in L0 trivialy moved:
// file 1 [0 => 300], file 2 [600 => 700]
TrivialMoveEventListener* trivial_move_listener1 =
new TrivialMoveEventListener(2 /*expected_trivially_moved_files*/);
options.listeners.emplace_back(trivial_move_listener1);
options.write_buffer_size = 10 * 1024 * 1024;
options.num_levels = 7;
options.max_subcompactions = max_subcompactions_;
@@ -2087,13 +2117,10 @@ TEST_P(DBDeleteFileRangeTest, DeleteFilesInRanges) {
auto begin_str1 = Key(0), end_str1 = Key(100);
auto begin_str2 = Key(100), end_str2 = Key(200);
auto begin_str3 = Key(200), end_str3 = Key(299);
Slice begin1(begin_str1), end1(end_str1);
Slice begin2(begin_str2), end2(end_str2);
Slice begin3(begin_str3), end3(end_str3);
std::vector<RangePtr> ranges;
ranges.emplace_back(&begin1, &end1);
ranges.emplace_back(&begin2, &end2);
ranges.emplace_back(&begin3, &end3);
std::vector<RangeOpt> ranges;
ranges.emplace_back(begin_str1, end_str1);
ranges.emplace_back(begin_str2, end_str2);
ranges.emplace_back(begin_str3, end_str3);
ASSERT_OK(DeleteFilesInRanges(db_, db_->DefaultColumnFamily(),
ranges.data(), ranges.size()));
ASSERT_EQ("0,3,7", FilesPerLevel(0));
@@ -2141,7 +2168,7 @@ TEST_P(DBDeleteFileRangeTest, DeleteFilesInRanges) {
// Delete all files.
{
RangePtr range;
RangeOpt range;
ASSERT_OK(DeleteFilesInRanges(db_, db_->DefaultColumnFamily(), &range, 1));
ASSERT_EQ("", FilesPerLevel(0));
@@ -5912,6 +5939,9 @@ TEST_F(DBCompactionTest, SubcompactionEvent) {
ASSERT_EQ(running_compactions_.find(ci.job_id),
running_compactions_.end());
running_compactions_.emplace(ci.job_id, std::unordered_set<int>());
if (expected_num_l0_files_pre_compaction_ != -1) {
ASSERT_EQ(expected_num_l0_files_pre_compaction_, ci.num_l0_files);
}
}
void OnCompactionCompleted(DB* /*db*/,
@@ -5921,6 +5951,9 @@ TEST_F(DBCompactionTest, SubcompactionEvent) {
ASSERT_NE(it, running_compactions_.end());
ASSERT_EQ(it->second.size(), 0);
running_compactions_.erase(it);
if (expected_num_l0_files_post_compaction_ != -1) {
ASSERT_EQ(expected_num_l0_files_post_compaction_, ci.num_l0_files);
}
}
void OnSubcompactionBegin(const SubcompactionJobInfo& si) override {
@@ -5950,10 +5983,25 @@ TEST_F(DBCompactionTest, SubcompactionEvent) {
return total_subcompaction_cnt_;
}
void SetExpectedNumL0FilesPreCompaction(int num) {
expected_num_l0_files_pre_compaction_ = num;
}
void SetExpectedNumL0FilesPostCompaction(int num) {
expected_num_l0_files_post_compaction_ = num;
}
void ResetExpectedNumL0Files() {
SetExpectedNumL0FilesPreCompaction(-1);
SetExpectedNumL0FilesPostCompaction(-1);
}
private:
InstrumentedMutex mutex_;
std::unordered_map<int, std::unordered_set<int>> running_compactions_;
size_t total_subcompaction_cnt_ = 0;
int expected_num_l0_files_pre_compaction_ = -1;
int expected_num_l0_files_post_compaction_ = -1;
};
Options options = CurrentOptions();
@@ -5973,6 +6021,7 @@ TEST_F(DBCompactionTest, SubcompactionEvent) {
ASSERT_OK(Flush());
}
MoveFilesToLevel(2);
ASSERT_EQ(FilesPerLevel(), "0,0,4");
// generate 2 files @ L1 which overlaps with L2 files
for (int i = 0; i < 2; i++) {
@@ -5982,11 +6031,18 @@ TEST_F(DBCompactionTest, SubcompactionEvent) {
}
ASSERT_OK(Flush());
}
listener->SetExpectedNumL0FilesPreCompaction(2 /* num */);
listener->SetExpectedNumL0FilesPostCompaction(0 /* num */);
MoveFilesToLevel(1);
ASSERT_EQ(FilesPerLevel(), "0,2,4");
listener->ResetExpectedNumL0Files();
CompactRangeOptions comp_opts;
comp_opts.max_subcompactions = 4;
listener->SetExpectedNumL0FilesPreCompaction(0 /* num */);
Status s = dbfull()->CompactRange(comp_opts, nullptr, nullptr);
ASSERT_OK(s);
ASSERT_OK(dbfull()->TEST_WaitForCompact());
@@ -5994,6 +6050,8 @@ TEST_F(DBCompactionTest, SubcompactionEvent) {
ASSERT_EQ(listener->GetRunningCompactionCount(), 0);
// and sub compaction is triggered
ASSERT_GT(listener->GetTotalSubcompactionCount(), 0);
listener->ResetExpectedNumL0Files();
}
TEST_F(DBCompactionTest, CompactFilesOutputRangeConflict) {
@@ -9376,104 +9434,134 @@ TEST_F(DBCompactionTest, CompactionWithChecksumHandoffManifest2) {
}
TEST_F(DBCompactionTest, FIFOChangeTemperature) {
for (bool write_time_default : {false, true}) {
SCOPED_TRACE("write time default? " + std::to_string(write_time_default));
for (bool should_allow_trivial_copy : {false, true}) {
for (bool write_time_default : {false, true}) {
int32_t before_compaction_calls = 0;
int32_t after_compaction_calls = 0;
if (should_allow_trivial_copy) {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"DBImpl::BackgroundCompaction:TriviaCopyBeforeCompaction",
[&](void*) { ++before_compaction_calls; });
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"DBImpl::BackgroundCompaction:TriviaCopyAfterCompaction",
[&](void*) { ++after_compaction_calls; });
} else {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"DBImpl::BackgroundCompaction:BeforeCompaction",
[&](void*) { ++before_compaction_calls; });
Options options = CurrentOptions();
options.compaction_style = kCompactionStyleFIFO;
options.num_levels = 1;
options.max_open_files = -1;
options.level0_file_num_compaction_trigger = 2;
options.create_if_missing = true;
CompactionOptionsFIFO fifo_options;
fifo_options.file_temperature_age_thresholds = {{Temperature::kCold, 1000}};
fifo_options.max_table_files_size = 100000000;
options.compaction_options_fifo = fifo_options;
env_->SetMockSleep();
if (write_time_default) {
options.default_write_temperature = Temperature::kWarm;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"DBImpl::BackgroundCompaction:AfterCompaction",
[&](void*) { ++after_compaction_calls; });
}
SCOPED_TRACE("write time default? " + std::to_string(write_time_default));
Options options = CurrentOptions();
options.compaction_style = kCompactionStyleFIFO;
options.num_levels = 1;
options.max_open_files = -1;
options.level0_file_num_compaction_trigger = 2;
options.create_if_missing = true;
CompactionOptionsFIFO fifo_options;
fifo_options.file_temperature_age_thresholds = {
{Temperature::kCold, 1000}};
fifo_options.max_table_files_size = 100000000;
fifo_options.allow_trivial_copy_when_change_temperature =
should_allow_trivial_copy;
fifo_options.trivial_copy_buffer_size = 4096;
options.compaction_options_fifo = fifo_options;
env_->SetMockSleep();
if (write_time_default) {
options.default_write_temperature = Temperature::kWarm;
}
// Should be ignored (TODO: fail?)
options.last_level_temperature = Temperature::kHot;
Reopen(options);
int total_cold = 0;
int total_warm = 0;
int total_hot = 0;
int total_unknown = 0;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"NewWritableFile::FileOptions.temperature", [&](void* arg) {
Temperature temperature = *(static_cast<Temperature*>(arg));
if (temperature == Temperature::kCold) {
total_cold++;
} else if (temperature == Temperature::kWarm) {
total_warm++;
} else if (temperature == Temperature::kHot) {
total_hot++;
} else {
assert(temperature == Temperature::kUnknown);
total_unknown++;
}
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
// The file system does not support checksum handoff. The check
// will be ignored.
ASSERT_OK(Put(Key(0), "value1"));
env_->MockSleepForSeconds(800);
ASSERT_OK(Put(Key(2), "value2"));
ASSERT_OK(Flush());
ASSERT_OK(Put(Key(0), "value1"));
ASSERT_OK(Put(Key(2), "value2"));
ASSERT_OK(Flush());
// First two L0 files both become eligible for temperature change
// compaction They should be compacted one-by-one.
ASSERT_OK(Put(Key(0), "value1"));
env_->MockSleepForSeconds(1200);
ASSERT_OK(Put(Key(2), "value2"));
ASSERT_OK(Flush());
ASSERT_OK(dbfull()->TEST_WaitForCompact());
if (write_time_default) {
// Also test dynamic option change
ASSERT_OK(db_->SetOptions({{"default_write_temperature", "kHot"}}));
}
ASSERT_OK(Put(Key(0), "value1"));
env_->MockSleepForSeconds(800);
ASSERT_OK(Put(Key(2), "value2"));
ASSERT_OK(Flush());
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
ColumnFamilyMetaData metadata;
db_->GetColumnFamilyMetaData(&metadata);
ASSERT_EQ(4, metadata.file_count);
if (write_time_default) {
ASSERT_EQ(Temperature::kHot, metadata.levels[0].files[0].temperature);
ASSERT_EQ(Temperature::kWarm, metadata.levels[0].files[1].temperature);
// Includes obsolete/deleted files moved to cold
ASSERT_EQ(total_warm, 3);
ASSERT_EQ(total_hot, 1);
// Includes non-SST DB files
ASSERT_GT(total_unknown, 0);
} else {
ASSERT_EQ(Temperature::kUnknown,
metadata.levels[0].files[0].temperature);
ASSERT_EQ(Temperature::kUnknown,
metadata.levels[0].files[1].temperature);
ASSERT_EQ(total_warm, 0);
ASSERT_EQ(total_hot, 0);
// Includes non-SST DB files
ASSERT_GT(total_unknown, 4);
}
ASSERT_EQ(Temperature::kCold, metadata.levels[0].files[2].temperature);
ASSERT_EQ(Temperature::kCold, metadata.levels[0].files[3].temperature);
ASSERT_EQ(2, total_cold);
ASSERT_EQ(2, before_compaction_calls);
ASSERT_EQ(2, after_compaction_calls);
Destroy(options);
}
// Should be ignored (TODO: fail?)
options.last_level_temperature = Temperature::kHot;
Reopen(options);
int total_cold = 0;
int total_warm = 0;
int total_hot = 0;
int total_unknown = 0;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"NewWritableFile::FileOptions.temperature", [&](void* arg) {
Temperature temperature = *(static_cast<Temperature*>(arg));
if (temperature == Temperature::kCold) {
total_cold++;
} else if (temperature == Temperature::kWarm) {
total_warm++;
} else if (temperature == Temperature::kHot) {
total_hot++;
} else {
assert(temperature == Temperature::kUnknown);
total_unknown++;
}
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
// The file system does not support checksum handoff. The check
// will be ignored.
ASSERT_OK(Put(Key(0), "value1"));
env_->MockSleepForSeconds(800);
ASSERT_OK(Put(Key(2), "value2"));
ASSERT_OK(Flush());
ASSERT_OK(Put(Key(0), "value1"));
ASSERT_OK(Put(Key(2), "value2"));
ASSERT_OK(Flush());
// First two L0 files both become eligible for temperature change compaction
// They should be compacted one-by-one.
ASSERT_OK(Put(Key(0), "value1"));
env_->MockSleepForSeconds(1200);
ASSERT_OK(Put(Key(2), "value2"));
ASSERT_OK(Flush());
ASSERT_OK(dbfull()->TEST_WaitForCompact());
if (write_time_default) {
// Also test dynamic option change
ASSERT_OK(db_->SetOptions({{"default_write_temperature", "kHot"}}));
}
ASSERT_OK(Put(Key(0), "value1"));
env_->MockSleepForSeconds(800);
ASSERT_OK(Put(Key(2), "value2"));
ASSERT_OK(Flush());
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
ColumnFamilyMetaData metadata;
db_->GetColumnFamilyMetaData(&metadata);
ASSERT_EQ(4, metadata.file_count);
if (write_time_default) {
ASSERT_EQ(Temperature::kHot, metadata.levels[0].files[0].temperature);
ASSERT_EQ(Temperature::kWarm, metadata.levels[0].files[1].temperature);
// Includes obsolete/deleted files moved to cold
ASSERT_EQ(total_warm, 3);
ASSERT_EQ(total_hot, 1);
// Includes non-SST DB files
ASSERT_GT(total_unknown, 0);
} else {
ASSERT_EQ(Temperature::kUnknown, metadata.levels[0].files[0].temperature);
ASSERT_EQ(Temperature::kUnknown, metadata.levels[0].files[1].temperature);
ASSERT_EQ(total_warm, 0);
ASSERT_EQ(total_hot, 0);
// Includes non-SST DB files
ASSERT_GT(total_unknown, 4);
}
ASSERT_EQ(Temperature::kCold, metadata.levels[0].files[2].temperature);
ASSERT_EQ(Temperature::kCold, metadata.levels[0].files[3].temperature);
ASSERT_EQ(2, total_cold);
Destroy(options);
}
}
@@ -10472,7 +10560,7 @@ TEST_F(DBCompactionTest, NumberOfSubcompactions) {
}
}
TEST_F(DBCompactionTest, VerifyRecordCount) {
TEST_F(DBCompactionTest, VerifyInputRecordCount) {
Options options = CurrentOptions();
options.compaction_style = kCompactionStyleLevel;
options.level0_file_num_compaction_trigger = 3;
@@ -10510,6 +10598,103 @@ TEST_F(DBCompactionTest, VerifyRecordCount) {
ASSERT_TRUE(std::strstr(s.getState(), expect));
}
TEST_F(DBCompactionTest, VerifyOutputRecordCountBlockBasedTable) {
Options options = CurrentOptions();
options.compaction_style = kCompactionStyleLevel;
options.level0_file_num_compaction_trigger = 3;
options.compaction_verify_record_count = true;
DestroyAndReopen(options);
Random rnd(301);
// Create 2 overlapping L0 files
for (int i = 1; i < 20; i += 2) {
ASSERT_OK(Put(Key(i), rnd.RandomString(100)));
}
ASSERT_OK(Flush());
ASSERT_OK(db_->DeleteRange(WriteOptions(), Key(10), Key(15)));
for (int i = 0; i < 20; i += 2) {
ASSERT_OK(Put(Key(i), rnd.RandomString(100)));
}
ASSERT_OK(Flush());
// Skip adding every 7th key in the output table
int num_iter = 0;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"BlockBasedTableBuilder::Add::skip", [&](void* skip) {
num_iter++;
if (num_iter % 7 == 0) {
*(bool*)skip = true;
}
});
SyncPoint::GetInstance()->EnableProcessing();
Status s = db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
ASSERT_TRUE(s.IsCorruption());
const char* expect =
"Number of keys in compaction output SST files does not match number of "
"keys added.";
ASSERT_TRUE(std::strstr(s.getState(), expect));
}
TEST_F(DBCompactionTest, VerifyOutputRecordCountPlainTable) {
Options options = CurrentOptions();
options.compaction_style = kCompactionStyleLevel;
options.level0_file_num_compaction_trigger = 3;
options.compaction_verify_record_count = true;
PlainTableOptions plain_table_options;
plain_table_options.user_key_len = 0;
plain_table_options.bloom_bits_per_key = 2;
plain_table_options.hash_table_ratio = 0.8;
plain_table_options.index_sparseness = 3;
plain_table_options.huge_page_tlb_size = 0;
plain_table_options.encoding_type = kPrefix;
plain_table_options.full_scan_mode = false;
plain_table_options.store_index_in_file = false;
options.table_factory.reset(NewPlainTableFactory(plain_table_options));
options.memtable_factory.reset(NewHashLinkListRepFactory(4, 0, 3, true));
options.prefix_extractor.reset(NewFixedPrefixTransform(8));
options.allow_mmap_reads = false;
options.allow_concurrent_memtable_write = false;
options.unordered_write = false;
DestroyAndReopen(options);
Random rnd(301);
// Create 2 overlapping L0 files
for (int i = 1; i < 20; i += 2) {
ASSERT_OK(Put(Key(i), rnd.RandomString(100)));
}
ASSERT_OK(Flush());
for (int i = 0; i < 20; i += 2) {
ASSERT_OK(Put(Key(i), rnd.RandomString(100)));
}
ASSERT_OK(Flush());
// Skip adding every 7th key in the output table
int num_iter = 0;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"PlainTableBuilder::Add::skip", [&](void* skip) {
num_iter++;
if (num_iter % 7 == 0) {
*(bool*)skip = true;
}
});
SyncPoint::GetInstance()->EnableProcessing();
Status s = db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
ASSERT_TRUE(s.IsCorruption());
const char* expect =
"Number of keys in compaction output SST files does not match number of "
"keys added.";
ASSERT_TRUE(std::strstr(s.getState(), expect));
}
TEST_F(DBCompactionTest, ErrorWhenReadFileHead) {
// This is to test a bug that is fixed in
// https://github.com/facebook/rocksdb/pull/11782.
+10 -14
View File
@@ -75,11 +75,9 @@ Status DBImpl::GetLiveFiles(std::vector<std::string>& ret,
ret.emplace_back(CurrentFileName(""));
ret.emplace_back(DescriptorFileName("", versions_->manifest_file_number()));
// The OPTIONS file number is zero in read-write mode when OPTIONS file
// writing failed and the DB was configured with
// `fail_if_options_file_error == false`. In read-only mode the OPTIONS file
// number is zero when no OPTIONS file exist at all. In those cases we do not
// record any OPTIONS file in the live file list.
// In read-only mode the OPTIONS file number is zero when no OPTIONS file
// exist at all. In this cases we do not record any OPTIONS file in the live
// file list.
if (versions_->options_file_number() != 0) {
ret.emplace_back(OptionsFileName("", versions_->options_file_number()));
}
@@ -185,14 +183,14 @@ Status DBImpl::GetSortedWalFilesImpl(VectorWalPtr& files, bool need_seqnos) {
return s;
}
Status DBImpl::GetCurrentWalFile(std::unique_ptr<WalFile>* current_log_file) {
Status DBImpl::GetCurrentWalFile(std::unique_ptr<WalFile>* current_wal_file) {
uint64_t current_logfile_number;
{
InstrumentedMutexLock l(&mutex_);
current_logfile_number = logfile_number_;
current_logfile_number = cur_wal_number_;
}
return wal_manager_.GetLiveWalFile(current_logfile_number, current_log_file);
return wal_manager_.GetLiveWalFile(current_logfile_number, current_wal_file);
}
Status DBImpl::GetLiveFilesStorageInfo(
@@ -332,7 +330,7 @@ Status DBImpl::GetLiveFilesStorageInfo(
const uint64_t options_size = versions_->options_file_size_;
const uint64_t min_log_num = MinLogNumberToKeep();
// Ensure consistency with manifest for track_and_verify_wals_in_manifest
const uint64_t max_log_num = logfile_number_;
const uint64_t max_log_num = cur_wal_number_;
mutex_.Unlock();
@@ -369,11 +367,9 @@ Status DBImpl::GetLiveFilesStorageInfo(
}
}
// The OPTIONS file number is zero in read-write mode when OPTIONS file
// writing failed and the DB was configured with
// `fail_if_options_file_error == false`. In read-only mode the OPTIONS file
// number is zero when no OPTIONS file exist at all. In those cases we do not
// record any OPTIONS file in the live file list.
// In read-only mode the OPTIONS file number is zero when no OPTIONS file
// exist at all. In this cases we do not record any OPTIONS file in the live
// file list.
if (options_number != 0) {
results.emplace_back();
LiveFileStorageInfo& info = results.back();
+57
View File
@@ -3504,6 +3504,63 @@ TEST_F(DBFlushTest, DBStuckAfterAtomicFlushError) {
ASSERT_OK(dbfull()->TEST_WaitForBackgroundWork());
ASSERT_EQ(1, NumTableFilesAtLevel(0));
}
TEST_F(DBFlushTest, VerifyOutputRecordCount) {
for (bool use_plain_table : {false, true}) {
Options options = CurrentOptions();
options.flush_verify_memtable_count = true;
options.merge_operator = MergeOperators::CreateStringAppendOperator();
DestroyAndReopen(options);
// Verify flush output record count verification in different table
// formats
if (use_plain_table) {
options.table_factory.reset(NewPlainTableFactory());
}
// Verify that flush output record count verification does not produce false
// positives.
ASSERT_OK(Merge("k0", "v1"));
ASSERT_OK(Put("k1", "v1"));
ASSERT_OK(Put("k2", "v1"));
ASSERT_OK(SingleDelete("k2"));
ASSERT_OK(Delete("k2"));
ASSERT_OK(Delete("k3"));
ASSERT_OK(db_->DeleteRange(WriteOptions(), "k1", "k3"));
ASSERT_OK(Flush());
// Verify that flush output record count verification catch corruption
DestroyAndReopen(options);
if (use_plain_table) {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"PlainTableBuilder::Add::skip",
[&](void* skip) { *(bool*)skip = true; });
} else {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"BlockBasedTableBuilder::Add::skip",
[&](void* skip) { *(bool*)skip = true; });
}
SyncPoint::GetInstance()->EnableProcessing();
const char* expect =
"Number of keys in flush output SST files does not match";
// 1. During DB open flush
ASSERT_OK(Put("k1", "v1"));
ASSERT_OK(Put("k2", "v1"));
Status s = TryReopen(options);
ASSERT_TRUE(s.IsCorruption());
ASSERT_TRUE(std::strstr(s.getState(), expect));
// 2. During regular flush
DestroyAndReopen(options);
ASSERT_OK(Put("k1", "v1"));
ASSERT_OK(Put("k2", "v1"));
s = Flush();
ASSERT_TRUE(s.IsCorruption());
ASSERT_TRUE(std::strstr(s.getState(), expect));
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
}
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+388 -320
View File
File diff suppressed because it is too large. Load diff
+208 -162
View File
@@ -173,10 +173,10 @@ struct DBOpenLogRecordReadReporter : public log::Reader::Reporter {
void OldLogRecord(size_t bytes) override;
uint64_t GetCorruptedLogNumber() const { return corrupted_log_number_; }
uint64_t GetCorruptedLogNumber() const { return corrupted_wal_number_; }
private:
uint64_t corrupted_log_number_ = kMaxSequenceNumber;
uint64_t corrupted_wal_number_ = kMaxSequenceNumber;
};
// While DB is the public interface of RocksDB, and DBImpl is the actual
@@ -256,6 +256,10 @@ class DBImpl : public DB {
Status WriteWithCallback(const WriteOptions& options, WriteBatch* updates,
UserWriteCallback* user_write_cb) override;
Status IngestWriteBatchWithIndex(
const WriteOptions& options,
std::shared_ptr<WriteBatchWithIndex> wbwi) override;
using DB::Get;
Status Get(const ReadOptions& _read_options,
ColumnFamilyHandle* column_family, const Slice& key,
@@ -379,6 +383,11 @@ class DBImpl : public DB {
const std::vector<ColumnFamilyHandle*>& column_families,
std::vector<Iterator*>* iterators) override;
using DB::NewMultiScan;
std::unique_ptr<MultiScan> NewMultiScan(
const ReadOptions& _read_options, ColumnFamilyHandle* column_family,
const std::vector<ScanOptions>& scan_opts) override;
const Snapshot* GetSnapshot() override;
void ReleaseSnapshot(const Snapshot* snapshot) override;
@@ -497,6 +506,9 @@ class DBImpl : public DB {
Status GetFullHistoryTsLow(ColumnFamilyHandle* column_family,
std::string* ts_low) override;
Status GetNewestUserDefinedTimestamp(ColumnFamilyHandle* column_family,
std::string* newest_timestamp) override;
Status GetDbIdentity(std::string& identity) const override;
virtual Status GetDbIdentityFromIdentityFile(const IOOptions& opts,
@@ -530,11 +542,11 @@ class DBImpl : public DB {
// Get the known flushed sizes of WALs that might still be written to
// or have pending sync.
// NOTE: unlike alive_log_files_, this function includes WALs that might
// NOTE: unlike alive_wal_files_, this function includes WALs that might
// be obsolete (but not obsolete to a pending Checkpoint) and not yet fully
// synced.
Status GetOpenWalSizes(std::map<uint64_t, uint64_t>& number_to_size);
Status GetCurrentWalFile(std::unique_ptr<WalFile>* current_log_file) override;
Status GetCurrentWalFile(std::unique_ptr<WalFile>* current_wal_file) override;
Status GetCreationTimeOfOldestFile(uint64_t* creation_time) override;
Status GetUpdatesSince(
@@ -542,7 +554,7 @@ class DBImpl : public DB {
const TransactionLogIterator::ReadOptions& read_options =
TransactionLogIterator::ReadOptions()) override;
Status DeleteFilesInRanges(ColumnFamilyHandle* column_family,
const RangePtr* ranges, size_t n,
const RangeOpt* ranges, size_t n,
bool include_end = true);
void GetLiveFilesMetaData(std::vector<LiveFileMetaData>* metadata) override;
@@ -651,6 +663,11 @@ class DBImpl : public DB {
ColumnFamilyHandle* column_family, const Range* range, std::size_t n,
TablePropertiesCollection* props) override;
Status GetPropertiesOfTablesByLevel(
ColumnFamilyHandle* column_family,
std::vector<std::unique_ptr<TablePropertiesCollection>>* props_by_level)
override;
// ---- End of implementations of the DB interface ----
SystemClock* GetSystemClock() const;
@@ -1063,7 +1080,7 @@ class DBImpl : public DB {
void AddToLogsToFreeQueue(log::Writer* log_writer) {
mutex_.AssertHeld();
logs_to_free_queue_.push_back(log_writer);
wals_to_free_queue_.push_back(log_writer);
}
void AddSuperVersionsToFreeQueue(SuperVersion* sv) {
@@ -1128,7 +1145,7 @@ class DBImpl : public DB {
bool TEST_UnableToReleaseOldestLog() { return unable_to_release_oldest_log_; }
bool TEST_IsLogGettingFlushed() {
return alive_log_files_.begin()->getting_flushed;
return alive_wal_files_.begin()->getting_flushed;
}
Status TEST_SwitchMemtable(ColumnFamilyData* cfd = nullptr);
@@ -1208,7 +1225,9 @@ class DBImpl : public DB {
uint64_t TEST_LogfileNumber();
uint64_t TEST_total_log_size() const { return total_log_size_; }
uint64_t TEST_wals_total_size() const {
return wals_total_size_.LoadRelaxed();
}
void TEST_GetAllBlockCaches(std::unordered_set<const Cache*>* cache_set);
@@ -1267,27 +1286,18 @@ class DBImpl : public DB {
// flush LOG out of application buffer
void FlushInfoLog();
// record current sequence number to time mapping. If
// populate_historical_seconds > 0 then pre-populate all the
// sequence numbers from [1, last] to map to [now minus
// populate_historical_seconds, now].
void RecordSeqnoToTimeMapping(uint64_t populate_historical_seconds);
// For the background timer job
void RecordSeqnoToTimeMapping();
// Everytime DB's seqno to time mapping changed (which already hold the db
// mutex), we install a new SuperVersion in each column family with a shared
// copy of the new mapping while holding the db mutex.
// This is done for all column families even though the column family does not
// explicitly enabled the
// `preclude_last_level_data_seconds` or `preserve_internal_time_seconds`
// features.
// This mapping supports iterators to fulfill the
// "rocksdb.iterator.write-time" iterator property for entries in memtables.
//
// Since this new SuperVersion doesn't involve an LSM tree shape change, we
// don't schedule work after installing this SuperVersion. It returns the used
// `SuperVersionContext` for clean up after release mutex.
void InstallSeqnoToTimeMappingInSV(
std::vector<SuperVersionContext>* sv_contexts);
// REQUIRES: DB mutex held
std::pair<SequenceNumber, uint64_t> GetSeqnoToTimeSample() const;
// REQUIRES: DB mutex held or during open
void EnsureSeqnoToTimeMapping(const MinAndMaxPreserveSeconds& preserve_secs);
// Only called during open
void PrepopulateSeqnoToTimeMapping(
const MinAndMaxPreserveSeconds& preserve_secs);
// Interface to block and signal the DB in case of stalling writes by
// WriteBufferManager. Each DBImpl object contains ptr to WBMStallInterface.
@@ -1375,16 +1385,16 @@ class DBImpl : public DB {
// State below is protected by mutex_
// With two_write_queues enabled, some of the variables that accessed during
// WriteToWAL need different synchronization: log_empty_, alive_log_files_,
// logs_, logfile_number_. Refer to the definition of each variable below for
// WriteToWAL need different synchronization: wal_empty_, alive_wal_files_,
// logs_, cur_wal_number_. Refer to the definition of each variable below for
// more description.
//
// `mutex_` can be a hot lock in some workloads, so it deserves dedicated
// cachelines.
mutable CacheAlignedInstrumentedMutex mutex_;
ColumnFamilyHandleImpl* default_cf_handle_;
InternalStats* default_cf_internal_stats_;
ColumnFamilyHandleImpl* default_cf_handle_ = nullptr;
InternalStats* default_cf_internal_stats_ = nullptr;
// table_cache_ provides its own synchronization
std::shared_ptr<Cache> table_cache_;
@@ -1396,7 +1406,7 @@ class DBImpl : public DB {
// only used for dynamically adjusting max_total_wal_size. it is a sum of
// [write_buffer_size * max_write_buffer_number] over all column families
std::atomic<uint64_t> max_total_in_memory_state_;
std::atomic<uint64_t> max_total_in_memory_state_ = 0;
// The options to access storage files
const FileOptions file_options_;
@@ -1423,14 +1433,14 @@ class DBImpl : public DB {
// Each flush or compaction gets its own job id. this counter makes sure
// they're unique
std::atomic<int> next_job_id_;
std::atomic<int> next_job_id_ = 1;
std::atomic<bool> shutting_down_;
std::atomic<bool> shutting_down_ = false;
// No new background jobs can be queued if true. This is used to prevent new
// background jobs from being queued after WaitForCompact() completes waiting
// all background jobs then attempts to close when close_db_ option is true.
bool reject_new_background_jobs_;
bool reject_new_background_jobs_ = false;
// RecoveryContext struct stores the context about version edits along
// with corresponding column_family_data and column_family_options.
@@ -1528,11 +1538,11 @@ class DBImpl : public DB {
// ingests `wbwi` is done.
// @param memtable_updated Whether the same write that ingests wbwi has
// updated memtable. This is useful for determining whether to set bg
// error when IngestWBWI fails.
Status IngestWBWI(std::shared_ptr<WriteBatchWithIndex> wbwi,
const WBWIMemTable::SeqnoRange& assigned_seqno,
uint64_t min_prep_log, SequenceNumber last_seqno,
bool memtable_updated, bool ignore_missing_cf);
// error when IngestWBWIAsMemtable fails.
Status IngestWBWIAsMemtable(std::shared_ptr<WriteBatchWithIndex> wbwi,
const WBWIMemTable::SeqnoRange& assigned_seqno,
uint64_t min_prep_log, SequenceNumber last_seqno,
bool memtable_updated, bool ignore_missing_cf);
// If disable_memtable is set the application logic must guarantee that the
// batch will still be skipped from memtable during the recovery. An excption
@@ -1562,18 +1572,17 @@ class DBImpl : public DB {
Status WriteImpl(const WriteOptions& options, WriteBatch* updates,
WriteCallback* callback = nullptr,
UserWriteCallback* user_write_cb = nullptr,
uint64_t* log_used = nullptr, uint64_t log_ref = 0,
uint64_t* wal_used = nullptr, uint64_t log_ref = 0,
bool disable_memtable = false, uint64_t* seq_used = nullptr,
size_t batch_cnt = 0,
PreReleaseCallback* pre_release_callback = nullptr,
PostMemTableCallback* post_memtable_callback = nullptr,
std::shared_ptr<WriteBatchWithIndex> wbwi = nullptr,
uint64_t min_prep_log = 0);
std::shared_ptr<WriteBatchWithIndex> wbwi = nullptr);
Status PipelinedWriteImpl(const WriteOptions& options, WriteBatch* updates,
WriteCallback* callback = nullptr,
UserWriteCallback* user_write_cb = nullptr,
uint64_t* log_used = nullptr, uint64_t log_ref = 0,
uint64_t* wal_used = nullptr, uint64_t log_ref = 0,
bool disable_memtable = false,
uint64_t* seq_used = nullptr);
@@ -1600,7 +1609,7 @@ class DBImpl : public DB {
Status WriteImplWALOnly(
WriteThread* write_thread, const WriteOptions& options,
WriteBatch* updates, WriteCallback* callback,
UserWriteCallback* user_write_cb, uint64_t* log_used,
UserWriteCallback* user_write_cb, uint64_t* wal_used,
const uint64_t log_ref, uint64_t* seq_used, const size_t sub_batch_cnt,
PreReleaseCallback* pre_release_callback, const AssignOrder assign_order,
const PublishLastSeq publish_last_seq, const bool disable_memtable);
@@ -1761,9 +1770,9 @@ class DBImpl : public DB {
}
};
struct LogFileNumberSize {
explicit LogFileNumberSize(uint64_t _number) : number(_number) {}
LogFileNumberSize() {}
struct WalFileNumberSize {
explicit WalFileNumberSize(uint64_t _number) : number(_number) {}
WalFileNumberSize() {}
void AddSize(uint64_t new_size) { size += new_size; }
uint64_t number;
uint64_t size = 0;
@@ -1785,6 +1794,13 @@ class DBImpl : public DB {
if (writer->file()) {
// TODO: plumb Env::IOActivity, Env::IOPriority
s = writer->WriteBuffer(WriteOptions());
if (attempt_truncate_size < SIZE_MAX &&
attempt_truncate_size < writer->file()->GetFileSize()) {
Status s2 = writer->file()->writable_file()->Truncate(
attempt_truncate_size, IOOptions{}, nullptr);
// This is just a best effort attempt
s2.PermitUncheckedError();
}
}
delete writer;
writer = nullptr;
@@ -1817,6 +1833,11 @@ class DBImpl : public DB {
getting_synced = false;
}
void SetAttemptTruncateSize(uint64_t size) {
assert(attempt_truncate_size == SIZE_MAX);
attempt_truncate_size = size;
}
uint64_t number;
// Visual Studio doesn't support deque's member to be noncopyable because
// of a std::unique_ptr as a member.
@@ -1829,15 +1850,20 @@ class DBImpl : public DB {
// to be persisted even if appends happen during sync so it can be used for
// tracking the synced size in MANIFEST.
uint64_t pre_sync_size = 0;
// When < SIZE_MAX, attempt to truncate the WAL to this size on close,
// because a bad entry was written to it beyond that point and it likely
// won't be recoverable with the bad entry.
uint64_t attempt_truncate_size = SIZE_MAX;
};
struct LogContext {
explicit LogContext(bool need_sync = false)
: need_log_sync(need_sync), need_log_dir_sync(need_sync) {}
bool need_log_sync = false;
bool need_log_dir_sync = false;
struct WalContext {
explicit WalContext(bool need_sync = false)
: need_wal_sync(need_sync), need_wal_dir_sync(need_sync) {}
bool need_wal_sync = false;
bool need_wal_dir_sync = false;
log::Writer* writer = nullptr;
LogFileNumberSize* log_file_number_size = nullptr;
WalFileNumberSize* wal_file_number_size = nullptr;
uint64_t prev_size = SIZE_MAX;
};
// PurgeFileInfo is a structure to hold information of files to be deleted in
@@ -1979,7 +2005,7 @@ class DBImpl : public DB {
// Follow-up work to user creating a column family or (families)
Status WrapUpCreateColumnFamilies(
const ReadOptions& read_options, const WriteOptions& write_options,
const WriteOptions& write_options,
const std::vector<const ColumnFamilyOptions*>& cf_options);
Status DropColumnFamilyImpl(ColumnFamilyHandle* column_family);
@@ -2045,10 +2071,10 @@ class DBImpl : public DB {
JobContext* job_context, LogBuffer* log_buffer, Env::Priority thread_pri);
// REQUIRES: log_numbers are sorted in ascending order
// corrupted_log_found is set to true if we recover from a corrupted log file.
// corrupted_wal_found is set to true if we recover from a corrupted log file.
Status RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
SequenceNumber* next_sequence, bool read_only,
bool is_retry, bool* corrupted_log_found,
bool is_retry, bool* corrupted_wal_found,
RecoveryContext* recovery_ctx);
void SetupLogFilesRecovery(
@@ -2138,6 +2164,11 @@ class DBImpl : public DB {
bool flushed, std::unordered_map<int, VersionEdit>* version_edits,
RecoveryContext* recovery_ctx);
// Check that DB sequence number is not set back during recovery between
// replaying of WAL files and between replaying of WriteBatches.
Status CheckSeqnoNotSetBackDuringRecovery(SequenceNumber prev_next_seqno,
SequenceNumber current_next_seqno);
void FinishLogFilesRecovery(int job_id, const Status& status);
// The following two methods are used to flush a memtable to
// storage. The first one is used at database RecoveryTime (when the
@@ -2151,12 +2182,12 @@ class DBImpl : public DB {
// log file to its actual size, thereby freeing preallocated space.
// Return success even if truncate fails
Status GetLogSizeAndMaybeTruncate(uint64_t wal_number, bool truncate,
LogFileNumberSize* log);
WalFileNumberSize* log);
// Restore alive_log_files_ and total_log_size_ after recovery.
// Restore alive_wal_files_ and wals_total_size_ after recovery.
// It needs to run only when there's no flush during recovery
// (e.g. avoid_flush_during_recovery=true). May also trigger flush
// in case total_log_size > max_total_wal_size.
// in case wals_total_size > max_total_wal_size.
Status RestoreAliveLogFiles(const std::vector<uint64_t>& log_numbers);
// num_bytes: for slowdown case, delay time is calculated based on
@@ -2305,7 +2336,7 @@ class DBImpl : public DB {
// REQUIRES: mutex locked
Status PreprocessWrite(const WriteOptions& write_options,
LogContext* log_context, WriteContext* write_context);
WalContext* log_context, WriteContext* write_context);
// Merge write batches in the write group into merged_batch.
// Returns OK if merge is successful.
@@ -2316,20 +2347,21 @@ class DBImpl : public DB {
IOStatus WriteToWAL(const WriteBatch& merged_batch,
const WriteOptions& write_options,
log::Writer* log_writer, uint64_t* log_used,
log::Writer* log_writer, uint64_t* wal_used,
uint64_t* log_size,
LogFileNumberSize& log_file_number_size,
WalFileNumberSize& wal_file_number_size,
SequenceNumber sequence);
IOStatus WriteToWAL(const WriteThread::WriteGroup& write_group,
log::Writer* log_writer, uint64_t* log_used,
bool need_log_sync, bool need_log_dir_sync,
SequenceNumber sequence,
LogFileNumberSize& log_file_number_size);
IOStatus WriteGroupToWAL(const WriteThread::WriteGroup& write_group,
log::Writer* log_writer, uint64_t* wal_used,
bool need_wal_sync, bool need_wal_dir_sync,
SequenceNumber sequence,
WalFileNumberSize& wal_file_number_size);
IOStatus ConcurrentWriteToWAL(const WriteThread::WriteGroup& write_group,
uint64_t* log_used,
SequenceNumber* last_sequence, size_t seq_inc);
IOStatus ConcurrentWriteGroupToWAL(const WriteThread::WriteGroup& write_group,
uint64_t* wal_used,
SequenceNumber* last_sequence,
size_t seq_inc);
// Used by WriteImpl to update bg_error_ if paranoid check is enabled.
// Caller must hold mutex_.
@@ -2343,7 +2375,7 @@ class DBImpl : public DB {
void WALIOStatusCheck(const IOStatus& status);
// Used by WriteImpl to update bg_error_ in case of memtable insert error.
void MemTableInsertStatusCheck(const Status& memtable_insert_status);
void HandleMemTableInsertFailure(const Status& nonok_memtable_insert_status);
Status CompactFilesImpl(const CompactionOptions& compact_options,
ColumnFamilyData* cfd, Version* version,
@@ -2450,9 +2482,7 @@ class DBImpl : public DB {
// Cancel scheduled periodic tasks
Status CancelPeriodicTaskScheduler();
Status RegisterRecordSeqnoTimeWorker(const ReadOptions& read_options,
const WriteOptions& write_options,
bool is_new_db);
Status RegisterRecordSeqnoTimeWorker();
void PrintStatistics();
@@ -2518,12 +2548,21 @@ class DBImpl : public DB {
// Background threads call this function, which is just a wrapper around
// the InstallSuperVersion() function. Background threads carry
// sv_context which can have new_superversion already
// allocated.
// sv_context to allow allocation of SuperVersion object outside of holding
// the DB mutex.
// All ColumnFamily state changes go through this function. Here we analyze
// the new state and we schedule background work if we detect that the new
// state needs flush or compaction.
void InstallSuperVersionAndScheduleWork(ColumnFamilyData* cfd,
// See also InstallSuperVersionForConfigChange().
void InstallSuperVersionAndScheduleWork(
ColumnFamilyData* cfd, SuperVersionContext* sv_context,
std::optional<std::shared_ptr<SeqnoToTimeMapping>>
new_seqno_to_time_mapping = {});
// A variant of InstallSuperVersionAndScheduleWork() that must be used for
// new CFs or for changes to mutable_cf_options. This is so that it can
// update seqno_to_time_mapping cached for the new SuperVersion as relevant.
void InstallSuperVersionForConfigChange(ColumnFamilyData* cfd,
SuperVersionContext* sv_context);
bool GetIntPropertyInternal(ColumnFamilyData* cfd,
@@ -2538,7 +2577,7 @@ class DBImpl : public DB {
bool ShouldntRunManualCompaction(ManualCompactionState* m);
bool HaveManualCompaction(ColumnFamilyData* cfd);
bool MCOverlap(ManualCompactionState* m, ManualCompactionState* m1);
void UpdateDeletionCompactionStats(const std::unique_ptr<Compaction>& c);
void UpdateFIFOCompactionStatus(const std::unique_ptr<Compaction>& c);
// May open and read table files for table property.
// Should not be called while holding mutex_.
@@ -2689,7 +2728,7 @@ class DBImpl : public DB {
ErrorIteratorFuncType error_iterator_func);
// Lock over the persistent DB state. Non-nullptr iff successfully acquired.
FileLock* db_lock_;
FileLock* db_lock_ = nullptr;
// Guards changes to DB and CF options to ensure consistency between
// * In-memory options objects
@@ -2703,20 +2742,20 @@ class DBImpl : public DB {
// Guards reads and writes to in-memory stats_history_.
InstrumentedMutex stats_history_mutex_;
// In addition to mutex_, log_write_mutex_ protects writes to logs_ and
// logfile_number_. With two_write_queues it also protects alive_log_files_,
// and log_empty_. Refer to the definition of each variable below for more
// In addition to mutex_, wal_write_mutex_ protects writes to logs_ and
// cur_wal_number_. With two_write_queues it also protects alive_wal_files_,
// and wal_empty_. Refer to the definition of each variable below for more
// details.
// Note: to avoid deadlock, if needed to acquire both log_write_mutex_ and
// mutex_, the order should be first mutex_ and then log_write_mutex_.
InstrumentedMutex log_write_mutex_;
// Note: to avoid deadlock, if needed to acquire both wal_write_mutex_ and
// mutex_, the order should be first mutex_ and then wal_write_mutex_.
InstrumentedMutex wal_write_mutex_;
// If zero, manual compactions are allowed to proceed. If non-zero, manual
// compactions may still be running, but will quickly fail with
// `Status::Incomplete`. The value indicates how many threads have paused
// manual compactions. It is accessed in read mode outside the DB mutex in
// compaction code paths.
std::atomic<int> manual_compaction_paused_;
std::atomic<int> manual_compaction_paused_ = false;
// This condition variable is signaled on these conditions:
// * whenever bg_compaction_scheduled_ goes down to 0
@@ -2732,106 +2771,114 @@ class DBImpl : public DB {
// * whenever SetOptions successfully updates options.
// * whenever a column family is dropped.
InstrumentedCondVar bg_cv_;
// Writes are protected by locking both mutex_ and log_write_mutex_, and reads
// must be under either mutex_ or log_write_mutex_. Since after ::Open,
// logfile_number_ is currently updated only in write_thread_, it can be read
ColumnFamilyHandleImpl* persist_stats_cf_handle_ = nullptr;
bool persistent_stats_cfd_exists_ = true;
// Writes are protected by locking both mutex_ and wal_write_mutex_, and reads
// must be under either mutex_ or wal_write_mutex_. Since after ::Open,
// cur_wal_number_ is currently updated only in write_thread_, it can be read
// from the same write_thread_ without any locks.
uint64_t logfile_number_;
uint64_t cur_wal_number_ = 0;
// Log files that we can recycle. Must be protected by db mutex_.
std::deque<uint64_t> log_recycle_files_;
std::deque<uint64_t> wal_recycle_files_;
// The minimum log file number taht can be recycled, if log recycling is
// enabled. This is used to ensure that log files created by previous
// instances of the database are not recycled, as we cannot be sure they
// were created in the recyclable format.
uint64_t min_log_number_to_recycle_;
// Protected by log_write_mutex_.
bool log_dir_synced_;
// Without two_write_queues, read and writes to log_empty_ are protected by
uint64_t min_wal_number_to_recycle_ = 0;
// Protected by wal_write_mutex_.
bool wal_dir_synced_ = false;
// Without two_write_queues, read and writes to wal_empty_ are protected by
// mutex_. Since it is currently updated/read only in write_thread_, it can be
// accessed from the same write_thread_ without any locks. With
// two_write_queues writes, where it can be updated in different threads,
// read and writes are protected by log_write_mutex_ instead. This is to avoid
// expensive mutex_ lock during WAL write, which update log_empty_.
bool log_empty_;
ColumnFamilyHandleImpl* persist_stats_cf_handle_;
bool persistent_stats_cfd_exists_ = true;
// read and writes are protected by wal_write_mutex_ instead. This is to avoid
// expensive mutex_ lock during WAL write, which update wal_empty_.
bool wal_empty_ = true;
// The current WAL file and those that have not been found obsolete from
// memtable flushes. A WAL not on this list might still be pending writer
// flush and/or sync and close and might still be in logs_. alive_log_files_
// is protected by mutex_ and log_write_mutex_ with details as follows:
// flush and/or sync and close and might still be in logs_. alive_wal_files_
// is protected by mutex_ and wal_write_mutex_ with details as follows:
// 1. read by FindObsoleteFiles() which can be called in either application
// thread or RocksDB bg threads, both mutex_ and log_write_mutex_ are
// thread or RocksDB bg threads, both mutex_ and wal_write_mutex_ are
// held.
// 2. pop_front() by FindObsoleteFiles(), both mutex_ and log_write_mutex_
// 2. pop_front() by FindObsoleteFiles(), both mutex_ and wal_write_mutex_
// are held.
// 3. push_back() by DBImpl::Open() and DBImpl::RestoreAliveLogFiles()
// (actually called by Open()), only mutex_ is held because at this point,
// the DB::Open() call has not returned success to application, and the
// only other thread(s) that can conflict are bg threads calling
// FindObsoleteFiles() which ensure that both mutex_ and log_write_mutex_
// are held when accessing alive_log_files_.
// FindObsoleteFiles() which ensure that both mutex_ and wal_write_mutex_
// are held when accessing alive_wal_files_.
// 4. read by DBImpl::Open() is protected by mutex_.
// 5. push_back() by SwitchMemtable(). Both mutex_ and log_write_mutex_ are
// 5. push_back() by SwitchMemtable(). Both mutex_ and wal_write_mutex_ are
// held. This is done by the write group leader. Note that in the case of
// two-write-queues, another WAL-only write thread can be writing to the
// WAL concurrently. See 9.
// 6. read by SwitchWAL() with both mutex_ and log_write_mutex_ held. This is
// 6. read by SwitchWAL() with both mutex_ and wal_write_mutex_ held. This is
// done by write group leader.
// 7. read by ConcurrentWriteToWAL() by the write group leader in the case of
// two-write-queues. Only log_write_mutex_ is held to protect concurrent
// two-write-queues. Only wal_write_mutex_ is held to protect concurrent
// pop_front() by FindObsoleteFiles().
// 8. read by PreprocessWrite() by the write group leader. log_write_mutex_
// 8. read by PreprocessWrite() by the write group leader. wal_write_mutex_
// is held to protect the data structure from concurrent pop_front() by
// FindObsoleteFiles().
// 9. read by ConcurrentWriteToWAL() by a WAL-only write thread in the case
// of two-write-queues. Only log_write_mutex_ is held. This suffices to
// of two-write-queues. Only wal_write_mutex_ is held. This suffices to
// protect the data structure from concurrent push_back() by current
// write group leader as well as pop_front() by FindObsoleteFiles().
std::deque<LogFileNumberSize> alive_log_files_;
std::deque<WalFileNumberSize> alive_wal_files_;
// Total size of all "alive" WALs (for easy access without synchronization)
RelaxedAtomic<uint64_t> wals_total_size_{0};
// Log files that aren't fully synced, and the current log file.
// Synchronization:
// 1. read by FindObsoleteFiles() which can be called either in application
// thread or RocksDB bg threads. log_write_mutex_ is always held, while
// thread or RocksDB bg threads. wal_write_mutex_ is always held, while
// some reads are performed without mutex_.
// 2. pop_front() by FindObsoleteFiles() with only log_write_mutex_ held.
// 3. read by DBImpl::Open() with both mutex_ and log_write_mutex_.
// 4. emplace_back() by DBImpl::Open() with both mutex_ and log_write_mutex.
// 2. pop_front() by FindObsoleteFiles() with only wal_write_mutex_ held.
// 3. read by DBImpl::Open() with both mutex_ and wal_write_mutex_.
// 4. emplace_back() by DBImpl::Open() with both mutex_ and wal_write_mutex.
// Note that at this point, DB::Open() has not returned success to
// application, thus the only other thread(s) that can conflict are bg
// threads calling FindObsoleteFiles(). See 1.
// 5. iteration and clear() from CloseHelper() always hold log_write_mutex
// 5. iteration and clear() from CloseHelper() always hold wal_write_mutex
// and mutex_.
// 6. back() called by APIs FlushWAL() and LockWAL() are protected by only
// log_write_mutex_. These two can be called by application threads after
// wal_write_mutex_. These two can be called by application threads after
// DB::Open() returns success to applications.
// 7. read by SyncWAL(), another API, protected by only log_write_mutex_.
// 7. read by SyncWAL(), another API, protected by only wal_write_mutex_.
// 8. read by MarkLogsNotSynced() and MarkLogsSynced() are protected by
// log_write_mutex_.
// 9. erase() by MarkLogsSynced() protected by log_write_mutex_.
// 10. read by SyncClosedWals() protected by only log_write_mutex_. This can
// wal_write_mutex_.
// 9. erase() by MarkLogsSynced() protected by wal_write_mutex_.
// 10. read by SyncClosedWals() protected by only wal_write_mutex_. This can
// happen in bg flush threads after DB::Open() returns success to
// applications.
// 11. reads, e.g. front(), iteration, and back() called by PreprocessWrite()
// holds only the log_write_mutex_. This is done by the write group
// holds only the wal_write_mutex_. This is done by the write group
// leader. A bg thread calling FindObsoleteFiles() or MarkLogsSynced()
// can happen concurrently. This is fine because log_write_mutex_ is used
// can happen concurrently. This is fine because wal_write_mutex_ is used
// by all parties. See 2, 5, 9.
// 12. reads, empty(), back() called by SwitchMemtable() hold both mutex_ and
// log_write_mutex_. This happens in the write group leader.
// wal_write_mutex_. This happens in the write group leader.
// 13. emplace_back() by SwitchMemtable() hold both mutex_ and
// log_write_mutex_. This happens in the write group leader. Can conflict
// wal_write_mutex_. This happens in the write group leader. Can conflict
// with bg threads calling FindObsoleteFiles(), MarkLogsSynced(),
// SyncClosedWals(), etc. as well as application threads calling
// FlushWAL(), SyncWAL(), LockWAL(). This is fine because all parties
// require at least log_write_mutex_.
// require at least wal_write_mutex_.
// 14. iteration called in WriteToWAL(write_group) protected by
// log_write_mutex_. This is done by write group leader when
// wal_write_mutex_. This is done by write group leader when
// two-write-queues is disabled and write needs to sync logs.
// 15. back() called in ConcurrentWriteToWAL() protected by log_write_mutex_.
// 15. back() called in ConcurrentWriteToWAL() protected by wal_write_mutex_.
// This can be done by the write group leader if two-write-queues is
// enabled. It can also be done by another WAL-only write thread.
//
@@ -2848,23 +2895,22 @@ class DBImpl : public DB {
std::deque<LogWriterNumber> logs_;
// Signaled when getting_synced becomes false for some of the logs_.
InstrumentedCondVar log_sync_cv_;
InstrumentedCondVar wal_sync_cv_;
// This is the app-level state that is written to the WAL but will be used
// only during recovery. Using this feature enables not writing the state to
// memtable on normal writes and hence improving the throughput. Each new
// write of the state will replace the previous state entirely even if the
// keys in the two consecutive states do not overlap.
// It is protected by log_write_mutex_ when two_write_queues_ is enabled.
// It is protected by wal_write_mutex_ when two_write_queues_ is enabled.
// Otherwise only the heaad of write_thread_ can access it.
WriteBatch cached_recoverable_state_;
std::atomic<bool> cached_recoverable_state_empty_ = {true};
std::atomic<uint64_t> total_log_size_;
// If this is non-empty, we need to delete these log files in background
// threads. Protected by log_write_mutex_.
autovector<log::Writer*> logs_to_free_;
// threads. Protected by wal_write_mutex_.
autovector<log::Writer*> wals_to_free_;
bool is_snapshot_supported_;
bool is_snapshot_supported_ = true;
std::map<uint64_t, std::map<std::string, uint64_t>> stats_history_;
@@ -2888,7 +2934,7 @@ class DBImpl : public DB {
// sleep if it uses up the quota.
// Note: This is to protect memtable and compaction. If the batch only writes
// to the WAL its size need not to be included in this.
uint64_t last_batch_group_size_;
uint64_t last_batch_group_size_ = 0;
FlushScheduler flush_scheduler_;
@@ -2947,32 +2993,32 @@ class DBImpl : public DB {
std::unordered_set<uint64_t> files_grabbed_for_purge_;
// A queue to store log writers to close. Protected by db mutex_.
std::deque<log::Writer*> logs_to_free_queue_;
std::deque<log::Writer*> wals_to_free_queue_;
std::deque<SuperVersion*> superversions_to_free_queue_;
int unscheduled_flushes_;
int unscheduled_flushes_ = 0;
int unscheduled_compactions_;
int unscheduled_compactions_ = 0;
// count how many background compactions are running or have been scheduled in
// the BOTTOM pool
int bg_bottom_compaction_scheduled_;
int bg_bottom_compaction_scheduled_ = 0;
// count how many background compactions are running or have been scheduled
int bg_compaction_scheduled_;
int bg_compaction_scheduled_ = 0;
// stores the number of compactions are currently running
int num_running_compactions_;
int num_running_compactions_ = 0;
// number of background memtable flush jobs, submitted to the HIGH pool
int bg_flush_scheduled_;
int bg_flush_scheduled_ = 0;
// stores the number of flushes are currently running
int num_running_flushes_;
int num_running_flushes_ = 0;
// number of background obsolete file purge jobs, submitted to the HIGH pool
int bg_purge_scheduled_;
int bg_purge_scheduled_ = 0;
std::deque<ManualCompactionState*> manual_compaction_dequeue_;
@@ -2982,11 +3028,11 @@ class DBImpl : public DB {
// This enables two different threads to call
// EnableFileDeletions() and DisableFileDeletions()
// without any synchronization
int disable_delete_obsolete_files_;
int disable_delete_obsolete_files_ = 0;
// Number of times FindObsoleteFiles has found deletable files and the
// corresponding call to PurgeObsoleteFiles has not yet finished.
int pending_purge_obsolete_files_;
int pending_purge_obsolete_files_ = 0;
// last time when DeleteObsoleteFiles with full scan was executed. Originally
// initialized with startup time.
@@ -2998,12 +3044,12 @@ class DBImpl : public DB {
// The mutex used by switch_cv_. mutex_ should be acquired beforehand.
std::mutex switch_mutex_;
// Number of threads intending to write to memtable
std::atomic<size_t> pending_memtable_writes_ = {};
std::atomic<size_t> pending_memtable_writes_{0};
// A flag indicating whether the current rocksdb database has any
// data that is not yet persisted into either WAL or SST file.
// Used when disableWAL is true.
std::atomic<bool> has_unpersisted_data_;
std::atomic<bool> has_unpersisted_data_{false};
// if an attempt was made to flush all column families that
// the oldest log depends on but uncommitted data in the oldest
@@ -3011,26 +3057,26 @@ class DBImpl : public DB {
// We must attempt to free the dependent memtables again
// at a later time after the transaction in the oldest
// log is fully commited.
bool unable_to_release_oldest_log_;
bool unable_to_release_oldest_log_{false};
// Number of running IngestExternalFile() or CreateColumnFamilyWithImport()
// calls.
// REQUIRES: mutex held
int num_running_ingest_file_;
int num_running_ingest_file_ = 0;
WalManager wal_manager_;
// A value of > 0 temporarily disables scheduling of background work
int bg_work_paused_;
int bg_work_paused_ = 0;
// A value of > 0 temporarily disables scheduling of background compaction
int bg_compaction_paused_;
int bg_compaction_paused_ = 0;
// Guard against multiple concurrent refitting
bool refitting_level_;
bool refitting_level_ = false;
// Indicate DB was opened successfully
bool opened_successfully_;
bool opened_successfully_ = false;
// The min threshold to triggere bottommost compaction for removing
// garbages, among all column families.
@@ -3076,13 +3122,13 @@ class DBImpl : public DB {
// error recovery from going on in parallel. The latter, shutting_down_,
// is set a little later during the shutdown after scheduling memtable
// flushes
std::atomic<bool> shutdown_initiated_;
std::atomic<bool> shutdown_initiated_{false};
// Flag to indicate whether sst_file_manager object was allocated in
// DB::Open() or passed to us
bool own_sfm_;
// Flag to check whether Close() has been called on this DB
bool closed_;
bool closed_ = false;
// save the closing status, for re-calling the close()
Status closing_status_;
// mutex for DB::Close()
@@ -3118,7 +3164,7 @@ class DBImpl : public DB {
// The number of LockWAL called without matching UnlockWAL call.
// See also lock_wal_write_token_
uint32_t lock_wal_count_;
uint32_t lock_wal_count_ = 0;
};
class GetWithTimestampReadCallback : public ReadCallback {
+268 -14
View File
@@ -13,12 +13,14 @@
#include "db/db_impl/db_impl.h"
#include "db/error_handler.h"
#include "db/event_helpers.h"
#include "file/file_util.h"
#include "file/sst_file_manager_impl.h"
#include "logging/logging.h"
#include "monitoring/iostats_context_imp.h"
#include "monitoring/perf_context_imp.h"
#include "monitoring/thread_status_updater.h"
#include "monitoring/thread_status_util.h"
#include "options/options_helper.h"
#include "rocksdb/file_system.h"
#include "rocksdb/io_status.h"
#include "rocksdb/options.h"
@@ -168,7 +170,7 @@ Status DBImpl::FlushMemTableToOutputFile(
// had not been committed yet. Make sure we sync them to keep the persisted
// WAL state at least as new as the persisted SST state.
const bool needs_to_sync_closed_wals =
logfile_number_ > 0 &&
cur_wal_number_ > 0 &&
(versions_->GetColumnFamilySet()->NumberOfColumnFamilies() > 1 ||
allow_2pc());
@@ -224,7 +226,7 @@ Status DBImpl::FlushMemTableToOutputFile(
bool need_cancel = false;
IOStatus log_io_s = IOStatus::OK();
if (needs_to_sync_closed_wals) {
// SyncClosedWals() may unlock and re-lock the log_write_mutex multiple
// SyncClosedWals() may unlock and re-lock the wal_write_mutex multiple
// times.
VersionEdit synced_wals;
bool error_recovery_in_prog = error_handler_.IsRecoveryInProgress();
@@ -512,7 +514,7 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
job_context->job_id, flush_reason);
}
if (logfile_number_ > 0) {
if (cur_wal_number_ > 0) {
// TODO (yanqin) investigate whether we should sync the closed logs for
// single column family case.
VersionEdit synced_wals;
@@ -528,7 +530,7 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
if (!log_io_s.ok() && !log_io_s.IsShutdownInProgress() &&
!log_io_s.IsColumnFamilyDropped()) {
if (total_log_size_ > 0) {
if (wals_total_size_.LoadRelaxed() > 0) {
error_handler_.SetBGError(log_io_s, BackgroundErrorReason::kFlush);
} else {
// If the WAL is empty, we use different error reason
@@ -981,7 +983,8 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
std::string begin_str, end_str;
auto [begin, end] =
MaybeAddTimestampsToRange(begin_without_ts, end_without_ts, ts_sz,
MaybeAddTimestampsToRange(OptSlice::CopyFromPtr(begin_without_ts),
OptSlice::CopyFromPtr(end_without_ts), ts_sz,
&begin_str, &end_str, false /*exclusive_end*/);
return CompactRangeInternal(
@@ -1695,11 +1698,13 @@ void DBImpl::NotifyOnCompactionBegin(ColumnFamilyData* cfd, Compaction* c,
}
c->SetNotifyOnCompactionCompleted();
int num_l0_files = c->input_version()->storage_info()->NumLevelFiles(0);
// release lock while notifying events
mutex_.Unlock();
TEST_SYNC_POINT("DBImpl::NotifyOnCompactionBegin::UnlockMutex");
{
CompactionJobInfo info{};
info.num_l0_files = num_l0_files;
BuildCompactionJobInfo(cfd, c, st, job_stats, job_id, &info);
for (const auto& listener : immutable_db_options_.listeners) {
listener->OnCompactionBegin(this, info);
@@ -1724,11 +1729,13 @@ void DBImpl::NotifyOnCompactionCompleted(
return;
}
int num_l0_files = cfd->current()->storage_info()->NumLevelFiles(0);
// release lock while notifying events
mutex_.Unlock();
TEST_SYNC_POINT("DBImpl::NotifyOnCompactionCompleted::UnlockMutex");
{
CompactionJobInfo info{};
info.num_l0_files = num_l0_files;
BuildCompactionJobInfo(cfd, c, st, compaction_job_stats, job_id, &info);
for (const auto& listener : immutable_db_options_.listeners) {
listener->OnCompactionCompleted(this, info);
@@ -3781,11 +3788,251 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
c->column_family_data()->GetName().c_str(),
c->num_input_files(0));
if (status.ok() && io_s.ok()) {
UpdateDeletionCompactionStats(c);
UpdateFIFOCompactionStatus(c);
}
*made_progress = true;
TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:AfterCompaction",
c->column_family_data());
} else if (c->is_trivial_copy_compaction()) {
TEST_SYNC_POINT_CALLBACK(
"DBImpl::BackgroundCompaction:TriviaCopyBeforeCompaction",
c->column_family_data());
assert(c->num_input_files(1) == 0);
assert(c->column_family_data()->ioptions().compaction_style ==
kCompactionStyleFIFO);
assert(c->compaction_reason() == CompactionReason::kChangeTemperature);
compaction_job_stats.num_input_files = c->num_input_files(0);
NotifyOnCompactionBegin(c->column_family_data(), c.get(), status,
compaction_job_stats, job_context->job_id);
std::vector<FileMetaData> out_files;
for (const auto& in_file : *c->inputs(0)) {
const uint64_t out_file_number = versions_->NewFileNumber();
const std::string in_fname =
TableFileName(c->immutable_options().cf_paths,
in_file->fd.GetNumber(), in_file->fd.GetPathId());
const std::string out_fname =
TableFileName(c->immutable_options().cf_paths, out_file_number,
c->output_path_id());
// TODO (mikechuang): Currently skip calling
// EventHelpers::NotifyTableFileCreationStarted for the trivial copy.
// Since it's a trivial copy we should ideally use the exact
// TableProperties from the input file but that will break some existing
// stress tests. For now skip the listener call for the FIFO
// kChangeTemperature trivial copy move.
int64_t tmp_current_time = 0;
auto get_time_status =
immutable_db_options_.clock->GetCurrentTime(&tmp_current_time);
if (!get_time_status.ok()) {
ROCKS_LOG_BUFFER(log_buffer,
"[%s] WARNING: Failed to get current time %s "
"status=%s",
c->column_family_data()->GetName().c_str(),
get_time_status.ToString().c_str());
}
uint64_t out_file_creation_time = static_cast<uint64_t>(tmp_current_time);
FileOptions copied_file_options = file_options_;
copied_file_options.temperature = c->output_temperature();
std::unique_ptr<WritableFileWriter> dest_writer;
{
std::unique_ptr<FSWritableFile> dest_file;
IOStatus writable_file_io_status =
immutable_db_options_.fs.get()->NewWritableFile(
out_fname, copied_file_options, &dest_file, nullptr /* dbg */);
TEST_SYNC_POINT_CALLBACK(
"NewWritableFile::FileOptions.temperature",
const_cast<Temperature*>(&copied_file_options.temperature));
if (!writable_file_io_status.ok()) {
io_s = writable_file_io_status;
ROCKS_LOG_BUFFER(
log_buffer,
"[%s] Error: Abort trivial copy compaction, failed to open "
"NewWritableFile %s\n"
" out_fname=%s, temperature=%s, io_status=%s",
c->column_family_data()->GetName().c_str(), out_fname.c_str(),
temperature_to_string[c->output_temperature()].c_str(),
io_s.ToString().c_str());
break;
}
FileTypeSet tmp_set = immutable_db_options_.checksum_handoff_file_types;
dest_writer.reset(new WritableFileWriter(
std::move(dest_file), out_fname, copied_file_options,
immutable_db_options_.clock, io_tracer_,
immutable_db_options_.stats, Histograms::SST_WRITE_MICROS,
c->immutable_options().listeners,
immutable_db_options_.file_checksum_gen_factory.get(),
tmp_set.Contains(FileType::kTableFile), false));
}
ROCKS_LOG_BUFFER(
log_buffer,
"[%s] Started copying from: %s\n"
" temperature=%s, to: %s, temperature=%s, buffer_size=%" PRIu64,
c->column_family_data()->GetName().c_str(), in_fname.c_str(),
temperature_to_string[in_file->temperature].c_str(),
out_fname.c_str(),
temperature_to_string[c->output_temperature()].c_str(),
c->mutable_cf_options()
.compaction_options_fifo.trivial_copy_buffer_size);
// Add IO_LOW HINT for compaction
IOOptions copy_files_compaction_io_options;
copy_files_compaction_io_options.rate_limiter_priority =
Env::IOPriority::IO_LOW;
copy_files_compaction_io_options.type = IOType::kData;
copy_files_compaction_io_options.io_activity =
Env::IOActivity::kCompaction;
IOStatus copy_file_io_status = CopyFile(
immutable_db_options_.fs.get() /* fileSystem */,
in_fname /* source */, in_file->temperature /* src_temp_hint */,
dest_writer /* dest_writer */, 0 /* size */, true /* use_fsync */,
io_tracer_ /* io_tracer*/,
c->mutable_cf_options()
.compaction_options_fifo
.trivial_copy_buffer_size /* max_read_buffer_size
*/
,
copy_files_compaction_io_options /* readIOOptions */,
copy_files_compaction_io_options /* writeIOOptions */);
if (dest_writer) {
IOOptions close_files_compaction_io_options;
close_files_compaction_io_options.rate_limiter_priority =
Env::IOPriority::IO_LOW;
close_files_compaction_io_options.type = IOType::kData;
close_files_compaction_io_options.io_activity =
Env::IOActivity::kCompaction;
// Close the dest_write
io_s = dest_writer->Close(close_files_compaction_io_options);
if (!io_s.ok()) {
ROCKS_LOG_BUFFER(
log_buffer,
"[%s] Failed to close the writer. Failed to copy from: %s\n"
" temperature=%s, to=%s, temperature=%s, io_status=%s",
c->column_family_data()->GetName().c_str(), in_fname.c_str(),
temperature_to_string[in_file->temperature].c_str(),
out_fname.c_str(),
temperature_to_string[c->output_temperature()].c_str(),
io_s.ToString().c_str());
break;
}
}
io_s = copy_file_io_status;
if (!io_s.ok()) {
ROCKS_LOG_BUFFER(log_buffer,
"[%s] Failed to copy from: %s\n"
" temperature=%s, to=%s, temperature=%s, io_status=%s",
c->column_family_data()->GetName().c_str(),
in_fname.c_str(),
temperature_to_string[in_file->temperature].c_str(),
out_fname.c_str(),
temperature_to_string[c->output_temperature()].c_str(),
io_s.ToString().c_str());
break;
}
ROCKS_LOG_BUFFER(log_buffer,
"[%s] Successfully copying from: %s\n"
" temperature=%s, to=%s, temperature=%s, io_status=%s",
c->column_family_data()->GetName().c_str(),
in_fname.c_str(),
temperature_to_string[in_file->temperature].c_str(),
out_fname.c_str(),
temperature_to_string[c->output_temperature()].c_str(),
io_s.ToString().c_str());
FileMetaData out_file_metadata{
out_file_number,
c->output_path_id(),
in_file->fd.GetFileSize(),
in_file->smallest,
in_file->largest,
in_file->fd.smallest_seqno,
in_file->fd.largest_seqno,
false /* marked_for_compact */,
c->output_temperature() /* temperature */,
in_file->oldest_blob_file_number,
in_file->oldest_ancester_time,
out_file_creation_time,
c->MinInputFileEpochNumber(),
dest_writer->GetFileChecksum(),
dest_writer->GetFileChecksumFuncName(),
in_file->unique_id,
in_file->compensated_range_deletion_size,
in_file->tail_size,
in_file->user_defined_timestamps_persisted};
out_files.push_back(std::move(out_file_metadata));
}
// Update version set
if (status.ok() && io_s.ok()) {
// NOTE: ChangeTemperature should only copy one file at one file
// hence *c->inputs(0) == out_files.size() == 1 if copy succeeded
assert(c->inputs(0)->size() == 1);
assert(out_files.size() == 1);
auto out_file_metadata_it = out_files.begin();
for (const auto& in_file : *c->inputs(0)) {
if (out_file_metadata_it == out_files.end()) {
break;
}
c->edit()->DeleteFile(c->level(), in_file->fd.GetNumber());
c->edit()->AddFile(c->level(), *out_file_metadata_it);
++out_file_metadata_it;
}
status = versions_->LogAndApply(
c->column_family_data(), read_options, write_options, c->edit(),
&mutex_, directories_.GetDbDir(),
/*new_descriptor_log=*/false, /*column_family_options=*/nullptr,
[&c, &compaction_released](const Status& s) {
c->ReleaseCompactionFiles(s);
compaction_released = true;
});
}
// TODO (mikechuang): Currently skip calling
// EventHelper::LogAndNotifyTableFileCreationFinished for the trivial copy.
// Since it's a trivial copy we should ideally use the exact TableProperties
// from the input file but that will break some existing stress tests. For
// now skip the listener call for the FIFO kChangeTemperature trivial copy
// move.
if (io_s.ok()) {
io_s = versions_->io_status();
}
InstallSuperVersionAndScheduleWork(
c->column_family_data(), job_context->superversion_contexts.data());
if (status.ok() && io_s.ok()) {
UpdateFIFOCompactionStatus(c);
} else {
for (const auto& in_file : *c->inputs(0)) {
const std::string in_fname =
TableFileName(c->immutable_options().cf_paths,
in_file->fd.GetNumber(), in_file->fd.GetPathId());
ROCKS_LOG_BUFFER(
log_buffer,
"[%s] Failed to do trvial copy compaction: %s"
" temperature=%s, to temperature=%s, status=%s, io_status=%s",
c->column_family_data()->GetName().c_str(), in_fname.c_str(),
temperature_to_string[in_file->temperature].c_str(),
temperature_to_string[c->output_temperature()].c_str(),
status.ToString().c_str(), io_s.ToString().c_str());
}
}
*made_progress = true;
TEST_SYNC_POINT_CALLBACK(
"DBImpl::BackgroundCompaction:TriviaCopyAfterCompaction",
c->column_family_data());
} else if (!trivial_move_disallowed && c->IsTrivialMove()) {
TEST_SYNC_POINT("DBImpl::BackgroundCompaction:TrivialMove");
TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:BeforeCompaction",
@@ -3798,6 +4045,8 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
compaction_job_stats.num_input_files = c->num_input_files(0);
// Trivial moves do not get compacted remotely
compaction_job_stats.is_remote_compaction = false;
compaction_job_stats.num_input_files_trivially_moved =
compaction_job_stats.num_input_files;
NotifyOnCompactionBegin(c->column_family_data(), c.get(), status,
compaction_job_stats, job_context->job_id);
@@ -4169,8 +4418,7 @@ bool DBImpl::MCOverlap(ManualCompactionState* m, ManualCompactionState* m1) {
return false;
}
void DBImpl::UpdateDeletionCompactionStats(
const std::unique_ptr<Compaction>& c) {
void DBImpl::UpdateFIFOCompactionStatus(const std::unique_ptr<Compaction>& c) {
if (c == nullptr) {
return;
}
@@ -4184,6 +4432,9 @@ void DBImpl::UpdateDeletionCompactionStats(
case CompactionReason::kFIFOTtl:
RecordTick(stats_, FIFO_TTL_COMPACTIONS);
break;
case CompactionReason::kChangeTemperature:
RecordTick(stats_, FIFO_CHANGE_TEMPERATURE_COMPACTIONS);
break;
default:
assert(false);
break;
@@ -4273,9 +4524,10 @@ void DBImpl::BuildCompactionJobInfo(
// for superversion_to_free
void DBImpl::InstallSuperVersionAndScheduleWork(
ColumnFamilyData* cfd, SuperVersionContext* sv_context) {
ColumnFamilyData* cfd, SuperVersionContext* sv_context,
std::optional<std::shared_ptr<SeqnoToTimeMapping>>
new_seqno_to_time_mapping) {
mutex_.AssertHeld();
const auto& mutable_cf_options = cfd->GetLatestMutableCFOptions();
// Update max_total_in_memory_state_
size_t old_memtable_size = 0;
@@ -4289,7 +4541,8 @@ void DBImpl::InstallSuperVersionAndScheduleWork(
if (UNLIKELY(sv_context->new_superversion == nullptr)) {
sv_context->NewSuperVersion();
}
cfd->InstallSuperVersion(sv_context, mutable_cf_options);
cfd->InstallSuperVersion(sv_context, &mutex_,
std::move(new_seqno_to_time_mapping));
// There may be a small data race here. The snapshot tricking bottommost
// compaction may already be released here. But assuming there will always be
@@ -4316,9 +4569,10 @@ void DBImpl::InstallSuperVersionAndScheduleWork(
MaybeScheduleFlushOrCompaction();
// Update max_total_in_memory_state_
max_total_in_memory_state_ = max_total_in_memory_state_ - old_memtable_size +
mutable_cf_options.write_buffer_size *
mutable_cf_options.max_write_buffer_number;
max_total_in_memory_state_ =
max_total_in_memory_state_ - old_memtable_size +
cfd->GetLatestMutableCFOptions().write_buffer_size *
cfd->GetLatestMutableCFOptions().max_write_buffer_number;
}
// ShouldPurge is called by FindObsoleteFiles when doing a full scan,
+9 -5
View File
@@ -84,6 +84,7 @@ void DBImpl::TEST_GetFilesMetaData(
}
uint64_t DBImpl::TEST_Current_Manifest_FileNo() {
InstrumentedMutexLock l(&mutex_);
return versions_->manifest_file_number();
}
@@ -224,13 +225,13 @@ void DBImpl::TEST_EndWrite(void* w) {
}
size_t DBImpl::TEST_LogsToFreeSize() {
InstrumentedMutexLock l(&log_write_mutex_);
return logs_to_free_.size();
InstrumentedMutexLock l(&wal_write_mutex_);
return wals_to_free_.size();
}
uint64_t DBImpl::TEST_LogfileNumber() {
InstrumentedMutexLock l(&mutex_);
return logfile_number_;
return cur_wal_number_;
}
void DBImpl::TEST_GetAllBlockCaches(
@@ -379,10 +380,13 @@ void DBImpl::TEST_VerifyNoObsoleteFilesCached(
uint64_t file_number;
GetUnaligned(reinterpret_cast<const uint64_t*>(key.data()), &file_number);
// Assert file is in live/quarantined set
if (live_and_quar_files.find(file_number) == live_and_quar_files.end()) {
bool cached_file_is_live_or_quar =
live_and_quar_files.find(file_number) != live_and_quar_files.end();
if (!cached_file_is_live_or_quar) {
// Fail with useful info
std::cerr << "File " << file_number << " is not live nor quarantined"
<< std::endl;
assert(false);
assert(cached_file_is_live_or_quar);
}
};
table_cache_->ApplyToAllEntries(fn, {});
+38 -35
View File
@@ -28,7 +28,7 @@ uint64_t DBImpl::MinLogNumberToKeep() {
return versions_->min_log_number_to_keep();
}
uint64_t DBImpl::MinLogNumberToRecycle() { return min_log_number_to_recycle_; }
uint64_t DBImpl::MinLogNumberToRecycle() { return min_wal_number_to_recycle_; }
uint64_t DBImpl::MinObsoleteSstNumberToKeep() {
mutex_.AssertHeld();
@@ -272,77 +272,77 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
// logs_ is empty when called during recovery, in which case there can't yet
// be any tracked obsolete logs
log_write_mutex_.Lock();
wal_write_mutex_.Lock();
if (alive_log_files_.empty() || logs_.empty()) {
if (alive_wal_files_.empty() || logs_.empty()) {
mutex_.AssertHeld();
// We may reach here if the db is DBImplSecondary
log_write_mutex_.Unlock();
wal_write_mutex_.Unlock();
return;
}
bool mutex_unlocked = false;
if (!alive_log_files_.empty() && !logs_.empty()) {
if (!alive_wal_files_.empty() && !logs_.empty()) {
uint64_t min_log_number = job_context->log_number;
size_t num_alive_log_files = alive_log_files_.size();
size_t num_alive_wal_files = alive_wal_files_.size();
// find newly obsoleted log files
while (alive_log_files_.begin()->number < min_log_number) {
auto& earliest = *alive_log_files_.begin();
while (alive_wal_files_.begin()->number < min_log_number) {
auto& earliest = *alive_wal_files_.begin();
if (immutable_db_options_.recycle_log_file_num >
log_recycle_files_.size() &&
wal_recycle_files_.size() &&
earliest.number >= MinLogNumberToRecycle()) {
ROCKS_LOG_INFO(immutable_db_options_.info_log,
"adding log %" PRIu64 " to recycle list\n",
earliest.number);
log_recycle_files_.push_back(earliest.number);
wal_recycle_files_.push_back(earliest.number);
} else {
job_context->log_delete_files.push_back(earliest.number);
}
if (job_context->size_log_to_delete == 0) {
job_context->prev_total_log_size = total_log_size_;
job_context->num_alive_log_files = num_alive_log_files;
job_context->prev_wals_total_size = wals_total_size_.LoadRelaxed();
job_context->num_alive_wal_files = num_alive_wal_files;
}
job_context->size_log_to_delete += earliest.size;
total_log_size_ -= earliest.size;
alive_log_files_.pop_front();
wals_total_size_.FetchSubRelaxed(earliest.size);
alive_wal_files_.pop_front();
// Current log should always stay alive since it can't have
// number < MinLogNumber().
assert(alive_log_files_.size());
assert(alive_wal_files_.size());
}
log_write_mutex_.Unlock();
wal_write_mutex_.Unlock();
mutex_.Unlock();
mutex_unlocked = true;
TEST_SYNC_POINT_CALLBACK("FindObsoleteFiles::PostMutexUnlock", nullptr);
log_write_mutex_.Lock();
wal_write_mutex_.Lock();
while (!logs_.empty() && logs_.front().number < min_log_number) {
auto& log = logs_.front();
if (log.IsSyncing()) {
log_sync_cv_.Wait();
wal_sync_cv_.Wait();
// logs_ could have changed while we were waiting.
continue;
}
// This WAL file is not live, so it's OK if we never sync the rest of it.
// If it's already closed, then it's been fully synced. If
// !background_close_inactive_wals then we need to Close it before
// removing from logs_ but not blocking while holding log_write_mutex_.
// removing from logs_ but not blocking while holding wal_write_mutex_.
if (!immutable_db_options_.background_close_inactive_wals &&
log.writer->file()) {
// We are taking ownership of and pinning the front entry, so we can
// expect it to be the same after releasing and re-acquiring the lock
log.PrepareForSync();
log_write_mutex_.Unlock();
wal_write_mutex_.Unlock();
// TODO: maybe check the return value of Close.
// TODO: plumb Env::IOActivity, Env::IOPriority
auto s = log.writer->file()->Close({});
s.PermitUncheckedError();
log_write_mutex_.Lock();
wal_write_mutex_.Lock();
log.writer->PublishIfClosed();
assert(&log == &logs_.front());
log.FinishSync();
log_sync_cv_.SignalAll();
wal_sync_cv_.SignalAll();
}
logs_to_free_.push_back(log.ReleaseWriter());
wals_to_free_.push_back(log.ReleaseWriter());
logs_.pop_front();
}
// Current log cannot be obsolete.
@@ -350,16 +350,16 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
}
// We're just cleaning up for DB::Write().
assert(job_context->logs_to_free.empty());
job_context->logs_to_free = logs_to_free_;
assert(job_context->wals_to_free.empty());
job_context->wals_to_free = wals_to_free_;
logs_to_free_.clear();
log_write_mutex_.Unlock();
wals_to_free_.clear();
wal_write_mutex_.Unlock();
if (mutex_unlocked) {
mutex_.Lock();
}
job_context->log_recycle_files.assign(log_recycle_files_.begin(),
log_recycle_files_.end());
job_context->log_recycle_files.assign(wal_recycle_files_.begin(),
wal_recycle_files_.end());
}
// Delete obsolete files and log status and information of file deletion
@@ -368,6 +368,7 @@ void DBImpl::DeleteObsoleteFileImpl(int job_id, const std::string& fname,
FileType type, uint64_t number) {
TEST_SYNC_POINT_CALLBACK("DBImpl::DeleteObsoleteFileImpl::BeforeDeletion",
const_cast<std::string*>(&fname));
IGNORE_STATUS_IF_ERROR(Status::IOError());
Status file_deletion_status;
if (type == kTableFile || type == kBlobFile || type == kWalFile) {
@@ -423,12 +424,14 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
// FindObsoleteFiles() should've populated this so nonzero
assert(state.manifest_file_number != 0);
IGNORE_STATUS_IF_ERROR(Status::IOError());
// Now, convert lists to unordered sets, WITHOUT mutex held; set is slow.
std::unordered_set<uint64_t> sst_live_set(state.sst_live.begin(),
state.sst_live.end());
std::unordered_set<uint64_t> blob_live_set(state.blob_live.begin(),
state.blob_live.end());
std::unordered_set<uint64_t> log_recycle_files_set(
std::unordered_set<uint64_t> wal_recycle_files_set(
state.log_recycle_files.begin(), state.log_recycle_files.end());
std::unordered_set<uint64_t> quarantine_files_set(
state.files_to_quarantine.begin(), state.files_to_quarantine.end());
@@ -488,13 +491,13 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
std::unique(candidate_files.begin(), candidate_files.end()),
candidate_files.end());
if (state.prev_total_log_size > 0) {
if (state.prev_wals_total_size > 0) {
ROCKS_LOG_INFO(immutable_db_options_.info_log,
"[JOB %d] Try to delete WAL files size %" PRIu64
", prev total WAL file size %" PRIu64
", number of live WAL files %" ROCKSDB_PRIszt ".\n",
state.job_id, state.size_log_to_delete,
state.prev_total_log_size, state.num_alive_log_files);
state.prev_wals_total_size, state.num_alive_wal_files);
}
std::vector<std::string> old_info_log_files;
@@ -529,7 +532,7 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
optsfile_num2 = std::min(optsfile_num2, state.min_options_file_number);
// Close WALs before trying to delete them.
for (const auto w : state.logs_to_free) {
for (const auto w : state.wals_to_free) {
// TODO: maybe check the return value of Close.
// TODO: plumb Env::IOActivity, Env::IOPriority
auto s = w->Close({});
@@ -556,8 +559,8 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
case kWalFile:
keep = ((number >= state.log_number) ||
(number == state.prev_log_number) ||
(log_recycle_files_set.find(number) !=
log_recycle_files_set.end()));
(wal_recycle_files_set.find(number) !=
wal_recycle_files_set.end()));
break;
case kDescriptorFile:
// Keep my manifest file, and any newer incarnations'
+127 -56
View File
@@ -35,8 +35,8 @@ Options SanitizeOptions(const std::string& dbname, const Options& src,
auto db_options =
SanitizeOptions(dbname, DBOptions(src), read_only, logger_creation_s);
ImmutableDBOptions immutable_db_options(db_options);
auto cf_options =
SanitizeOptions(immutable_db_options, ColumnFamilyOptions(src));
auto cf_options = SanitizeCfOptions(immutable_db_options, read_only,
ColumnFamilyOptions(src));
return Options(db_options, cf_options);
}
@@ -1119,7 +1119,7 @@ void DBOpenLogRecordReadReporter::Corruption(size_t bytes, const Status& s,
static_cast<int>(bytes), s.ToString().c_str());
if (status != nullptr && status->ok()) {
*status = s;
corrupted_log_number_ = log_number;
corrupted_wal_number_ = log_number;
}
}
@@ -1203,6 +1203,13 @@ Status DBImpl::ProcessLogFiles(
PredecessorWALInfo predecessor_wal_info;
for (auto wal_number : wal_numbers) {
// Detecting early break on the next iteration after `wal_number` has been
// advanced since this `wal_number` doesn't affect follow-up handling after
// breaking out of the for loop.
if (!status.ok()) {
break;
}
SequenceNumber prev_next_sequence = *next_sequence;
if (status.ok()) {
status = ProcessLogFile(
wal_number, min_wal_number, is_retry, read_only, job_id,
@@ -1210,6 +1217,10 @@ Status DBImpl::ProcessLogFiles(
&stop_replay_by_wal_filter, &corrupted_wal_number,
corrupted_wal_found, version_edits, &flushed, predecessor_wal_info);
}
if (status.ok()) {
status = CheckSeqnoNotSetBackDuringRecovery(prev_next_sequence,
*next_sequence);
}
}
if (status.ok()) {
@@ -1317,6 +1328,7 @@ Status DBImpl::ProcessLogFile(
}
// FIXME(hx235): consolidate `process_status` and `status`
SequenceNumber prev_next_sequence = *next_sequence;
Status process_status = ProcessLogRecord(
record, reader, running_ts_sz, wal_number, fname, read_only, job_id,
logFileDropped, &reporter, &record_checksum, &last_seqno_observed,
@@ -1325,6 +1337,12 @@ Status DBImpl::ProcessLogFile(
if (!process_status.ok()) {
return process_status;
} else if (Status seqno_check_status = CheckSeqnoNotSetBackDuringRecovery(
prev_next_sequence, *next_sequence);
!seqno_check_status.ok()) {
// Sequence number being set back indicates a serious software bug, the DB
// should not be opened in this case.
return seqno_check_status;
} else if (*stop_replay_for_corruption) {
break;
}
@@ -1863,6 +1881,20 @@ Status DBImpl::MaybeFlushFinalMemtableOrRestoreActiveLogFiles(
return status;
}
Status DBImpl::CheckSeqnoNotSetBackDuringRecovery(
SequenceNumber prev_next_seqno, SequenceNumber current_next_seqno) {
if (prev_next_seqno == kMaxSequenceNumber ||
prev_next_seqno <= current_next_seqno) {
return Status::OK();
}
std::string msg =
"Sequence number is being set backwards during recovery, this is likely "
"a software bug or a data corruption. Prev next seqno: " +
std::to_string(prev_next_seqno) +
" , current next seqno: " + std::to_string(current_next_seqno);
return Status::Corruption(msg);
}
void DBImpl::FinishLogFilesRecovery(int job_id, const Status& status) {
event_logger_.Log() << "job" << job_id << "event"
<< (status.ok() ? "recovery_finished" : "recovery_failed")
@@ -1870,8 +1902,8 @@ void DBImpl::FinishLogFilesRecovery(int job_id, const Status& status) {
}
Status DBImpl::GetLogSizeAndMaybeTruncate(uint64_t wal_number, bool truncate,
LogFileNumberSize* log_ptr) {
LogFileNumberSize log(wal_number);
WalFileNumberSize* log_ptr) {
WalFileNumberSize log(wal_number);
std::string fname =
LogFileName(immutable_db_options_.GetWalDir(), wal_number);
Status s;
@@ -1914,27 +1946,27 @@ Status DBImpl::RestoreAliveLogFiles(const std::vector<uint64_t>& wal_numbers) {
assert(immutable_db_options_.avoid_flush_during_recovery);
// Mark these as alive so they'll be considered for deletion later by
// FindObsoleteFiles()
total_log_size_ = 0;
log_empty_ = false;
wals_total_size_.StoreRelaxed(0);
wal_empty_ = false;
uint64_t min_wal_with_unflushed_data =
versions_->MinLogNumberWithUnflushedData();
for (auto wal_number : wal_numbers) {
if (!allow_2pc() && wal_number < min_wal_with_unflushed_data) {
// In non-2pc mode, the WAL files not backing unflushed data are not
// alive, thus should not be added to the alive_log_files_.
// alive, thus should not be added to the alive_wal_files_.
continue;
}
// We preallocate space for wals, but then after a crash and restart, those
// preallocated space are not needed anymore. It is likely only the last
// log has such preallocated space, so we only truncate for the last log.
LogFileNumberSize log;
WalFileNumberSize log;
s = GetLogSizeAndMaybeTruncate(
wal_number, /*truncate=*/(wal_number == wal_numbers.back()), &log);
if (!s.ok()) {
break;
}
total_log_size_ += log.size;
alive_log_files_.push_back(log);
wals_total_size_.FetchAddRelaxed(log.size);
alive_wal_files_.push_back(log);
}
return s;
}
@@ -1968,6 +2000,9 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
const size_t ts_sz = ucmp->timestamp_size();
const bool logical_strip_timestamp =
ts_sz > 0 && !cfd->ioptions().persist_user_defined_timestamps;
// Note that here we treat flush as level 0 compaction in internal stats
InternalStats::CompactionStats flush_stats(CompactionReason::kFlush,
1 /* count */);
{
ScopedArenaPtr<InternalIterator> iter(
logical_strip_timestamp
@@ -1995,8 +2030,9 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
meta.oldest_ancester_time = current_time;
meta.epoch_number = cfd->NewEpochNumber();
{
auto write_hint =
cfd->current()->storage_info()->CalculateSSTWriteHint(/*level=*/0);
auto write_hint = cfd->current()->storage_info()->CalculateSSTWriteHint(
/*level=*/0,
immutable_db_options_.calculate_sst_write_lifetime_hint_set);
mutex_.Unlock();
SequenceNumber earliest_write_conflict_snapshot;
@@ -2039,19 +2075,20 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
kMaxSequenceNumber);
Version* version = cfd->current();
version->Ref();
uint64_t num_input_entries = 0;
s = BuildTable(dbname_, versions_.get(), immutable_db_options_, tboptions,
file_options_for_compaction_, cfd->table_cache(),
iter.get(), std::move(range_del_iters), &meta,
&blob_file_additions, snapshot_seqs, earliest_snapshot,
earliest_write_conflict_snapshot, kMaxSequenceNumber,
snapshot_checker, paranoid_file_checks,
cfd->internal_stats(), &io_s, io_tracer_,
BlobFileCreationReason::kRecovery,
nullptr /* seqno_to_time_mapping */, &event_logger_,
job_id, nullptr /* table_properties */, write_hint,
nullptr /*full_history_ts_low*/, &blob_callback_, version,
&num_input_entries);
TableProperties temp_table_proerties;
s = BuildTable(
dbname_, versions_.get(), immutable_db_options_, tboptions,
file_options_for_compaction_, cfd->table_cache(), iter.get(),
std::move(range_del_iters), &meta, &blob_file_additions,
snapshot_seqs, earliest_snapshot, earliest_write_conflict_snapshot,
kMaxSequenceNumber, snapshot_checker, paranoid_file_checks,
cfd->internal_stats(), &io_s, io_tracer_,
BlobFileCreationReason::kRecovery,
nullptr /* seqno_to_time_mapping */, &event_logger_, job_id,
&temp_table_proerties /* table_properties */, write_hint,
nullptr /*full_history_ts_low*/, &blob_callback_, version,
nullptr /* memtable_payload_bytes */,
nullptr /* memtable_garbage_bytes */, &flush_stats);
version->Unref();
LogFlush(immutable_db_options_.info_log);
ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
@@ -2067,10 +2104,31 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
}
uint64_t total_num_entries = mem->NumEntries();
if (s.ok() && total_num_entries != num_input_entries) {
if (s.ok() && total_num_entries != flush_stats.num_input_records) {
std::string msg = "Expected " + std::to_string(total_num_entries) +
" entries in memtable, but read " +
std::to_string(num_input_entries);
std::to_string(flush_stats.num_input_records);
ROCKS_LOG_WARN(immutable_db_options_.info_log,
"[%s] [JOB %d] Level-0 flush during recover: %s",
cfd->GetName().c_str(), job_id, msg.c_str());
if (immutable_db_options_.flush_verify_memtable_count) {
s = Status::Corruption(msg);
}
}
// Only verify on table with format collects table properties
const auto& mutable_cf_options = cfd->GetLatestMutableCFOptions();
if (s.ok() &&
(mutable_cf_options.table_factory->IsInstanceOf(
TableFactory::kBlockBasedTableName()) ||
mutable_cf_options.table_factory->IsInstanceOf(
TableFactory::kPlainTableName())) &&
flush_stats.num_output_records != temp_table_proerties.num_entries) {
std::string msg =
"Number of keys in flush output SST files does not match "
"number of keys added to the table. Expected " +
std::to_string(flush_stats.num_output_records) + " but there are " +
std::to_string(temp_table_proerties.num_entries) +
" in output SST files";
ROCKS_LOG_WARN(immutable_db_options_.info_log,
"[%s] [JOB %d] Level-0 flush during recover: %s",
cfd->GetName().c_str(), job_id, msg.c_str());
@@ -2118,25 +2176,25 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
}
}
InternalStats::CompactionStats stats(CompactionReason::kFlush, 1);
stats.micros = immutable_db_options_.clock->NowMicros() - start_micros;
flush_stats.micros = immutable_db_options_.clock->NowMicros() - start_micros;
if (has_output) {
stats.bytes_written = meta.fd.GetFileSize();
stats.num_output_files = 1;
flush_stats.bytes_written = meta.fd.GetFileSize();
flush_stats.num_output_files = 1;
}
const auto& blobs = edit->GetBlobFileAdditions();
for (const auto& blob : blobs) {
stats.bytes_written_blob += blob.GetTotalBlobBytes();
flush_stats.bytes_written_blob += blob.GetTotalBlobBytes();
}
stats.num_output_files_blob = static_cast<int>(blobs.size());
flush_stats.num_output_files_blob = static_cast<int>(blobs.size());
cfd->internal_stats()->AddCompactionStats(level, Env::Priority::USER, stats);
cfd->internal_stats()->AddCompactionStats(level, Env::Priority::USER,
flush_stats);
cfd->internal_stats()->AddCFStats(
InternalStats::BYTES_FLUSHED,
stats.bytes_written + stats.bytes_written_blob);
flush_stats.bytes_written + flush_stats.bytes_written_blob);
RecordTick(stats_, COMPACT_WRITE_BYTES, meta.fd.GetFileSize());
return s;
}
@@ -2264,6 +2322,7 @@ IOStatus DBImpl::CreateWAL(const WriteOptions& write_options,
BuildDBOptions(immutable_db_options_, mutable_db_options_);
FileOptions opt_file_options =
fs_->OptimizeForLogWrite(file_options_, db_options);
opt_file_options.write_hint = CalculateWALWriteHint();
// DB option takes precedence when not kUnknown
if (immutable_db_options_.wal_write_temperature != Temperature::kUnknown) {
opt_file_options.temperature = immutable_db_options_.wal_write_temperature;
@@ -2285,7 +2344,9 @@ IOStatus DBImpl::CreateWAL(const WriteOptions& write_options,
}
if (io_s.ok()) {
lfile->SetWriteLifeTimeHint(CalculateWALWriteHint());
// Subsequent attempts to override the hint via SetWriteLifeTimeHint
// with the very same value will be ignored by the fs.
lfile->SetWriteLifeTimeHint(opt_file_options.write_hint);
lfile->SetPreallocationBlockSize(preallocate_block_size);
const auto& listeners = immutable_db_options_.listeners;
@@ -2340,9 +2401,11 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
handles->clear();
size_t max_write_buffer_size = 0;
MinAndMaxPreserveSeconds preserve_info;
for (const auto& cf : column_families) {
max_write_buffer_size =
std::max(max_write_buffer_size, cf.options.write_buffer_size);
preserve_info.Combine(cf.options);
}
auto impl = std::make_unique<DBImpl>(db_options, dbname, seq_per_batch,
@@ -2411,18 +2474,18 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
if (s.ok()) {
// Prevent log files created by previous instance from being recycled.
// They might be in alive_log_file_, and might get recycled otherwise.
impl->min_log_number_to_recycle_ = new_log_number;
impl->min_wal_number_to_recycle_ = new_log_number;
}
if (s.ok()) {
InstrumentedMutexLock wl(&impl->log_write_mutex_);
impl->logfile_number_ = new_log_number;
InstrumentedMutexLock wl(&impl->wal_write_mutex_);
impl->cur_wal_number_ = new_log_number;
assert(new_log != nullptr);
assert(impl->logs_.empty());
impl->logs_.emplace_back(new_log_number, new_log);
}
if (s.ok()) {
impl->alive_log_files_.emplace_back(impl->logfile_number_);
impl->alive_wal_files_.emplace_back(impl->cur_wal_number_);
// In WritePrepared there could be gap in sequence numbers. This breaks
// the trick we use in kPointInTimeRecovery which assumes the first seq in
// the log right after the corrupted log is one larger than the last seq
@@ -2435,14 +2498,14 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
if (recovered_seq != kMaxSequenceNumber) {
WriteBatch empty_batch;
WriteBatchInternal::SetSequence(&empty_batch, recovered_seq);
uint64_t log_used, log_size;
uint64_t wal_used, log_size;
log::Writer* log_writer = impl->logs_.back().writer;
LogFileNumberSize& log_file_number_size = impl->alive_log_files_.back();
WalFileNumberSize& wal_file_number_size = impl->alive_wal_files_.back();
assert(log_writer->get_log_number() == log_file_number_size.number);
assert(log_writer->get_log_number() == wal_file_number_size.number);
impl->mutex_.AssertHeld();
s = impl->WriteToWAL(empty_batch, write_options, log_writer, &log_used,
&log_size, log_file_number_size, recovered_seq);
s = impl->WriteToWAL(empty_batch, write_options, log_writer, &wal_used,
&log_size, wal_file_number_size, recovered_seq);
if (s.ok()) {
// Need to fsync, otherwise it might get lost after a power reset.
s = impl->FlushWAL(write_options, false);
@@ -2475,6 +2538,12 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
s = impl->InitPersistStatsColumnFamily();
}
// After reaching the post-recovery seqno but before creating SuperVersions
// ensure seqno to time mapping is pre-populated as needed.
if (s.ok() && recovery_ctx.is_new_db_ && preserve_info.IsEnabled()) {
impl->PrepopulateSeqnoToTimeMapping(preserve_info);
}
if (s.ok()) {
// set column family handles
for (const auto& cf : column_families) {
@@ -2484,6 +2553,9 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
handles->push_back(
new ColumnFamilyHandleImpl(cfd, impl.get(), &impl->mutex_));
impl->NewThreadStatusCfInfo(cfd);
SuperVersionContext sv_context(/* create_superversion */ true);
impl->InstallSuperVersionForConfigChange(cfd, &sv_context);
sv_context.Clean();
} else {
if (db_options.create_missing_column_families) {
// missing column family, create it
@@ -2491,6 +2563,7 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
impl->mutex_.Unlock();
// NOTE: the work normally done in WrapUpCreateColumnFamilies will
// be done separately below.
// This includes InstallSuperVersionForConfigChange.
s = impl->CreateColumnFamilyImpl(read_options, write_options,
cf.options, cf.name, &handle);
impl->mutex_.Lock();
@@ -2507,15 +2580,14 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
}
}
if (s.ok()) {
SuperVersionContext sv_context(/* create_superversion */ true);
for (auto cfd : *impl->versions_->GetColumnFamilySet()) {
impl->InstallSuperVersionAndScheduleWork(cfd, &sv_context);
}
sv_context.Clean();
}
if (s.ok() && impl->immutable_db_options_.persist_stats_to_disk) {
// Install SuperVersion for hidden column family
assert(impl->persist_stats_cf_handle_);
assert(impl->persist_stats_cf_handle_->cfd());
SuperVersionContext sv_context(/* create_superversion */ true);
impl->InstallSuperVersionForConfigChange(
impl->persist_stats_cf_handle_->cfd(), &sv_context);
sv_context.Clean();
// try to read format version
s = impl->PersistentStatsProcessFormatVersion();
}
@@ -2624,8 +2696,7 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
s = impl->StartPeriodicTaskScheduler();
}
if (s.ok()) {
s = impl->RegisterRecordSeqnoTimeWorker(read_options, write_options,
recovery_ctx.is_new_db_);
s = impl->RegisterRecordSeqnoTimeWorker();
}
impl->options_mutex_.Unlock();
if (s.ok()) {
+13 -23
View File
@@ -185,16 +185,10 @@ Iterator* DBImplReadOnly::NewIterator(const ReadOptions& _read_options,
? static_cast<const SnapshotImpl*>(read_options.snapshot)->number_
: latest_snapshot;
ReadCallback* read_callback = nullptr; // No read callback provided.
auto db_iter = NewArenaWrappedDbIterator(
env_, read_options, cfd->ioptions(), super_version->mutable_cf_options,
super_version->current, read_seq,
super_version->mutable_cf_options.max_sequential_skip_in_iterations,
super_version->version_number, read_callback);
auto internal_iter = NewInternalIterator(
db_iter->GetReadOptions(), cfd, super_version, db_iter->GetArena(),
read_seq, /* allow_unprepared_value */ true, db_iter);
db_iter->SetIterUnderDBIter(internal_iter);
return db_iter;
return NewArenaWrappedDbIterator(
env_, read_options, cfh, super_version, read_seq, read_callback, this,
/*expose_blob_index=*/false, /*allow_refresh=*/false,
/*allow_mark_memtable_for_flush=*/false);
}
Status DBImplReadOnly::NewIterators(
@@ -231,36 +225,32 @@ Status DBImplReadOnly::NewIterators(
? static_cast<const SnapshotImpl*>(read_options.snapshot)->number_
: latest_snapshot;
autovector<std::tuple<ColumnFamilyData*, SuperVersion*>> cfd_to_sv;
autovector<std::tuple<ColumnFamilyHandleImpl*, SuperVersion*>> cfh_to_sv;
const bool check_read_ts =
read_options.timestamp && read_options.timestamp->size() > 0;
for (auto cfh : column_families) {
auto* cfd = static_cast_with_check<ColumnFamilyHandleImpl>(cfh)->cfd();
auto* sv = cfd->GetSuperVersion()->Ref();
cfd_to_sv.emplace_back(cfd, sv);
cfh_to_sv.emplace_back(static_cast_with_check<ColumnFamilyHandleImpl>(cfh),
sv);
if (check_read_ts) {
const Status s =
FailIfReadCollapsedHistory(cfd, sv, *(read_options.timestamp));
if (!s.ok()) {
for (auto prev_entry : cfd_to_sv) {
for (auto prev_entry : cfh_to_sv) {
std::get<1>(prev_entry)->Unref();
}
return s;
}
}
}
assert(cfd_to_sv.size() == column_families.size());
for (auto [cfd, sv] : cfd_to_sv) {
assert(cfh_to_sv.size() == column_families.size());
for (auto [cfh, sv] : cfh_to_sv) {
auto* db_iter = NewArenaWrappedDbIterator(
env_, read_options, cfd->ioptions(), sv->mutable_cf_options,
sv->current, read_seq,
sv->mutable_cf_options.max_sequential_skip_in_iterations,
sv->version_number, read_callback);
auto* internal_iter = NewInternalIterator(
db_iter->GetReadOptions(), cfd, sv, db_iter->GetArena(), read_seq,
/* allow_unprepared_value */ true, db_iter);
db_iter->SetIterUnderDBIter(internal_iter);
env_, read_options, cfh, sv, read_seq, read_callback, this,
/*expose_blob_index=*/false, /*allow_refresh=*/false,
/*allow_mark_memtable_for_flush=*/false);
iterators->push_back(db_iter);
}
+23
View File
@@ -155,6 +155,29 @@ class DBImplReadOnly : public DBImpl {
return Status::NotSupported("Not supported operation in read only mode.");
}
using DB::CreateColumnFamily;
using DBImpl::CreateColumnFamily;
Status CreateColumnFamily(const ColumnFamilyOptions& /*cf_options*/,
const std::string& /*column_family*/,
ColumnFamilyHandle** /*handle*/) override {
return Status::NotSupported("Not supported operation in read only mode.");
}
using DB::CreateColumnFamilies;
using DBImpl::CreateColumnFamilies;
Status CreateColumnFamilies(
const ColumnFamilyOptions& /*cf_options*/,
const std::vector<std::string>& /*column_family_names*/,
std::vector<ColumnFamilyHandle*>* /*handles*/) override {
return Status::NotSupported("Not supported operation in read only mode.");
}
Status CreateColumnFamilies(
const std::vector<ColumnFamilyDescriptor>& /*column_families*/,
std::vector<ColumnFamilyHandle*>* /*handles*/) override {
return Status::NotSupported("Not supported operation in read only mode.");
}
// FIXME: some missing overrides for more "write" functions
protected:
+42 -23
View File
@@ -566,17 +566,10 @@ ArenaWrappedDBIter* DBImplSecondary::NewIteratorImpl(
assert(snapshot == kMaxSequenceNumber);
snapshot = versions_->LastSequence();
assert(snapshot != kMaxSequenceNumber);
auto db_iter = NewArenaWrappedDbIterator(
env_, read_options, cfh->cfd()->ioptions(),
super_version->mutable_cf_options, super_version->current, snapshot,
super_version->mutable_cf_options.max_sequential_skip_in_iterations,
super_version->version_number, read_callback, cfh, expose_blob_index,
allow_refresh);
auto internal_iter = NewInternalIterator(
db_iter->GetReadOptions(), cfh->cfd(), super_version, db_iter->GetArena(),
snapshot, /* allow_unprepared_value */ true, db_iter);
db_iter->SetIterUnderDBIter(internal_iter);
return db_iter;
return NewArenaWrappedDbIterator(env_, read_options, cfh, super_version,
snapshot, read_callback, this,
expose_blob_index, allow_refresh,
/*allow_mark_memtable_for_flush=*/false);
}
Status DBImplSecondary::NewIterators(
@@ -705,13 +698,13 @@ Status DBImplSecondary::CheckConsistency() {
Status DBImplSecondary::TryCatchUpWithPrimary() {
assert(versions_.get() != nullptr);
assert(manifest_reader_.get() != nullptr);
Status s;
// read the manifest and apply new changes to the secondary instance
std::unordered_set<ColumnFamilyData*> cfds_changed;
JobContext job_context(0, true /*create_superversion*/);
{
InstrumentedMutexLock lock_guard(&mutex_);
assert(manifest_reader_.get() != nullptr);
s = static_cast_with_check<ReactiveVersionSet>(versions_.get())
->ReadAndApply(&mutex_, &manifest_reader_,
manifest_reader_status_.get(), &cfds_changed,
@@ -735,13 +728,13 @@ Status DBImplSecondary::TryCatchUpWithPrimary() {
// instance
if (s.ok()) {
s = FindAndRecoverLogFiles(&cfds_changed, &job_context);
}
if (s.IsPathNotFound()) {
ROCKS_LOG_INFO(
immutable_db_options_.info_log,
"Secondary tries to read WAL, but WAL file(s) have already "
"been purged by primary.");
s = Status::OK();
if (s.IsPathNotFound()) {
ROCKS_LOG_INFO(
immutable_db_options_.info_log,
"Secondary tries to read WAL, but WAL file(s) have already "
"been purged by primary.");
s = Status::OK();
}
}
if (s.ok()) {
for (auto cfd : cfds_changed) {
@@ -916,7 +909,7 @@ Status DBImplSecondary::CompactWithoutInstallation(
ROCKS_LOG_ERROR(
immutable_db_options_.info_log,
"GetCompactionInputsFromFileNumbers() failed - %s.\n DebugString: %s",
s.ToString().c_str(), version->DebugString().c_str());
s.ToString().c_str(), version->DebugString(/*hex=*/true).c_str());
return s;
}
@@ -991,7 +984,7 @@ Status DB::OpenAndCompact(
}
// 2. Load the options
DBOptions db_options;
DBOptions base_db_options;
ConfigOptions config_options;
config_options.env = override_options.env;
config_options.ignore_unknown_options = true;
@@ -1004,13 +997,22 @@ Status DB::OpenAndCompact(
std::string options_file_name =
OptionsFileName(name, compaction_input.options_file_number);
s = LoadOptionsFromFile(config_options, options_file_name, &db_options,
s = LoadOptionsFromFile(config_options, options_file_name, &base_db_options,
&all_column_families);
if (!s.ok()) {
return s;
}
// 3. Override pointer configurations in DBOptions with
// 3. Options to Override
// Override serializable configurations from override_options.options_map
DBOptions db_options;
s = GetDBOptionsFromMap(config_options, base_db_options,
override_options.options_map, &db_options);
if (!s.ok()) {
return s;
}
// Override options that are directly set as shared ptrs in
// CompactionServiceOptionsOverride
db_options.env = override_options.env;
db_options.file_checksum_gen_factory =
@@ -1021,6 +1023,7 @@ Status DB::OpenAndCompact(
// We will close the DB after the compaction anyway.
// Open as many files as needed for the compaction.
db_options.max_open_files = -1;
db_options.info_log = override_options.info_log;
// 4. Filter CFs that are needed for OpenAndCompact()
// We do not need to open all column families for the remote compaction.
@@ -1030,6 +1033,18 @@ Status DB::OpenAndCompact(
std::vector<ColumnFamilyDescriptor> column_families;
for (auto& cf : all_column_families) {
if (cf.name == compaction_input.cf_name) {
ColumnFamilyOptions cf_options;
// Override serializable configurations from override_options.options_map
s = GetColumnFamilyOptionsFromMap(config_options, cf.options,
override_options.options_map,
&cf_options);
if (!s.ok()) {
return s;
}
cf.options = std::move(cf_options);
// Override options that are directly set as shared ptrs in
// CompactionServiceOptionsOverride
cf.options.comparator = override_options.comparator;
cf.options.merge_operator = override_options.merge_operator;
cf.options.compaction_filter = override_options.compaction_filter;
@@ -1041,6 +1056,7 @@ Status DB::OpenAndCompact(
override_options.sst_partitioner_factory;
cf.options.table_properties_collector_factories =
override_options.table_properties_collector_factories;
column_families.emplace_back(cf);
} else if (cf.name == kDefaultColumnFamilyName) {
column_families.emplace_back(cf);
@@ -1057,6 +1073,9 @@ Status DB::OpenAndCompact(
}
assert(db);
TEST_SYNC_POINT_CALLBACK(
"DBImplSecondary::OpenAndCompact::AfterOpenAsSecondary:0", db);
// 6. Find the handle of the Column Family that this will compact
ColumnFamilyHandle* cfh = nullptr;
for (auto* handle : handles) {
+275 -176
View File
@@ -157,7 +157,7 @@ Status DBImpl::Write(const WriteOptions& write_options, WriteBatch* my_batch) {
if (s.ok()) {
s = WriteImpl(write_options, my_batch, /*callback=*/nullptr,
/*user_write_cb=*/nullptr,
/*log_used=*/nullptr);
/*wal_used=*/nullptr);
}
return s;
}
@@ -190,11 +190,38 @@ Status DBImpl::WriteWithCallback(const WriteOptions& write_options,
return s;
}
Status DBImpl::IngestWBWI(std::shared_ptr<WriteBatchWithIndex> wbwi,
const WBWIMemTable::SeqnoRange& assigned_seqno,
uint64_t prep_log,
SequenceNumber last_seqno_after_ingest,
bool memtable_updated, bool ignore_missing_cf) {
Status DBImpl::IngestWriteBatchWithIndex(
const WriteOptions& write_options,
std::shared_ptr<WriteBatchWithIndex> wbwi) {
if (!wbwi) {
return Status::InvalidArgument("Batch is nullptr!");
}
if (!write_options.disableWAL) {
return Status::NotSupported(
"IngestWriteBatchWithIndex does not support disableWAL=true");
}
Status s;
if (write_options.protection_bytes_per_key > 0) {
s = WriteBatchInternal::UpdateProtectionInfo(
wbwi->GetWriteBatch(), write_options.protection_bytes_per_key);
}
if (s.ok()) {
WriteBatch dummy_empty_batch;
s = WriteImpl(
write_options, /*updates=*/&dummy_empty_batch, /*callback=*/nullptr,
/*user_write_cb=*/nullptr, /*log_used=*/nullptr, /*log_ref=*/0,
/*disable_memtable=*/false, /*seq_used=*/nullptr,
/*batch_cnt=*/0, /*pre_release_callback=*/nullptr,
/*post_memtable_callback=*/nullptr, /*wbwi=*/wbwi);
}
return s;
}
Status DBImpl::IngestWBWIAsMemtable(
std::shared_ptr<WriteBatchWithIndex> wbwi,
const WBWIMemTable::SeqnoRange& assigned_seqno, uint64_t min_prep_log,
SequenceNumber last_seqno_after_ingest, bool memtable_updated,
bool ignore_missing_cf) {
// Keys in new memtable have seqno > last_seqno_after_ingest >= keys in wbwi.
assert(assigned_seqno.upper_bound <= last_seqno_after_ingest);
// Keys in the current memtable have seqno <= LastSequence() < keys in wbwi.
@@ -238,12 +265,30 @@ Status DBImpl::IngestWBWI(std::shared_ptr<WriteBatchWithIndex> wbwi,
wbwi_memtable->AssignSequenceNumbers(assigned_seqno);
// This is needed to keep the WAL that contains Prepare alive until
// committed data in this memtable is persisted.
wbwi_memtable->SetMinPrepLog(prep_log);
wbwi_memtable->SetMinPrepLog(min_prep_log);
memtables.push_back(wbwi_memtable);
cfd->Ref();
cfds.push_back(cfd);
}
autovector<ColumnFamilyData*> cfds_for_atomic_flush;
if (immutable_db_options_.atomic_flush) {
SelectColumnFamiliesForAtomicFlush(&cfds_for_atomic_flush);
for (auto cfd : cfds_for_atomic_flush) {
bool found = false;
for (auto existing_cfd : cfds) {
if (existing_cfd == cfd) {
found = true;
break;
}
}
if (!found) {
cfd->Ref();
cfds.push_back(cfd);
}
}
}
// Stop writes to the DB by entering both write threads
WriteThread::Writer nonmem_w;
if (two_write_queues_) {
@@ -253,15 +298,16 @@ Status DBImpl::IngestWBWI(std::shared_ptr<WriteBatchWithIndex> wbwi,
// Switch memtable and add WBWIMemTables
Status s;
for (size_t i = 0; i < memtables.size(); ++i) {
assert(!immutable_db_options_.atomic_flush);
// NOTE: to support atomic flush, need to call
// SelectColumnFamiliesForAtomicFlush()
for (size_t i = 0; i < cfds.size(); ++i) {
WriteContext write_context;
// TODO: not switch on empty memtable, may need to update metadata
// like NextLogNumber(), earliest_seqno and memtable id.
s = SwitchMemtable(cfds[i], &write_context, memtables[i],
last_seqno_after_ingest);
if (i < memtables.size()) {
s = SwitchMemtable(cfds[i], &write_context, memtables[i],
last_seqno_after_ingest);
} else {
s = SwitchMemtable(cfds[i], &write_context);
}
if (!s.ok()) {
// SwitchMemtable() can only fail if a new WAL is to be created, this
// should only happen for the first call to SwitchMemtable(). log will
@@ -301,9 +347,18 @@ Status DBImpl::IngestWBWI(std::shared_ptr<WriteBatchWithIndex> wbwi,
continue;
}
cfd->imm()->FlushRequested();
if (!immutable_db_options_.atomic_flush) {
FlushRequest flush_req;
// TODO: a new flush reason for ingesting memtable
GenerateFlushRequest({cfd}, FlushReason::kExternalFileIngestion,
&flush_req);
EnqueuePendingFlush(flush_req);
}
}
if (immutable_db_options_.atomic_flush) {
AssignAtomicFlushSeq(cfds);
FlushRequest flush_req;
// TODO: a new flush reason for ingesting memtable
GenerateFlushRequest({cfd}, FlushReason::kExternalFileIngestion,
GenerateFlushRequest(cfds, FlushReason::kExternalFileIngestion,
&flush_req);
EnqueuePendingFlush(flush_req);
}
@@ -314,13 +369,12 @@ Status DBImpl::IngestWBWI(std::shared_ptr<WriteBatchWithIndex> wbwi,
Status DBImpl::WriteImpl(const WriteOptions& write_options,
WriteBatch* my_batch, WriteCallback* callback,
UserWriteCallback* user_write_cb, uint64_t* log_used,
UserWriteCallback* user_write_cb, uint64_t* wal_used,
uint64_t log_ref, bool disable_memtable,
uint64_t* seq_used, size_t batch_cnt,
PreReleaseCallback* pre_release_callback,
PostMemTableCallback* post_memtable_callback,
std::shared_ptr<WriteBatchWithIndex> wbwi,
uint64_t prep_log) {
std::shared_ptr<WriteBatchWithIndex> wbwi) {
assert(!seq_per_batch_ || batch_cnt != 0);
assert(my_batch == nullptr || my_batch->Count() == 0 ||
write_options.protection_bytes_per_key == 0 ||
@@ -409,9 +463,17 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
return Status::NotSupported(
"DeleteRange is not compatible with row cache.");
}
// Whether the WBWI is from transaction commit or a direct write
// (IngestWriteBatchWithIndex())
bool ingest_wbwi_for_commit = false;
if (wbwi) {
assert(prep_log > 0);
// Used only in WriteCommittedTxn::CommitInternal() with no `callback`.
if (my_batch->HasCommit()) {
ingest_wbwi_for_commit = true;
assert(log_ref);
} else {
// Only supports disableWAL for directly ingesting WBWI for now.
assert(write_options.disableWAL);
}
assert(!callback);
if (immutable_db_options_.unordered_write) {
return Status::NotSupported(
@@ -421,9 +483,9 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
return Status::NotSupported(
"Ingesting WriteBatch does not support pipelined_write");
}
if (immutable_db_options_.atomic_flush) {
if (!wbwi->GetOverwriteKey()) {
return Status::NotSupported(
"Ingesting WriteBatch does not support atomic_flush");
"WriteBatchWithIndex ingestion requires overwrite_key=true");
}
}
// Otherwise IsLatestPersistentState optimization does not make sense
@@ -444,7 +506,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
// they don't consume sequence.
return WriteImplWALOnly(
&nonmem_write_thread_, write_options, my_batch, callback, user_write_cb,
log_used, log_ref, seq_used, batch_cnt, pre_release_callback,
wal_used, log_ref, seq_used, batch_cnt, pre_release_callback,
assign_order, kDontPublishLastSeq, disable_memtable);
}
@@ -458,7 +520,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
// sequence in in increasing order, iii) call pre_release_callback serially
Status status = WriteImplWALOnly(
&write_thread_, write_options, my_batch, callback, user_write_cb,
log_used, log_ref, &seq, sub_batch_cnt, pre_release_callback,
wal_used, log_ref, &seq, sub_batch_cnt, pre_release_callback,
kDoAssignOrder, kDoPublishLastSeq, disable_memtable);
TEST_SYNC_POINT("DBImpl::WriteImpl:UnorderedWriteAfterWriteWAL");
if (!status.ok()) {
@@ -477,7 +539,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
if (immutable_db_options_.enable_pipelined_write) {
return PipelinedWriteImpl(write_options, my_batch, callback, user_write_cb,
log_used, log_ref, disable_memtable, seq_used);
wal_used, log_ref, disable_memtable, seq_used);
}
PERF_TIMER_GUARD(write_pre_and_post_process_time);
@@ -524,16 +586,19 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
assert(tmp_s.ok());
}
}
versions_->SetLastSequence(last_sequence);
MemTableInsertStatusCheck(w.status);
if (w.status.ok()) { // Don't publish a partial batch write
versions_->SetLastSequence(last_sequence);
} else {
HandleMemTableInsertFailure(w.status);
}
write_thread_.ExitAsBatchGroupFollower(&w);
}
assert(w.state == WriteThread::STATE_COMPLETED);
// STATE_COMPLETED conditional below handles exit
}
if (w.state == WriteThread::STATE_COMPLETED) {
if (log_used != nullptr) {
*log_used = w.log_used;
if (wal_used != nullptr) {
*wal_used = w.wal_used;
}
if (seq_used != nullptr) {
*seq_used = w.sequence;
@@ -549,7 +614,8 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
// when it finds suitable, and finish them in the same write batch.
// This is how a write job could be done by the other writer.
WriteContext write_context;
LogContext log_context(write_options.sync);
// FIXME: also check disableWAL like others?
WalContext wal_context(write_options.sync);
WriteThread::WriteGroup write_group;
bool in_parallel_group = false;
uint64_t last_sequence = kMaxSequenceNumber;
@@ -563,7 +629,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
// PreprocessWrite does its own perf timing.
PERF_TIMER_STOP(write_pre_and_post_process_time);
status = PreprocessWrite(write_options, &log_context, &write_context);
status = PreprocessWrite(write_options, &wal_context, &write_context);
if (!two_write_queues_) {
// Assign it after ::PreprocessWrite since the sequence might advance
// inside it by WriteRecoverableState
@@ -631,7 +697,13 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
continue;
}
// TODO: maybe handle the tracing status?
tracer_->Write(writer->batch).PermitUncheckedError();
if (wbwi && !ingest_wbwi_for_commit) {
// for transaction write, tracer only needs the commit marker which
// is in writer->batch
tracer_->Write(wbwi->GetWriteBatch()).PermitUncheckedError();
} else {
tracer_->Write(writer->batch).PermitUncheckedError();
}
}
}
}
@@ -689,22 +761,21 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
if (!two_write_queues_) {
if (status.ok() && !write_options.disableWAL) {
assert(log_context.log_file_number_size);
LogFileNumberSize& log_file_number_size =
*(log_context.log_file_number_size);
assert(wal_context.wal_file_number_size);
wal_context.prev_size = wal_context.writer->file()->GetFileSize();
PERF_TIMER_GUARD(write_wal_time);
io_s =
WriteToWAL(write_group, log_context.writer, log_used,
log_context.need_log_sync, log_context.need_log_dir_sync,
last_sequence + 1, log_file_number_size);
io_s = WriteGroupToWAL(write_group, wal_context.writer, wal_used,
wal_context.need_wal_sync,
wal_context.need_wal_dir_sync, last_sequence + 1,
*wal_context.wal_file_number_size);
}
} else {
if (status.ok() && !write_options.disableWAL) {
PERF_TIMER_GUARD(write_wal_time);
// LastAllocatedSequence is increased inside WriteToWAL under
// wal_write_mutex_ to ensure ordered events in WAL
io_s = ConcurrentWriteToWAL(write_group, log_used, &last_sequence,
seq_inc);
io_s = ConcurrentWriteGroupToWAL(write_group, wal_used, &last_sequence,
seq_inc);
} else {
// Otherwise we inc seq number for memtable writes
last_sequence = versions_->FetchAddLastAllocatedSequence(seq_inc);
@@ -716,16 +787,16 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
last_sequence += seq_inc;
// Seqno assigned to this write are [current_sequence, last_sequence]
if (log_context.need_log_sync) {
if (wal_context.need_wal_sync) {
VersionEdit synced_wals;
log_write_mutex_.Lock();
wal_write_mutex_.Lock();
if (status.ok()) {
MarkLogsSynced(logfile_number_, log_context.need_log_dir_sync,
MarkLogsSynced(cur_wal_number_, wal_context.need_wal_dir_sync,
&synced_wals);
} else {
MarkLogsNotSynced(logfile_number_);
MarkLogsNotSynced(cur_wal_number_);
}
log_write_mutex_.Unlock();
wal_write_mutex_.Unlock();
if (status.ok() && synced_wals.IsWalAddition()) {
InstrumentedMutexLock l(&mutex_);
// TODO: plumb Env::IOActivity, Env::IOPriority
@@ -760,7 +831,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
writer->sequence = next_sequence;
if (writer->pre_release_callback) {
Status ws = writer->pre_release_callback->Callback(
writer->sequence, disable_memtable, writer->log_used, index++,
writer->sequence, disable_memtable, writer->wal_used, index++,
pre_release_callback_cnt);
if (!ws.ok()) {
status = pre_release_cb_status = ws;
@@ -834,12 +905,13 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
// handle exit, false means somebody else did
should_exit_batch_group = write_thread_.CompleteParallelMemTableWriter(&w);
}
if (wbwi) {
if (status.ok() && w.status.ok()) {
if (wbwi && status.ok() && w.status.ok()) {
uint32_t wbwi_count = wbwi->GetWriteBatch()->Count();
// skip empty batch case
if (wbwi_count) {
// w.batch contains (potentially empty) commit time batch updates,
// only ingest wbwi if w.batch is applied to memtable successfully
uint32_t memtable_update_count = w.batch->Count();
uint32_t wbwi_count = wbwi->GetWriteBatch()->Count();
// Seqno assigned to this write are [last_seq + 1 - seq_inc, last_seq].
// seq_inc includes w.batch (memtable updates) and wbwi
// w.batch gets first `memtable_update_count` sequence numbers.
@@ -852,10 +924,12 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
if (two_write_queues_) {
assert(ub <= versions_->LastAllocatedSequence());
}
status = IngestWBWI(wbwi, {/*lower_bound=*/lb, /*upper_bound=*/ub},
prep_log, last_sequence,
/*memtable_updated=*/memtable_update_count > 0,
write_options.ignore_missing_column_families);
status =
IngestWBWIAsMemtable(wbwi, {/*lower_bound=*/lb, /*upper_bound=*/ub},
/*min_prep_log=*/log_ref, last_sequence,
/*memtable_updated=*/memtable_update_count > 0,
write_options.ignore_missing_column_families);
RecordTick(stats_, NUMBER_WBWI_INGEST);
}
}
@@ -873,9 +947,19 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
}
// Note: if we are to resume after non-OK statuses we need to revisit how
// we react to non-OK statuses here.
versions_->SetLastSequence(last_sequence);
if (w.status.ok()) { // Don't publish a partial batch write
versions_->SetLastSequence(last_sequence);
}
}
if (!w.status.ok()) {
if (wal_context.prev_size < SIZE_MAX) {
InstrumentedMutexLock l(&wal_write_mutex_);
if (logs_.back().number == wal_context.wal_file_number_size->number) {
logs_.back().SetAttemptTruncateSize(wal_context.prev_size);
}
}
HandleMemTableInsertFailure(w.status);
}
MemTableInsertStatusCheck(w.status);
write_thread_.ExitAsBatchGroupLeader(write_group, status);
}
@@ -888,7 +972,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
WriteBatch* my_batch, WriteCallback* callback,
UserWriteCallback* user_write_cb,
uint64_t* log_used, uint64_t log_ref,
uint64_t* wal_used, uint64_t log_ref,
bool disable_memtable, uint64_t* seq_used) {
PERF_TIMER_GUARD(write_pre_and_post_process_time);
StopWatch write_sw(immutable_db_options_.clock, stats_, DB_WRITE);
@@ -905,10 +989,10 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
if (w.callback && !w.callback->AllowWriteBatching()) {
write_thread_.WaitForMemTableWriters();
}
LogContext log_context(!write_options.disableWAL && write_options.sync);
WalContext wal_context(!write_options.disableWAL && write_options.sync);
// PreprocessWrite does its own perf timing.
PERF_TIMER_STOP(write_pre_and_post_process_time);
w.status = PreprocessWrite(write_options, &log_context, &write_context);
w.status = PreprocessWrite(write_options, &wal_context, &write_context);
PERF_TIMER_START(write_pre_and_post_process_time);
// This can set non-OK status if callback fail.
@@ -977,13 +1061,13 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
wal_write_group.size - 1);
RecordTick(stats_, WRITE_DONE_BY_OTHER, wal_write_group.size - 1);
}
assert(log_context.log_file_number_size);
LogFileNumberSize& log_file_number_size =
*(log_context.log_file_number_size);
io_s =
WriteToWAL(wal_write_group, log_context.writer, log_used,
log_context.need_log_sync, log_context.need_log_dir_sync,
current_sequence, log_file_number_size);
assert(wal_context.wal_file_number_size);
WalFileNumberSize& wal_file_number_size =
*(wal_context.wal_file_number_size);
io_s = WriteGroupToWAL(wal_write_group, wal_context.writer, wal_used,
wal_context.need_wal_sync,
wal_context.need_wal_dir_sync, current_sequence,
wal_file_number_size);
w.status = io_s;
}
@@ -995,13 +1079,13 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
}
VersionEdit synced_wals;
if (log_context.need_log_sync) {
InstrumentedMutexLock l(&log_write_mutex_);
if (wal_context.need_wal_sync) {
InstrumentedMutexLock l(&wal_write_mutex_);
if (w.status.ok()) {
MarkLogsSynced(logfile_number_, log_context.need_log_dir_sync,
MarkLogsSynced(cur_wal_number_, wal_context.need_wal_dir_sync,
&synced_wals);
} else {
MarkLogsNotSynced(logfile_number_);
MarkLogsNotSynced(cur_wal_number_);
}
}
if (w.status.ok() && synced_wals.IsWalAddition()) {
@@ -1032,7 +1116,12 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
&flush_scheduler_, &trim_history_scheduler_,
write_options.ignore_missing_column_families, 0 /*log_number*/, this,
false /*concurrent_memtable_writes*/, seq_per_batch_, batch_per_txn_);
versions_->SetLastSequence(memtable_write_group.last_sequence);
if (memtable_write_group.status
.ok()) { // Don't publish a partial batch write
versions_->SetLastSequence(memtable_write_group.last_sequence);
} else {
HandleMemTableInsertFailure(memtable_write_group.status);
}
write_thread_.ExitAsMemTableWriter(&w, memtable_write_group);
}
} else {
@@ -1061,8 +1150,11 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
PERF_TIMER_START(write_pre_and_post_process_time);
if (write_thread_.CompleteParallelMemTableWriter(&w)) {
MemTableInsertStatusCheck(w.status);
versions_->SetLastSequence(w.write_group->last_sequence);
if (w.status.ok()) { // Don't publish a partial batch write
versions_->SetLastSequence(w.write_group->last_sequence);
} else {
HandleMemTableInsertFailure(w.status);
}
write_thread_.ExitAsMemTableWriter(&w, *w.write_group);
}
}
@@ -1134,7 +1226,7 @@ Status DBImpl::UnorderedWriteMemtable(const WriteOptions& write_options,
Status DBImpl::WriteImplWALOnly(
WriteThread* write_thread, const WriteOptions& write_options,
WriteBatch* my_batch, WriteCallback* callback,
UserWriteCallback* user_write_cb, uint64_t* log_used,
UserWriteCallback* user_write_cb, uint64_t* wal_used,
const uint64_t log_ref, uint64_t* seq_used, const size_t sub_batch_cnt,
PreReleaseCallback* pre_release_callback, const AssignOrder assign_order,
const PublishLastSeq publish_last_seq, const bool disable_memtable) {
@@ -1147,8 +1239,8 @@ Status DBImpl::WriteImplWALOnly(
write_thread->JoinBatchGroup(&w);
assert(w.state != WriteThread::STATE_PARALLEL_MEMTABLE_WRITER);
if (w.state == WriteThread::STATE_COMPLETED) {
if (log_used != nullptr) {
*log_used = w.log_used;
if (wal_used != nullptr) {
*wal_used = w.wal_used;
}
if (seq_used != nullptr) {
*seq_used = w.sequence;
@@ -1164,10 +1256,10 @@ Status DBImpl::WriteImplWALOnly(
// TODO(myabandeh): Make preliminary checks thread-safe so we could do them
// without paying the cost of obtaining the mutex.
LogContext log_context;
WalContext wal_context;
WriteContext write_context;
Status status =
PreprocessWrite(write_options, &log_context, &write_context);
PreprocessWrite(write_options, &wal_context, &write_context);
WriteStatusCheckOnLocked(status);
if (!status.ok()) {
@@ -1264,8 +1356,8 @@ Status DBImpl::WriteImplWALOnly(
}
Status status;
if (!write_options.disableWAL) {
IOStatus io_s =
ConcurrentWriteToWAL(write_group, log_used, &last_sequence, seq_inc);
IOStatus io_s = ConcurrentWriteGroupToWAL(write_group, wal_used,
&last_sequence, seq_inc);
status = io_s;
// last_sequence may not be set if there is an error
// This error checking and return is moved up to avoid using uninitialized
@@ -1317,7 +1409,7 @@ Status DBImpl::WriteImplWALOnly(
if (!writer->CallbackFailed() && writer->pre_release_callback) {
assert(writer->sequence != kMaxSequenceNumber);
Status ws = writer->pre_release_callback->Callback(
writer->sequence, disable_memtable, writer->log_used, index++,
writer->sequence, disable_memtable, writer->wal_used, index++,
pre_release_callback_cnt);
if (!ws.ok()) {
status = ws;
@@ -1386,24 +1478,22 @@ void DBImpl::WALIOStatusCheck(const IOStatus& io_status) {
}
}
void DBImpl::MemTableInsertStatusCheck(const Status& status) {
// A non-OK status here indicates that the state implied by the
// WAL has diverged from the in-memory state. This could be
// because of a corrupt write_batch (very bad), or because the
// client specified an invalid column family and didn't specify
// ignore_missing_column_families.
if (!status.ok()) {
mutex_.Lock();
assert(!error_handler_.IsBGWorkStopped());
error_handler_.SetBGError(status, BackgroundErrorReason::kMemTable);
mutex_.Unlock();
}
void DBImpl::HandleMemTableInsertFailure(const Status& status) {
assert(!status.ok());
// A non-OK status on memtable insert indicates that the state implied by the
// WAL has diverged from the in-memory state. This could be because of a
// corrupt write_batch (very bad), or because the client specified an invalid
// column family and didn't specify ignore_missing_column_families.
mutex_.Lock();
assert(!error_handler_.IsBGWorkStopped());
error_handler_.SetBGError(status, BackgroundErrorReason::kMemTable);
mutex_.Unlock();
}
Status DBImpl::PreprocessWrite(const WriteOptions& write_options,
LogContext* log_context,
WalContext* wal_context,
WriteContext* write_context) {
assert(write_context != nullptr && log_context != nullptr);
assert(write_context != nullptr && wal_context != nullptr);
Status status;
if (error_handler_.IsDBStopped()) {
@@ -1413,7 +1503,8 @@ Status DBImpl::PreprocessWrite(const WriteOptions& write_options,
PERF_TIMER_GUARD(write_scheduling_flushes_compactions_time);
if (UNLIKELY(status.ok() && total_log_size_ > GetMaxTotalWalSize())) {
if (UNLIKELY(status.ok() &&
wals_total_size_.LoadRelaxed() > GetMaxTotalWalSize())) {
assert(versions_);
InstrumentedMutexLock l(&mutex_);
const ColumnFamilySet* const column_families =
@@ -1482,17 +1573,17 @@ Status DBImpl::PreprocessWrite(const WriteOptions& write_options,
WriteBufferManagerStallWrites();
}
}
InstrumentedMutexLock l(&log_write_mutex_);
if (status.ok() && log_context->need_log_sync) {
InstrumentedMutexLock l(&wal_write_mutex_);
if (status.ok() && wal_context->need_wal_sync) {
// Wait until the parallel syncs are finished. Any sync process has to sync
// the front log too so it is enough to check the status of front()
// We do a while loop since log_sync_cv_ is signalled when any sync is
// We do a while loop since wal_sync_cv_ is signalled when any sync is
// finished
// Note: there does not seem to be a reason to wait for parallel sync at
// this early step but it is not important since parallel sync (SyncWAL) and
// need_log_sync are usually not used together.
// need_wal_sync are usually not used together.
while (logs_.front().IsSyncing()) {
log_sync_cv_.Wait();
wal_sync_cv_.Wait();
}
for (auto& log : logs_) {
// This is just to prevent the logs to be synced by a parallel SyncWAL
@@ -1503,12 +1594,12 @@ Status DBImpl::PreprocessWrite(const WriteOptions& write_options,
log.PrepareForSync();
}
} else {
log_context->need_log_sync = false;
wal_context->need_wal_sync = false;
}
log_context->writer = logs_.back().writer;
log_context->need_log_dir_sync =
log_context->need_log_dir_sync && !log_dir_synced_;
log_context->log_file_number_size = std::addressof(alive_log_files_.back());
wal_context->writer = logs_.back().writer;
wal_context->need_wal_dir_sync =
wal_context->need_wal_dir_sync && !wal_dir_synced_;
wal_context->wal_file_number_size = std::addressof(alive_wal_files_.back());
return status;
}
@@ -1559,12 +1650,12 @@ Status DBImpl::MergeBatch(const WriteThread::WriteGroup& write_group,
}
// When two_write_queues_ is disabled, this function is called from the only
// write thread. Otherwise this must be called holding log_write_mutex_.
// write thread. Otherwise this must be called holding wal_write_mutex_.
IOStatus DBImpl::WriteToWAL(const WriteBatch& merged_batch,
const WriteOptions& write_options,
log::Writer* log_writer, uint64_t* log_used,
log::Writer* log_writer, uint64_t* wal_used,
uint64_t* log_size,
LogFileNumberSize& log_file_number_size,
WalFileNumberSize& wal_file_number_size,
SequenceNumber sequence) {
assert(log_size != nullptr);
@@ -1576,7 +1667,7 @@ IOStatus DBImpl::WriteToWAL(const WriteBatch& merged_batch,
}
*log_size = log_entry.size();
// When two_write_queues_ WriteToWAL has to be protected from concurretn calls
// from the two queues anyway and log_write_mutex_ is already held. Otherwise
// from the two queues anyway and wal_write_mutex_ is already held. Otherwise
// if manual_wal_flush_ is enabled we need to protect log_writer->AddRecord
// from possible concurrent calls via the FlushWAL by the application.
const bool needs_locking = manual_wal_flush_ && !two_write_queues_;
@@ -1584,7 +1675,7 @@ IOStatus DBImpl::WriteToWAL(const WriteBatch& merged_batch,
// manual_wal_flush_ feature (by UNLIKELY) instead of the more common case
// when we do not need any locking.
if (UNLIKELY(needs_locking)) {
log_write_mutex_.Lock();
wal_write_mutex_.Lock();
}
IOStatus io_s = log_writer->MaybeAddUserDefinedTimestampSizeRecord(
write_options, versions_->GetColumnFamiliesTimestampSizeForRecord());
@@ -1594,23 +1685,24 @@ IOStatus DBImpl::WriteToWAL(const WriteBatch& merged_batch,
io_s = log_writer->AddRecord(write_options, log_entry, sequence);
if (UNLIKELY(needs_locking)) {
log_write_mutex_.Unlock();
wal_write_mutex_.Unlock();
}
if (log_used != nullptr) {
*log_used = logfile_number_;
if (wal_used != nullptr) {
*wal_used = cur_wal_number_;
assert(*wal_used == wal_file_number_size.number);
}
total_log_size_ += log_entry.size();
log_file_number_size.AddSize(*log_size);
log_empty_ = false;
wals_total_size_.FetchAddRelaxed(log_entry.size());
wal_file_number_size.AddSize(*log_size);
wal_empty_ = false;
return io_s;
}
IOStatus DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
log::Writer* log_writer, uint64_t* log_used,
bool need_log_sync, bool need_log_dir_sync,
SequenceNumber sequence,
LogFileNumberSize& log_file_number_size) {
IOStatus DBImpl::WriteGroupToWAL(const WriteThread::WriteGroup& write_group,
log::Writer* log_writer, uint64_t* wal_used,
bool need_wal_sync, bool need_wal_dir_sync,
SequenceNumber sequence,
WalFileNumberSize& wal_file_number_size) {
IOStatus io_s;
assert(!two_write_queues_);
assert(!write_group.leader->disable_wal);
@@ -1625,10 +1717,10 @@ IOStatus DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
}
if (merged_batch == write_group.leader->batch) {
write_group.leader->log_used = logfile_number_;
write_group.leader->wal_used = cur_wal_number_;
} else if (write_with_wal > 1) {
for (auto writer : write_group) {
writer->log_used = logfile_number_;
writer->wal_used = cur_wal_number_;
}
}
@@ -1640,14 +1732,14 @@ IOStatus DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
WriteOptions write_options;
write_options.rate_limiter_priority =
write_group.leader->rate_limiter_priority;
io_s = WriteToWAL(*merged_batch, write_options, log_writer, log_used,
&log_size, log_file_number_size, sequence);
io_s = WriteToWAL(*merged_batch, write_options, log_writer, wal_used,
&log_size, wal_file_number_size, sequence);
if (to_be_cached_state) {
cached_recoverable_state_ = *to_be_cached_state;
cached_recoverable_state_empty_ = false;
}
if (io_s.ok() && need_log_sync) {
if (io_s.ok() && need_wal_sync) {
StopWatch sw(immutable_db_options_.clock, stats_, WAL_FILE_SYNC_MICROS);
// It's safe to access logs_ with unlocked mutex_ here because:
// - we've set getting_synced=true for all logs,
@@ -1657,15 +1749,15 @@ IOStatus DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
// - as long as other threads don't modify it, it's safe to read
// from std::deque from multiple threads concurrently.
//
// Sync operation should work with locked log_write_mutex_, because:
// Sync operation should work with locked wal_write_mutex_, because:
// when DBOptions.manual_wal_flush_ is set,
// FlushWAL function will be invoked by another thread.
// if without locked log_write_mutex_, the log file may get data
// if without locked wal_write_mutex_, the log file may get data
// corruption
const bool needs_locking = manual_wal_flush_ && !two_write_queues_;
if (UNLIKELY(needs_locking)) {
log_write_mutex_.Lock();
wal_write_mutex_.Lock();
}
if (io_s.ok()) {
@@ -1688,10 +1780,10 @@ IOStatus DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
}
if (UNLIKELY(needs_locking)) {
log_write_mutex_.Unlock();
wal_write_mutex_.Unlock();
}
if (io_s.ok() && need_log_dir_sync) {
if (io_s.ok() && need_wal_dir_sync) {
// We only sync WAL directory the first time WAL syncing is
// requested, so that in case users never turn on WAL sync,
// we can avoid the disk I/O in the write code path.
@@ -1706,7 +1798,7 @@ IOStatus DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
}
if (io_s.ok()) {
auto stats = default_cf_internal_stats_;
if (need_log_sync) {
if (need_wal_sync) {
stats->AddDBStats(InternalStats::kIntStatsWalFileSynced, 1);
RecordTick(stats_, WAL_FILE_SYNCED);
}
@@ -1723,8 +1815,8 @@ IOStatus DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
return io_s;
}
IOStatus DBImpl::ConcurrentWriteToWAL(
const WriteThread::WriteGroup& write_group, uint64_t* log_used,
IOStatus DBImpl::ConcurrentWriteGroupToWAL(
const WriteThread::WriteGroup& write_group, uint64_t* wal_used,
SequenceNumber* last_sequence, size_t seq_inc) {
IOStatus io_s;
@@ -1741,14 +1833,14 @@ IOStatus DBImpl::ConcurrentWriteToWAL(
return io_s;
}
// We need to lock log_write_mutex_ since logs_ and alive_log_files might be
// We need to lock wal_write_mutex_ since logs_ and alive_wal_files might be
// pushed back concurrently
log_write_mutex_.Lock();
wal_write_mutex_.Lock();
if (merged_batch == write_group.leader->batch) {
write_group.leader->log_used = logfile_number_;
write_group.leader->wal_used = cur_wal_number_;
} else if (write_with_wal > 1) {
for (auto writer : write_group) {
writer->log_used = logfile_number_;
writer->wal_used = cur_wal_number_;
}
}
*last_sequence = versions_->FetchAddLastAllocatedSequence(seq_inc);
@@ -1756,9 +1848,9 @@ IOStatus DBImpl::ConcurrentWriteToWAL(
WriteBatchInternal::SetSequence(merged_batch, sequence);
log::Writer* log_writer = logs_.back().writer;
LogFileNumberSize& log_file_number_size = alive_log_files_.back();
WalFileNumberSize& wal_file_number_size = alive_wal_files_.back();
assert(log_writer->get_log_number() == log_file_number_size.number);
assert(log_writer->get_log_number() == wal_file_number_size.number);
uint64_t log_size;
@@ -1766,13 +1858,13 @@ IOStatus DBImpl::ConcurrentWriteToWAL(
WriteOptions write_options;
write_options.rate_limiter_priority =
write_group.leader->rate_limiter_priority;
io_s = WriteToWAL(*merged_batch, write_options, log_writer, log_used,
&log_size, log_file_number_size, sequence);
io_s = WriteToWAL(*merged_batch, write_options, log_writer, wal_used,
&log_size, wal_file_number_size, sequence);
if (to_be_cached_state) {
cached_recoverable_state_ = *to_be_cached_state;
cached_recoverable_state_empty_ = false;
}
log_write_mutex_.Unlock();
wal_write_mutex_.Unlock();
if (io_s.ok()) {
const bool concurrent = true;
@@ -1800,7 +1892,7 @@ Status DBImpl::WriteRecoverableState() {
bool dont_care_bool;
SequenceNumber next_seq;
if (two_write_queues_) {
log_write_mutex_.Lock();
wal_write_mutex_.Lock();
}
SequenceNumber seq;
if (two_write_queues_) {
@@ -1815,13 +1907,17 @@ Status DBImpl::WriteRecoverableState() {
0 /*recovery_log_number*/, this, false /* concurrent_memtable_writes */,
&next_seq, &dont_care_bool, seq_per_batch_);
auto last_seq = next_seq - 1;
if (two_write_queues_) {
versions_->FetchAddLastAllocatedSequence(last_seq - seq);
versions_->SetLastPublishedSequence(last_seq);
if (status.ok()) { // Don't publish a partial batch write
if (two_write_queues_) {
versions_->FetchAddLastAllocatedSequence(last_seq - seq);
versions_->SetLastPublishedSequence(last_seq);
}
versions_->SetLastSequence(last_seq);
} else {
HandleMemTableInsertFailure(status);
}
versions_->SetLastSequence(last_seq);
if (two_write_queues_) {
log_write_mutex_.Unlock();
wal_write_mutex_.Unlock();
}
if (status.ok() && recoverable_state_pre_release_callback_) {
const bool DISABLE_MEMTABLE = true;
@@ -1893,7 +1989,10 @@ void DBImpl::AssignAtomicFlushSeq(const autovector<ColumnFamilyData*>& cfds) {
assert(immutable_db_options_.atomic_flush);
auto seq = versions_->LastSequence();
for (auto cfd : cfds) {
cfd->imm()->AssignAtomicFlushSeq(seq);
// cfd can be nullptr, see ScheduleFlushes()
if (cfd) {
cfd->imm()->AssignAtomicFlushSeq(seq);
}
}
}
@@ -1902,11 +2001,11 @@ Status DBImpl::SwitchWAL(WriteContext* write_context) {
assert(write_context != nullptr);
Status status;
if (alive_log_files_.begin()->getting_flushed) {
if (alive_wal_files_.begin()->getting_flushed) {
return status;
}
auto oldest_alive_log = alive_log_files_.begin()->number;
auto oldest_alive_log = alive_wal_files_.begin()->number;
bool flush_wont_release_oldest_log = false;
if (allow_2pc()) {
auto oldest_log_with_uncommitted_prep =
@@ -1936,14 +2035,14 @@ Status DBImpl::SwitchWAL(WriteContext* write_context) {
// transactions then we cannot flush this log until those transactions are
// commited.
unable_to_release_oldest_log_ = false;
alive_log_files_.begin()->getting_flushed = true;
alive_wal_files_.begin()->getting_flushed = true;
}
ROCKS_LOG_INFO(
immutable_db_options_.info_log,
"Flushing all column families with data in WAL number %" PRIu64
". Total log size is %" PRIu64 " while max_total_wal_size is %" PRIu64,
oldest_alive_log, total_log_size_.load(), GetMaxTotalWalSize());
oldest_alive_log, wals_total_size_.LoadRelaxed(), GetMaxTotalWalSize());
// no need to refcount because drop is happening in write thread, so can't
// happen while we're in the write thread
autovector<ColumnFamilyData*> cfds;
@@ -2413,21 +2512,21 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
// Do this without holding the dbmutex lock.
assert(versions_->prev_log_number() == 0);
if (two_write_queues_) {
log_write_mutex_.Lock();
wal_write_mutex_.Lock();
}
bool creating_new_log = !log_empty_;
bool creating_new_log = !wal_empty_;
if (two_write_queues_) {
log_write_mutex_.Unlock();
wal_write_mutex_.Unlock();
}
uint64_t recycle_log_number = 0;
// If file deletion is disabled, don't recycle logs since it'll result in
// the file getting renamed
if (creating_new_log && immutable_db_options_.recycle_log_file_num &&
!log_recycle_files_.empty() && IsFileDeletionsEnabled()) {
recycle_log_number = log_recycle_files_.front();
!wal_recycle_files_.empty() && IsFileDeletionsEnabled()) {
recycle_log_number = wal_recycle_files_.front();
}
uint64_t new_log_number =
creating_new_log ? versions_->NewFileNumber() : logfile_number_;
creating_new_log ? versions_->NewFileNumber() : cur_wal_number_;
// For use outside of holding DB mutex
const MutableCFOptions mutable_cf_options_copy =
cfd->GetLatestMutableCFOptions();
@@ -2453,14 +2552,14 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
mutex_.Unlock();
if (creating_new_log) {
PredecessorWALInfo info;
log_write_mutex_.Lock();
wal_write_mutex_.Lock();
if (!logs_.empty()) {
log::Writer* cur_log_writer = logs_.back().writer;
info = PredecessorWALInfo(cur_log_writer->get_log_number(),
cur_log_writer->file()->GetFileSize(),
cur_log_writer->GetLastSeqnoRecorded());
}
log_write_mutex_.Unlock();
wal_write_mutex_.Unlock();
// TODO: Write buffer size passed in should be max of all CF's instead
// of mutable_cf_options.write_buffer_size.
io_s = CreateWAL(write_options, new_log_number, recycle_log_number,
@@ -2501,11 +2600,11 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
// concurrent full purges don't delete the file while we're recycling it.
// To achieve that we hold the old log number in the recyclable list until
// after it has been renamed.
assert(log_recycle_files_.front() == recycle_log_number);
log_recycle_files_.pop_front();
assert(wal_recycle_files_.front() == recycle_log_number);
wal_recycle_files_.pop_front();
}
if (s.ok() && creating_new_log) {
InstrumentedMutexLock l(&log_write_mutex_);
InstrumentedMutexLock l(&wal_write_mutex_);
assert(new_log != nullptr);
if (!logs_.empty()) {
// Alway flush the buffer of the last log before switching to a new one
@@ -2527,11 +2626,11 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
}
}
if (s.ok()) {
logfile_number_ = new_log_number;
log_empty_ = true;
log_dir_synced_ = false;
logs_.emplace_back(logfile_number_, new_log);
alive_log_files_.emplace_back(logfile_number_);
cur_wal_number_ = new_log_number;
wal_empty_ = true;
wal_dir_synced_ = false;
logs_.emplace_back(cur_wal_number_, new_log);
alive_wal_files_.emplace_back(cur_wal_number_);
}
}
@@ -2562,7 +2661,7 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
// obsolete. So we should track the WAL obsoletion event before actually
// updating the empty CF's log number.
uint64_t min_wal_number_to_keep =
versions_->PreComputeMinLogNumberWithUnflushedData(logfile_number_);
versions_->PreComputeMinLogNumberWithUnflushedData(cur_wal_number_);
if (min_wal_number_to_keep >
versions_->GetWalSet().GetMinWalNumberToKeep()) {
// TODO: plumb Env::IOActivity, Env::IOPriority
@@ -2597,7 +2696,7 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
for (auto cf : empty_cfs) {
if (cf->IsEmpty()) {
cf->SetLogNumber(logfile_number_);
cf->SetLogNumber(cur_wal_number_);
// MEMPURGE: No need to change this, because new adds
// should still receive new sequence numbers.
cf->mem()->SetCreationSeq(versions_->LastSequence());
@@ -2614,14 +2713,14 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
// advance the log number. no need to persist this in the manifest
if (cf->IsEmpty()) {
if (creating_new_log) {
cf->SetLogNumber(logfile_number_);
cf->SetLogNumber(cur_wal_number_);
}
cf->mem()->SetCreationSeq(versions_->LastSequence());
}
}
}
cfd->mem()->SetNextLogNumber(logfile_number_);
cfd->mem()->SetNextLogNumber(cur_wal_number_);
assert(new_mem != nullptr);
cfd->imm()->Add(cfd->mem(), &context->memtables_to_free_);
if (new_imm) {
@@ -2633,7 +2732,7 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
// we always try to flush all immutable memtable. For atomic flush, these
// two memtables will be marked eligible for flush in the same call to
// AssignAtomicFlushSeq().
new_imm->SetNextLogNumber(logfile_number_);
new_imm->SetNextLogNumber(cur_wal_number_);
cfd->imm()->Add(new_imm, &context->memtables_to_free_);
}
new_mem->Ref();
+2
View File
@@ -7,6 +7,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. See the AUTHORS file for names of contributors.
#include <iomanip>
#include "db/db_test_util.h"
#include "port/stack_trace.h"
#include "test_util/testutil.h"
+29 -26
View File
@@ -9,7 +9,6 @@
#include "db/db_iter.h"
#include <iostream>
#include <limits>
#include <string>
@@ -42,9 +41,8 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
const MutableCFOptions& mutable_cf_options,
const Comparator* cmp, InternalIterator* iter,
const Version* version, SequenceNumber s, bool arena_mode,
uint64_t max_sequential_skip_in_iterations,
ReadCallback* read_callback, ColumnFamilyHandleImpl* cfh,
bool expose_blob_index)
bool expose_blob_index, ReadOnlyMemTable* active_mem)
: prefix_extractor_(mutable_cf_options.prefix_extractor.get()),
env_(_env),
clock_(ioptions.clock),
@@ -58,11 +56,21 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
read_callback_(read_callback),
sequence_(s),
statistics_(ioptions.stats),
max_skip_(max_sequential_skip_in_iterations),
max_skip_(mutable_cf_options.max_sequential_skip_in_iterations),
max_skippable_internal_keys_(read_options.max_skippable_internal_keys),
num_internal_keys_skipped_(0),
iterate_lower_bound_(read_options.iterate_lower_bound),
iterate_upper_bound_(read_options.iterate_upper_bound),
cfh_(cfh),
timestamp_ub_(read_options.timestamp),
timestamp_lb_(read_options.iter_start_ts),
timestamp_size_(timestamp_ub_ ? timestamp_ub_->size() : 0),
active_mem_(active_mem),
memtable_seqno_lb_((active_mem_ && !active_mem_->IsEmpty())
? active_mem_->GetFirstSequenceNumber()
: kMaxSequenceNumber),
memtable_op_scan_flush_trigger_(
mutable_cf_options.memtable_op_scan_flush_trigger),
direction_(kForward),
valid_(false),
current_entry_is_merged_(false),
@@ -76,11 +84,7 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
expose_blob_index_(expose_blob_index),
allow_unprepared_value_(read_options.allow_unprepared_value),
is_blob_(false),
arena_mode_(arena_mode),
cfh_(cfh),
timestamp_ub_(read_options.timestamp),
timestamp_lb_(read_options.iter_start_ts),
timestamp_size_(timestamp_ub_ ? timestamp_ub_->size() : 0) {
arena_mode_(arena_mode) {
RecordTick(statistics_, NO_ITERATOR_CREATED);
if (pin_thru_lifetime_) {
pinned_iters_mgr_.StartPinning();
@@ -369,6 +373,8 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
// to one.
bool reseek_done = false;
uint64_t mem_ops_scanned = 0;
bool marked_for_flush = false;
do {
// Will update is_key_seqnum_zero_ as soon as we parsed the current key
// but we need to save the previous value to be used in the loop.
@@ -425,6 +431,12 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
CompareKeyForSkip(ikey_.user_key, saved_key_.GetUserKey()) <= 0) {
num_skipped++; // skip this entry
PERF_COUNTER_ADD(internal_key_skipped_count, 1);
if (memtable_op_scan_flush_trigger_ && active_mem_ &&
ikey_.sequence >= memtable_seqno_lb_ && !marked_for_flush &&
++mem_ops_scanned >= memtable_op_scan_flush_trigger_) {
active_mem_->MarkForFlush();
marked_for_flush = true;
}
} else {
assert(!skipping_saved_key ||
CompareKeyForSkip(ikey_.user_key, saved_key_.GetUserKey()) > 0);
@@ -446,6 +458,12 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
!iter_.iter()->IsKeyPinned() /* copy */);
skipping_saved_key = true;
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
if (memtable_op_scan_flush_trigger_ && active_mem_ &&
ikey_.sequence >= memtable_seqno_lb_ && !marked_for_flush &&
++mem_ops_scanned >= memtable_op_scan_flush_trigger_) {
active_mem_->MarkForFlush();
marked_for_flush = true;
}
}
break;
case kTypeValue:
@@ -1224,6 +1242,8 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
if (timestamp_lb_ != nullptr) {
saved_key_.SetInternalKey(ikey);
} else {
saved_key_.SetUserKey(ikey.user_key);
}
valid_ = true;
@@ -1790,21 +1810,4 @@ void DBIter::SeekToLast() {
StripTimestampFromUserKey(saved_key_.GetUserKey(), timestamp_size_)));
}
}
Iterator* NewDBIterator(Env* env, const ReadOptions& read_options,
const ImmutableOptions& ioptions,
const MutableCFOptions& mutable_cf_options,
const Comparator* user_key_comparator,
InternalIterator* internal_iter, const Version* version,
const SequenceNumber& sequence,
uint64_t max_sequential_skip_in_iterations,
ReadCallback* read_callback,
ColumnFamilyHandleImpl* cfh, bool expose_blob_index) {
DBIter* db_iter = new DBIter(
env, read_options, ioptions, mutable_cf_options, user_key_comparator,
internal_iter, version, sequence, false,
max_sequential_skip_in_iterations, read_callback, cfh, expose_blob_index);
return db_iter;
}
} // namespace ROCKSDB_NAMESPACE
+61 -30
View File
@@ -57,6 +57,33 @@ class Version;
// numbers, deletion markers, overwrites, etc.
class DBIter final : public Iterator {
public:
// Return a new DBIter that reads from `internal_iter` at the specified
// `sequence` number.
//
// @param active_mem Pointer to the active memtable that `internal_iter`
// is reading from. If not null, the memtable can be marked for flush
// according to option mutable_cf_options.memtable_op_scan_flush_trigger.
// @param arena_mode If true, the DBIter will be allocated from the arena.
static DBIter* NewIter(Env* env, const ReadOptions& read_options,
const ImmutableOptions& ioptions,
const MutableCFOptions& mutable_cf_options,
const Comparator* user_key_comparator,
InternalIterator* internal_iter,
const Version* version, const SequenceNumber& sequence,
ReadCallback* read_callback,
ReadOnlyMemTable* active_mem,
ColumnFamilyHandleImpl* cfh = nullptr,
bool expose_blob_index = false,
Arena* arena = nullptr) {
void* mem = arena ? arena->AllocateAligned(sizeof(DBIter))
: operator new(sizeof(DBIter));
DBIter* db_iter = new (mem)
DBIter(env, read_options, ioptions, mutable_cf_options,
user_key_comparator, internal_iter, version, sequence, arena,
read_callback, cfh, expose_blob_index, active_mem);
return db_iter;
}
// The following is grossly complicated. TODO: clean it up
// Which direction is the iterator currently moving?
// (1) When moving forward:
@@ -113,14 +140,6 @@ class DBIter final : public Iterator {
uint64_t skip_count_;
};
DBIter(Env* _env, const ReadOptions& read_options,
const ImmutableOptions& ioptions,
const MutableCFOptions& mutable_cf_options, const Comparator* cmp,
InternalIterator* iter, const Version* version, SequenceNumber s,
bool arena_mode, uint64_t max_sequential_skip_in_iterations,
ReadCallback* read_callback, ColumnFamilyHandleImpl* cfh,
bool expose_blob_index);
// No copying allowed
DBIter(const DBIter&) = delete;
void operator=(const DBIter&) = delete;
@@ -220,7 +239,26 @@ class DBIter final : public Iterator {
bool PrepareValue() override;
void Prepare(const std::vector<ScanOptions>& scan_opts) override {
std::optional<std::vector<ScanOptions>> new_scan_opts;
new_scan_opts.emplace(scan_opts);
scan_opts_.swap(new_scan_opts);
if (!scan_opts.empty()) {
iter_.Prepare(&scan_opts_.value());
} else {
iter_.Prepare(nullptr);
}
}
private:
DBIter(Env* _env, const ReadOptions& read_options,
const ImmutableOptions& ioptions,
const MutableCFOptions& mutable_cf_options, const Comparator* cmp,
InternalIterator* iter, const Version* version, SequenceNumber s,
bool arena_mode, ReadCallback* read_callback,
ColumnFamilyHandleImpl* cfh, bool expose_blob_index,
ReadOnlyMemTable* active_mem);
class BlobReader {
public:
BlobReader(const Version* version, ReadTier read_tier,
@@ -425,6 +463,21 @@ class DBIter final : public Iterator {
IterKey prefix_;
Status status_;
Slice lazy_blob_index_;
// List of operands for merge operator.
MergeContext merge_context_;
LocalStatistics local_stats_;
PinnedIteratorsManager pinned_iters_mgr_;
ColumnFamilyHandleImpl* cfh_;
const Slice* const timestamp_ub_;
const Slice* const timestamp_lb_;
const size_t timestamp_size_;
std::string saved_timestamp_;
std::optional<std::vector<ScanOptions>> scan_opts_;
ReadOnlyMemTable* const active_mem_;
const SequenceNumber memtable_seqno_lb_;
const uint32_t memtable_op_scan_flush_trigger_;
Direction direction_;
bool valid_;
bool current_entry_is_merged_;
@@ -443,29 +496,7 @@ class DBIter final : public Iterator {
// the stacked BlobDB implementation is used, false otherwise.
bool expose_blob_index_;
bool allow_unprepared_value_;
Slice lazy_blob_index_;
bool is_blob_;
bool arena_mode_;
// List of operands for merge operator.
MergeContext merge_context_;
LocalStatistics local_stats_;
PinnedIteratorsManager pinned_iters_mgr_;
ColumnFamilyHandleImpl* cfh_;
const Slice* const timestamp_ub_;
const Slice* const timestamp_lb_;
const size_t timestamp_size_;
std::string saved_timestamp_;
};
// Return a new iterator that converts internal keys (yielded by
// "*internal_iter") that were live at the specified `sequence` number
// into appropriate user keys.
Iterator* NewDBIterator(
Env* env, const ReadOptions& read_options, const ImmutableOptions& ioptions,
const MutableCFOptions& mutable_cf_options,
const Comparator* user_key_comparator, InternalIterator* internal_iter,
const Version* version, const SequenceNumber& sequence,
uint64_t max_sequential_skip_in_iterations, ReadCallback* read_callback,
ColumnFamilyHandleImpl* cfh = nullptr, bool expose_blob_index = false);
} // namespace ROCKSDB_NAMESPACE
+3 -4
View File
@@ -528,12 +528,11 @@ TEST_F(DBIteratorStressTest, StressTest) {
internal_iter->target_hidden_fraction =
target_hidden_fraction;
internal_iter->trace = trace;
db_iter.reset(NewDBIterator(
db_iter.reset(DBIter::NewIter(
env_, ropt, ImmutableOptions(options),
MutableCFOptions(options), BytewiseComparator(),
internal_iter, nullptr /* version */, sequence,
options.max_sequential_skip_in_iterations,
nullptr /*read_callback*/));
internal_iter, /*version=*/nullptr, sequence,
nullptr /*read_callback*/, /*active_mem=*/nullptr));
}
// Do a random operation. It's important to do it on ref_it
+168 -228
View File
@@ -259,11 +259,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
internal_iter->Finish();
ReadOptions ro;
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 10 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
@@ -294,11 +293,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
internal_iter->Finish();
ReadOptions ro;
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 10 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
@@ -322,11 +320,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
ReadOptions ro;
ro.iterate_upper_bound = &prefix;
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 10 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
@@ -356,11 +353,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
ReadOptions ro;
ro.iterate_upper_bound = &prefix;
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 10 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
@@ -393,11 +389,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
ReadOptions ro;
ro.iterate_upper_bound = &prefix;
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 10 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(!db_iter->Valid());
@@ -425,11 +420,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
ReadOptions ro;
ro.iterate_upper_bound = &prefix;
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 7 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
SetPerfLevel(kEnableCount);
ASSERT_TRUE(GetPerfLevel() == kEnableCount);
@@ -465,11 +459,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
ReadOptions ro;
ro.iterate_upper_bound = &prefix;
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 4 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
@@ -492,11 +485,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
ReadOptions ro;
ro.iterate_upper_bound = &prefix;
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 10 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(!db_iter->Valid());
@@ -517,11 +509,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
ReadOptions ro;
ro.iterate_upper_bound = &prefix;
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 10 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
@@ -554,11 +545,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
ReadOptions ro;
ro.iterate_upper_bound = &prefix;
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 7 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
SetPerfLevel(kEnableCount);
ASSERT_TRUE(GetPerfLevel() == kEnableCount);
@@ -586,11 +576,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
internal_iter->Finish();
ReadOptions ro;
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 10 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToFirst();
ASSERT_TRUE(db_iter->Valid());
@@ -631,11 +620,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
internal_iter->Finish();
ReadOptions ro;
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 2 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), "b");
@@ -664,11 +652,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
internal_iter->Finish();
ReadOptions ro;
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 10 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), "c");
@@ -696,11 +683,10 @@ TEST_F(DBIteratorTest, DBIteratorEmpty) {
TestIterator* internal_iter = new TestIterator(BytewiseComparator());
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 0 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(!db_iter->Valid());
ASSERT_OK(db_iter->status());
@@ -710,11 +696,10 @@ TEST_F(DBIteratorTest, DBIteratorEmpty) {
TestIterator* internal_iter = new TestIterator(BytewiseComparator());
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 0 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToFirst();
ASSERT_TRUE(!db_iter->Valid());
ASSERT_OK(db_iter->status());
@@ -735,11 +720,10 @@ TEST_F(DBIteratorTest, DBIteratorUseSkipCountSkips) {
}
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
BytewiseComparator(), internal_iter, nullptr /* version */,
2 /* sequence */, options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
2 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), "c");
@@ -782,11 +766,11 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) {
internal_iter->Finish();
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, i + 2 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
@@ -820,11 +804,11 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) {
internal_iter->AddPut("c", "200");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, i + 2 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
@@ -851,11 +835,10 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) {
internal_iter->AddPut("c", "200");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 202 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
@@ -886,11 +869,11 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) {
}
internal_iter->AddPut("c", "200");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, i /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(!db_iter->Valid());
ASSERT_OK(db_iter->status());
@@ -906,11 +889,10 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) {
}
internal_iter->AddPut("c", "200");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 200 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), "c");
@@ -944,11 +926,11 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) {
}
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, i + 2 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
@@ -981,11 +963,11 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) {
}
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, i + 2 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
@@ -1033,11 +1015,10 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) {
internal_iter->Finish();
ro.max_skippable_internal_keys = 0;
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 10 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToFirst();
ASSERT_TRUE(db_iter->Valid());
@@ -1081,11 +1062,10 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) {
internal_iter->Finish();
ro.max_skippable_internal_keys = 2;
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 10 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToFirst();
ASSERT_TRUE(db_iter->Valid());
@@ -1127,11 +1107,10 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) {
internal_iter->Finish();
ro.max_skippable_internal_keys = 2;
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 10 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToFirst();
ASSERT_TRUE(db_iter->Valid());
@@ -1167,11 +1146,10 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) {
internal_iter->Finish();
ro.max_skippable_internal_keys = 2;
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 10 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToFirst();
ASSERT_TRUE(db_iter->Valid());
@@ -1204,11 +1182,10 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) {
internal_iter->Finish();
ro.max_skippable_internal_keys = 2;
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 10 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
@@ -1236,11 +1213,10 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) {
internal_iter->Finish();
ro.max_skippable_internal_keys = 2;
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 10 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToFirst();
ASSERT_TRUE(db_iter->Valid());
@@ -1275,11 +1251,10 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) {
internal_iter->Finish();
ro.max_skippable_internal_keys = 2;
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 10 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToFirst();
ASSERT_TRUE(db_iter->Valid());
@@ -1314,11 +1289,11 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) {
internal_iter->Finish();
ro.max_skippable_internal_keys = i;
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 2 * i + 1 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToFirst();
ASSERT_TRUE(db_iter->Valid());
@@ -1369,11 +1344,10 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) {
options.max_sequential_skip_in_iterations = 1000;
ro.max_skippable_internal_keys = i;
std::unique_ptr<Iterator> db_iter(NewDBIterator(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, MutableCFOptions(options), BytewiseComparator(),
internal_iter, nullptr /* version */, 2 * i + 1 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToFirst();
ASSERT_TRUE(db_iter->Valid());
@@ -1412,11 +1386,11 @@ TEST_F(DBIteratorTest, DBIteratorTimedPutBasic) {
internal_iter->AddTimedPut("d", "3", /*write_unix_time=*/0);
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
options.max_sequential_skip_in_iterations = 1;
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
BytewiseComparator(), internal_iter, nullptr /* version */,
7 /* sequence */, /*max_sequential_skip_in_iterations*/ 1,
nullptr /* read_callback */));
7 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToFirst();
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), "a");
@@ -1463,11 +1437,10 @@ TEST_F(DBIteratorTest, DBIterator1) {
internal_iter->AddMerge("b", "2");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
BytewiseComparator(), internal_iter, nullptr /* version */,
1 /* sequence */, options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
1 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToFirst();
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), "a");
@@ -1493,11 +1466,10 @@ TEST_F(DBIteratorTest, DBIterator2) {
internal_iter->AddMerge("b", "2");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
BytewiseComparator(), internal_iter, nullptr /* version */,
0 /* sequence */, options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
0 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToFirst();
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), "a");
@@ -1519,11 +1491,10 @@ TEST_F(DBIteratorTest, DBIterator3) {
internal_iter->AddMerge("b", "2");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
BytewiseComparator(), internal_iter, nullptr /* version */,
2 /* sequence */, options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
2 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToFirst();
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), "a");
@@ -1545,11 +1516,10 @@ TEST_F(DBIteratorTest, DBIterator4) {
internal_iter->AddMerge("b", "2");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
BytewiseComparator(), internal_iter, nullptr /* version */,
4 /* sequence */, options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
4 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToFirst();
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), "a");
@@ -1580,11 +1550,10 @@ TEST_F(DBIteratorTest, DBIterator5) {
internal_iter->AddMerge("a", "merge_6");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 0 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), "a");
@@ -1605,11 +1574,10 @@ TEST_F(DBIteratorTest, DBIterator5) {
internal_iter->AddMerge("a", "merge_6");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 1 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), "a");
@@ -1630,11 +1598,10 @@ TEST_F(DBIteratorTest, DBIterator5) {
internal_iter->AddMerge("a", "merge_6");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 2 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), "a");
@@ -1655,11 +1622,10 @@ TEST_F(DBIteratorTest, DBIterator5) {
internal_iter->AddMerge("a", "merge_6");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 3 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), "a");
@@ -1680,11 +1646,10 @@ TEST_F(DBIteratorTest, DBIterator5) {
internal_iter->AddMerge("a", "merge_6");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 4 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), "a");
@@ -1705,11 +1670,10 @@ TEST_F(DBIteratorTest, DBIterator5) {
internal_iter->AddMerge("a", "merge_6");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 5 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), "a");
@@ -1730,11 +1694,10 @@ TEST_F(DBIteratorTest, DBIterator5) {
internal_iter->AddMerge("a", "merge_6");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 6 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), "a");
@@ -1753,11 +1716,10 @@ TEST_F(DBIteratorTest, DBIterator5) {
internal_iter->AddMerge("a", "merge_2");
internal_iter->AddPut("b", "val_b");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 10 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->Seek("b");
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), "b");
@@ -1785,11 +1747,10 @@ TEST_F(DBIteratorTest, DBIterator6) {
internal_iter->AddMerge("a", "merge_6");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 0 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), "a");
@@ -1810,11 +1771,10 @@ TEST_F(DBIteratorTest, DBIterator6) {
internal_iter->AddMerge("a", "merge_6");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 1 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), "a");
@@ -1835,11 +1795,10 @@ TEST_F(DBIteratorTest, DBIterator6) {
internal_iter->AddMerge("a", "merge_6");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 2 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), "a");
@@ -1860,11 +1819,10 @@ TEST_F(DBIteratorTest, DBIterator6) {
internal_iter->AddMerge("a", "merge_6");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 3 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(!db_iter->Valid());
ASSERT_OK(db_iter->status());
@@ -1881,11 +1839,10 @@ TEST_F(DBIteratorTest, DBIterator6) {
internal_iter->AddMerge("a", "merge_6");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 4 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), "a");
@@ -1906,11 +1863,10 @@ TEST_F(DBIteratorTest, DBIterator6) {
internal_iter->AddMerge("a", "merge_6");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 5 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), "a");
@@ -1931,11 +1887,10 @@ TEST_F(DBIteratorTest, DBIterator6) {
internal_iter->AddMerge("a", "merge_6");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 6 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), "a");
@@ -1976,11 +1931,10 @@ TEST_F(DBIteratorTest, DBIterator7) {
internal_iter->AddDeletion("c");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 0 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), "a");
@@ -2013,11 +1967,10 @@ TEST_F(DBIteratorTest, DBIterator7) {
internal_iter->AddDeletion("c");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 2 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
@@ -2056,11 +2009,10 @@ TEST_F(DBIteratorTest, DBIterator7) {
internal_iter->AddDeletion("c");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 4 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
@@ -2099,11 +2051,10 @@ TEST_F(DBIteratorTest, DBIterator7) {
internal_iter->AddDeletion("c");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 5 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
@@ -2147,11 +2098,10 @@ TEST_F(DBIteratorTest, DBIterator7) {
internal_iter->AddDeletion("c");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 6 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
@@ -2196,11 +2146,10 @@ TEST_F(DBIteratorTest, DBIterator7) {
internal_iter->AddDeletion("c");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 7 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
@@ -2239,11 +2188,10 @@ TEST_F(DBIteratorTest, DBIterator7) {
internal_iter->AddDeletion("c");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 9 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
@@ -2288,11 +2236,10 @@ TEST_F(DBIteratorTest, DBIterator7) {
internal_iter->AddDeletion("c");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 13 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
@@ -2338,11 +2285,10 @@ TEST_F(DBIteratorTest, DBIterator7) {
internal_iter->AddDeletion("c");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
internal_iter, nullptr /* version */, 14 /* sequence */,
options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
@@ -2371,11 +2317,10 @@ TEST_F(DBIteratorTest, DBIterator8) {
internal_iter->AddPut("b", "0");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
BytewiseComparator(), internal_iter, nullptr /* version */,
10 /* sequence */, options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
10 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), "b");
@@ -2403,11 +2348,11 @@ TEST_F(DBIteratorTest, DBIterator9) {
internal_iter->AddMerge("d", "merge_6");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
BytewiseComparator(), internal_iter, nullptr /* version */,
10 /* sequence */, options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
std::unique_ptr<Iterator> db_iter(
DBIter::NewIter(env_, ro, ImmutableOptions(options),
MutableCFOptions(options), BytewiseComparator(),
internal_iter, nullptr /* version */, 10 /* sequence */,
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
@@ -2471,11 +2416,10 @@ TEST_F(DBIteratorTest, DBIterator10) {
internal_iter->AddPut("d", "4");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
BytewiseComparator(), internal_iter, nullptr /* version */,
10 /* sequence */, options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
10 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->Seek("c");
ASSERT_TRUE(db_iter->Valid());
@@ -2512,10 +2456,10 @@ TEST_F(DBIteratorTest, SeekToLastOccurrenceSeq0) {
internal_iter->AddPut("b", "2");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
BytewiseComparator(), internal_iter, nullptr /* version */,
10 /* sequence */, 0 /* force seek */, nullptr /* read_callback */));
10 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToFirst();
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), "a");
@@ -2542,11 +2486,10 @@ TEST_F(DBIteratorTest, DBIterator11) {
internal_iter->AddMerge("b", "2");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
BytewiseComparator(), internal_iter, nullptr /* version */,
1 /* sequence */, options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
1 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToFirst();
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), "a");
@@ -2571,10 +2514,10 @@ TEST_F(DBIteratorTest, DBIterator12) {
internal_iter->AddSingleDeletion("b");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
BytewiseComparator(), internal_iter, nullptr /* version */,
10 /* sequence */, 0 /* force seek */, nullptr /* read_callback */));
10 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), "c");
@@ -2610,11 +2553,11 @@ TEST_F(DBIteratorTest, DBIterator13) {
internal_iter->AddPut(key, "8");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
options.max_sequential_skip_in_iterations = 3;
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
BytewiseComparator(), internal_iter, nullptr /* version */,
2 /* sequence */, 3 /* max_sequential_skip_in_iterations */,
nullptr /* read_callback */));
2 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->Seek("b");
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), key);
@@ -2640,11 +2583,11 @@ TEST_F(DBIteratorTest, DBIterator14) {
internal_iter->AddPut("c", "9");
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
options.max_sequential_skip_in_iterations = 1;
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
BytewiseComparator(), internal_iter, nullptr /* version */,
4 /* sequence */, 1 /* max_sequential_skip_in_iterations */,
nullptr /* read_callback */));
4 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->Seek("b");
ASSERT_TRUE(db_iter->Valid());
ASSERT_EQ(db_iter->key().ToString(), "b");
@@ -2680,11 +2623,12 @@ class DBIterWithMergeIterTest : public testing::Test {
InternalIterator* merge_iter =
NewMergingIterator(&icomp_, child_iters.data(), 2u);
db_iter_.reset(NewDBIterator(
options_.max_sequential_skip_in_iterations = 3;
db_iter_.reset(DBIter::NewIter(
env_, ro_, ImmutableOptions(options_), MutableCFOptions(options_),
BytewiseComparator(), merge_iter, nullptr /* version */,
8 /* read data earlier than seqId 8 */,
3 /* max iterators before reseek */, nullptr /* read_callback */));
8 /* read data earlier than seqId 8 */, nullptr /* read_callback */,
/*active_mem=*/nullptr));
}
Env* env_;
@@ -3120,11 +3064,10 @@ TEST_F(DBIteratorTest, SeekPrefixTombstones) {
internal_iter->Finish();
ro.prefix_same_as_start = true;
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
BytewiseComparator(), internal_iter, nullptr /* version */,
10 /* sequence */, options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
10 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
int skipped_keys = 0;
@@ -3157,11 +3100,11 @@ TEST_F(DBIteratorTest, SeekToFirstLowerBound) {
Slice lower_bound(lower_bound_str);
ro.iterate_lower_bound = &lower_bound;
Options options;
std::unique_ptr<Iterator> db_iter(NewDBIterator(
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
BytewiseComparator(), internal_iter, nullptr /* version */,
10 /* sequence */, options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
std::unique_ptr<Iterator> db_iter(
DBIter::NewIter(env_, ro, ImmutableOptions(options),
MutableCFOptions(options), BytewiseComparator(),
internal_iter, nullptr /* version */, 10 /* sequence */,
nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToFirst();
if (i == kNumKeys + 1) {
@@ -3197,11 +3140,10 @@ TEST_F(DBIteratorTest, PrevLowerBound) {
Slice lower_bound(lower_bound_str);
ro.iterate_lower_bound = &lower_bound;
Options options;
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
BytewiseComparator(), internal_iter, nullptr /* version */,
10 /* sequence */, options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
10 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekToLast();
for (int i = kNumKeys; i >= kLowerBound; --i) {
@@ -3226,11 +3168,10 @@ TEST_F(DBIteratorTest, SeekLessLowerBound) {
Slice lower_bound(lower_bound_str);
ro.iterate_lower_bound = &lower_bound;
Options options;
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
BytewiseComparator(), internal_iter, nullptr /* version */,
10 /* sequence */, options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
10 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
auto before_lower_bound_str = std::to_string(kLowerBound - 1);
Slice before_lower_bound(lower_bound_str);
@@ -3252,11 +3193,10 @@ TEST_F(DBIteratorTest, ReverseToForwardWithDisappearingKeys) {
}
internal_iter->Finish();
std::unique_ptr<Iterator> db_iter(NewDBIterator(
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
env_, ReadOptions(), ImmutableOptions(options), MutableCFOptions(options),
BytewiseComparator(), internal_iter, nullptr /* version */,
10 /* sequence */, options.max_sequential_skip_in_iterations,
nullptr /* read_callback */));
10 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
db_iter->SeekForPrev("a");
ASSERT_TRUE(db_iter->Valid());
+160
View File
@@ -3824,6 +3824,166 @@ TEST_F(DBIteratorTest, IteratorsConsistentViewExplicitSnapshot) {
}
}
TEST_P(DBIteratorTest, MemtableOpsScanFlushTriggerWithSeek) {
// Tests that option memtable_op_scan_flush_trigger works when the limit
// is reached during a Seek() operation.
const int kTrigger = 10;
Random* r = Random::GetTLSInstance();
for (int trigger : {kTrigger, kTrigger + 1}) {
for (bool delete_only : {false, true}) {
Options options;
options.create_if_missing = true;
options.memtable_op_scan_flush_trigger = trigger;
options.level_compaction_dynamic_level_bytes = true;
DestroyAndReopen(options);
// Base data that will be covered by a consecutive sequence of tombstones.
int kNumKeys = delete_only ? kTrigger : kTrigger / 2;
for (int i = 0; i < kNumKeys; ++i) {
ASSERT_OK(Put(Key(i), r->RandomString(100)));
}
ASSERT_OK(Flush());
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
ASSERT_EQ(1, NumTableFilesAtLevel(6));
if (delete_only) {
for (int i = 0; i < kNumKeys; ++i) {
ASSERT_OK(SingleDelete(Key(i)));
}
} else {
for (int i = 0; i < kNumKeys; ++i) {
ASSERT_OK(Put(Key(i), r->RandomString(100)));
}
for (int i = 0; i < kNumKeys; ++i) {
ASSERT_OK(Delete(Key(i)));
}
}
SetPerfLevel(PerfLevel::kEnableCount);
get_perf_context()->Reset();
ReadOptions ro;
std::unique_ptr<Iterator> iter(db_->NewIterator(ro));
// Seek to the first key, this will scan through all the tombstones and
// hidden puts
iter->Seek(Key(0));
ASSERT_FALSE(
iter->Valid()); // All keys are deleted, so iterator is not valid
ASSERT_OK(iter->status());
ASSERT_EQ(get_perf_context()->next_on_memtable_count, kTrigger);
// Skipping kNumTrigger memtable entries in a single iterator operation
// should mark the memtable for flush.
//
// At the end of a write, we check and update memtable to request a flush
ASSERT_OK(Put(Key(11), "val"));
// Before a write, we schedule memtables for flush if requested.
ASSERT_OK(Put(Key(12), "val"));
ASSERT_OK(db_->WaitForCompact({}));
if (trigger <= kTrigger) {
// Check if memtable was flushed due to scan trigger
ASSERT_EQ(1, NumTableFilesAtLevel(0));
uint64_t val = 0;
ASSERT_TRUE(
db_->GetIntProperty("rocksdb.num-deletes-active-mem-table", &val));
ASSERT_EQ(0, val);
} else {
uint64_t val = 0;
ASSERT_TRUE(
db_->GetIntProperty("rocksdb.num-deletes-active-mem-table", &val));
ASSERT_EQ(kNumKeys, val);
}
}
}
}
TEST_P(DBIteratorTest, MemtableOpsScanFlushTriggerWithNext) {
// Tests that option memtable_op_scan_flush_trigger works when the limit
// is reached during a Next() operation, and not trigger a flush when
// the limit is reached across multiple Next() operations.
const int kTrigger = 10;
Random* r = Random::GetTLSInstance();
for (int trigger : {kTrigger, kTrigger + 1}) {
for (bool delete_only : {false, true}) {
Options options;
options.create_if_missing = true;
options.memtable_op_scan_flush_trigger = trigger;
options.level_compaction_dynamic_level_bytes = true;
DestroyAndReopen(options);
// Base data that will be covered by a consecutive sequence of tombstones.
int kNumKeys = delete_only ? kTrigger : kTrigger / 2;
for (int i = 0; i <= kNumKeys; ++i) {
ASSERT_OK(Put(Key(i), r->RandomString(100)));
}
ASSERT_OK(Flush());
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
ASSERT_EQ(1, NumTableFilesAtLevel(6));
ASSERT_OK(Put(Key(0), "val"));
if (delete_only) {
for (int i = 1; i <= kNumKeys; ++i) {
ASSERT_OK(SingleDelete(Key(i)));
}
} else {
for (int i = 1; i <= kNumKeys; ++i) {
ASSERT_OK(Put(Key(i), r->RandomString(100)));
}
for (int i = 1; i <= kNumKeys; ++i) {
ASSERT_OK(Delete(Key(i)));
}
}
// Total number of tombstones and hidden puts scanned across multiple
// Next() operations below will be kTrigger, and it should not trigger a
// flush when the limit is kTrigger + 1.
ASSERT_OK(Put(Key(kNumKeys + 1), "v1"));
ASSERT_OK(Delete(Key(kNumKeys + 2)));
ASSERT_OK(Put(Key(kNumKeys + 3), "v3"));
SetPerfLevel(PerfLevel::kEnableCount);
get_perf_context()->Reset();
ReadOptions ro;
std::unique_ptr<Iterator> iter(db_->NewIterator(ro));
iter->Seek(Key(0));
ASSERT_TRUE(iter->Valid());
ASSERT_EQ(iter->value(), "val");
ASSERT_OK(iter->status());
ASSERT_EQ(get_perf_context()->next_on_memtable_count, 0);
iter->Next();
// kTrigger tombstones and invisible puts and 1 for the visible put
ASSERT_EQ(get_perf_context()->next_on_memtable_count, kTrigger + 1);
iter->Next();
ASSERT_EQ(get_perf_context()->next_on_memtable_count, kTrigger + 3);
// Skipping kNumTrigger memtable entries in a single iterator operation
// should mark the memtable for flush.
//
// At the end of a write, we check and update memtable to request a flush
ASSERT_OK(Put(Key(11), "val"));
// Before a write, we schedule memtables for flush if requested.
ASSERT_OK(Put(Key(12), "val"));
ASSERT_OK(db_->WaitForCompact({}));
if (trigger <= kTrigger) {
// Check if memtable was flushed due to scan trigger
ASSERT_EQ(1, NumTableFilesAtLevel(0));
uint64_t val = 0;
ASSERT_TRUE(
db_->GetIntProperty("rocksdb.num-deletes-active-mem-table", &val));
ASSERT_EQ(0, val);
} else {
uint64_t val = 0;
ASSERT_TRUE(
db_->GetIntProperty("rocksdb.num-deletes-active-mem-table", &val));
ASSERT_EQ(kNumKeys + 1, val);
}
}
}
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+8 -8
View File
@@ -312,12 +312,12 @@ TEST_P(DbKvChecksumTest, WriteToWALCorrupted) {
// Corrupted write batch leads to read-only mode, so we have to
// reopen for every attempt.
Reopen(options);
auto log_size_pre_write = dbfull()->TEST_total_log_size();
auto log_size_pre_write = dbfull()->TEST_wals_total_size();
SyncPoint::GetInstance()->EnableProcessing();
ASSERT_TRUE(ExecuteWrite(nullptr /* cf_handle */).IsCorruption());
// Confirm that nothing was written to WAL
ASSERT_EQ(log_size_pre_write, dbfull()->TEST_total_log_size());
ASSERT_EQ(log_size_pre_write, dbfull()->TEST_wals_total_size());
ASSERT_TRUE(dbfull()->TEST_GetBGError().IsCorruption());
SyncPoint::GetInstance()->DisableProcessing();
@@ -350,12 +350,12 @@ TEST_P(DbKvChecksumTest, WriteToWALWithColumnFamilyCorrupted) {
// Corrupted write batch leads to read-only mode, so we have to
// reopen for every attempt.
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options);
auto log_size_pre_write = dbfull()->TEST_total_log_size();
auto log_size_pre_write = dbfull()->TEST_wals_total_size();
SyncPoint::GetInstance()->EnableProcessing();
ASSERT_TRUE(ExecuteWrite(nullptr /* cf_handle */).IsCorruption());
// Confirm that nothing was written to WAL
ASSERT_EQ(log_size_pre_write, dbfull()->TEST_total_log_size());
ASSERT_EQ(log_size_pre_write, dbfull()->TEST_wals_total_size());
ASSERT_TRUE(dbfull()->TEST_GetBGError().IsCorruption());
SyncPoint::GetInstance()->DisableProcessing();
@@ -487,7 +487,7 @@ TEST_P(DbKvChecksumTestMergedBatch, WriteToWALCorrupted) {
// Reopen DB since it failed WAL write which lead to read-only mode
Reopen(options);
SyncPoint::GetInstance()->EnableProcessing();
auto log_size_pre_write = dbfull()->TEST_total_log_size();
auto log_size_pre_write = dbfull()->TEST_wals_total_size();
leader_batch_and_status =
GetWriteBatch(GetCFHandleToUse(nullptr, op_type1_),
8 /* protection_bytes_per_key */, op_type1_);
@@ -499,7 +499,7 @@ TEST_P(DbKvChecksumTestMergedBatch, WriteToWALCorrupted) {
SyncPoint::GetInstance()->ClearCallBack("WriteThread::JoinBatchGroup:Wait");
ASSERT_EQ(1, leader_count);
// Nothing should have been written to WAL
ASSERT_EQ(log_size_pre_write, dbfull()->TEST_total_log_size());
ASSERT_EQ(log_size_pre_write, dbfull()->TEST_wals_total_size());
ASSERT_TRUE(dbfull()->TEST_GetBGError().IsCorruption());
corrupt_byte_offset++;
@@ -599,7 +599,7 @@ TEST_P(DbKvChecksumTestMergedBatch, WriteToWALWithColumnFamilyCorrupted) {
// Reopen DB since it failed WAL write which lead to read-only mode
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "ramen"}, options);
SyncPoint::GetInstance()->EnableProcessing();
auto log_size_pre_write = dbfull()->TEST_total_log_size();
auto log_size_pre_write = dbfull()->TEST_wals_total_size();
leader_batch_and_status =
GetWriteBatch(GetCFHandleToUse(handles_[1], op_type1_),
8 /* protection_bytes_per_key */, op_type1_);
@@ -612,7 +612,7 @@ TEST_P(DbKvChecksumTestMergedBatch, WriteToWALWithColumnFamilyCorrupted) {
ASSERT_EQ(1, leader_count);
// Nothing should have been written to WAL
ASSERT_EQ(log_size_pre_write, dbfull()->TEST_total_log_size());
ASSERT_EQ(log_size_pre_write, dbfull()->TEST_wals_total_size());
ASSERT_TRUE(dbfull()->TEST_GetBGError().IsCorruption());
corrupt_byte_offset++;
+3 -3
View File
@@ -971,7 +971,7 @@ TEST_F(DBMergeOperatorTest, MaxSuccessiveMergesBaseValues) {
// No base value
{
constexpr char key[] = "key1";
const std::string key = "key1";
ASSERT_OK(db_->Merge(WriteOptions(), db_->DefaultColumnFamily(), key, foo));
ASSERT_OK(db_->Merge(WriteOptions(), db_->DefaultColumnFamily(), key, bar));
@@ -994,7 +994,7 @@ TEST_F(DBMergeOperatorTest, MaxSuccessiveMergesBaseValues) {
// Plain base value
{
constexpr char key[] = "key2";
const std::string key = "key2";
ASSERT_OK(db_->Put(WriteOptions(), db_->DefaultColumnFamily(), key, foo));
ASSERT_OK(db_->Merge(WriteOptions(), db_->DefaultColumnFamily(), key, bar));
@@ -1019,7 +1019,7 @@ TEST_F(DBMergeOperatorTest, MaxSuccessiveMergesBaseValues) {
// Wide-column base value
{
constexpr char key[] = "key3";
const std::string key = "key3";
const WideColumns columns{{kDefaultWideColumnName, foo}, {bar, baz}};
ASSERT_OK(db_->PutEntity(WriteOptions(), db_->DefaultColumnFamily(), key,
+19 -23
View File
@@ -70,7 +70,8 @@ class DBOptionsTest : public DBTestBase {
options.env = env_;
ImmutableDBOptions db_options(options);
test::RandomInitCFOptions(&options, options, rnd);
auto sanitized_options = SanitizeOptions(db_options, options);
auto sanitized_options =
SanitizeCfOptions(db_options, /*read_only*/ false, options);
auto opt_map = GetMutableCFOptionsMap(sanitized_options);
delete options.compaction_filter;
return opt_map;
@@ -321,31 +322,26 @@ TEST_F(DBOptionsTest, SetWithCustomMemTableFactory) {
}
Options options;
options.create_if_missing = true;
// Try with fail_if_options_file_error=false/true to update the options
for (bool on_error : {false, true}) {
options.fail_if_options_file_error = on_error;
options.env = env_;
options.disable_auto_compactions = false;
options.env = env_;
options.disable_auto_compactions = false;
options.memtable_factory.reset(new DummySkipListFactory());
Reopen(options);
options.memtable_factory.reset(new DummySkipListFactory());
Reopen(options);
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
ASSERT_OK(
dbfull()->SetOptions(cfh, {{"disable_auto_compactions", "true"}}));
ColumnFamilyDescriptor cfd;
ASSERT_OK(cfh->GetDescriptor(&cfd));
ASSERT_STREQ(cfd.options.memtable_factory->Name(),
DummySkipListFactory::kClassName());
ColumnFamilyHandle* test = nullptr;
ASSERT_OK(dbfull()->CreateColumnFamily(options, "test", &test));
ASSERT_OK(test->GetDescriptor(&cfd));
ASSERT_STREQ(cfd.options.memtable_factory->Name(),
DummySkipListFactory::kClassName());
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
ASSERT_OK(dbfull()->SetOptions(cfh, {{"disable_auto_compactions", "true"}}));
ColumnFamilyDescriptor cfd;
ASSERT_OK(cfh->GetDescriptor(&cfd));
ASSERT_STREQ(cfd.options.memtable_factory->Name(),
DummySkipListFactory::kClassName());
ColumnFamilyHandle* test = nullptr;
ASSERT_OK(dbfull()->CreateColumnFamily(options, "test", &test));
ASSERT_OK(test->GetDescriptor(&cfd));
ASSERT_STREQ(cfd.options.memtable_factory->Name(),
DummySkipListFactory::kClassName());
ASSERT_OK(dbfull()->DropColumnFamily(test));
delete test;
}
ASSERT_OK(dbfull()->DropColumnFamily(test));
delete test;
}
TEST_F(DBOptionsTest, SetBytesPerSync) {
+3 -1
View File
@@ -377,6 +377,8 @@ TEST_F(DBPropertiesTest, AggregatedTableProperties) {
NewBloomFilterPolicy(kBloomBitsPerKey, false));
table_options.block_size = 1024;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
// The checks assume kTableCount number of files
options.disable_auto_compactions = true;
DestroyAndReopen(options);
@@ -567,7 +569,7 @@ TEST_F(DBPropertiesTest, AggregatedTablePropertiesAtLevel) {
options.target_file_size_base = 8192;
options.max_bytes_for_level_base = 10000;
options.max_bytes_for_level_multiplier = 2;
// This ensures there no compaction happening when we call GetProperty().
// The checks assume kTableCount number of files
options.disable_auto_compactions = true;
options.merge_operator.reset(new TestPutOperator());
+76
View File
@@ -160,6 +160,7 @@ TEST_F(DBSecondaryTest, NonExistingDb) {
TEST_F(DBSecondaryTest, ReopenAsSecondary) {
Options options;
options.env = env_;
options.preserve_internal_time_seconds = 300;
Reopen(options);
ASSERT_OK(Put("foo", "foo_value"));
ASSERT_OK(Put("bar", "bar_value"));
@@ -507,6 +508,81 @@ TEST_F(DBSecondaryTest, OpenAsSecondary) {
verify_db_func("new_foo_value", "new_bar_value");
}
TEST_F(DBSecondaryTest, OptionsOverrideTest) {
Options options;
options.env = env_;
options.preserve_internal_time_seconds = 300;
options.compaction_readahead_size = 200;
options.blob_compaction_readahead_size = 100;
Reopen(options);
for (int i = 0; i < 3; ++i) {
ASSERT_OK(Put("foo", "foo_value" + std::to_string(i)));
ASSERT_OK(Put("bar", "bar_value" + std::to_string(i)));
ASSERT_OK(Flush());
}
CompactionServiceInput input;
ColumnFamilyMetaData meta;
db_->GetColumnFamilyMetaData(&meta);
for (auto& file : meta.levels[0].files) {
ASSERT_EQ(0, meta.levels[0].level);
input.input_files.push_back(file.name);
}
ASSERT_EQ(input.input_files.size(), 3);
input.output_level = 1;
input.options_file_number = dbfull()->GetVersionSet()->options_file_number();
input.cf_name = kDefaultColumnFamilyName;
ASSERT_OK(db_->GetDbIdentity(input.db_id));
ASSERT_EQ(db_->GetOptions().compaction_readahead_size, 200);
ASSERT_EQ(db_->GetOptions().blob_compaction_readahead_size, 100);
Close();
std::string compaction_input_binary;
ASSERT_OK(input.Write(&compaction_input_binary));
std::string compaction_result_binary;
CompactionServiceOptionsOverride override_options;
override_options.env = env_;
override_options.table_factory.reset(
NewBlockBasedTableFactory(BlockBasedTableOptions()));
ASSERT_OK(
StringToMap("compaction_readahead_size=8388608;"
"blob_compaction_readahead_size=4194304;"
"some_invalid_option=ignore_me;"
"env=this_should_not_fail;"
"max_open_files=100;", // this should be always overriden as
// -1 in remote compaction
&override_options.options_map));
bool verified = false;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"DBImplSecondary::OpenAndCompact::AfterOpenAsSecondary:0",
[&](void* arg) {
auto secondary_db = static_cast<DB*>(arg);
auto secondary_db_options = secondary_db->GetOptions();
// DBOption
ASSERT_EQ(secondary_db_options.compaction_readahead_size, 8388608);
ASSERT_EQ(secondary_db_options.max_open_files, -1);
// CFOption
ASSERT_EQ(secondary_db_options.blob_compaction_readahead_size, 4194304);
verified = true;
});
SyncPoint::GetInstance()->EnableProcessing();
ASSERT_OK(DB::OpenAndCompact(OpenAndCompactOptions(), dbname_,
secondary_path_, compaction_input_binary,
&compaction_result_binary, override_options));
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
ASSERT_TRUE(verified);
}
namespace {
class TraceFileEnv : public EnvWrapper {
public:
+51 -1
View File
@@ -229,6 +229,56 @@ TEST_F(DBTablePropertiesTest, CreateOnDeletionCollectorFactory) {
ASSERT_EQ(0.5, del_factory->GetDeletionRatio());
}
TEST_F(DBTablePropertiesTest, GetPropertiesOfTablesByLevelTest) {
Random rnd(202);
Options options;
options.level_compaction_dynamic_level_bytes = false;
options.create_if_missing = true;
options.write_buffer_size = 4096;
options.max_write_buffer_number = 2;
options.level0_file_num_compaction_trigger = 2;
options.level0_slowdown_writes_trigger = 2;
options.level0_stop_writes_trigger = 2;
options.target_file_size_base = 2048;
options.max_bytes_for_level_base = 40960;
options.max_bytes_for_level_multiplier = 4;
options.hard_pending_compaction_bytes_limit = 16 * 1024;
options.num_levels = 8;
options.env = env_;
DestroyAndReopen(options);
// build a decent LSM
for (int i = 0; i < 10000; i++) {
EXPECT_OK(Put(test::RandomKey(&rnd, 5), rnd.RandomString(102)));
}
ASSERT_OK(Flush());
ASSERT_OK(dbfull()->TEST_WaitForCompact());
if (NumTableFilesAtLevel(0) == 0) {
EXPECT_OK(Put(test::RandomKey(&rnd, 5), rnd.RandomString(102)));
ASSERT_OK(Flush());
}
ASSERT_OK(db_->PauseBackgroundWork());
// Ensure that we have at least L0, L1 and L2
ASSERT_GT(NumTableFilesAtLevel(0), 0);
ASSERT_GT(NumTableFilesAtLevel(1), 0);
ASSERT_GT(NumTableFilesAtLevel(2), 0);
ColumnFamilyMetaData cf_meta;
db_->GetColumnFamilyMetaData(&cf_meta);
std::vector<std::unique_ptr<TablePropertiesCollection>> levels_props;
ASSERT_OK(db_->GetPropertiesOfTablesByLevel(db_->DefaultColumnFamily(),
&levels_props));
for (int i = 0; i < 8; i++) {
const std::unique_ptr<TablePropertiesCollection>& level_props =
levels_props[i];
ASSERT_EQ(level_props->size(), cf_meta.levels[i].files.size());
}
Close();
}
// Test params:
// 1) whether to enable user-defined timestamps
class DBTablePropertiesInRangeTest : public DBTestBase,
@@ -292,7 +342,7 @@ class DBTablePropertiesInRangeTest : public DBTestBase,
keys.reserve(range_size * 2);
for (auto& r : ranges) {
auto [start, limit] = MaybeAddTimestampsToRange(
&r.start, &r.limit, ts_sz, &keys.emplace_back(), &keys.emplace_back(),
r.start, r.limit, ts_sz, &keys.emplace_back(), &keys.emplace_back(),
/*exclusive_end=*/false);
EXPECT_TRUE(start.has_value());
EXPECT_TRUE(limit.has_value());
+135 -9
View File
@@ -59,11 +59,13 @@
#include "rocksdb/utilities/checkpoint.h"
#include "rocksdb/utilities/optimistic_transaction_db.h"
#include "rocksdb/utilities/write_batch_with_index.h"
#include "table/block_based/block_based_table_factory.h"
#include "table/mock_table.h"
#include "test_util/sync_point.h"
#include "test_util/testharness.h"
#include "test_util/testutil.h"
#include "util/compression.h"
#include "util/defer.h"
#include "util/mutexlock.h"
#include "util/random.h"
#include "util/rate_limiter_impl.h"
@@ -142,6 +144,103 @@ TEST_F(DBTest, MockEnvTest) {
delete db;
}
TEST_F(DBTest, RequestIdPlumbingTest) {
// test that request_id is passed to the filesystem, from
// ReadOptions to IODebugContext
Options options = CurrentOptions();
options.env = env_;
// Create a mock environment to capture IODebugContext during reads
const std::string* captured_request_id_dbg;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"RandomAccessFileReader::Read:IODebugContext", [&](void* arg) {
IODebugContext* dbg = static_cast<IODebugContext*>(arg);
if (dbg == nullptr) {
captured_request_id_dbg = nullptr;
} else {
captured_request_id_dbg = dbg->request_id;
}
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
ASSERT_OK(Put("k1", "v1"));
ASSERT_OK(Flush());
// test request_id plumbing during a get
{
const std::string test_request_id = "test_request_id_123";
ReadOptions read_opts;
read_opts.request_id = &test_request_id;
std::string value;
ASSERT_OK(db_->Get(read_opts, "k1", &value));
// Verify the request_id was propagated to the file system
ASSERT_NE(captured_request_id_dbg, nullptr);
ASSERT_EQ(*captured_request_id_dbg, test_request_id);
}
captured_request_id_dbg = nullptr;
// test request_id plumbing during iterator seek
ASSERT_OK(Put("k2", "v2"));
ASSERT_OK(Flush());
{
ReadOptions read_opts;
const std::string request_id = "test_request_id_456";
read_opts.request_id = &request_id;
std::unique_ptr<Iterator> iter(db_->NewIterator(read_opts));
iter->Seek("k2");
ASSERT_TRUE(iter->Valid());
// Verify the request_id was propagated to the file system
ASSERT_NE(captured_request_id_dbg, nullptr);
ASSERT_EQ(*captured_request_id_dbg, request_id);
}
// test request_id plumbing during multiget
captured_request_id_dbg = nullptr;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"RandomAccessFileReader::MultiRead:IODebugContext", [&](void* arg) {
IODebugContext* dbg = static_cast<IODebugContext*>(arg);
if (dbg == nullptr) {
captured_request_id_dbg = nullptr;
} else {
captured_request_id_dbg = dbg->request_id;
}
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
ASSERT_OK(Put("k3", "v3"));
ASSERT_OK(Put("k4", "v4"));
ASSERT_OK(Flush());
{
ReadOptions read_opts;
const std::string multiget_request_id = "test_request_id_789";
read_opts.request_id = &multiget_request_id;
std::vector<std::string> values;
std::vector<Slice> keys = {Slice("k3"), Slice("k4")};
values.resize(keys.size());
std::vector<ColumnFamilyHandle*> cfhs(keys.size(),
db_->DefaultColumnFamily());
db_->MultiGet(read_opts, cfhs, keys, &values);
ASSERT_NE(captured_request_id_dbg, nullptr);
ASSERT_EQ(*captured_request_id_dbg, multiget_request_id);
}
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
}
TEST_F(DBTest, MemEnvTest) {
std::unique_ptr<Env> env{NewMemEnv(Env::Default())};
Options options;
@@ -3180,6 +3279,15 @@ class ModelDB : public DB {
return Status();
}
using DB::GetPropertiesOfTablesByLevel;
Status GetPropertiesOfTablesByLevel(
ColumnFamilyHandle* /* column_family */,
std::vector<
std::unique_ptr<TablePropertiesCollection>>* /* props_by_level */)
override {
return Status();
}
using DB::KeyMayExist;
bool KeyMayExist(const ReadOptions& /*options*/,
ColumnFamilyHandle* /*column_family*/, const Slice& /*key*/,
@@ -3340,11 +3448,6 @@ class ModelDB : public DB {
using DB::NumberLevels;
int NumberLevels(ColumnFamilyHandle* /*column_family*/) override { return 1; }
using DB::MaxMemCompactionLevel;
int MaxMemCompactionLevel(ColumnFamilyHandle* /*column_family*/) override {
return 1;
}
using DB::Level0StopWriteTrigger;
int Level0StopWriteTrigger(ColumnFamilyHandle* /*column_family*/) override {
return -1;
@@ -3401,7 +3504,7 @@ class ModelDB : public DB {
}
Status GetCurrentWalFile(
std::unique_ptr<LogFile>* /*current_log_file*/) override {
std::unique_ptr<LogFile>* /*current_wal_file*/) override {
return Status::OK();
}
@@ -3440,6 +3543,11 @@ class ModelDB : public DB {
return Status::OK();
}
Status GetNewestUserDefinedTimestamp(
ColumnFamilyHandle* /*cf*/, std::string* /*newest_timestamp*/) override {
return Status::OK();
}
ColumnFamilyHandle* DefaultColumnFamily() const override { return nullptr; }
private:
@@ -5400,8 +5508,7 @@ TEST_F(DBTest, DynamicLevelCompressionPerLevel) {
for (const auto& file : cf_meta.levels[4].files) {
listener->SetExpectedFileName(dbname_ + file.name);
Slice start(file.smallestkey), limit(file.largestkey);
const RangePtr ranges(&start, &limit);
const RangeOpt ranges(file.smallestkey, file.largestkey);
// Given verification from above, we're guaranteed that by deleting all the
// files in [<smallestkey>, <largestkey>] range, we're effectively deleting
// that very single file and nothing more.
@@ -6084,6 +6191,11 @@ TEST_F(DBTest, L0L1L2AndUpHitCounter) {
}
TEST_F(DBTest, EncodeDecompressedBlockSizeTest) {
bool& allow_unsupported_fv =
BlockBasedTableFactory::AllowUnsupportedFormatVersion();
SaveAndRestore guard(&allow_unsupported_fv);
ASSERT_FALSE(allow_unsupported_fv);
// iter 0 -- zlib
// iter 1 -- bzip2
// iter 2 -- lz4
@@ -6106,7 +6218,16 @@ TEST_F(DBTest, EncodeDecompressedBlockSizeTest) {
table_options.format_version = first_table_version;
table_options.filter_policy.reset(NewBloomFilterPolicy(10));
Options options = CurrentOptions();
// Hack to generate old files (checked in factory construction)
allow_unsupported_fv = true;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
ASSERT_EQ(options.table_factory->GetOptions<BlockBasedTableOptions>()
->format_version,
first_table_version);
// Able to read old files without the hack
allow_unsupported_fv = false;
options.create_if_missing = true;
options.compression = comp;
DestroyAndReopen(options);
@@ -6118,9 +6239,14 @@ TEST_F(DBTest, EncodeDecompressedBlockSizeTest) {
// compressible string
ASSERT_OK(Put(Key(i), rnd.RandomString(128) + std::string(128, 'a')));
}
ASSERT_OK(Flush());
table_options.format_version = first_table_version == 1 ? 2 : 1;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
// format_version (for writing) is sanitized to minimum supported
ASSERT_EQ(options.table_factory->GetOptions<BlockBasedTableOptions>()
->format_version,
BlockBasedTableFactory::kMinSupportedFormatVersion);
Reopen(options);
for (int i = 0; i < kNumKeysWritten; ++i) {
auto r = Get(Key(i));
@@ -6389,7 +6515,7 @@ TEST_F(DBTest, TestLogCleanup) {
for (int i = 0; i < 100000; ++i) {
ASSERT_OK(Put(Key(i), "val"));
// only 2 memtables will be alive, so logs_to_free needs to always be below
// only 2 memtables will be alive, so wals_to_free needs to always be below
// 2
ASSERT_LT(dbfull()->TEST_LogsToFreeSize(), static_cast<size_t>(3));
}
+2 -4
View File
@@ -1722,16 +1722,14 @@ TEST_P(CompressionFailuresTest, CompressionFailures) {
});
} else if (compression_failure_type_ == kTestDecompressionFail) {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"UncompressBlockData:TamperWithReturnValue", [](void* arg) {
"DecompressBlockData:TamperWithReturnValue", [](void* arg) {
Status* ret = static_cast<Status*>(arg);
ASSERT_OK(*ret);
*ret = Status::Corruption("kTestDecompressionFail");
});
} else if (compression_failure_type_ == kTestDecompressionCorruption) {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"UncompressBlockData:"
"TamperWithDecompressionOutput",
[](void* arg) {
"DecompressBlockData:TamperWithDecompressionOutput", [](void* arg) {
BlockContents* contents = static_cast<BlockContents*>(arg);
// Ensure uncompressed data != original data
const size_t len = contents->data.size() + 1;
+12 -1
View File
@@ -11,6 +11,7 @@
#include "cache/cache_reservation_manager.h"
#include "db/forward_iterator.h"
#include "env/fs_readonly.h"
#include "env/mock_env.h"
#include "port/lang.h"
#include "rocksdb/cache.h"
@@ -591,7 +592,6 @@ Options DBTestBase::GetOptions(
options_override.level_compaction_dynamic_level_bytes;
options.env = env_;
options.create_if_missing = true;
options.fail_if_options_file_error = true;
return options;
}
@@ -716,6 +716,17 @@ Status DBTestBase::ReadOnlyReopen(const Options& options) {
return DB::OpenForReadOnly(options, dbname_, &db_);
}
Status DBTestBase::EnforcedReadOnlyReopen(const Options& options) {
Close();
Options options_copy = options;
MaybeInstallTimeElapseOnlySleep(options_copy);
auto fs_read_only =
std::make_shared<ReadOnlyFileSystem>(env_->GetFileSystem());
env_read_only_ = std::make_shared<CompositeEnvWrapper>(env_, fs_read_only);
options_copy.env = env_read_only_.get();
return DB::OpenForReadOnly(options_copy, dbname_, &db_);
}
Status DBTestBase::TryReopen(const Options& options) {
Close();
last_options_.table_factory.reset();
+4
View File
@@ -1062,6 +1062,7 @@ class DBTestBase : public testing::Test {
MockEnv* mem_env_;
Env* encrypted_env_;
SpecialEnv* env_;
std::shared_ptr<Env> env_read_only_;
std::shared_ptr<Env> env_guard_;
DB* db_;
std::vector<ColumnFamilyHandle*> handles_;
@@ -1178,6 +1179,9 @@ class DBTestBase : public testing::Test {
Status ReadOnlyReopen(const Options& options);
// With a filesystem wrapper that fails on attempted write
Status EnforcedReadOnlyReopen(const Options& options);
Status TryReopen(const Options& options);
bool IsDirectIOSupported();
+2 -2
View File
@@ -3024,13 +3024,13 @@ TEST_F(DBWALTest, GetCompressedWalsAfterSync) {
options.wal_compression = kZSTD;
DestroyAndReopen(options);
// Write something to memtable and WAL so that log_empty_ will be false after
// Write something to memtable and WAL so that wal_empty_ will be false after
// next DB::Open().
ASSERT_OK(Put("a", "v"));
Reopen(options);
// New WAL is created, thanks to !log_empty_.
// New WAL is created, thanks to !wal_empty_.
ASSERT_OK(dbfull()->TEST_SwitchWAL());
ASSERT_OK(Put("b", "v"));
+220 -39
View File
@@ -19,6 +19,13 @@
#include "utilities/merge_operators/string_append/stringappend2.h"
namespace ROCKSDB_NAMESPACE {
namespace {
std::string EncodeAsUint64(uint64_t v) {
std::string dst;
PutFixed64(&dst, v);
return dst;
}
} // namespace
class DBBasicTestWithTimestamp : public DBBasicTestWithTimestampBase {
public:
DBBasicTestWithTimestamp()
@@ -1480,13 +1487,24 @@ TEST_F(DBBasicTestWithTimestamp, ReseekToUserKeyBeforeSavedKey) {
Close();
}
TEST_F(DBBasicTestWithTimestamp,
FIXME_ReverseIterationWithBlobAndUnpreparedValue) {
class ReverseIterationWithUnpreparedBlobTest
: public DBBasicTestWithTimestampBase,
public testing::WithParamInterface<std::tuple<bool, uint64_t>> {
public:
ReverseIterationWithUnpreparedBlobTest()
: DBBasicTestWithTimestampBase(
"db_basic_test_with_timestamp_reverse_with_unprepare") {}
};
INSTANTIATE_TEST_CASE_P(ReverseIterationWithUnpreparedBlobTest,
ReverseIterationWithUnpreparedBlobTest,
::testing::Combine(::testing::Values(true, false),
::testing::Values(0, 2)));
TEST_P(ReverseIterationWithUnpreparedBlobTest, Basic) {
Options options = CurrentOptions();
options.create_if_missing = true;
options.env = env_;
options.enable_blob_files = true;
options.max_sequential_skip_in_iterations = 0;
options.max_sequential_skip_in_iterations = std::get<1>(GetParam());
const size_t kTimestampSize = Timestamp(0, 0).size();
TestComparator test_cmp(kTimestampSize);
@@ -1501,7 +1519,7 @@ TEST_F(DBBasicTestWithTimestamp,
for (uint64_t key = 0; key <= kMaxKey; ++key) {
for (size_t i = 0; i < write_timestamps.size(); ++i) {
ASSERT_OK(db_->Put(WriteOptions(), Key1(key), write_timestamps[i],
"value" + std::to_string(i)));
Key1(key) + "value" + std::to_string(i)));
}
}
@@ -1513,17 +1531,28 @@ TEST_F(DBBasicTestWithTimestamp,
ReadOptions read_opts;
read_opts.timestamp = &read_timestamp;
read_opts.allow_unprepared_value = true;
read_opts.allow_unprepared_value = std::get<0>(GetParam());
std::unique_ptr<Iterator> it(db_->NewIterator(read_opts));
it->SeekForPrev(Key1(kMaxKey));
ASSERT_TRUE(it->Valid());
ASSERT_OK(it->status());
uint64_t key = kMaxKey;
int count = 0;
while (it->Valid()) {
ASSERT_OK(it->status());
// FIXME: PrepareValue() should succeed and status() should remain OK
ASSERT_FALSE(it->PrepareValue());
ASSERT_TRUE(it->status().IsCorruption());
ASSERT_TRUE(it->PrepareValue());
ASSERT_TRUE(it->Valid());
ASSERT_OK(it->status());
ASSERT_EQ(it->key(), Key1(key));
ASSERT_EQ(it->timestamp(), Timestamp(3, 0));
ASSERT_EQ(it->value(), Key1(key) + "value" + std::to_string(1));
key--;
count++;
it->Prev();
}
ASSERT_OK(it->status());
ASSERT_EQ(kMaxKey + 1, count);
}
Close();
@@ -3746,17 +3775,42 @@ INSTANTIATE_TEST_CASE_P(
test::UserDefinedTimestampTestMode::kStripUserDefinedTimestamp,
test::UserDefinedTimestampTestMode::kNormal));
TEST_F(DBBasicTestWithTimestamp, EnableDisableUDT) {
// Test params:
// 1) whether to flush before close
class EnableDisableUDTTest : public DBBasicTestWithTimestampBase,
public testing::WithParamInterface<bool> {
public:
EnableDisableUDTTest()
: DBBasicTestWithTimestampBase("/enable_disable_udt") {}
};
INSTANTIATE_TEST_CASE_P(EnableDisableUDTTest, EnableDisableUDTTest,
::testing::Values(true, false));
TEST_P(EnableDisableUDTTest, Basic) {
Options options = CurrentOptions();
// Un-flushed data before close will involve a WAL replay on DB reopen.
bool flush_before_close = GetParam();
options.env = env_;
// Create a column family without user-defined timestamps.
options.comparator = BytewiseComparator();
options.persist_user_defined_timestamps = true;
DestroyAndReopen(options);
ReadOptions ropts;
std::string read_ts;
std::string value;
std::string key_ts;
// Create one SST file, its user keys have no user-defined timestamps.
ASSERT_OK(db_->Put(WriteOptions(), "foo", "val1"));
ASSERT_OK(Flush(0));
ASSERT_OK(db_->Put(WriteOptions(), "foo", "val0"));
ASSERT_OK(db_->Put(WriteOptions(), "bar", "val0"));
ASSERT_OK(db_->DeleteRange(WriteOptions(), "bar", "baz"));
ASSERT_OK(db_->Get(ReadOptions(), "foo", &value));
ASSERT_EQ("val0", value);
ASSERT_TRUE(db_->Get(ReadOptions(), "bar", &value).IsNotFound());
if (flush_before_close) {
ASSERT_OK(Flush(0));
}
Close();
// Reopen the existing column family and enable user-defined timestamps
@@ -3765,47 +3819,63 @@ TEST_F(DBBasicTestWithTimestamp, EnableDisableUDT) {
options.persist_user_defined_timestamps = false;
options.allow_concurrent_memtable_write = false;
Reopen(options);
std::string value;
ASSERT_TRUE(db_->Get(ReadOptions(), "foo", &value).IsInvalidArgument());
std::string read_ts;
PutFixed64(&read_ts, 0);
ReadOptions ropts;
// Read data from previous session before and after compaction.
read_ts = EncodeAsUint64(1);
Slice read_ts_slice = read_ts;
ropts.timestamp = &read_ts_slice;
std::string key_ts;
// Entries in pre-existing SST files are treated as if they have minimum
// user-defined timestamps.
ASSERT_OK(db_->Get(ropts, "foo", &value, &key_ts));
ASSERT_EQ("val1", value);
ASSERT_EQ(read_ts, key_ts);
for (int i = 0; i < 2; i++) {
ASSERT_TRUE(db_->Get(ReadOptions(), "foo", &value).IsInvalidArgument());
// Entries in pre-existing SST files are treated as if they have minimum
// user-defined timestamps.
ASSERT_OK(db_->Get(ropts, "foo", &value, &key_ts));
ASSERT_EQ("val0", value);
ASSERT_EQ(EncodeAsUint64(0), key_ts);
ASSERT_TRUE(db_->Get(ropts, "bar", &value, &key_ts).IsNotFound());
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
}
// Do timestamped read / write.
std::string write_ts;
PutFixed64(&write_ts, 1);
ASSERT_OK(db_->Put(WriteOptions(), "foo", write_ts, "val2"));
read_ts.clear();
PutFixed64(&read_ts, 1);
ASSERT_OK(db_->Put(WriteOptions(), "foo", EncodeAsUint64(1), "val1"));
ASSERT_OK(db_->Put(WriteOptions(), "bar", EncodeAsUint64(1), "val1"));
ASSERT_OK(db_->DeleteRange(WriteOptions(), "bar", "baz", EncodeAsUint64(2)));
ASSERT_OK(db_->Get(ropts, "foo", &value, &key_ts));
ASSERT_EQ("val2", value);
ASSERT_EQ(write_ts, key_ts);
ASSERT_EQ("val1", value);
ASSERT_EQ(EncodeAsUint64(1), key_ts);
ASSERT_OK(db_->Get(ropts, "bar", &value, &key_ts));
ASSERT_EQ("val1", value);
ASSERT_EQ(EncodeAsUint64(1), key_ts);
read_ts = EncodeAsUint64(2);
ASSERT_TRUE(db_->Get(ropts, "bar", &value, &key_ts).IsNotFound());
// The user keys in this SST file don't have user-defined timestamps either,
// because `persist_user_defined_timestamps` flag is set to false.
ASSERT_OK(Flush(0));
if (flush_before_close) {
ASSERT_OK(Flush(0));
}
Close();
// Reopen the existing column family while disabling user-defined timestamps.
options.comparator = BytewiseComparator();
Reopen(options);
ASSERT_TRUE(db_->Get(ropts, "foo", &value).IsInvalidArgument());
ASSERT_OK(db_->Get(ReadOptions(), "foo", &value));
ASSERT_EQ("val2", value);
// Read data from previous session before and after compaction.
for (int i = 0; i < 2; i++) {
ASSERT_TRUE(db_->Get(ropts, "foo", &value).IsInvalidArgument());
ASSERT_OK(db_->Get(ReadOptions(), "foo", &value));
ASSERT_EQ("val1", value);
ASSERT_TRUE(db_->Get(ReadOptions(), "bar", &value).IsNotFound());
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
}
// Continue to write / read the column family without user-defined timestamps.
ASSERT_OK(db_->Put(WriteOptions(), "foo", "val3"));
ASSERT_OK(db_->Put(WriteOptions(), "foo", "val2"));
ASSERT_OK(db_->Put(WriteOptions(), "bar", "val2"));
ASSERT_OK(db_->DeleteRange(WriteOptions(), "bar", "baz"));
ASSERT_OK(db_->Get(ReadOptions(), "foo", &value));
ASSERT_EQ("val3", value);
ASSERT_EQ("val2", value);
ASSERT_TRUE(db_->Get(ReadOptions(), "bar", &value).IsNotFound());
if (flush_before_close) {
ASSERT_OK(Flush(0));
}
Close();
}
@@ -4844,6 +4914,117 @@ TEST_F(DBBasicTestWithTimestamp, TimestampFilterTableReadOnGet) {
Close();
}
class GetNewestUserDefinedTimestampTest : public DBBasicTestWithTimestampBase {
public:
explicit GetNewestUserDefinedTimestampTest()
: DBBasicTestWithTimestampBase("get_newest_udt_test") {}
};
TEST_F(GetNewestUserDefinedTimestampTest, Basic) {
std::string newest_timestamp;
// UDT disabled, get InvalidArgument.
ASSERT_TRUE(db_->GetNewestUserDefinedTimestamp(nullptr, &newest_timestamp)
.IsInvalidArgument());
Options options = CurrentOptions();
options.env = env_;
options.create_if_missing = true;
options.max_write_buffer_number = 5;
options.min_write_buffer_number_to_merge = 4;
options.comparator = test::BytewiseComparatorWithU64TsWrapper();
DestroyAndReopen(options);
// UDT persisted, get NotSupported.
ASSERT_TRUE(db_->GetNewestUserDefinedTimestamp(nullptr, &newest_timestamp)
.IsNotSupported());
options.persist_user_defined_timestamps = false;
options.allow_concurrent_memtable_write = false;
DestroyAndReopen(options);
ASSERT_TRUE(
db_->GetNewestUserDefinedTimestamp(nullptr, nullptr).IsInvalidArgument());
ColumnFamilyHandleImpl* cfh = static_cast_with_check<ColumnFamilyHandleImpl>(
db_->DefaultColumnFamily());
ColumnFamilyData* cfd = cfh->cfd();
// The column family hasn't seen any user defined timestamp
ASSERT_OK(db_->GetNewestUserDefinedTimestamp(nullptr, &newest_timestamp));
ASSERT_TRUE(newest_timestamp.empty());
ASSERT_OK(db_->Put(WriteOptions(), Key(1), EncodeAsUint64(1), "val1"));
// Testing get newest timestamp from mutable memtable.
ASSERT_OK(db_->GetNewestUserDefinedTimestamp(nullptr, &newest_timestamp));
ASSERT_EQ(EncodeAsUint64(1), newest_timestamp);
ASSERT_OK(db_->Put(WriteOptions(), Key(1), EncodeAsUint64(2), "val2"));
ASSERT_OK(dbfull()->TEST_SwitchMemtable(cfd));
// Testing get the newest timestamp from immutable memtable because the
// mutable one is empty.
ASSERT_OK(db_->GetNewestUserDefinedTimestamp(nullptr, &newest_timestamp));
ASSERT_EQ(EncodeAsUint64(2), newest_timestamp);
ASSERT_OK(db_->Put(WriteOptions(), Key(1), EncodeAsUint64(3), "val3"));
ASSERT_OK(db_->Put(WriteOptions(), Key(1), EncodeAsUint64(4), "val4"));
ASSERT_OK(dbfull()->TEST_SwitchMemtable(cfd));
// Testing get the newest timestamp from the more recent immutable memtable
// when there are multiple immutable memtables.
ASSERT_OK(db_->GetNewestUserDefinedTimestamp(nullptr, &newest_timestamp));
ASSERT_EQ(EncodeAsUint64(4), newest_timestamp);
ASSERT_OK(db_->Put(WriteOptions(), Key(1), EncodeAsUint64(5), "val5"));
// Testing get newest timestamp from mutable memtable when it has data, in the
// presence of immutable memtables.
ASSERT_OK(db_->GetNewestUserDefinedTimestamp(nullptr, &newest_timestamp));
ASSERT_EQ(EncodeAsUint64(5), newest_timestamp);
ASSERT_OK(Flush());
// After flushing and all the user defined timestamp are flushed. User defined
// timestamp info for SST files is available from MANIFEST.
ASSERT_OK(db_->GetNewestUserDefinedTimestamp(nullptr, &newest_timestamp));
ASSERT_EQ(EncodeAsUint64(5), newest_timestamp);
Reopen(options);
// Similar after flush, when there is no memtables, but some SST files,
// if MANIFEST records the upperbound of flushed timestamps because timestamps
// are not persisted in SST files, this info can be found.
ASSERT_OK(db_->GetNewestUserDefinedTimestamp(nullptr, &newest_timestamp));
ASSERT_EQ(EncodeAsUint64(5), newest_timestamp);
Close();
}
TEST_F(GetNewestUserDefinedTimestampTest, ConcurrentWrites) {
Options options = CurrentOptions();
options.create_if_missing = true;
options.comparator = test::BytewiseComparatorWithU64TsWrapper();
options.persist_user_defined_timestamps = false;
options.allow_concurrent_memtable_write = false;
DestroyAndReopen(options);
std::vector<std::thread> threads;
threads.reserve(10);
std::atomic<uint64_t> current_ts{0};
for (int i = 0; i < 10; i++) {
threads.emplace_back([this, i, &current_ts]() {
if (i % 2 == 0) {
std::string newest_timestamp;
ASSERT_OK(
db_->GetNewestUserDefinedTimestamp(nullptr, &newest_timestamp));
} else {
uint64_t write_ts = current_ts.fetch_add(1);
ASSERT_OK(db_->Put(WriteOptions(), Key(1), EncodeAsUint64(write_ts),
"val" + std::to_string(i)));
}
});
}
for (auto& t : threads) {
t.join();
}
Close();
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+75 -1
View File
@@ -987,7 +987,7 @@ TEST_P(DBWriteTest, RecycleLogToggleTest) {
options.recycle_log_file_num = 1;
Reopen(options);
// 1.log is added to alive_log_files_
// 1.log is added to alive_wal_files_
ASSERT_OK(Put(Key(2), "val1"));
ASSERT_OK(Flush());
// 1.log should be deleted and not recycled, since it
@@ -1000,6 +1000,80 @@ TEST_P(DBWriteTest, RecycleLogToggleTest) {
ASSERT_EQ(Get(Key(1)), "val2");
}
TEST_P(DBWriteTest, IngestWriteBatchWithIndex) {
if (GetParam() == kPipelinedWrite) {
return;
}
Options options = GetOptions();
options.disable_auto_compactions = true;
Reopen(options);
Options cf_options = GetOptions();
cf_options.merge_operator = MergeOperators::CreateStringAppendOperator();
CreateColumnFamilies({"cf1", "cf2"}, cf_options);
ReopenWithColumnFamilies({"default", "cf1", "cf2"},
{options, cf_options, cf_options});
// default cf
auto wbwi1 = std::make_shared<WriteBatchWithIndex>(options.comparator, 0,
/*overwrite_key=*/true);
ASSERT_OK(wbwi1->Put("key1", "value1"));
ASSERT_OK(wbwi1->Put("key2", "value2"));
if (GetParam() == kPipelinedWrite) {
ASSERT_TRUE(db_->IngestWriteBatchWithIndex({}, wbwi1).IsNotSupported());
return;
}
// Test disableWAL=false
ASSERT_TRUE(db_->IngestWriteBatchWithIndex({}, wbwi1).IsNotSupported());
WriteOptions wo;
wo.disableWAL = true;
ASSERT_OK(db_->IngestWriteBatchWithIndex(wo, wbwi1));
ASSERT_EQ("value1", Get("key1"));
ASSERT_EQ("value2", Get("key2"));
// Test with overwrites
auto wbwi = std::make_shared<WriteBatchWithIndex>(options.comparator, 0,
/*overwrite_key=*/true);
ASSERT_OK(wbwi->Put("key2", "value3"));
ASSERT_OK(wbwi->Delete("key1")); // Delete an existing key
ASSERT_OK(db_->IngestWriteBatchWithIndex(wo, wbwi));
ASSERT_EQ("NOT_FOUND", Get("key1"));
ASSERT_EQ("value3", Get("key2"));
auto wbwi2 = std::make_shared<WriteBatchWithIndex>(options.comparator, 0,
/*overwrite_key=*/true);
ASSERT_OK(wbwi2->Put(handles_[1], "cf1_key1", "cf1_value1"));
ASSERT_OK(wbwi2->Delete(handles_[1], "cf1_key2"));
// Test ingestion with column family
ASSERT_OK(db_->IngestWriteBatchWithIndex(wo, wbwi2));
ASSERT_EQ("cf1_value1", Get(1, "cf1_key1"));
ASSERT_EQ("NOT_FOUND", Get(1, "cf1_key2"));
auto wbwi3 = std::make_shared<WriteBatchWithIndex>(options.comparator, 0,
/*overwrite_key=*/true);
ASSERT_OK(wbwi3->Merge(handles_[2], "cf2_key1", "cf2_value1"));
ASSERT_OK(wbwi3->Merge(handles_[2], "cf2_key1", "cf2_value2"));
// Test ingestion with merge operations
ASSERT_OK(db_->IngestWriteBatchWithIndex(wo, wbwi3));
ASSERT_EQ("cf2_value1,cf2_value2", Get(2, "cf2_key1"));
// Test with overwrite_key = false
auto wbwi_no_overwrite = std::make_shared<WriteBatchWithIndex>(
options.comparator, 0, /*overwrite_key=*/false);
ASSERT_OK(wbwi_no_overwrite->Put("key1", "value1"));
Status s = db_->IngestWriteBatchWithIndex(wo, wbwi_no_overwrite);
ASSERT_TRUE(s.IsNotSupported());
auto empty_wbwi = std::make_shared<WriteBatchWithIndex>(
options.comparator, 0, /*overwrite_key=*/true);
ASSERT_OK(db_->IngestWriteBatchWithIndex(wo, empty_wbwi));
DestroyAndReopen(options);
// Should fail when trying to ingest to non-existent column family
ASSERT_NOK(db_->IngestWriteBatchWithIndex(wo, wbwi2));
}
INSTANTIATE_TEST_CASE_P(DBWriteTestInstance, DBWriteTest,
testing::Values(DBTestBase::kDefault,
DBTestBase::kConcurrentWALWrites,
+8 -6
View File
@@ -83,6 +83,8 @@ extern const ValueType kValueTypeForSeekForPrev;
// A range of user keys used internally by RocksDB. Also see `Range` used by
// public APIs.
// TODO: merge with Range in pubic API, but this is generally inclusive limit
// and it is maybe exclusive limit
struct UserKeyRange {
// In case of user_defined timestamp, if enabled, `start` and `limit` should
// include user_defined timestamps.
@@ -93,18 +95,17 @@ struct UserKeyRange {
UserKeyRange(const Slice& s, const Slice& l) : start(s), limit(l) {}
};
// A range of user keys used internally by RocksDB. Also see `RangePtr` used by
// A range of user keys used internally by RocksDB. Also see `RangeOpt` used by
// public APIs.
struct UserKeyRangePtr {
struct UserKeyRangeOpt {
// In case of user_defined timestamp, if enabled, `start` and `limit` should
// point to key with timestamp part.
// An optional range start, if missing, indicating a start before all keys.
std::optional<Slice> start;
OptSlice start;
// An optional range end, if missing, indicating an end after all keys.
std::optional<Slice> limit;
OptSlice limit;
UserKeyRangePtr(const std::optional<Slice>& s, const std::optional<Slice>& l)
: start(s), limit(l) {}
UserKeyRangeOpt(const OptSlice& s, const OptSlice& l) : start(s), limit(l) {}
};
// Checks whether a type is an inline value type
@@ -469,6 +470,7 @@ class InternalKey {
Slice user_key() const { return ExtractUserKey(rep_); }
size_t size() const { return rep_.size(); }
bool unset() const { return rep_.empty(); }
void Set(const Slice& _user_key, SequenceNumber s, ValueType t) {
SetFrom(ParsedInternalKey(_user_key, s, t));
+10 -4
View File
@@ -275,9 +275,6 @@ void ErrorHandler::HandleKnownErrors(const Status& bg_err,
return;
}
ROCKS_LOG_INFO(db_options_.info_log,
"ErrorHandler: Set regular background error\n");
bool paranoid = db_options_.paranoid_checks;
Status::Severity sev = Status::Severity::kFatalError;
Status new_bg_err;
@@ -335,12 +332,21 @@ void ErrorHandler::HandleKnownErrors(const Status& bg_err,
if (!s.ok() && (s.severity() > bg_error_.severity())) {
bg_error_ = s;
} else {
ROCKS_LOG_INFO(db_options_.info_log,
"ErrorHandler: Hit less severe background error\n");
// This error is less severe than previously encountered error. Don't
// take any further action
return;
}
}
bool stop = bg_error_.severity() >= Status::Severity::kHardError;
ROCKS_LOG_INFO(
db_options_.info_log,
"ErrorHandler: Set regular background error, auto_recovery=%d, stop=%d\n",
int{auto_recovery}, int{stop});
recover_context_ = context;
if (auto_recovery) {
recovery_in_prog_ = true;
@@ -351,7 +357,7 @@ void ErrorHandler::HandleKnownErrors(const Status& bg_err,
RecoverFromNoSpace();
}
}
if (bg_error_.severity() >= Status::Severity::kHardError) {
if (stop) {
is_db_stopped_.store(true, std::memory_order_release);
}
}
+2 -1
View File
@@ -57,7 +57,8 @@ Status GetFileChecksumsFromCurrentManifest(FileSystem* fs,
}
assert(checksum_list);
const ReadOptions read_options(Env::IOActivity::kReadManifest);
const ReadOptions read_options(
Env::IOActivity::kGetFileChecksumsFromCurrentManifest);
checksum_list->reset();
std::unique_ptr<SequentialFileReader> file_reader;
+308 -58
View File
@@ -260,55 +260,6 @@ TEST_F(ExternalSSTFileBasicTest, Basic) {
s = sst_file_writer.DeleteRange(Key(100), Key(200));
ASSERT_NOK(s) << s.ToString();
DestroyAndReopen(options);
SyncPoint::GetInstance()->LoadDependency({
{"DBImpl::IngestExternalFile:AfterIncIngestFileCounter",
"ExternalSSTFileBasicTest.LiveWriteStart"},
{"WriteThread::JoinBatchGroup:Wait",
"DBImpl::IngestExternalFile:AfterIncIngestFileCounter:2"},
});
SyncPoint::GetInstance()->EnableProcessing();
PerfContext* write_thread_perf_context;
std::thread write_thread([&] {
TEST_SYNC_POINT("ExternalSSTFileBasicTest.LiveWriteStart");
SetPerfLevel(kEnableWait);
write_thread_perf_context = get_perf_context();
write_thread_perf_context->Reset();
ASSERT_OK(db_->Put(WriteOptions(), "bar", "v2"));
ASSERT_GT(write_thread_perf_context->write_thread_wait_nanos, 0);
// Test sync points were used to make sure this live write enter write
// thread after the file ingestion entered write thread. So by the time this
// live write finishes, the latest seqno is 1 means file ingestion used
// seqno 0.
ASSERT_EQ(db_->GetLatestSequenceNumber(), 1U);
});
// Add file using file path
SetPerfLevel(kEnableTimeExceptForMutex);
PerfContext* perf_ctx = get_perf_context();
perf_ctx->Reset();
s = DeprecatedAddFile({file1});
ASSERT_GT(perf_context.file_ingestion_nanos, 0);
ASSERT_GT(perf_context.file_ingestion_blocking_live_writes_nanos, 0);
ASSERT_OK(s) << s.ToString();
for (int k = 0; k < 100; k++) {
ASSERT_EQ(Get(Key(k)), Key(k) + "_val");
}
write_thread.join();
SyncPoint::GetInstance()->DisableProcessing();
// Re-ingest the file just to check the perf context not enabled at and below
// kEnableWait.
SetPerfLevel(kEnableWait);
perf_ctx->Reset();
IngestExternalFileOptions opts;
opts.allow_global_seqno = true;
opts.allow_blocking_flush = true;
ASSERT_OK(db_->IngestExternalFile({file1}, opts));
ASSERT_EQ(perf_context.file_ingestion_nanos, 0);
ASSERT_EQ(perf_context.file_ingestion_blocking_live_writes_nanos, 0);
DestroyAndRecreateExternalSSTFilesDir();
}
@@ -1954,21 +1905,44 @@ TEST_F(ExternalSSTFileBasicTest, OverlappingFiles) {
SstFileWriter sst_file_writer(EnvOptions(), options);
std::string file3 = sst_files_dir_ + "file3.sst";
ASSERT_OK(sst_file_writer.Open(file3));
ASSERT_OK(sst_file_writer.Put("j", "j1"));
ASSERT_OK(sst_file_writer.Put("k", "k1"));
ASSERT_OK(sst_file_writer.Put("m", "m1"));
ExternalSstFileInfo file3_info;
ASSERT_OK(sst_file_writer.Finish(&file3_info));
files.push_back(std::move(file3));
}
// This could be ingested to the same level as file3 and file4, but the
// greedy/simple overlap check relegates it to a later level
{
SstFileWriter sst_file_writer(EnvOptions(), options);
std::string file4 = sst_files_dir_ + "file4.sst";
ASSERT_OK(sst_file_writer.Open(file4));
ASSERT_OK(sst_file_writer.Put("j", "j1"));
ExternalSstFileInfo file4_info;
ASSERT_OK(sst_file_writer.Finish(&file4_info));
files.push_back(std::move(file4));
}
{
SstFileWriter sst_file_writer(EnvOptions(), options);
std::string file5 = sst_files_dir_ + "file5.sst";
ASSERT_OK(sst_file_writer.Open(file5));
ASSERT_OK(sst_file_writer.Put("i", "i3"));
ExternalSstFileInfo file5_info;
ASSERT_OK(sst_file_writer.Finish(&file5_info));
files.push_back(std::move(file5));
}
IngestExternalFileOptions ifo;
ifo.allow_global_seqno = false;
ASSERT_NOK(db_->IngestExternalFile(files, ifo));
ifo.allow_global_seqno = true;
ASSERT_OK(db_->IngestExternalFile(files, ifo));
ASSERT_EQ(Get("a"), "a1");
ASSERT_EQ(Get("i"), "i2");
ASSERT_EQ(Get("i"), "i3");
ASSERT_EQ(Get("j"), "j1");
ASSERT_EQ(Get("k"), "k1");
ASSERT_EQ(Get("m"), "m1");
int total_keys = 0;
@@ -1979,10 +1953,11 @@ TEST_F(ExternalSSTFileBasicTest, OverlappingFiles) {
}
ASSERT_OK(iter->status());
delete iter;
ASSERT_EQ(total_keys, 4);
ASSERT_EQ(total_keys, 5);
ASSERT_EQ(1, NumTableFilesAtLevel(6));
ASSERT_EQ(2, NumTableFilesAtLevel(5));
ASSERT_EQ(2, NumTableFilesAtLevel(4));
}
class CompactionJobStatsCheckerForFilteredFiles : public EventListener {
@@ -2669,6 +2644,81 @@ TEST_F(ExternalSSTFileBasicTest, IngestWithTemperature) {
}
}
// This tests an internal user's exact usage and expectation of the
// IngestExternalFiles APIs to bulk load and replace files.
TEST_F(ExternalSSTFileBasicTest,
AtomicReplaceColumnFamilyWithIngestedVersionKey) {
Options options = GetDefaultOptions();
options.create_if_missing = true;
options.compaction_style = CompactionStyle::kCompactionStyleUniversal;
options.num_levels = 7;
options.disallow_memtable_writes = false;
DestroyAndReopen(options);
SstFileWriter sst_file_writer(EnvOptions(), options);
std::string data_file_original = sst_files_dir_ + "data_original";
ASSERT_OK(sst_file_writer.Open(data_file_original));
ASSERT_OK(sst_file_writer.Put("ukey1", "uval1_orig"));
ASSERT_OK(sst_file_writer.Put("ukey2", "uval2_orig"));
ASSERT_OK(sst_file_writer.Finish());
ASSERT_OK(db_->IngestExternalFile(db_->DefaultColumnFamily(),
{data_file_original},
IngestExternalFileOptions()));
ASSERT_OK(Put("data_version", "v_original"));
ASSERT_OK(Flush());
std::string value;
ASSERT_OK(db_->Get(ReadOptions(), "data_version", &value));
ASSERT_EQ(value, "v_original");
ASSERT_OK(db_->Get(ReadOptions(), "ukey1", &value));
ASSERT_EQ(value, "uval1_orig");
ASSERT_OK(db_->Get(ReadOptions(), "ukey2", &value));
ASSERT_EQ(value, "uval2_orig");
// Set up a 1) data version key file on L0, and 2) a user data file on L6
// to test the initial transitioning to use `atomic_replace_range`.
ASSERT_EQ("1,0,0,0,0,0,1", FilesPerLevel());
// Test multiple cycles of replacing by atomically ingest a data file and a
// version key file while replace the whole range in the column family.
for (int i = 0; i < 10; i++) {
std::string version_file_path =
sst_files_dir_ + "version" + std::to_string(i);
ASSERT_OK(sst_file_writer.Open(version_file_path));
ASSERT_OK(sst_file_writer.Put("data_version", "v" + std::to_string(i)));
ASSERT_OK(sst_file_writer.Finish());
std::string file_path = sst_files_dir_ + std::to_string(i);
ASSERT_OK(sst_file_writer.Open(file_path));
ASSERT_OK(sst_file_writer.Put("ukey1", "uval1" + std::to_string(i)));
ASSERT_OK(sst_file_writer.Put("ukey2", "uval2" + std::to_string(i)));
ASSERT_OK(sst_file_writer.Finish());
IngestExternalFileArg arg;
arg.column_family = db_->DefaultColumnFamily();
arg.external_files = {version_file_path, file_path};
arg.atomic_replace_range = {{nullptr, nullptr}};
// Test both fail_if_not_bottomost_level: true and false
arg.options.fail_if_not_bottommost_level = i % 2 == 0;
arg.options.snapshot_consistency = false;
// Ingest 1) a new data version file and 2) a new user data file while erase
// the whole column family
Status s = db_->IngestExternalFiles({arg});
ASSERT_OK(s);
// Check ingestion result and the expected LSM shape:
// Two files on L6, 1) a data version file 2) a user data file.
ASSERT_OK(db_->Get(ReadOptions(), "ukey1", &value));
ASSERT_EQ(value, "uval1" + std::to_string(i));
ASSERT_OK(db_->Get(ReadOptions(), "ukey2", &value));
ASSERT_EQ(value, "uval2" + std::to_string(i));
ASSERT_OK(db_->Get(ReadOptions(), "data_version", &value));
ASSERT_EQ(value, "v" + std::to_string(i));
ASSERT_EQ("0,0,0,0,0,0,2", FilesPerLevel());
}
Close();
}
TEST_F(ExternalSSTFileBasicTest, FailIfNotBottommostLevelAndDisallowMemtable) {
for (bool disallow_memtable : {false, true}) {
Options options = GetDefaultOptions();
@@ -2686,10 +2736,11 @@ TEST_F(ExternalSSTFileBasicTest, FailIfNotBottommostLevelAndDisallowMemtable) {
// Ingest with snapshot consistency
std::string file_path = sst_files_dir_ + std::to_string(1);
std::string file_path2 = sst_files_dir_ + std::to_string(2);
SstFileWriter sfw(EnvOptions(), options);
ASSERT_OK(sfw.Open(file_path));
ASSERT_OK(sfw.Put("b", "dontcare"));
ASSERT_OK(sfw.Put("b", "0"));
ASSERT_OK(sfw.Finish());
{
@@ -2700,6 +2751,7 @@ TEST_F(ExternalSSTFileBasicTest, FailIfNotBottommostLevelAndDisallowMemtable) {
ifo.snapshot_consistency = true;
ASSERT_OK(db_->IngestExternalFile(handles_[0], {file_path}, ifo));
}
ASSERT_EQ(Get(0, "b"), "0");
// Test level compaction
options.compaction_style = CompactionStyle::kCompactionStyleLevel;
@@ -2721,8 +2773,6 @@ TEST_F(ExternalSSTFileBasicTest, FailIfNotBottommostLevelAndDisallowMemtable) {
EXPECT_EQ(Put(1, "a", "1").code(), Status::Code::kInvalidArgument);
// Use ingestion to get to the same state as above
std::string file_path2 = sst_files_dir_ + std::to_string(2);
ASSERT_OK(sfw.Open(file_path2));
ASSERT_OK(sfw.Put("a", "1"));
ASSERT_OK(sfw.Put("c", "3"));
@@ -2735,16 +2785,216 @@ TEST_F(ExternalSSTFileBasicTest, FailIfNotBottommostLevelAndDisallowMemtable) {
ASSERT_OK(sfw.Finish());
ASSERT_OK(db_->IngestExternalFile(handles_[1], {file_path2}, {}));
}
ASSERT_EQ(Get(1, "a"), "1");
ASSERT_EQ(Get(1, "b"), "2");
ASSERT_EQ(Get(1, "c"), "3");
ASSERT_EQ(Get(1, "d"), "4");
{
// Test fail_if_not_bottommost_level, which fails if there's any overlap
// anywhere, even with snapshot_consistency=false
IngestExternalFileOptions ifo;
ASSERT_FALSE(ifo.fail_if_not_bottommost_level);
ifo.fail_if_not_bottommost_level = true;
ifo.snapshot_consistency = false;
// Fails with overlap on earlier level
Status s = db_->IngestExternalFile(handles_[1], {file_path}, ifo);
ASSERT_EQ(s.code(), Status::Code::kTryAgain);
CompactRangeOptions cro;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
ASSERT_OK(db_->CompactRange(cro, handles_[1], nullptr, nullptr));
// Fails with overlap on last level
s = db_->IngestExternalFile(handles_[1], {file_path}, ifo);
ASSERT_EQ(s.code(), Status::Code::kTryAgain);
// No change to data
ASSERT_EQ(Get(1, "a"), "1");
ASSERT_EQ(Get(1, "b"), "2");
ASSERT_EQ(Get(1, "c"), "3");
ASSERT_EQ(Get(1, "d"), "4");
}
if (!disallow_memtable) {
// Test allow_blocking_flush=false (fail because of memtable overlap)
IngestExternalFileOptions ifo;
ifo.fail_if_not_bottommost_level = true;
const Status s = db_->IngestExternalFile(handles_[1], {file_path}, ifo);
ASSERT_TRUE(s.IsTryAgain());
ASSERT_TRUE(ifo.allow_blocking_flush);
ifo.allow_blocking_flush = false;
ASSERT_OK(Put(1, "b", "42"));
Status s = db_->IngestExternalFile(handles_[1], {file_path}, ifo);
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
ASSERT_EQ(Get(1, "a"), "1");
ASSERT_EQ(Get(1, "b"), "42");
ASSERT_EQ(Get(1, "c"), "3");
ASSERT_EQ(Get(1, "d"), "4");
// Revert state
ASSERT_OK(Put(1, "b", "2"));
ASSERT_OK(Flush(1));
}
{
// Test atomic_replace_range
IngestExternalFileArg arg;
arg.column_family = handles_[1];
arg.external_files = {file_path};
arg.atomic_replace_range = {{"a", "zzz"}};
// start with some failure cases
// TODO: support snapshot consistency with tombstone file
ASSERT_TRUE(arg.options.snapshot_consistency);
Status s = db_->IngestExternalFiles({arg});
ASSERT_EQ(s.code(), Status::Code::kNotSupported);
ASSERT_EQ(Get(1, "a"), "1");
ASSERT_EQ(Get(1, "b"), "2");
ASSERT_EQ(Get(1, "c"), "3");
ASSERT_EQ(Get(1, "d"), "4");
arg.options.snapshot_consistency = false;
// Can usually be used with atomic_replace_range and
// snapshot_consistency=false, except it requires no input overlap
arg.options.fail_if_not_bottommost_level = true;
// one-sided ranges not yet supported
arg.atomic_replace_range = {{{}, "zzz"}};
s = db_->IngestExternalFiles({arg});
ASSERT_EQ(s.code(), Status::Code::kNotSupported);
arg.atomic_replace_range = {{"a", {}}};
s = db_->IngestExternalFiles({arg});
ASSERT_EQ(s.code(), Status::Code::kNotSupported);
// rejected because doesn't cover ingested file
arg.atomic_replace_range = {{"x", "z"}};
s = db_->IngestExternalFiles({arg});
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
// rejected because of partial file overlap
arg.atomic_replace_range = {{"a", "c"}};
s = db_->IngestExternalFiles({arg});
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
if (!disallow_memtable) {
// memtable overlap with replace range
ASSERT_OK(Put(1, "e", "5"));
arg.options.allow_blocking_flush = false;
// rejected because of memtable overlap
arg.atomic_replace_range = {{"a", "z"}};
s = db_->IngestExternalFiles({arg});
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
// rejected because of memtable overlap
arg.atomic_replace_range = {{nullptr, nullptr}};
s = db_->IngestExternalFiles({arg});
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
// FIXME: upper bound should be exclusive (DeleteRange semantics).
// currently rejected because of documented bug
arg.atomic_replace_range = {{"a", "e"}};
s = db_->IngestExternalFiles({arg});
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
// work-around ensuring no memtable overlap
arg.atomic_replace_range = {{"a", "d2"}};
ASSERT_OK(db_->IngestExternalFiles({arg}));
ASSERT_EQ(Get(1, "e"), "5");
} else {
// rejected because of partial file overlap
arg.atomic_replace_range = {{"b", "z"}};
s = db_->IngestExternalFiles({arg});
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
// no memtable complications
arg.atomic_replace_range = {{"a", "z"}};
ASSERT_OK(db_->IngestExternalFiles({arg}));
ASSERT_EQ(Get(1, "e"), "NOT_FOUND");
}
ASSERT_EQ(Get(1, "a"), "NOT_FOUND");
ASSERT_EQ(Get(1, "b"), "0");
ASSERT_EQ(Get(1, "c"), "NOT_FOUND");
ASSERT_EQ(Get(1, "d"), "NOT_FOUND");
// The single ingested file replaced everything (except perhaps memtable)
std::vector<LiveFileMetaData> live_files;
db_->GetLiveFilesMetaData(&live_files);
// One file in each CF
ASSERT_EQ(live_files.size(), 2);
ASSERT_OK(sfw.Open(file_path));
ASSERT_OK(sfw.Put("f", "6"));
ASSERT_OK(sfw.Finish());
// Another file
ASSERT_OK(sfw.Open(file_path2));
ASSERT_OK(sfw.Put("f", "7"));
ASSERT_OK(sfw.Put("g", "8"));
ASSERT_OK(sfw.Finish());
if (!disallow_memtable) {
// rejected because of memtable overlap with range
arg.atomic_replace_range = {{"e", "z"}};
s = db_->IngestExternalFiles({arg});
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
// allow blocking flush of "e" (which is then replaced), and the file
// with just "b" is not replaced
arg.options.allow_blocking_flush = true;
ASSERT_OK(db_->IngestExternalFiles({arg}));
ASSERT_EQ(Get(1, "b"), "0");
ASSERT_EQ(Get(1, "e"), "NOT_FOUND");
ASSERT_EQ(Get(1, "f"), "6");
ASSERT_EQ(Get(1, "g"), "NOT_FOUND");
// memtable overlap with replace range
ASSERT_OK(Put(1, "e", "5"));
arg.options.allow_blocking_flush = false;
arg.external_files = {file_path2};
// rejected because of memtable overlap
arg.atomic_replace_range = {{nullptr, nullptr}};
s = db_->IngestExternalFiles({arg});
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
// Replace everything, including with memtable flush
arg.options.allow_blocking_flush = true;
ASSERT_OK(db_->IngestExternalFiles({arg}));
ASSERT_EQ(Get(1, "b"), "NOT_FOUND");
ASSERT_EQ(Get(1, "e"), "NOT_FOUND");
ASSERT_EQ(Get(1, "f"), "7");
ASSERT_EQ(Get(1, "g"), "8");
} else {
arg.external_files = {file_path2, file_path};
// rejected because of overlap in files to ingest with fail_if_ = true
arg.atomic_replace_range = {{"e", "z"}};
s = db_->IngestExternalFiles({arg});
ASSERT_EQ(s.code(), Status::Code::kTryAgain);
arg.options.fail_if_not_bottommost_level = false;
// rejected because range doesn't cover ingested files
// FIXME: upper bound should be exclusive "g" instead
arg.atomic_replace_range = {{"e", "f2"}};
s = db_->IngestExternalFiles({arg});
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
// Loaded into different levels, and the file with just "b" is not
// replaced
arg.atomic_replace_range = {{"e", "z"}};
ASSERT_OK(db_->IngestExternalFiles({arg}));
ASSERT_EQ(Get(1, "b"), "0");
ASSERT_EQ(Get(1, "f"), "6"); // earlier file listed later to ingest
ASSERT_EQ(Get(1, "g"), "8");
}
}
}
}
+114 -24
View File
@@ -29,6 +29,7 @@ Status ExternalSstFileIngestionJob::Prepare(
const std::vector<std::string>& external_files_paths,
const std::vector<std::string>& files_checksums,
const std::vector<std::string>& files_checksum_func_names,
const std::optional<RangeOpt>& atomic_replace_range,
const Temperature& file_temperature, uint64_t next_file_number,
SuperVersion* sv) {
Status status;
@@ -80,15 +81,47 @@ Status ExternalSstFileIngestionJob::Prepare(
std::sort(sorted_files.begin(), sorted_files.end(), file_range_checker_);
for (size_t i = 0; i + 1 < num_files; i++) {
if (file_range_checker_.OverlapsWithPrev(sorted_files[i],
sorted_files[i + 1],
/* ranges_sorted= */ true)) {
if (file_range_checker_.Overlaps(*sorted_files[i], *sorted_files[i + 1],
/* known_sorted= */ true)) {
files_overlap_ = true;
break;
}
}
}
if (atomic_replace_range.has_value()) {
atomic_replace_range_.emplace();
if (atomic_replace_range->start && atomic_replace_range->limit) {
// User keys to internal keys (with timestamps)
const size_t ts_sz = ucmp_->timestamp_size();
std::string start_with_ts, limit_with_ts;
auto [start, limit] = MaybeAddTimestampsToRange(
atomic_replace_range->start, atomic_replace_range->limit, ts_sz,
&start_with_ts, &limit_with_ts);
assert(start.has_value());
assert(limit.has_value());
atomic_replace_range_->smallest_internal_key.Set(
*start, kMaxSequenceNumber, kValueTypeForSeek);
atomic_replace_range_->largest_internal_key.Set(
*limit, kMaxSequenceNumber, kValueTypeForSeek);
// Check files to ingest against replace range
for (size_t i = 0; i < num_files; i++) {
if (!file_range_checker_.Contains(*atomic_replace_range_,
files_to_ingest_[i])) {
return Status::InvalidArgument(
"Atomic replace range does not contain all files");
}
}
} else {
// Currently if either bound is not present, both must be
assert(atomic_replace_range->start.has_value() == false);
assert(atomic_replace_range->limit.has_value() == false);
assert(atomic_replace_range_->smallest_internal_key.unset());
assert(atomic_replace_range_->largest_internal_key.unset());
}
}
if (ingestion_options_.ingest_behind && files_overlap_) {
return Status::NotSupported(
"Files with overlapping ranges cannot be ingested with ingestion "
@@ -359,9 +392,9 @@ void ExternalSstFileIngestionJob::DivideInputFilesIntoBatches() {
file_batches_to_ingest_.emplace_back(/* _track_batch_range= */ true);
for (auto& file : files_to_ingest_) {
if (file_range_checker_.OverlapsWithPrev(&file_batches_to_ingest_.back(),
&file,
/* ranges_sorted= */ false)) {
if (!file_batches_to_ingest_.back().unset() &&
file_range_checker_.Overlaps(file_batches_to_ingest_.back(), file,
/* known_sorted= */ false)) {
file_batches_to_ingest_.emplace_back(/* _track_batch_range= */ true);
}
file_batches_to_ingest_.back().AddFile(&file, file_range_checker_);
@@ -370,14 +403,32 @@ void ExternalSstFileIngestionJob::DivideInputFilesIntoBatches() {
Status ExternalSstFileIngestionJob::NeedsFlush(bool* flush_needed,
SuperVersion* super_version) {
size_t n = files_to_ingest_.size();
autovector<UserKeyRange> ranges;
ranges.reserve(n);
for (const IngestedFileInfo& file_to_ingest : files_to_ingest_) {
ranges.emplace_back(file_to_ingest.start_ukey, file_to_ingest.limit_ukey);
Status status;
if (atomic_replace_range_.has_value() && atomic_replace_range_->unset()) {
// For replacing whole CF, we can simply check whether memtable is empty
*flush_needed = !super_version->mem->IsEmpty();
} else {
autovector<UserKeyRange> ranges;
if (atomic_replace_range_.has_value()) {
assert(!atomic_replace_range_->smallest_internal_key.unset());
assert(!atomic_replace_range_->largest_internal_key.unset());
// NOTE: we already checked in Prepare() that the atomic_replace_range
// covers all the files_to_ingest
// FIXME: need to make upper bound key exclusive (not easy here because
// the existing internal APIs deal in inclusive upper bound user keys)
ranges.emplace_back(
atomic_replace_range_->smallest_internal_key.user_key(),
atomic_replace_range_->largest_internal_key.user_key());
} else {
ranges.reserve(files_to_ingest_.size());
for (const IngestedFileInfo& file_to_ingest : files_to_ingest_) {
ranges.emplace_back(file_to_ingest.start_ukey,
file_to_ingest.limit_ukey);
}
}
status = cfd_->RangesOverlapWithMemtables(
ranges, super_version, db_options_.allow_data_in_errors, flush_needed);
}
Status status = cfd_->RangesOverlapWithMemtables(
ranges, super_version, db_options_.allow_data_in_errors, flush_needed);
if (status.ok() && *flush_needed) {
if (!ingestion_options_.allow_blocking_flush) {
status = Status::InvalidArgument("External file requires flush");
@@ -430,8 +481,49 @@ Status ExternalSstFileIngestionJob::Run() {
// the only active writer, and hence they are equal
SequenceNumber last_seqno = versions_->LastSequence();
edit_.SetColumnFamily(cfd_->GetID());
// The levels that the files will be ingested into
if (atomic_replace_range_.has_value()) {
auto* vstorage = super_version->current->storage_info();
if (atomic_replace_range_->unset()) {
if (cfd_->compaction_picker()->IsCompactionInProgress()) {
return Status::InvalidArgument(
"Atomic replace range (full) overlaps with pending compaction");
}
for (int lvl = 0; lvl < cfd_->NumberLevels(); lvl++) {
for (auto file : vstorage->LevelFiles(lvl)) {
// Set up to delete file to be replaced
edit_.DeleteFile(lvl, file->fd.GetNumber());
}
}
} else {
assert(!atomic_replace_range_->smallest_internal_key.unset());
assert(!atomic_replace_range_->largest_internal_key.unset());
for (int lvl = 0; lvl < cfd_->NumberLevels(); lvl++) {
if (cfd_->RangeOverlapWithCompaction(
atomic_replace_range_->smallest_internal_key.user_key(),
atomic_replace_range_->largest_internal_key.user_key(), lvl)) {
return Status::InvalidArgument(
"Atomic replace range overlaps with pending compaction");
}
for (auto file : vstorage->LevelFiles(lvl)) {
if (file_range_checker_.Overlaps(*atomic_replace_range_,
file->smallest, file->largest)) {
if (file_range_checker_.Contains(*atomic_replace_range_,
file->smallest, file->largest)) {
// Set up to delete file to be replaced
edit_.DeleteFile(lvl, file->fd.GetNumber());
} else {
// TODO: generate and ingest a tombstone file also
return Status::InvalidArgument(
"Atomic replace range partially overlaps with existing file");
}
}
}
}
}
}
// Find levels to ingest into
std::optional<int> prev_batch_uppermost_level;
for (auto& batch : file_batches_to_ingest_) {
int batch_uppermost_level = 0;
@@ -518,16 +610,8 @@ Status ExternalSstFileIngestionJob::AssignLevelsForOneBatch(
current_time = oldest_ancester_time =
static_cast<uint64_t>(temp_current_time);
}
uint64_t tail_size = 0;
bool contain_no_data_blocks = file->table_properties.num_entries > 0 &&
(file->table_properties.num_entries ==
file->table_properties.num_range_deletions);
if (file->table_properties.tail_start_offset > 0 ||
contain_no_data_blocks) {
uint64_t file_size = file->fd.GetFileSize();
assert(file->table_properties.tail_start_offset <= file_size);
tail_size = file_size - file->table_properties.tail_start_offset;
}
uint64_t tail_size = FileMetaData::CalculateTailSize(
file->fd.GetFileSize(), file->table_properties);
bool marked_for_compaction =
file->table_properties.num_range_deletions == 1 &&
@@ -1104,6 +1188,10 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
if (lvl > 0 && lvl < vstorage->base_level()) {
continue;
}
if (atomic_replace_range_.has_value()) {
target_level = lvl;
continue;
}
if (cfd_->RangeOverlapWithCompaction(file_to_ingest->start_ukey,
file_to_ingest->limit_ukey, lvl)) {
// We must use L0 or any level higher than `lvl` to be able to overwrite
@@ -1172,6 +1260,8 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
Status ExternalSstFileIngestionJob::CheckLevelForIngestedBehindFile(
IngestedFileInfo* file_to_ingest) {
assert(!atomic_replace_range_.has_value());
auto* vstorage = cfd_->current()->storage_info();
// First, check if new files fit in the last level
int last_lvl = cfd_->NumberLevels() - 1;
+54 -25
View File
@@ -27,50 +27,77 @@ class SystemClock;
struct KeyRangeInfo {
// Smallest internal key in an external file or for a batch of external files.
// unset() could be either invalid or "before all keys"
InternalKey smallest_internal_key;
// Largest internal key in an external file or for a batch of external files.
// unset() could be either invalid or "after all keys"
InternalKey largest_internal_key;
bool empty() const {
return smallest_internal_key.size() == 0 &&
largest_internal_key.size() == 0;
bool unset() const {
// Legal internal keys are at least 8 bytes.
return smallest_internal_key.unset() || largest_internal_key.unset();
}
};
// Helper class to apply SST file key range checks to the external files.
// XXX: using sstableKeyCompare with user comparator on internal keys is
// very broken
class ExternalFileRangeChecker {
public:
explicit ExternalFileRangeChecker(const Comparator* ucmp) : ucmp_(ucmp) {}
// Operator used for sorting ranges.
bool operator()(const KeyRangeInfo* prev_range,
const KeyRangeInfo* range) const {
assert(prev_range);
assert(range);
return sstableKeyCompare(ucmp_, prev_range->smallest_internal_key,
range->smallest_internal_key) < 0;
bool operator()(const KeyRangeInfo* range1,
const KeyRangeInfo* range2) const {
assert(range1);
assert(range2);
assert(!range1->unset());
assert(!range2->unset());
return sstableKeyCompare(ucmp_, range1->smallest_internal_key,
range2->smallest_internal_key) < 0;
}
// Check whether `range` overlaps with `prev_range`. `ranges_sorted` can be
// set to true when the inputs are already sorted based on the sorting logic
// provided by this checker's operator(), which can help simplify the check.
bool OverlapsWithPrev(const KeyRangeInfo* prev_range,
const KeyRangeInfo* range,
bool ranges_sorted = false) const {
assert(prev_range);
assert(range);
if (prev_range->empty() || range->empty()) {
bool Overlaps(const KeyRangeInfo& range1, const KeyRangeInfo& range2,
bool known_sorted = false) const {
return Overlaps(range1, range2.smallest_internal_key,
range2.largest_internal_key, known_sorted);
}
bool Overlaps(const KeyRangeInfo& range1, const InternalKey& range2_smallest,
const InternalKey& range2_largest,
bool known_sorted = false) const {
bool any_unset =
range1.unset() || range2_smallest.unset() || range2_largest.unset();
if (any_unset) {
assert(!any_unset);
return false;
}
if (ranges_sorted) {
return sstableKeyCompare(ucmp_, prev_range->largest_internal_key,
range->smallest_internal_key) >= 0;
if (known_sorted) {
return sstableKeyCompare(ucmp_, range1.largest_internal_key,
range2_smallest) >= 0;
}
return sstableKeyCompare(ucmp_, prev_range->largest_internal_key,
range->smallest_internal_key) >= 0 &&
sstableKeyCompare(ucmp_, prev_range->smallest_internal_key,
range->largest_internal_key) <= 0;
return sstableKeyCompare(ucmp_, range1.largest_internal_key,
range2_smallest) >= 0 &&
sstableKeyCompare(ucmp_, range1.smallest_internal_key,
range2_largest) <= 0;
}
bool Contains(const KeyRangeInfo& range1, const KeyRangeInfo& range2) {
return Contains(range1, range2.smallest_internal_key,
range2.largest_internal_key);
}
bool Contains(const KeyRangeInfo& range1, const InternalKey& range2_smallest,
const InternalKey& range2_largest) {
bool any_unset =
range1.unset() || range2_smallest.unset() || range2_largest.unset();
if (any_unset) {
assert(!any_unset);
return false;
}
return sstableKeyCompare(ucmp_, range1.smallest_internal_key,
range2_smallest) <= 0 &&
sstableKeyCompare(ucmp_, range1.largest_internal_key,
range2_largest) >= 0;
}
void MaybeUpdateRange(const InternalKey& start_key,
@@ -218,6 +245,7 @@ class ExternalSstFileIngestionJob {
Status Prepare(const std::vector<std::string>& external_files_paths,
const std::vector<std::string>& files_checksums,
const std::vector<std::string>& files_checksum_func_names,
const std::optional<RangeOpt>& atomic_replace_range,
const Temperature& file_temperature, uint64_t next_file_number,
SuperVersion* sv);
@@ -362,6 +390,7 @@ class ExternalSstFileIngestionJob {
autovector<IngestedFileInfo> files_to_ingest_;
std::vector<FileBatchInfo> file_batches_to_ingest_;
const IngestExternalFileOptions& ingestion_options_;
std::optional<KeyRangeInfo> atomic_replace_range_;
Directories* directories_;
EventLogger* event_logger_;
VersionEdit edit_;
+1 -3
View File
@@ -3915,9 +3915,7 @@ TEST_P(IngestDBGeneratedFileTest, FailureCase) {
s = db_->IngestExternalFile(to_ingest_files, ingest_opts);
ASSERT_TRUE(s.ToString().find(err) != std::string::npos);
ASSERT_NOK(s);
if (options.compaction_style != kCompactionStyleUniversal) {
// FIXME: after fixing ingestion with universal compaction, currently
// will always ingest into L0.
if (options.num_levels > 1) {
ingest_opts.fail_if_not_bottommost_level = true;
s = db_->IngestExternalFile(to_ingest_files, ingest_opts);
ASSERT_NOK(s);
+56 -33
View File
@@ -868,9 +868,12 @@ Status FlushJob::WriteLevel0Table() {
ts_sz > 0 && !cfd_->ioptions().persist_user_defined_timestamps;
std::vector<BlobFileAddition> blob_file_additions;
// Note that here we treat flush as level 0 compaction in internal stats
InternalStats::CompactionStats flush_stats(CompactionReason::kFlush,
1 /* count**/);
{
auto write_hint = base_->storage_info()->CalculateSSTWriteHint(/*level=*/0);
auto write_hint = base_->storage_info()->CalculateSSTWriteHint(
/*level=*/0, db_options_.calculate_sst_write_lifetime_hint_set);
Env::IOPriority io_priority = GetRateLimiterPriority();
db_mutex_->Unlock();
if (log_buffer_) {
@@ -886,7 +889,7 @@ Status FlushJob::WriteLevel0Table() {
ro.total_order_seek = true;
ro.io_activity = Env::IOActivity::kFlush;
Arena arena;
uint64_t total_num_entries = 0, total_num_deletes = 0;
uint64_t total_num_input_entries = 0, total_num_deletes = 0;
uint64_t total_data_size = 0;
size_t total_memory_usage = 0;
uint64_t total_num_range_deletes = 0;
@@ -900,9 +903,9 @@ Status FlushJob::WriteLevel0Table() {
for (ReadOnlyMemTable* m : mems_) {
ROCKS_LOG_INFO(db_options_.info_log,
"[%s] [JOB %d] Flushing memtable id %" PRIu64
" with next log file: %" PRIu64 "\n",
" with next log file: %" PRIu64 ", marked_for_flush: %d\n",
cfd_->GetName().c_str(), job_context_->job_id, m->GetID(),
m->GetNextLogNumber());
m->GetNextLogNumber(), m->IsMarkedForFlush());
if (logical_strip_timestamp) {
memtables.push_back(m->NewTimestampStrippingIterator(
ro, /*seqno_to_time_mapping=*/nullptr, &arena,
@@ -921,7 +924,7 @@ Status FlushJob::WriteLevel0Table() {
if (range_del_iter != nullptr) {
range_del_iters.emplace_back(range_del_iter);
}
total_num_entries += m->NumEntries();
total_num_input_entries += m->NumEntries();
total_num_deletes += m->NumDeletion();
total_data_size += m->GetDataSize();
total_memory_usage += m->ApproximateMemoryUsage();
@@ -933,11 +936,12 @@ Status FlushJob::WriteLevel0Table() {
// "Write Buffer Full", should make update flush_reason_ accordingly.
event_logger_->Log() << "job" << job_context_->job_id << "event"
<< "flush_started" << "num_memtables" << mems_.size()
<< "num_entries" << total_num_entries << "num_deletes"
<< total_num_deletes << "total_data_size"
<< total_data_size << "memory_usage"
<< total_memory_usage << "num_range_deletes"
<< total_num_range_deletes << "flush_reason"
<< "total_num_input_entries" << total_num_input_entries
<< "num_deletes" << total_num_deletes
<< "total_data_size" << total_data_size
<< "memory_usage" << total_memory_usage
<< "num_range_deletes" << total_num_range_deletes
<< "flush_reason"
<< GetFlushReasonString(flush_reason_);
{
@@ -975,7 +979,6 @@ Status FlushJob::WriteLevel0Table() {
meta_.oldest_ancester_time = oldest_ancester_time;
meta_.file_creation_time = current_time;
uint64_t num_input_entries = 0;
uint64_t memtable_payload_bytes = 0;
uint64_t memtable_garbage_bytes = 0;
IOStatus io_s;
@@ -1009,16 +1012,38 @@ Status FlushJob::WriteLevel0Table() {
cfd_->internal_stats(), &io_s, io_tracer_,
BlobFileCreationReason::kFlush, seqno_to_time_mapping_.get(),
event_logger_, job_context_->job_id, &table_properties_, write_hint,
full_history_ts_low, blob_callback_, base_, &num_input_entries,
&memtable_payload_bytes, &memtable_garbage_bytes);
full_history_ts_low, blob_callback_, base_, &memtable_payload_bytes,
&memtable_garbage_bytes, &flush_stats);
TEST_SYNC_POINT_CALLBACK("FlushJob::WriteLevel0Table:s", &s);
// TODO: Cleanup io_status in BuildTable and table builders
assert(!s.ok() || io_s.ok());
io_s.PermitUncheckedError();
if (num_input_entries != total_num_entries && s.ok()) {
std::string msg = "Expected " + std::to_string(total_num_entries) +
if (s.ok() && total_num_input_entries != flush_stats.num_input_records) {
std::string msg = "Expected " +
std::to_string(total_num_input_entries) +
" entries in memtables, but read " +
std::to_string(num_input_entries);
std::to_string(flush_stats.num_input_records);
ROCKS_LOG_WARN(db_options_.info_log, "[%s] [JOB %d] Level-0 flush %s",
cfd_->GetName().c_str(), job_context_->job_id,
msg.c_str());
if (db_options_.flush_verify_memtable_count) {
s = Status::Corruption(msg);
}
}
// Only verify on table with format collects table properties
if (s.ok() &&
(mutable_cf_options_.table_factory->IsInstanceOf(
TableFactory::kBlockBasedTableName()) ||
mutable_cf_options_.table_factory->IsInstanceOf(
TableFactory::kPlainTableName())) &&
flush_stats.num_output_records != table_properties_.num_entries) {
std::string msg =
"Number of keys in flush output SST files does not match "
"number of keys added to the table. Expected " +
std::to_string(flush_stats.num_output_records) + " but there are " +
std::to_string(table_properties_.num_entries) +
" in output SST files";
ROCKS_LOG_WARN(db_options_.info_log, "[%s] [JOB %d] Level-0 flush %s",
cfd_->GetName().c_str(), job_context_->job_id,
msg.c_str());
@@ -1084,12 +1109,10 @@ Status FlushJob::WriteLevel0Table() {
// Piggyback FlushJobInfo on the first first flushed memtable.
mems_[0]->SetFlushJobInfo(GetFlushJobInfo());
// Note that here we treat flush as level 0 compaction in internal stats
InternalStats::CompactionStats stats(CompactionReason::kFlush, 1);
const uint64_t micros = clock_->NowMicros() - start_micros;
const uint64_t cpu_micros = clock_->CPUMicros() - start_cpu_micros;
stats.micros = micros;
stats.cpu_micros = cpu_micros;
flush_stats.micros = micros;
flush_stats.cpu_micros = cpu_micros;
ROCKS_LOG_INFO(db_options_.info_log,
"[%s] [JOB %d] Flush lasted %" PRIu64
@@ -1098,22 +1121,23 @@ Status FlushJob::WriteLevel0Table() {
cpu_micros);
if (has_output) {
stats.bytes_written = meta_.fd.GetFileSize();
stats.num_output_files = 1;
flush_stats.bytes_written = meta_.fd.GetFileSize();
flush_stats.num_output_files = 1;
}
const auto& blobs = edit_->GetBlobFileAdditions();
for (const auto& blob : blobs) {
stats.bytes_written_blob += blob.GetTotalBlobBytes();
flush_stats.bytes_written_blob += blob.GetTotalBlobBytes();
}
stats.num_output_files_blob = static_cast<int>(blobs.size());
flush_stats.num_output_files_blob = static_cast<int>(blobs.size());
RecordTimeToHistogram(stats_, FLUSH_TIME, stats.micros);
cfd_->internal_stats()->AddCompactionStats(0 /* level */, thread_pri_, stats);
RecordTimeToHistogram(stats_, FLUSH_TIME, flush_stats.micros);
cfd_->internal_stats()->AddCompactionStats(0 /* level */, thread_pri_,
flush_stats);
cfd_->internal_stats()->AddCFStats(
InternalStats::BYTES_FLUSHED,
stats.bytes_written + stats.bytes_written_blob);
flush_stats.bytes_written + flush_stats.bytes_written_blob);
RecordFlushIOStats();
return s;
@@ -1193,13 +1217,12 @@ void FlushJob::GetEffectiveCutoffUDTForPickedMemTables() {
}
void FlushJob::GetPrecludeLastLevelMinSeqno() {
if (mutable_cf_options_.preclude_last_level_data_seconds == 0 ||
// FIXME: create FlushJob and build SuperVersions such that
// preclude_last_level_data_seconds > 0 implies
// seqno_to_time_mapping_ != nullptr
seqno_to_time_mapping_ == nullptr) {
if (mutable_cf_options_.preclude_last_level_data_seconds == 0) {
return;
}
// SuperVersion should guarantee this
assert(seqno_to_time_mapping_);
assert(!seqno_to_time_mapping_->Empty());
int64_t current_time = 0;
Status s = db_options_.clock->GetCurrentTime(&current_time);
if (!s.ok()) {
+1 -1
View File
@@ -234,7 +234,7 @@ class FlushJob {
// The current minimum seqno that compaction jobs will preclude the data from
// the last level. Data with seqnos larger than this or larger than
// `earliest_snapshot_` will be output to the penultimate level had it gone
// `earliest_snapshot_` will be output to the proximal level had it gone
// through a compaction to the last level.
SequenceNumber preclude_last_level_min_seqno_ = kMaxSequenceNumber;
};
+1
View File
@@ -42,6 +42,7 @@ using MinIterHeap =
std::priority_queue<InternalIterator*, std::vector<InternalIterator*>,
MinIterComparator>;
// TODO: name to TailingIterator
/**
* ForwardIterator is a special type of iterator that only supports Seek()
* and Next(). It is expected to perform better than TailingIterator by
+2 -9
View File
@@ -201,15 +201,8 @@ Status ImportColumnFamilyJob::Run() {
const auto& f = files_to_import_[i][j];
const auto& file_metadata = *metadatas_[i][j];
uint64_t tail_size = 0;
bool contain_no_data_blocks = f.table_properties.num_entries > 0 &&
(f.table_properties.num_entries ==
f.table_properties.num_range_deletions);
if (f.table_properties.tail_start_offset > 0 || contain_no_data_blocks) {
uint64_t file_size = f.fd.GetFileSize();
assert(f.table_properties.tail_start_offset <= file_size);
tail_size = file_size - f.table_properties.tail_start_offset;
}
uint64_t tail_size = FileMetaData::CalculateTailSize(f.fd.GetFileSize(),
f.table_properties);
VersionEdit dummy_version_edit;
dummy_version_edit.AddFile(
+15 -8
View File
@@ -45,6 +45,8 @@ const std::map<LevelStatType, LevelStat> InternalStats::compaction_level_stats =
{LevelStatType::RN_GB, LevelStat{"RnGB", "Rn(GB)"}},
{LevelStatType::RNP1_GB, LevelStat{"Rnp1GB", "Rnp1(GB)"}},
{LevelStatType::WRITE_GB, LevelStat{"WriteGB", "Write(GB)"}},
{LevelStatType::WRITE_PRE_COMP_GB,
LevelStat{"WPreCompGB", "WPreComp(GB)"}},
{LevelStatType::W_NEW_GB, LevelStat{"WnewGB", "Wnew(GB)"}},
{LevelStatType::MOVED_GB, LevelStat{"MovedGB", "Moved(GB)"}},
{LevelStatType::WRITE_AMP, LevelStat{"WriteAmp", "W-Amp"}},
@@ -100,19 +102,20 @@ void PrintLevelStatsHeader(char* buf, size_t len, const std::string& cf_name,
int line_size = snprintf(
buf + written_size, len - written_size,
"%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s "
"%s "
"%s\n",
// Note that we skip COMPACTED_FILES and merge it with Files column
group_by.c_str(), hdr(LevelStatType::NUM_FILES),
hdr(LevelStatType::SIZE_BYTES), hdr(LevelStatType::SCORE),
hdr(LevelStatType::READ_GB), hdr(LevelStatType::RN_GB),
hdr(LevelStatType::RNP1_GB), hdr(LevelStatType::WRITE_GB),
hdr(LevelStatType::W_NEW_GB), hdr(LevelStatType::MOVED_GB),
hdr(LevelStatType::WRITE_AMP), hdr(LevelStatType::READ_MBPS),
hdr(LevelStatType::WRITE_MBPS), hdr(LevelStatType::COMP_SEC),
hdr(LevelStatType::COMP_CPU_SEC), hdr(LevelStatType::COMP_COUNT),
hdr(LevelStatType::AVG_SEC), hdr(LevelStatType::KEY_IN),
hdr(LevelStatType::KEY_DROP), hdr(LevelStatType::R_BLOB_GB),
hdr(LevelStatType::W_BLOB_GB));
hdr(LevelStatType::WRITE_PRE_COMP_GB), hdr(LevelStatType::W_NEW_GB),
hdr(LevelStatType::MOVED_GB), hdr(LevelStatType::WRITE_AMP),
hdr(LevelStatType::READ_MBPS), hdr(LevelStatType::WRITE_MBPS),
hdr(LevelStatType::COMP_SEC), hdr(LevelStatType::COMP_CPU_SEC),
hdr(LevelStatType::COMP_COUNT), hdr(LevelStatType::AVG_SEC),
hdr(LevelStatType::KEY_IN), hdr(LevelStatType::KEY_DROP),
hdr(LevelStatType::R_BLOB_GB), hdr(LevelStatType::W_BLOB_GB));
written_size += line_size;
written_size = std::min(written_size, static_cast<int>(len));
@@ -140,6 +143,8 @@ void PrepareLevelStats(std::map<LevelStatType, double>* level_stats,
stats.bytes_read_non_output_levels / kGB;
(*level_stats)[LevelStatType::RNP1_GB] = stats.bytes_read_output_level / kGB;
(*level_stats)[LevelStatType::WRITE_GB] = stats.bytes_written / kGB;
(*level_stats)[LevelStatType::WRITE_PRE_COMP_GB] =
stats.bytes_written_pre_comp / kGB;
(*level_stats)[LevelStatType::W_NEW_GB] = bytes_new / kGB;
(*level_stats)[LevelStatType::MOVED_GB] = stats.bytes_moved / kGB;
(*level_stats)[LevelStatType::WRITE_AMP] = w_amp;
@@ -164,12 +169,13 @@ void PrintLevelStats(char* buf, size_t len, const std::string& name,
buf, len,
"%4s " /* Level */
"%6d/%-3d " /* Files */
"%8s " /* Size */
"%10s " /* Size */
"%5.1f " /* Score */
"%8.1f " /* Read(GB) */
"%7.1f " /* Rn(GB) */
"%8.1f " /* Rnp1(GB) */
"%9.1f " /* Write(GB) */
"%9.1f " /* WPreComp(GB) */
"%8.1f " /* Wnew(GB) */
"%9.1f " /* Moved(GB) */
"%5.1f " /* W-Amp */
@@ -193,6 +199,7 @@ void PrintLevelStats(char* buf, size_t len, const std::string& name,
stat_value.at(LevelStatType::RN_GB),
stat_value.at(LevelStatType::RNP1_GB),
stat_value.at(LevelStatType::WRITE_GB),
stat_value.at(LevelStatType::WRITE_PRE_COMP_GB),
stat_value.at(LevelStatType::W_NEW_GB),
stat_value.at(LevelStatType::MOVED_GB),
stat_value.at(LevelStatType::WRITE_AMP),
+41 -52
View File
@@ -71,6 +71,7 @@ enum class LevelStatType {
RN_GB,
RNP1_GB,
WRITE_GB,
WRITE_PRE_COMP_GB,
W_NEW_GB,
MOVED_GB,
WRITE_AMP,
@@ -153,23 +154,6 @@ class InternalStats {
InternalStats(int num_levels, SystemClock* clock, ColumnFamilyData* cfd);
// Per level compaction stats
struct CompactionOutputsStats {
uint64_t num_output_records = 0;
uint64_t bytes_written = 0;
uint64_t bytes_written_blob = 0;
uint64_t num_output_files = 0;
uint64_t num_output_files_blob = 0;
void Add(const CompactionOutputsStats& stats) {
this->num_output_records += stats.num_output_records;
this->bytes_written += stats.bytes_written;
this->bytes_written_blob += stats.bytes_written_blob;
this->num_output_files += stats.num_output_files;
this->num_output_files_blob += stats.num_output_files_blob;
}
};
// Per level compaction stats. comp_stats_[level] stores the stats for
// compactions that produced data for the specified "level".
struct CompactionStats {
@@ -196,6 +180,9 @@ class InternalStats {
// Total number of bytes written to table files during compaction
uint64_t bytes_written;
// Total number of bytes written pre-compression during compaction
uint64_t bytes_written_pre_comp;
// Total number of bytes written to blob files during compaction
uint64_t bytes_written_blob;
@@ -248,6 +235,7 @@ class InternalStats {
bytes_skipped_output_level(0),
bytes_read_blob(0),
bytes_written(0),
bytes_written_pre_comp(0),
bytes_written_blob(0),
bytes_moved(0),
num_input_files_in_non_output_levels(0),
@@ -275,6 +263,7 @@ class InternalStats {
bytes_skipped_output_level(0),
bytes_read_blob(0),
bytes_written(0),
bytes_written_pre_comp(0),
bytes_written_blob(0),
bytes_moved(0),
num_input_files_in_non_output_levels(0),
@@ -308,6 +297,7 @@ class InternalStats {
bytes_skipped_output_level(c.bytes_skipped_output_level),
bytes_read_blob(c.bytes_read_blob),
bytes_written(c.bytes_written),
bytes_written_pre_comp(c.bytes_written_pre_comp),
bytes_written_blob(c.bytes_written_blob),
bytes_moved(c.bytes_moved),
num_input_files_in_non_output_levels(
@@ -338,6 +328,7 @@ class InternalStats {
bytes_skipped_output_level = c.bytes_skipped_output_level;
bytes_read_blob = c.bytes_read_blob;
bytes_written = c.bytes_written;
bytes_written_pre_comp = c.bytes_written_pre_comp;
bytes_written_blob = c.bytes_written_blob;
bytes_moved = c.bytes_moved;
num_input_files_in_non_output_levels =
@@ -370,6 +361,7 @@ class InternalStats {
this->bytes_skipped_output_level = 0;
this->bytes_read_blob = 0;
this->bytes_written = 0;
this->bytes_written_pre_comp = 0;
this->bytes_written_blob = 0;
this->bytes_moved = 0;
this->num_input_files_in_non_output_levels = 0;
@@ -398,6 +390,7 @@ class InternalStats {
this->bytes_skipped_output_level += c.bytes_skipped_output_level;
this->bytes_read_blob += c.bytes_read_blob;
this->bytes_written += c.bytes_written;
this->bytes_written_pre_comp += c.bytes_written_pre_comp;
this->bytes_written_blob += c.bytes_written_blob;
this->bytes_moved += c.bytes_moved;
this->num_input_files_in_non_output_levels +=
@@ -420,15 +413,6 @@ class InternalStats {
}
}
void Add(const CompactionOutputsStats& stats) {
this->num_output_files += static_cast<int>(stats.num_output_files);
this->num_output_records += stats.num_output_records;
this->bytes_written += stats.bytes_written;
this->bytes_written_blob += stats.bytes_written_blob;
this->num_output_files_blob +=
static_cast<int>(stats.num_output_files_blob);
}
void Subtract(const CompactionStats& c) {
this->micros -= c.micros;
this->cpu_micros -= c.cpu_micros;
@@ -439,6 +423,7 @@ class InternalStats {
this->bytes_skipped_output_level -= c.bytes_skipped_output_level;
this->bytes_read_blob -= c.bytes_read_blob;
this->bytes_written -= c.bytes_written;
this->bytes_written_pre_comp -= c.bytes_written_pre_comp;
this->bytes_written_blob -= c.bytes_written_blob;
this->bytes_moved -= c.bytes_moved;
this->num_input_files_in_non_output_levels -=
@@ -473,49 +458,51 @@ class InternalStats {
}
};
// Compaction stats, for per_key_placement compaction, it includes 2 levels
// stats: the last level and the penultimate level.
// Compaction internal stats, for per_key_placement compaction, it includes 2
// output level stats: the last level and the proximal level.
struct CompactionStatsFull {
// the stats for the target primary output level
CompactionStats stats;
CompactionStats output_level_stats;
// stats for penultimate level output if exist
bool has_penultimate_level_output = false;
CompactionStats penultimate_level_stats;
// stats for proximal level output if exist
bool has_proximal_level_output = false;
CompactionStats proximal_level_stats;
explicit CompactionStatsFull() : stats(), penultimate_level_stats() {}
explicit CompactionStatsFull()
: output_level_stats(), proximal_level_stats() {}
explicit CompactionStatsFull(CompactionReason reason, int c)
: stats(reason, c), penultimate_level_stats(reason, c) {}
: output_level_stats(reason, c), proximal_level_stats(reason, c) {}
uint64_t TotalBytesWritten() const {
uint64_t bytes_written = stats.bytes_written + stats.bytes_written_blob;
if (has_penultimate_level_output) {
bytes_written += penultimate_level_stats.bytes_written +
penultimate_level_stats.bytes_written_blob;
uint64_t bytes_written = output_level_stats.bytes_written +
output_level_stats.bytes_written_blob;
if (has_proximal_level_output) {
bytes_written += proximal_level_stats.bytes_written +
proximal_level_stats.bytes_written_blob;
}
return bytes_written;
}
uint64_t DroppedRecords() {
uint64_t output_records = stats.num_output_records;
if (has_penultimate_level_output) {
output_records += penultimate_level_stats.num_output_records;
uint64_t output_records = output_level_stats.num_output_records;
if (has_proximal_level_output) {
output_records += proximal_level_stats.num_output_records;
}
if (stats.num_input_records > output_records) {
return stats.num_input_records - output_records;
if (output_level_stats.num_input_records > output_records) {
return output_level_stats.num_input_records - output_records;
}
return 0;
}
void SetMicros(uint64_t val) {
stats.micros = val;
penultimate_level_stats.micros = val;
output_level_stats.micros = val;
proximal_level_stats.micros = val;
}
void AddCpuMicros(uint64_t val) {
stats.cpu_micros += val;
penultimate_level_stats.cpu_micros += val;
output_level_stats.cpu_micros += val;
proximal_level_stats.cpu_micros += val;
}
};
@@ -587,10 +574,9 @@ class InternalStats {
void AddCompactionStats(int level, Env::Priority thread_pri,
const CompactionStatsFull& comp_stats_full) {
AddCompactionStats(level, thread_pri, comp_stats_full.stats);
if (comp_stats_full.has_penultimate_level_output) {
per_key_placement_comp_stats_.Add(
comp_stats_full.penultimate_level_stats);
AddCompactionStats(level, thread_pri, comp_stats_full.output_level_stats);
if (comp_stats_full.has_proximal_level_output) {
per_key_placement_comp_stats_.Add(comp_stats_full.proximal_level_stats);
}
}
@@ -722,7 +708,10 @@ class InternalStats {
// a full cache, which would force a re-scan on the next GetStats.
std::shared_ptr<CacheEntryStatsCollector<CacheEntryRoleStats>>
cache_entry_stats_collector_;
// Per-ColumnFamily/level compaction stats
// Per-column family and level compaction statistics, including flush and file
// ingestion. These are treated as compactions to L0 or the level where the
// file was ingested.
std::vector<CompactionStats> comp_stats_;
std::vector<CompactionStats> comp_stats_by_pri_;
CompactionStats per_key_placement_comp_stats_;
+10 -13
View File
@@ -22,6 +22,9 @@ namespace ROCKSDB_NAMESPACE {
class MemTable;
struct SuperVersion;
// The purpose of this struct is to simplify pushing work such as
// allocation/construction, de-allocation/destruction, and notifications to
// outside of holding the DB mutex.
struct SuperVersionContext {
struct WriteStallNotification {
WriteStallInfo write_stall_info;
@@ -35,12 +38,6 @@ struct SuperVersionContext {
std::unique_ptr<SuperVersion>
new_superversion; // if nullptr no new superversion
// If not nullptr, a new seqno to time mapping is available to be installed.
// Otherwise, make a shared copy of the one in the existing SuperVersion and
// carry it over to the new SuperVersion. This is moved to the SuperVersion
// during installation.
std::shared_ptr<const SeqnoToTimeMapping> new_seqno_to_time_mapping{nullptr};
explicit SuperVersionContext(bool create_superversion = false)
: new_superversion(create_superversion ? new SuperVersion() : nullptr) {}
@@ -126,7 +123,7 @@ struct JobContext {
break;
}
}
return memtables_to_free.size() > 0 || logs_to_free.size() > 0 ||
return memtables_to_free.size() > 0 || wals_to_free.size() > 0 ||
job_snapshot != nullptr || sv_have_sth;
}
@@ -196,7 +193,7 @@ struct JobContext {
// contexts for installing superversions for multiple column families
std::vector<SuperVersionContext> superversion_contexts;
autovector<log::Writer*> logs_to_free;
autovector<log::Writer*> wals_to_free;
// the current manifest_file_number, log_number and prev_log_number
// that corresponds to the set of files in 'live'.
@@ -210,8 +207,8 @@ struct JobContext {
uint64_t prev_log_number;
uint64_t min_pending_output = 0;
uint64_t prev_total_log_size = 0;
size_t num_alive_log_files = 0;
uint64_t prev_wals_total_size = 0;
size_t num_alive_wal_files = 0;
uint64_t size_log_to_delete = 0;
// Snapshot taken before flush/compaction job.
@@ -240,18 +237,18 @@ struct JobContext {
for (auto m : memtables_to_free) {
delete m;
}
for (auto l : logs_to_free) {
for (auto l : wals_to_free) {
delete l;
}
memtables_to_free.clear();
logs_to_free.clear();
wals_to_free.clear();
job_snapshot.reset();
}
~JobContext() {
assert(memtables_to_free.size() == 0);
assert(logs_to_free.size() == 0);
assert(wals_to_free.size() == 0);
}
};
+7 -5
View File
@@ -147,7 +147,6 @@ MemTable::MemTable(const InternalKeyComparator& cmp,
const Comparator* ucmp = cmp.user_comparator();
assert(ucmp);
ts_sz_ = ucmp->timestamp_size();
persist_user_defined_timestamps_ = ioptions.persist_user_defined_timestamps;
}
MemTable::~MemTable() {
@@ -175,6 +174,11 @@ size_t MemTable::ApproximateMemoryUsage() {
}
bool MemTable::ShouldFlushNow() {
if (IsMarkedForFlush()) {
// TODO: dedicated flush reason when marked for flush
return true;
}
// This is set if memtable_max_range_deletions is > 0,
// and that many range deletions are done
if (memtable_max_range_deletions_ > 0 &&
@@ -1801,7 +1805,7 @@ uint64_t MemTable::GetMinLogContainingPrepSection() {
}
void MemTable::MaybeUpdateNewestUDT(const Slice& user_key) {
if (ts_sz_ == 0 || persist_user_defined_timestamps_) {
if (ts_sz_ == 0) {
return;
}
const Comparator* ucmp = GetInternalKeyComparator().user_comparator();
@@ -1812,9 +1816,7 @@ void MemTable::MaybeUpdateNewestUDT(const Slice& user_key) {
}
const Slice& MemTable::GetNewestUDT() const {
// This path should not be invoked for MemTables that does not enable the UDT
// in Memtable only feature.
assert(ts_sz_ > 0 && !persist_user_defined_timestamps_);
assert(ts_sz_ > 0);
return newest_udt_;
}
+13 -8
View File
@@ -30,6 +30,7 @@
#include "rocksdb/db.h"
#include "rocksdb/memtablerep.h"
#include "table/multiget_context.h"
#include "util/atomic.h"
#include "util/cast_util.h"
#include "util/dynamic_bloom.h"
#include "util/hash.h"
@@ -354,13 +355,13 @@ class ReadOnlyMemTable {
// be flushed to storage
// REQUIRES: external synchronization to prevent simultaneous
// operations on the same MemTable.
uint64_t GetNextLogNumber() const { return mem_next_logfile_number_; }
uint64_t GetNextLogNumber() const { return mem_next_walfile_number_; }
// Sets the next active logfile number when this memtable is about to
// be flushed to storage
// REQUIRES: external synchronization to prevent simultaneous
// operations on the same MemTable.
void SetNextLogNumber(uint64_t num) { mem_next_logfile_number_ = num; }
void SetNextLogNumber(uint64_t num) { mem_next_walfile_number_ = num; }
// REQUIRES: db_mutex held.
void SetID(uint64_t id) { id_ = id; }
@@ -496,6 +497,10 @@ class ReadOnlyMemTable {
return false;
}
void MarkForFlush() { marked_for_flush_.StoreRelaxed(true); }
bool IsMarkedForFlush() const { return marked_for_flush_.LoadRelaxed(); }
protected:
friend class MemTableList;
@@ -511,7 +516,7 @@ class ReadOnlyMemTable {
VersionEdit edit_;
// The log files earlier than this number can be deleted.
uint64_t mem_next_logfile_number_{0};
uint64_t mem_next_walfile_number_{0};
// Memtable id to track flush.
uint64_t id_ = 0;
@@ -524,6 +529,8 @@ class ReadOnlyMemTable {
// Flush job info of the current memtable.
std::unique_ptr<FlushJobInfo> flush_job_info_;
RelaxedAtomic<bool> marked_for_flush_{false};
};
class MemTable final : public ReadOnlyMemTable {
@@ -818,6 +825,8 @@ class MemTable final : public ReadOnlyMemTable {
is_range_del_table_empty_;
}
// Gets the newest user defined timestamps in the memtable. This should only
// be called when user defined timestamp is enabled.
const Slice& GetNewestUDT() const override;
// Returns Corruption status if verification fails.
@@ -893,14 +902,10 @@ class MemTable final : public ReadOnlyMemTable {
// Size in bytes for the user-defined timestamps.
size_t ts_sz_;
// Whether to persist user-defined timestamps
bool persist_user_defined_timestamps_;
// Newest user-defined timestamp contained in this MemTable. For ts1, and ts2
// if Comparator::CompareTimestamp(ts1, ts2) > 0, ts1 is considered newer than
// ts2. We track this field for a MemTable if its column family has UDT
// feature enabled and the `persist_user_defined_timestamp` flag is false.
// Otherwise, this field just contains an empty Slice.
// feature enabled.
Slice newest_udt_;
// Updates flush_state_ using ShouldFlushNow()
+16 -11
View File
@@ -51,9 +51,7 @@ void MemTableListVersion::UnrefMemTable(
MemTableListVersion::MemTableListVersion(
size_t* parent_memtable_list_memory_usage, const MemTableListVersion& old)
: max_write_buffer_number_to_maintain_(
old.max_write_buffer_number_to_maintain_),
max_write_buffer_size_to_maintain_(
: max_write_buffer_size_to_maintain_(
old.max_write_buffer_size_to_maintain_),
parent_memtable_list_memory_usage_(parent_memtable_list_memory_usage) {
memlist_ = old.memlist_;
@@ -69,10 +67,8 @@ MemTableListVersion::MemTableListVersion(
MemTableListVersion::MemTableListVersion(
size_t* parent_memtable_list_memory_usage,
int max_write_buffer_number_to_maintain,
int64_t max_write_buffer_size_to_maintain)
: max_write_buffer_number_to_maintain_(max_write_buffer_number_to_maintain),
max_write_buffer_size_to_maintain_(max_write_buffer_size_to_maintain),
: max_write_buffer_size_to_maintain_(max_write_buffer_size_to_maintain),
parent_memtable_list_memory_usage_(parent_memtable_list_memory_usage) {}
void MemTableListVersion::Ref() { ++refs_; }
@@ -323,8 +319,7 @@ void MemTableListVersion::Remove(ReadOnlyMemTable* m,
memlist_.remove(m);
m->MarkFlushed();
if (max_write_buffer_size_to_maintain_ > 0 ||
max_write_buffer_number_to_maintain_ > 0) {
if (max_write_buffer_size_to_maintain_ > 0) {
memlist_history_.push_front(m);
// Unable to get size of mutable memtable at this point, pass 0 to
// TrimHistory as a best effort.
@@ -356,9 +351,6 @@ bool MemTableListVersion::MemtableLimitExceeded(size_t usage) {
// whether to trim history
return MemoryAllocatedBytesExcludingLast() + usage >=
static_cast<size_t>(max_write_buffer_size_to_maintain_);
} else if (max_write_buffer_number_to_maintain_ > 0) {
return memlist_.size() + memlist_history_.size() >
static_cast<size_t>(max_write_buffer_number_to_maintain_);
} else {
return false;
}
@@ -382,6 +374,19 @@ bool MemTableListVersion::TrimHistory(autovector<ReadOnlyMemTable*>* to_delete,
return ret;
}
const Slice& MemTableListVersion::GetNewestUDT() const {
static Slice kEmptySlice;
for (auto it = memlist_.begin(); it != memlist_.end(); ++it) {
ReadOnlyMemTable* m = *it;
Slice timestamp = m->GetNewestUDT();
assert(!timestamp.empty() || m->IsEmpty());
if (!timestamp.empty()) {
return m->GetNewestUDT();
}
}
return kEmptySlice;
}
// Returns true if there is at least one memtable on which flush has
// not yet started.
bool MemTableList::IsFlushPending() const {
+6 -5
View File
@@ -45,7 +45,6 @@ class MemTableListVersion {
explicit MemTableListVersion(size_t* parent_memtable_list_memory_usage,
const MemTableListVersion& old);
explicit MemTableListVersion(size_t* parent_memtable_list_memory_usage,
int max_write_buffer_number_to_maintain,
int64_t max_write_buffer_size_to_maintain);
void Ref();
@@ -150,6 +149,12 @@ class MemTableListVersion {
int NumFlushed() const { return static_cast<int>(memlist_history_.size()); }
// Gets the newest user defined timestamps from the immutable memtables.
// This returns the newest user defined timestamp found in the most recent
// immutable memtable. This should only be called when user defined timestamp
// is enabled.
const Slice& GetNewestUDT() const;
private:
friend class MemTableList;
@@ -209,8 +214,6 @@ class MemTableListVersion {
// (used during Transaction validation)
std::list<ReadOnlyMemTable*> memlist_history_;
// Maximum number of MemTables to keep in memory (including both flushed
const int max_write_buffer_number_to_maintain_;
// Maximum size of MemTables to keep in memory (including both flushed
// and not-yet-flushed tables).
const int64_t max_write_buffer_size_to_maintain_;
@@ -238,13 +241,11 @@ class MemTableList {
public:
// A list of memtables.
explicit MemTableList(int min_write_buffer_number_to_merge,
int max_write_buffer_number_to_maintain,
int64_t max_write_buffer_size_to_maintain)
: imm_flush_needed(false),
imm_trim_needed(false),
min_write_buffer_number_to_merge_(min_write_buffer_number_to_merge),
current_(new MemTableListVersion(&current_memory_usage_,
max_write_buffer_number_to_maintain,
max_write_buffer_size_to_maintain)),
num_flush_not_started_(0),
commit_in_progress_(false),
+1 -11
View File
@@ -220,7 +220,7 @@ class MemTableListTest : public testing::Test {
TEST_F(MemTableListTest, Empty) {
// Create an empty MemTableList and validate basic functions.
MemTableList list(1, 0, 0);
MemTableList list(1, 0);
ASSERT_EQ(0, list.NumNotFlushed());
ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire));
@@ -239,10 +239,8 @@ TEST_F(MemTableListTest, Empty) {
TEST_F(MemTableListTest, GetTest) {
// Create MemTableList
int min_write_buffer_number_to_merge = 2;
int max_write_buffer_number_to_maintain = 0;
int64_t max_write_buffer_size_to_maintain = 0;
MemTableList list(min_write_buffer_number_to_merge,
max_write_buffer_number_to_maintain,
max_write_buffer_size_to_maintain);
SequenceNumber seq = 1;
@@ -407,10 +405,8 @@ TEST_F(MemTableListTest, GetTest) {
TEST_F(MemTableListTest, GetFromHistoryTest) {
// Create MemTableList
int min_write_buffer_number_to_merge = 2;
int max_write_buffer_number_to_maintain = 2;
int64_t max_write_buffer_size_to_maintain = 2 * Arena::kInlineSize;
MemTableList list(min_write_buffer_number_to_merge,
max_write_buffer_number_to_maintain,
max_write_buffer_size_to_maintain);
SequenceNumber seq = 1;
@@ -653,11 +649,9 @@ TEST_F(MemTableListTest, FlushPendingTest) {
// Create MemTableList
int min_write_buffer_number_to_merge = 3;
int max_write_buffer_number_to_maintain = 7;
int64_t max_write_buffer_size_to_maintain =
7 * static_cast<int>(options.write_buffer_size);
MemTableList list(min_write_buffer_number_to_merge,
max_write_buffer_number_to_maintain,
max_write_buffer_size_to_maintain);
// Create some MemTables
@@ -949,13 +943,11 @@ TEST_F(MemTableListTest, AtomicFlushTest) {
// Create MemTableLists
int min_write_buffer_number_to_merge = 3;
int max_write_buffer_number_to_maintain = 7;
int64_t max_write_buffer_size_to_maintain =
7 * static_cast<int64_t>(options.write_buffer_size);
autovector<MemTableList*> lists;
for (int i = 0; i != num_cfs; ++i) {
lists.emplace_back(new MemTableList(min_write_buffer_number_to_merge,
max_write_buffer_number_to_maintain,
max_write_buffer_size_to_maintain));
}
@@ -1104,11 +1096,9 @@ TEST_F(MemTableListWithTimestampTest, GetTableNewestUDT) {
// Create MemTableList
int min_write_buffer_number_to_merge = 1;
int max_write_buffer_number_to_maintain = 4;
int64_t max_write_buffer_size_to_maintain =
4 * static_cast<int>(options.write_buffer_size);
MemTableList list(min_write_buffer_number_to_merge,
max_write_buffer_number_to_maintain,
max_write_buffer_size_to_maintain);
// Create some MemTables
+41
View File
@@ -1338,6 +1338,47 @@ TEST_P(PlainTableDBTest, AdaptiveTable) {
INSTANTIATE_TEST_CASE_P(PlainTableDBTest, PlainTableDBTest, ::testing::Bool());
TEST_P(PlainTableDBTest, DeleteRangeNotSupported) {
for (bool use_write_batch : {false, true}) {
DestroyAndReopen();
ASSERT_OK(Put("a0001111", "1"));
ASSERT_OK(Put("b0001111", "2"));
ASSERT_OK(Put("c0001111", "3"));
if (use_write_batch) {
WriteBatch wb;
ASSERT_OK(wb.Put("d0001111", "4"));
ASSERT_OK(wb.DeleteRange("a", "b"));
ASSERT_OK(wb.Put("e0001111", "5"));
ASSERT_EQ(dbfull()->Write({}, &wb).code(), Status::Code::kNotSupported);
} else {
ASSERT_EQ(dbfull()->DeleteRange({}, "az", "bz").code(),
Status::Code::kNotSupported);
}
ASSERT_EQ(Get("a0001111"), "1");
ASSERT_EQ(Get("b0001111"), "2");
ASSERT_EQ(Get("c0001111"), "3");
ASSERT_EQ(Get("d0001111"), "NOT_FOUND"); // expect WriteBatch atomicity
ASSERT_EQ(Get("e0001111"), "NOT_FOUND");
ASSERT_EQ(Put("e0001111", "5").code(), Status::Code::kNotSupported);
ASSERT_EQ(Get("e0001111"), "NOT_FOUND");
// Even trying to flush
ASSERT_EQ(dbfull()->TEST_FlushMemTable().code(),
Status::Code::kNotSupported);
// WAL is recoverable (at least in standard configurations)
ASSERT_OK(TryReopen());
ASSERT_EQ(Get("a0001111"), "1");
ASSERT_EQ(Get("b0001111"), "2");
ASSERT_EQ(Get("c0001111"), "3");
ASSERT_EQ(Get("d0001111"), "NOT_FOUND");
ASSERT_EQ(Get("e0001111"), "NOT_FOUND");
}
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+10 -14
View File
@@ -100,13 +100,15 @@ class Repairer {
db_options_(SanitizeOptions(dbname_, db_options)),
immutable_db_options_(ImmutableDBOptions(db_options_)),
icmp_(default_cf_opts.comparator),
default_cf_opts_(
SanitizeOptions(immutable_db_options_, default_cf_opts)),
default_cf_opts_(SanitizeCfOptions(immutable_db_options_,
/*read_only*/ false,
default_cf_opts)),
default_iopts_(
ImmutableOptions(immutable_db_options_, default_cf_opts_)),
default_mopts_(MutableCFOptions(default_cf_opts_)),
unknown_cf_opts_(
SanitizeOptions(immutable_db_options_, unknown_cf_opts)),
unknown_cf_opts_(SanitizeCfOptions(immutable_db_options_,
/*read_only*/ false,
unknown_cf_opts)),
create_unknown_cfs_(create_unknown_cfs),
raw_table_cache_(
// TableCache can be small since we expect each table to be opened
@@ -456,8 +458,9 @@ class Repairer {
meta.file_creation_time = current_time;
SnapshotChecker* snapshot_checker = DisableGCSnapshotChecker::Instance();
auto write_hint =
cfd->current()->storage_info()->CalculateSSTWriteHint(/*level=*/0);
auto write_hint = cfd->current()->storage_info()->CalculateSSTWriteHint(
/*level=*/0, db_options_.calculate_sst_write_lifetime_hint_set);
std::vector<std::unique_ptr<FragmentedRangeTombstoneIterator>>
range_del_iters;
auto range_del_iter = mem->NewRangeTombstoneIterator(
@@ -575,14 +578,7 @@ class Repairer {
static_cast<bool>(props->user_defined_timestamps_persisted);
}
if (status.ok()) {
uint64_t tail_size = 0;
bool contain_no_data_blocks =
props->num_entries > 0 &&
(props->num_entries == props->num_range_deletions);
if (props->tail_start_offset > 0 || contain_no_data_blocks) {
assert(props->tail_start_offset <= file_size);
tail_size = file_size - props->tail_start_offset;
}
uint64_t tail_size = FileMetaData::CalculateTailSize(file_size, *props);
t->meta.tail_size = tail_size;
}
ColumnFamilyData* cfd = nullptr;
+8 -8
View File
@@ -96,7 +96,7 @@ TEST_F(SeqnoTimeTest, TemperatureBasicUniversal) {
}
ASSERT_OK(dbfull()->TEST_WaitForCompact());
// All data is hot, only output to penultimate level
// All data is hot, only output to proximal level
ASSERT_EQ("0,0,0,0,0,1", FilesPerLevel());
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
@@ -185,7 +185,7 @@ TEST_F(SeqnoTimeTest, TemperatureBasicLevel) {
options.num_levels = kNumLevels;
options.level_compaction_dynamic_level_bytes = true;
// TODO(zjay): for level compaction, auto-compaction may stuck in deadloop, if
// the penultimate level score > 1, but the hot is not cold enough to compact
// the proximal level score > 1, but the hot is not cold enough to compact
// to last level, which will keep triggering compaction.
options.disable_auto_compactions = true;
DestroyAndReopen(options);
@@ -205,7 +205,7 @@ TEST_F(SeqnoTimeTest, TemperatureBasicLevel) {
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
// All data is hot, only output to penultimate level
// All data is hot, only output to proximal level
ASSERT_EQ("0,0,0,0,0,1", FilesPerLevel());
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
@@ -753,7 +753,7 @@ TEST_P(SeqnoTimeTablePropTest, SeqnoToTimeMappingUniversal) {
CompactRangeOptions cro;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
// make sure the data is all compacted to penultimate level if the feature is
// make sure the data is all compacted to proximal level if the feature is
// on, otherwise, compacted to the last level.
if (options.preclude_last_level_data_seconds > 0) {
ASSERT_GT(NumTableFilesAtLevel(5), 0);
@@ -792,8 +792,7 @@ TEST_P(SeqnoTimeTablePropTest, SeqnoToTimeMappingUniversal) {
}
ASSERT_GT(num_seqno_zeroing, 0);
std::vector<KeyVersion> key_versions;
ASSERT_OK(GetAllKeyVersions(db_, Slice(), Slice(),
std::numeric_limits<size_t>::max(),
ASSERT_OK(GetAllKeyVersions(db_, {}, {}, std::numeric_limits<size_t>::max(),
&key_versions));
// make sure there're more than 300 keys and first 100 keys are having seqno
// zeroed out, the last 100 key seqno not zeroed out
@@ -919,10 +918,11 @@ TEST_P(SeqnoTimeTablePropTest, PrePopulateInDB) {
ASSERT_EQ(db_->GetLatestSequenceNumber(), 0);
// And even if we re-open read-write, we do not get pre-population,
// because that's only for new DBs.
// because that's only for new DBs. We just get a single bootstrap
// entry as a lower bound on write times of future writes.
Reopen(track_options);
sttm = dbfull()->TEST_GetSeqnoToTimeMapping();
ASSERT_EQ(sttm.Size(), 0);
ASSERT_EQ(sttm.Size(), 1);
ASSERT_EQ(db_->GetLatestSequenceNumber(), 0);
}
}
Loaded 100 of 276 files, more files were not shown because too many files have changed in this diff. Show more