Compare commits

..
26 Commits
Author SHA1 Message Date
anand76 bb4497729c Update HISTORY and version for 10.7.6 2025-10-27 16:42:03 -07:00
Xingbo Wang 75623f6a7a Follow up on MultiScan change in #14040 (#14055)
Summary:
* Address feedback from https://github.com/facebook/rocksdb/issues/14040
* Add additional test for MultiScan
* Fix a bug when del range and data are in same file for multi-scan
* Rewrite the cases need to be handled in SeekMultiScan

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

Test Plan: Unit test

Reviewed By: cbi42, anand1976

Differential Revision: D84851788

Pulled By: xingbowang

fbshipit-source-id: 0f69632733afb99685f6341badbf239681010c38
2025-10-27 16:19:57 -07:00
anand76 812b12bc78 Update HISTORY and version for 10.7.5 2025-10-20 11:17:17 -07:00
Xingbo Wang fba76b2ab8 Fix a nullptr access bug in MultiScan (#14062)
Summary:
Fixing a nullptr access in multiscan, under following situation.

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

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

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

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

Test Plan:
Unit Test
rand_seed 304010984 on UserDefinedIndexStressTest

Reviewed By: cbi42

Differential Revision: D84976410

Pulled By: xingbowang

fbshipit-source-id: 6b99bf85fc9d4108c5267ae77be77ccfe08923cd
2025-10-20 11:13:02 -07:00
Xingbo Wang 24efab2f04 Release RocksDB 10.7.4
Summary:

Release RocksDB 10.7.4

Test Plan:

Unit test

Reviewers:

Subscribers:

Tasks:

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

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

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

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

Reviewed By: xingbowang

Differential Revision: D84623871

Pulled By: anand1976

fbshipit-source-id: 2418e629f92b1c46c555ddea3761140f700819e4
2025-10-14 14:29:16 -07:00
anand76 6507b9ced8 Use wget for folly dependency download (#14030)
Summary:
Fix the binutils truncated download issue by switching to wget in the folly build scripts for downloading dependencies.

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

Test Plan: make build_folly

Reviewed By: jaykorean

Differential Revision: D84033126

Pulled By: anand1976

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

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

Test Plan: Unit test

Reviewed By: anand1976

Differential Revision: D84292297

Pulled By: xingbowang

fbshipit-source-id: 7b31f727e67e7c0bfc53c2f9a6552e0c3d324869
2025-10-13 15:42:01 -07:00
anand76 d18c2906fa Pass the correct comparator to MultiScanArgs (#14033)
Summary:
Fix assertion failure in crash tests with timestamp due to the wrong comparator passed to MultiScanArgs

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

Reviewed By: xingbowang

Differential Revision: D84036954

Pulled By: anand1976

fbshipit-source-id: 526be21c0754dcccf8e4d2b9fba33716fe35860a
2025-10-13 15:36:08 -07:00
Xingbo Wang f972d77133 Allow passing comparator in UDI (#14001)
Summary:
Pass the comparator to UDI interface for both reader and builder.

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

Test Plan: Unit test

Reviewed By: anand1976

Differential Revision: D83339943

Pulled By: xingbowang

fbshipit-source-id: 7f6541776b0995260e28224329f0cca37f13b3d4
2025-10-13 15:35:57 -07:00
Xingbo Wang 9775409298 Update version and HISTORY for 10.7.3 patch release 2025-10-06 16:54:02 -07:00
Xingbo Wang 4e7fe99741 Fix range delete file caused MultiScan issue (#14028)
Summary:
When there is an ingested SST file that only contains delete range operations, MultiScan may return error "Scan does not intersect with file". This is due to file selection during Prepare uses the file smallest and largest key without considering whether there is any key in the file. This is only a temporary fix.

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

Test Plan: Unit test

Reviewed By: anand1976

Differential Revision: D83986964

Pulled By: xingbowang

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

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

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

Reviewed By: anand1976

Differential Revision: D83166088

Pulled By: cbi42

fbshipit-source-id: 241a7d43c8c00d9a98eea0cabb03d2174d51aae5
2025-10-06 16:50:59 -07:00
Peter Dillinger a74934f0f5 Update version and HISTORY for 10.7.2 patch release 2025-10-02 10:27:59 -07:00
anand76 b4a3612256 Fix incorrect MultiScan handling of range limit between files (#14011)
Summary:
This PR fixes a bug in how MultiScan handled a scan range limit falling in the key range between files. The bug was in LevelIterator, where Prepare() relied on FindFile to determine the lower bound file for the range limit. FindFile returns the smallest file index with `range.limit < file.largest_key`. However, that doesn't guarantee that the range overlaps the file, as the `range.limit` could be smaller than `file.smallest_key`.

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

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

Test Plan: Add unit tests in db_iterator_test and table_test

Reviewed By: cbi42

Differential Revision: D83496439

Pulled By: anand1976

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

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

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

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

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

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

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

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

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

Reviewed By: hx235

Differential Revision: D83722880

Pulled By: pdillinger

fbshipit-source-id: 30149dd187686d5dd98321e6aa7d74bd7653a905
2025-10-02 09:16:59 -07:00
Peter Dillinger 12b6696e7f Resolve missing/inconsistent tickers in Java (#14012)
Summary:
Pretty self-explanatory from the changes, including re-arranging the "COOL" entries for easier tracking of which values are used.

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

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

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

Reviewed By: hx235

Differential Revision: D83497503

Pulled By: pdillinger

fbshipit-source-id: ec0bd7e28188e0430fb03fc5bd79c2ed7b28f3ad
2025-09-30 11:20:13 -07:00
Peter Dillinger 10985ceb00 Add kCool Temperature (#14000)
Summary:
also requested by internal user, like kIce in https://github.com/facebook/rocksdb/issues/13927

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

Test Plan: unit tests updated

Reviewed By: archang19

Differential Revision: D83200479

Pulled By: pdillinger

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

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

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

Reviewed By: pdillinger

Differential Revision: D83091423

Pulled By: archang19

fbshipit-source-id: 4db86d33cf162ed39114b1cd115fcd8964c8ff9b
2025-09-29 09:03:51 -07:00
Changyu Bi 09074d7fb7 update HISTORY 2025-09-24 16:11:38 -07:00
anand76 f9ef31a08e Allow UDIs with non BytewiseComparator (#13999)
Summary:
Remove the restriction of only using BytewiseComparator(). In a follow on PR, the UDI interface will be updated to take the Comparator as a parameter.

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

Test Plan: Add a unit test in table_test.cc

Reviewed By: cbi42

Differential Revision: D83179747

Pulled By: anand1976

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

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

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

Reviewed By: cbi42

Differential Revision: D83178493

Pulled By: pdillinger

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

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

Test Plan: Add new test in db_iterator_test and table_test

Reviewed By: xingbowang

Differential Revision: D82843944

Pulled By: anand1976

fbshipit-source-id: f12756c40ebd38d8d4e4425e97438b6e766a4663
2025-09-23 09:55:58 -07:00
Changyu Bi 66236860f3 Revert "Create a new API FileSystem::SyncFile for file sync (#13762)" (#13987)
Summary:
This is causing some internal failure, we decide to revert this for now until we have a proper fix.

This reverts commit 961880b458.

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

Reviewed By: anand1976

Differential Revision: D82990294

Pulled By: cbi42

fbshipit-source-id: 5f5b4d18d0afe47599738d27e11e3eb2d08d88a0
2025-09-23 09:55:50 -07:00
Changyu Bi 6302542b9c Update HISTORY.md for 10.7.0 2025-09-19 18:21:09 -07:00
209 changed files with 2244 additions and 12097 deletions

No files matched your search

-10
View File
@@ -1,17 +1,7 @@
name: build-folly
description: Build folly and dependencies (skipped if cache hit)
inputs:
cache-hit:
description: Whether the folly cache was hit
required: true
runs:
using: composite
steps:
- name: Build folly and dependencies
if: ${{ inputs.cache-hit != 'true' }}
run: make build_folly
shell: bash
- name: Skip folly build (using cached version)
if: ${{ inputs.cache-hit == 'true' }}
run: echo "Folly build skipped - using cached version"
shell: bash
-33
View File
@@ -1,33 +0,0 @@
name: cache-folly
description: Cache folly build to speed up CI
outputs:
cache-hit:
description: Whether the cache was hit
value: ${{ steps.cache-folly-build.outputs.cache-hit }}
runs:
using: composite
steps:
- name: Extract FOLLY_MK_HASH
id: extract-folly-hash
shell: bash
run: |
FOLLY_MK_HASH=$(md5sum folly.mk | cut -d' ' -f1)
echo "hash=$FOLLY_MK_HASH" >> $GITHUB_OUTPUT
- name: Extract FOLLY_INSTALL_DIR
id: extract-folly-install-dir
shell: bash
run: |
FOLLY_INSTALL_DIR=$(cd third-party/folly && python3 build/fbcode_builder/getdeps.py show-inst-dir)
echo "dir=$(echo $FOLLY_INSTALL_DIR | sed 's|installed/folly|installed|')" >> $GITHUB_OUTPUT
- name: Cache folly build
id: cache-folly-build
uses: actions/cache@v4
with:
# Cache the folly build directory
path: ${{ steps.extract-folly-install-dir.outputs.dir }}
# Key is based on:
# - OS and architecture
# - The docker image, which may not always be specified/known
# - Hash of folly.mk, which includes the folly repository commit hash
# NOTE: this is still only intended for DEBUG folly builds
key: folly-build-${{ runner.os }}-${{ runner.arch }}-${{ github.job_container.image }}-${{ steps.extract-folly-hash.outputs.hash }}
+1 -3
View File
@@ -5,7 +5,5 @@ runs:
- name: Checkout folly sources
run: |
make checkout_folly
shell: bash
- name: Install patchelf
run: apt-get update -y && apt-get install -y patchelf
echo "GETDEPS_USE_WGET=1" >> "$GITHUB_ENV"
shell: bash
+2 -17
View File
@@ -4,16 +4,6 @@ runs:
steps:
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v1.3.1
- name: Cache ccache directory
id: ccache-cache
uses: actions/cache@v4
with:
path: C:\a\rocksdb\rocksdb\.ccache
key: rocksdb-build-${{ runner.os }}-${{ runner.arch }}-ccache-${{ hashFiles('CMakeLists.txt', 'cmake/**/*.cmake') }}-v1
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2
with:
max-size: "10GB"
- name: Custom steps
env:
THIRDPARTY_HOME: ${{ github.workspace }}/thirdparty
@@ -48,12 +38,11 @@ runs:
$env:Path = $env:JAVA_HOME + ";" + $env:Path
mkdir build
cd build
& cmake -G "$Env:CMAKE_GENERATOR" -DCMAKE_BUILD_TYPE=Debug -DWIN_CI=1 -DPORTABLE="$Env:CMAKE_PORTABLE" -DSNAPPY=1 -DXPRESS=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"
# use more parallel processes than the number of processes available, as most of the compile command would be cache hit
msbuild build/rocksdb.sln /m:32 /p:LinkIncremental=false -property:Configuration=Debug -property:Platform=x64
msbuild build/rocksdb.sln -maxCpuCount -property:Configuration=Debug -property:Platform=x64
if(!$?) { Exit $LASTEXITCODE }
echo ========================= Test RocksDB =========================
build_tools\run_ci_db_test.ps1 -SuiteRun arena_test,db_basic_test,db_test,db_test2,db_merge_operand_test,bloom_test,c_test,coding_test,crc32c_test,dynamic_bloom_test,env_basic_test,env_test,hash_test,random_test -Concurrency 16
@@ -63,7 +52,3 @@ runs:
& ctest -C Debug -j 16
if(!$?) { Exit $LASTEXITCODE }
shell: pwsh
- name: Show ccache stats
shell: pwsh
run: |
ccache --show-stats -v
+4 -25
View File
@@ -58,7 +58,7 @@ jobs:
- uses: "./.github/actions/build-folly"
- run: LIB_MODE=static USE_CLANG=1 USE_FOLLY=1 COMPILE_WITH_UBSAN=1 COMPILE_WITH_ASAN=1 make -j32 check
- uses: "./.github/actions/post-steps"
build-linux-cmake-with-folly:
build-linux-valgrind:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
@@ -68,28 +68,7 @@ jobs:
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- uses: "./.github/actions/setup-folly"
- uses: "./.github/actions/cache-folly"
id: cache-folly
- uses: "./.github/actions/build-folly"
with:
cache-hit: ${{ steps.cache-folly.outputs.cache-hit }}
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 .. && make VERBOSE=1 -j20 && ctest -j20)"
- uses: "./.github/actions/post-steps"
build-linux-release-with-folly:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- uses: "./.github/actions/setup-folly"
- run: "DEBUG_LEVEL=0 make -j20 build_folly"
- run: "USE_FOLLY=1 LIB_MODE=static DEBUG_LEVEL=0 V=1 make -j20 release"
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 -DCMAKE_BUILD_TYPE=Release .. && make VERBOSE=1 -j20 && ctest -j20)"
- run: make V=1 -j32 valgrind_test
- uses: "./.github/actions/post-steps"
build-windows-vs2022-avx2:
if: ${{ github.repository_owner == 'facebook' }}
@@ -138,7 +117,7 @@ jobs:
- name: Build fuzzers
run: cd fuzz && make sst_file_writer_fuzzer db_fuzzer db_map_fuzzer
- uses: "./.github/actions/post-steps"
build-linux-cmake-with-folly-lite:
build-linux-cmake-with-folly-lite-no-test:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
@@ -149,5 +128,5 @@ jobs:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- uses: "./.github/actions/setup-folly"
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY_LITE=1 -DWITH_GFLAGS=1 -DCMAKE_CXX_FLAGS=-DGLOG_USE_GLOG_EXPORT .. && make VERBOSE=1 -j20 && ctest -j20)"
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY_LITE=1 -DWITH_GFLAGS=1 -DCMAKE_CXX_FLAGS=-DGLOG_USE_GLOG_EXPORT .. && make V=1 -j20)"
- uses: "./.github/actions/post-steps"
+37 -37
View File
@@ -92,6 +92,20 @@ jobs:
which javac && javac -version
mkdir build && cd build && cmake -DJNI=1 -DWITH_GFLAGS=OFF .. -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++ -DCMAKE_SYSTEM_NAME=Windows && make -j4 rocksdb rocksdbjni
- uses: "./.github/actions/post-steps"
build-linux-cmake-with-folly:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- uses: "./.github/actions/setup-folly"
- uses: "./.github/actions/build-folly"
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 .. && make V=1 -j20 && ctest -j20)"
- uses: "./.github/actions/post-steps"
build-linux-make-with-folly:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
@@ -103,11 +117,7 @@ jobs:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- uses: "./.github/actions/setup-folly"
- uses: "./.github/actions/cache-folly"
id: cache-folly
- uses: "./.github/actions/build-folly"
with:
cache-hit: ${{ steps.cache-folly.outputs.cache-hit }}
- run: USE_FOLLY=1 LIB_MODE=static V=1 make -j32 check
- uses: "./.github/actions/post-steps"
build-linux-make-with-folly-lite-no-test:
@@ -134,14 +144,10 @@ jobs:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- uses: "./.github/actions/setup-folly"
- uses: "./.github/actions/cache-folly"
id: cache-folly
- uses: "./.github/actions/build-folly"
with:
cache-hit: ${{ steps.cache-folly.outputs.cache-hit }}
- run: "(mkdir build && cd build && cmake -DUSE_COROUTINES=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 .. && make VERBOSE=1 -j20 && ctest -j20)"
- run: "(mkdir build && cd build && cmake -DUSE_COROUTINES=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 .. && make V=1 -j20 && ctest -j20)"
- uses: "./.github/actions/post-steps"
build-linux-cmake-with-benchmark-no-thread-status:
build-linux-cmake-with-benchmark:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
@@ -151,7 +157,7 @@ jobs:
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- run: mkdir build && cd build && cmake -DWITH_GFLAGS=1 -DWITH_BENCHMARK=1 -DCMAKE_CXX_FLAGS=-DNROCKSDB_THREAD_STATUS .. && make VERBOSE=1 -j20 && ctest -j20
- run: mkdir build && cd build && cmake -DWITH_GFLAGS=1 -DWITH_BENCHMARK=1 .. && make V=1 -j20 && ctest -j20
- uses: "./.github/actions/post-steps"
build-linux-encrypted_env-no_compression:
if: ${{ github.repository_owner == 'facebook' }}
@@ -322,57 +328,51 @@ jobs:
# ========================= MacOS build only ======================== #
build-macos:
if: ${{ github.repository_owner == 'facebook' }}
runs-on: macos-15-xlarge
runs-on: macos-13
env:
ROCKSDB_DISABLE_JEMALLOC: 1
steps:
- uses: actions/checkout@v4.1.0
- uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: 16.4.0
xcode-version: 14.3.1
- uses: "./.github/actions/increase-max-open-files-on-macos"
- uses: "./.github/actions/install-gflags-on-macos"
- uses: "./.github/actions/pre-steps-macos"
- name: Build
run: ulimit -S -n `ulimit -H -n` && make V=1 J=16 -j8 all
run: ulimit -S -n `ulimit -H -n` && make V=1 J=16 -j16 all
- uses: "./.github/actions/post-steps"
# ========================= MacOS with Tests ======================== #
build-macos-cmake:
if: ${{ github.repository_owner == 'facebook' }}
runs-on: macos-15-xlarge
runs-on: macos-13
strategy:
matrix:
run_sharded_tests: [0, 1, 2, 3]
run_even_tests: [true, false]
steps:
- uses: actions/checkout@v4.1.0
- uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: 16.4.0
xcode-version: 14.3.1
- uses: "./.github/actions/increase-max-open-files-on-macos"
- uses: "./.github/actions/install-gflags-on-macos"
- uses: "./.github/actions/pre-steps-macos"
- name: cmake generate project file
run: ulimit -S -n `ulimit -H -n` && mkdir build && cd build && cmake -DWITH_GFLAGS=1 ..
- name: Build tests
run: cd build && make VERBOSE=1 -j8
- name: Run shard 0 out of 4 test shards
run: ulimit -S -n `ulimit -H -n` && cd build && ctest -j8 -I 0,,4
if: ${{ matrix.run_sharded_tests == 0 }}
- name: Run shard 1 out of 4 test shards
run: ulimit -S -n `ulimit -H -n` && cd build && ctest -j8 -I 1,,4
if: ${{ matrix.run_sharded_tests == 1 }}
- name: Run shard 2 out of 4 test shards
run: ulimit -S -n `ulimit -H -n` && cd build && ctest -j8 -I 2,,4
if: ${{ matrix.run_sharded_tests == 2 }}
- name: Run shard 3 out of 4 test shards
run: ulimit -S -n `ulimit -H -n` && cd build && ctest -j8 -I 3,,4
if: ${{ matrix.run_sharded_tests == 3 }}
run: cd build && make V=1 -j16
- name: Run even tests
run: ulimit -S -n `ulimit -H -n` && cd build && ctest -j16 -I 0,,2
if: ${{ matrix.run_even_tests }}
- name: Run odd tests
run: ulimit -S -n `ulimit -H -n` && cd build && ctest -j16 -I 1,,2
if: ${{ ! matrix.run_even_tests }}
- uses: "./.github/actions/post-steps"
# ======================== Windows with Tests ======================= #
# NOTE: some windows jobs are in "nightly" to save resources
build-windows-vs2022:
if: ${{ github.repository_owner == 'facebook' }}
runs-on: windows-8-core
runs-on: windows-2022
env:
CMAKE_GENERATOR: Visual Studio 17 2022
CMAKE_PORTABLE: 1
@@ -444,7 +444,7 @@ jobs:
# post-steps skipped because of compatibility issues with docker image
build-macos-java:
if: ${{ github.repository_owner == 'facebook' }}
runs-on: macos-15-xlarge
runs-on: macos-13
env:
JAVA_HOME: "/Library/Java/JavaVirtualMachines/liberica-jdk-8.jdk/Contents/Home"
ROCKSDB_DISABLE_JEMALLOC: 1
@@ -452,7 +452,7 @@ jobs:
- uses: actions/checkout@v4.1.0
- uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: 16.4.0
xcode-version: 14.3.1
- uses: "./.github/actions/increase-max-open-files-on-macos"
- uses: "./.github/actions/install-gflags-on-macos"
- uses: "./.github/actions/install-jdk8-on-macos"
@@ -467,14 +467,14 @@ jobs:
- uses: "./.github/actions/post-steps"
build-macos-java-static:
if: ${{ github.repository_owner == 'facebook' }}
runs-on: macos-15-xlarge
runs-on: macos-13
env:
JAVA_HOME: "/Library/Java/JavaVirtualMachines/liberica-jdk-8.jdk/Contents/Home"
steps:
- uses: actions/checkout@v4.1.0
- uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: 16.4.0
xcode-version: 14.3.1
- uses: "./.github/actions/increase-max-open-files-on-macos"
- uses: "./.github/actions/install-gflags-on-macos"
- uses: "./.github/actions/install-jdk8-on-macos"
@@ -489,14 +489,14 @@ jobs:
- uses: "./.github/actions/post-steps"
build-macos-java-static-universal:
if: ${{ github.repository_owner == 'facebook' }}
runs-on: macos-15-xlarge
runs-on: macos-13
env:
JAVA_HOME: "/Library/Java/JavaVirtualMachines/liberica-jdk-8.jdk/Contents/Home"
steps:
- uses: actions/checkout@v4.1.0
- uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: 16.4.0
xcode-version: 14.3.1
- uses: "./.github/actions/increase-max-open-files-on-macos"
- uses: "./.github/actions/install-gflags-on-macos"
- uses: "./.github/actions/install-jdk8-on-macos"
-20
View File
@@ -1,20 +0,0 @@
name: facebook/rocksdb/weekly
on:
schedule:
- cron: 0 9 * * 0
workflow_dispatch:
permissions: {}
jobs:
build-linux-valgrind:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
timeout-minutes: 840
container:
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- run: make V=1 -j20 valgrind_test
- uses: "./.github/actions/post-steps"
+2 -11
View File
@@ -1,14 +1,12 @@
# This file @generated by:
#$ python3 buckifier/buckify_rocksdb.py
# --> DO NOT EDIT MANUALLY <--
# This file is a Meta-specific integration for buck builds, so can
# only be validated by Meta employees.
# This file is a Facebook-specific integration for buck builds, so can
# only be validated by Facebook employees.
load("//rocks/buckifier:defs.bzl", "cpp_library_wrapper","rocks_cpp_library_wrapper","cpp_binary_wrapper","cpp_unittest_wrapper","fancy_bench_wrapper","add_c_test_wrapper")
load("@fbcode_macros//build_defs:export_files.bzl", "export_file")
oncall("rocksdb_point_of_contact")
cpp_library_wrapper(name="rocksdb_lib", srcs=[
"cache/cache.cc",
"cache/cache_entry_roles.cc",
@@ -427,7 +425,6 @@ rocks_cpp_library_wrapper(name="rocksdb_stress_lib", srcs=[
"db_stress_tool/batched_ops_stress.cc",
"db_stress_tool/cf_consistency_stress.cc",
"db_stress_tool/db_stress_common.cc",
"db_stress_tool/db_stress_compaction_service.cc",
"db_stress_tool/db_stress_compression_manager.cc",
"db_stress_tool/db_stress_driver.cc",
"db_stress_tool/db_stress_filters.cc",
@@ -4846,12 +4843,6 @@ cpp_unittest_wrapper(name="db_encryption_test",
extra_compiler_flags=[])
cpp_unittest_wrapper(name="db_etc3_test",
srcs=["db/db_etc3_test.cc"],
deps=[":rocksdb_test_lib"],
extra_compiler_flags=[])
cpp_unittest_wrapper(name="db_flush_test",
srcs=["db/db_flush_test.cc"],
deps=[":rocksdb_test_lib"],
+15 -51
View File
@@ -80,7 +80,6 @@ if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE STRING
"Default BUILD_TYPE is ${default_build_type}" FORCE)
endif()
message(STATUS "CMAKE_BUILD_TYPE is set to ${CMAKE_BUILD_TYPE}")
find_program(CCACHE_FOUND ccache)
if(CCACHE_FOUND)
@@ -133,9 +132,7 @@ else()
option(WITH_GFLAGS "build with GFlags" ON)
endif()
set(GFLAGS_LIB)
# Skip all gflags detection and setup when USE_FOLLY or USE_COROUTINES is enabled
# since Folly provides its own gflags (USE_COROUTINES automatically sets USE_FOLLY)
if(WITH_GFLAGS AND NOT USE_FOLLY AND NOT USE_COROUTINES)
if(WITH_GFLAGS)
# Config with namespace available since gflags 2.2.2
option(GFLAGS_USE_TARGET_NAMESPACE "Use gflags import target with namespace." ON)
find_package(gflags CONFIG)
@@ -154,9 +151,6 @@ else()
include_directories(${GFLAGS_INCLUDE_DIR})
list(APPEND THIRDPARTY_LIBS ${GFLAGS_LIB})
add_definitions(-DGFLAGS=1)
elseif(WITH_GFLAGS AND (USE_FOLLY OR USE_COROUTINES))
# Still set the DGFLAGS=1 define when using Folly since Folly provides gflags
add_definitions(-DGFLAGS=1)
endif()
if(WITH_SNAPPY)
@@ -209,16 +203,9 @@ if(WIN32 AND MSVC)
endif()
endif()
option(WIN_CI "Accelerate build speed and reduce build artifect size for github CI with MSVC" OFF)
if(MSVC)
if(WIN_CI)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /nologo /EHsc /Gd /GR /GF /fp:precise /Zc:wchar_t /Zc:forScope /errorReport:queue")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FC /W4 /wd4127 /wd4996 /wd4100 /wd4324 /wd4702")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi /nologo /EHsc /GS /Gd /GR /GF /fp:precise /Zc:wchar_t /Zc:forScope /errorReport:queue")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FC /d2Zi+ /W4 /wd4127 /wd4996 /wd4100 /wd4324")
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi /nologo /EHsc /GS /Gd /GR /GF /fp:precise /Zc:wchar_t /Zc:forScope /errorReport:queue")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FC /d2Zi+ /W4 /wd4127 /wd4996 /wd4100 /wd4324")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wextra -Wall -pthread")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-compare -Wshadow -Wno-unused-parameter -Wno-unused-variable -Woverloaded-virtual -Wnon-virtual-dtor -Wno-missing-field-initializers -Wno-strict-aliasing -Wno-invalid-offsetof")
@@ -326,7 +313,7 @@ if(NOT MSVC)
endif()
# Check if -latomic is required or not
if (NOT MSVC AND NOT APPLE)
if (NOT MSVC)
CHECK_CXX_SOURCE_COMPILES("
#include <atomic>
std::atomic<uint64_t> x(0);
@@ -463,33 +450,24 @@ else()
endif()
endif()
# Used to run optimized debug build and tests so we can run faster
# Used to run CI build and tests so we can run faster
option(OPTDBG "Build optimized debug build with MSVC" OFF)
option(WITH_RUNTIME_DEBUG "build with debug version of runtime library" ON)
if(MSVC)
if (WIN_CI)
if(OPTDBG)
message(STATUS "Debug optimization is enabled")
set(CMAKE_CXX_FLAGS_DEBUG "/Oxt")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /DEBUG:FASTLINK")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG:FASTLINK")
else()
if(OPTDBG)
message(STATUS "Debug optimization is enabled")
set(CMAKE_CXX_FLAGS_DEBUG "/Oxt")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /RTC1")
# Minimal Build is deprecated after MSVC 2015
if( MSVC_VERSION GREATER 1900 )
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Gm-")
else()
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /RTC1")
# Minimal Build is deprecated after MSVC 2015
if( MSVC_VERSION GREATER 1900 )
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Gm-")
else()
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Gm")
endif()
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Gm")
endif()
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /DEBUG")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG")
endif()
endif()
if(WITH_RUNTIME_DEBUG)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /${RUNTIME_LIBRARY}d")
else()
@@ -497,6 +475,8 @@ if(MSVC)
endif()
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Oxt /Zp8 /Gm- /Gy /${RUNTIME_LIBRARY}")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /DEBUG")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG")
endif()
if(CMAKE_COMPILER_IS_GNUCXX)
@@ -648,12 +628,6 @@ if(USE_FOLLY)
${FOLLY_INST_PATH}/lib/cmake/folly/folly-targets.cmake)
include(${FOLLY_INST_PATH}/lib/cmake/folly/folly-config.cmake)
# Fix gflags library name for debug builds
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath=${GFLAGS_INST_PATH}/lib")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GFLAGS_INST_PATH}/lib/libgflags_debug.so.2.2")
endif()
endif()
add_compile_definitions(USE_FOLLY FOLLY_NO_CONFIG HAVE_CXX11_ATOMIC)
@@ -1094,21 +1068,12 @@ if(USE_FOLLY_LITE)
third-party/folly/folly/synchronization/DistributedMutex.cpp
third-party/folly/folly/synchronization/ParkingLot.cpp)
include_directories(${PROJECT_SOURCE_DIR}/third-party/folly)
# Add boost to the include path
exec_program(python3 ${PROJECT_SOURCE_DIR}/third-party/folly ARGS
build/fbcode_builder/getdeps.py show-source-dir boost OUTPUT_VARIABLE
BOOST_SOURCE_PATH)
exec_program(ls ARGS -d ${BOOST_SOURCE_PATH}/boost* OUTPUT_VARIABLE
BOOST_INCLUDE_DIR)
include_directories(${BOOST_INCLUDE_DIR})
# Add fmt to the include path
exec_program(python3 ${PROJECT_SOURCE_DIR}/third-party/folly ARGS
build/fbcode_builder/getdeps.py show-source-dir fmt OUTPUT_VARIABLE
FMT_SOURCE_PATH)
exec_program(ls ARGS -d ${FMT_SOURCE_PATH}/fmt*/include OUTPUT_VARIABLE
FMT_INCLUDE_DIR)
include_directories(${FMT_INCLUDE_DIR})
add_definitions(-DUSE_FOLLY -DFOLLY_NO_CONFIG)
list(APPEND THIRDPARTY_LIBS glog)
endif()
@@ -1380,7 +1345,6 @@ if(WITH_TESTS)
db/db_clip_test.cc
db/db_dynamic_level_test.cc
db/db_encryption_test.cc
db/db_etc3_test.cc
db/db_flush_test.cc
db/db_inplace_update_test.cc
db/db_io_failure_test.cc
-9
View File
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<PropertyGroup>
<CLToolExe>ccache_msvc_compiler.bat</CLToolExe>
<CLToolPath>$(MSBuildThisFileDirectory)</CLToolPath>
<UseMultiToolTask>true</UseMultiToolTask>
<EnforceProcessCountAcrossBuilds>true</EnforceProcessCountAcrossBuilds>
</PropertyGroup>
</Project>
+20 -45
View File
@@ -1,58 +1,32 @@
# Rocksdb Change Log
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
## 10.9.1 (12/11/2025)
## 10.7.6 (10/23/2025)
* Fix a bug when del range and data are in same file for multi-scan
## 10.7.5 (10/20/2025)
### Bug Fixes
* Fix resumable compaction incorrectly allowing resumption from a truncated range deletion that is not well handled currently.
## 10.9.0 (11/21/2025)
### New Features
* Added an auto-tuning feature for DB manifest file size that also (by default) improves the safety of existing configurations in case `max_manifest_file_size` is repeatedly exceeded. The new recommendation is to set `max_manifest_file_size` to something small like 1MB and tune `max_manifest_space_amp_pct` as needed to balance write amp and space amp in the manifest. Refer to comments on those options in `DBOptions` for details. Both options are (now) mutable.
* Added a new API to support option migration for multiple column families
* Added new option target_file_size_is_upper_bound that makes most compaction output SST files come close to the target file size without exceeding it, rather than commonly exceeding it by some fraction (current behavior). For now the new behavior is off by default, but we expect to enable it by default in the future.
* Add a new option allow_trivial_move in CompactionOptions to allow CompactFiles to perform trivial move if possible. By default the flag of allow_trivial_move is false, so it preserve the original behavior.
* Fix a bug in Page unpinning in MultiScan
## 10.7.4 (10/14/2025)
### Public API Changes
* To reduce risk of ODR violations or similar, `ROCKSDB_USING_THREAD_STATUS` has been removed from public headers and replaced with static `const bool ThreadStatus::kEnabled`. Some other uses of conditional compilation have been removed from public API headers to reduce risk of ODR violations or other issues.
### Behavior Changes
* PosixWritableFile now repositions the seek pointer to the new end of file after a call to Truncate.
* Updated standalone range deletion L0 file compaction behavior to avoid compacting with any newer L0 files (which is expensive and not useful).
### Bug Fixes
* Fix a bug where compaction with range deletion can persist kTypeMaxValid in MANIFEST as file metadata. kTypeMaxValid is not supposed to be persisted and can change as new value types are introduced. This can cause a forward compatibility issue where older versions of RocksDB don't recognize kTypeMaxValid from newer versions. A new placeholder value type kTypeTruncatedRangeDeletionSentinel is also introduced to replace kTypeMaxValid when reading existing SST files' metadata from MANIFEST. This allows us to strengthen some checks to avoid using kTypeMaxValid in the future.
* Fixed a bug where `DB::GetSortedWalFiles()` could hang when waiting for a purge operation that found nothing to do (potentially triggered by iterator release, flush, compaction, etc.).
* Fixed a bug in MultiScan where `max_sequential_skip_in_iterations` could cause the iterator to seek backward to already-unpinned blocks when the same user key spans multiple data blocks, leading to assertion failures or seg fault.
* Fixed a bug for `WAL_ttl_seconds > 0` use cases where the newest archived WAL files could be incorrectly deleted when the system clock moved backwards.
### Performance Improvements
* Added optimization that allowed for the asynchronous prefetching of all data outlined in a multiscan iterator. This optimization was applied to the level iterator, which prefetches all data through each of the block-based iterators.
## 10.8.0 (10/21/2025)
### New Features
* Add kFSPrefetch to FSSupportedOps enum to allow file systems to indicate prefetch support capability, avoiding unnecessary prefetch system calls on file systems that don't support them.
* Added experimental support `OpenAndCompactOptions::allow_resumption` for resumable compaction that persists progress during `OpenAndCompact()`, allowing interrupted compactions to resume from the last progress persitence. The default behavior is to not persist progress.
### Public API Changes
* Allow specifying output temperature in CompactionOptions
* Added `DB::FlushWAL(const FlushWALOptions&)` as an alternative to `DB::FlushWAL(bool sync)`, where `FlushWALOptions` includes a new `rate_limiter_priority` field (default `Env::IO_TOTAL`) that allows rate limiting and priority passing of manual WAL flush's IO operations.
* The MultiScan API contract is updated. After a multi scan range got prepared with Prepare API call, the following seeks must seek the start of each prepared scan range in order. In addition, when limit is set, upper bound must be set to the same value of limit before each seek
### Behavior Changes
* `kChangeTemperature` FIFO compaction will now honor `compaction_target_temp` to all levels regardless of `cf_options::last_level_temperature`
* Allow UDIs with a non BytewiseComparator
## 10.7.3 (10/06/2025)
### Bug Fixes
* Fix a few bugs in MultiScan
## 10.7.2 (09/30/2025)
### Bug Fixes
* Fix incorrect MultiScan seek error status due to bugs in handling range limit falling between adjacent SST files key range.
* Fix a bug in Page unpinning in MultiScan
### Performance Improvements
* Fixed a performance regression in LZ4 compression that started in version 10.6.0
## 10.7.0 (09/19/2025)
## 10.7.0 (09/24/2025)
### New Features
* Add the fail_if_no_udi_on_open flag in BlockBasedTableOption to control whether a missing user defined index block in a SST is a hard error or not.
* A new flag memtable_verify_per_key_checksum_on_seek is added to AdvancedColumnFamilyOptions. When it is enabled, it will validate key checksum along the binary search path on skiplist based memtable during seek operation.
* A new flag memtable_veirfy_per_key_checksum_on_seek is added to AdvancedColumnFamilyOptions. When it is enabled, it will validate key checksum along the binary search path on skiplist based memtable during seek operation.
* Introduce option MultiScanArgs::use_async_io to enable asynchronous I/O during MultiScan, instead of waiting for I/O to be done in Prepare().
* Add new option `MultiScanArgs::max_prefetch_size` that limits the memory usage of per file pinning of prefetched blocks.
* Improved `sst_dump` by allowing standalone file and directory arguments without `--file=`. Also added new options and better output for `sst_dump --command=recompress`. See `sst_dump --help`
@@ -64,6 +38,7 @@
### Behavior Changes
* The default provided block cache implementation is now HyperClockCache instead of LRUCache, when `block_cache` is nullptr (default) and `no_block_cache==false` (default). We recommend explicitly creating a HyperClockCache block cache based on memory budget and sharing it across all column families and even DB instances. This change could expose previously hidden memory or resource leaks.
* Allow UDIs with a non BytewiseComparator
### Bug Fixes
* Reported numbers for compaction and flush CPU usage now include time spent by parallel compression worker threads. This now means compaction/flush CPU usage could exceed the wall clock time.
@@ -110,7 +85,7 @@ Together, a new configuration TransactionOptions::deadlock_timeout_us is added,
* DB option skip_checking_sst_file_sizes_on_db_open is deprecated, in favor of validating file size in parallel in a thread pool, when db is opened. When DB is opened, with paranoid check enabled, a file with the wrong size would fail the DB open. With paranoid check disabled, the DB open would succeed, the column family with the corrupted file would not be read or write, while the other healthy column families could be read and write normally. When max_open_files option is not set to -1, only a subset of the files will be opened and checked. The rest of the files will be opened and checked when they are accessed.
### Behavior Changes
* PessimisticTransaction::GetWaitingTxns now returns waiting transaction information even if the current transaction has timed out. This allows the information to be surfaced to users for debugging purposes once it is known that the timeout has occurred.
* PessimisticTransaction::GetWaitingTxns now returns waiting transaction information even if the current transaction has timed out. This allows the information to be surfaced to users for debugging purposes once it is known that the timeout has occured.
* A new API GetFileSize is added to FSRandomAccessFile interface class. It uses fstat vs stat on the posix implementation which is more efficient. Caller could use it to get file size faster. This function might be required in the future for FileSystem implementation outside of the RocksDB code base.
* RocksDB now triggers eligible compactions every 12 hours when periodic compaction is configured. This solves a limitation of the compaction trigger mechanism, which would only trigger compaction after specific events like flush, compaction, or SetOptions.
@@ -167,7 +142,7 @@ system's prefetch) on SST file during compaction read
* Deprecated API `DB::MaxMemCompactionLevel()`.
* Deprecated `ReadOptions::ignore_range_deletions`.
* Deprecated API `experimental::PromoteL0()`.
* Added arbitrary string map for additional options to be overridden for remote compactions
* 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
@@ -317,7 +292,7 @@ system's prefetch) on SST file during compaction read
* In FIFO compaction, compactions for changing file temperature (configured by option `file_temperature_age_thresholds`) will compact one file at a time, instead of merging multiple eligible file together (#13018).
* Support ingesting db generated files using hard link, i.e. IngestExternalFileOptions::move_files/link_files and IngestExternalFileOptions::allow_db_generated_files.
* Add a new file ingestion option `IngestExternalFileOptions::link_files` to hard link input files and preserve original files links after ingestion.
* DB::Close now untracks files in SstFileManager, making available any space used
* DB::Close now untracks files in SstFileManager, making avaialble any space used
by them. Prior to this change they would be orphaned until the DB is re-opened.
### Bug Fixes
@@ -513,7 +488,7 @@ MultiGetBenchmarks.multiGetList10 no_column_family 10000 16 100 1024 thrpt 25 76
* Removed deprecated option `ColumnFamilyOptions::check_flush_compaction_key_order`
* Remove the default `WritableFile::GetFileSize` and `FSWritableFile::GetFileSize` implementation that returns 0 and make it pure virtual, so that subclasses are enforced to explicitly provide an implementation.
* Removed deprecated option `ColumnFamilyOptions::level_compaction_dynamic_file_size`
* Removed tickers with typos "rocksdb.error.handler.bg.error.count", "rocksdb.error.handler.bg.io.error.count", "rocksdb.error.handler.bg.retryable.io.error.count".
* Removed tickers with typos "rocksdb.error.handler.bg.errro.count", "rocksdb.error.handler.bg.io.errro.count", "rocksdb.error.handler.bg.retryable.io.errro.count".
* Remove the force mode for `EnableFileDeletions` API because it is unsafe with no known legitimate use.
* Removed deprecated option `ColumnFamilyOptions::ignore_max_compaction_bytes_for_input`
* `sst_dump --command=check` now compares the number of records in a table with `num_entries` in table property, and reports corruption if there is a mismatch. API `SstFileDumper::ReadSequential()` is updated to optionally do this verification. (#12322)
@@ -540,7 +515,7 @@ MultiGetBenchmarks.multiGetList10 no_column_family 10000 16 100 1024 thrpt 25 76
* Exposed options ttl via c api.
### Behavior Changes
* `rocksdb.blobdb.blob.file.write.micros` expands to also measure time writing the header and footer. Therefore the COUNT may be higher and values may be smaller than before. For stacked BlobDB, it no longer measures the time of explicitly flushing blob file.
* `rocksdb.blobdb.blob.file.write.micros` expands to also measure time writing the header and footer. Therefore the COUNT may be higher and values may be smaller than before. For stacked BlobDB, it no longer measures the time of explictly flushing blob file.
* Files will be compacted to the next level if the data age exceeds periodic_compaction_seconds except for the last level.
* Reduced the compaction debt ratio trigger for scheduling parallel compactions
* For leveled compaction with default compaction pri (kMinOverlappingRatio), files marked for compaction will be prioritized over files not marked when picking a file from a level for compaction.
@@ -605,7 +580,7 @@ want to continue to use force enabling, they need to explicitly pass a `true` to
### Behavior Changes
* During off-peak hours defined by `daily_offpeak_time_utc`, the compaction picker will select a larger number of files for periodic compaction. This selection will include files that are projected to expire by the next off-peak start time, ensuring that these files are not chosen for periodic compaction outside of off-peak hours.
* If an error occurs when writing to a trace file after `DB::StartTrace()`, the subsequent trace writes are skipped to avoid writing to a file that has previously seen error. In this case, `DB::EndTrace()` will also return a non-ok status with info about the error occurred previously in its status message.
* If an error occurs when writing to a trace file after `DB::StartTrace()`, the subsequent trace writes are skipped to avoid writing to a file that has previously seen error. In this case, `DB::EndTrace()` will also return a non-ok status with info about the error occured previously in its status message.
* Deleting stale files upon recovery are delegated to SstFileManger if available so they can be rate limited.
* Make RocksDB only call `TablePropertiesCollector::Finish()` once.
* When `WAL_ttl_seconds > 0`, we now process archived WALs for deletion at least every `WAL_ttl_seconds / 2` seconds. Previously it could be less frequent in case of small `WAL_ttl_seconds` values when size-based expiration (`WAL_size_limit_MB > 0 `) was simultaneously enabled.
@@ -1393,7 +1368,7 @@ Note: The next release will be major release 7.0. See https://github.com/faceboo
### Public API change
* Extend WriteBatch::AssignTimestamp and AssignTimestamps API so that both functions can accept an optional `checker` argument that performs additional checking on timestamp sizes.
* Introduce a new EventListener callback that will be called upon the end of automatic error recovery.
* Add IncreaseFullHistoryTsLow API so users can advance each column family's full_history_ts_low separately.
* Add IncreaseFullHistoryTsLow API so users can advance each column family's full_history_ts_low seperately.
* Add GetFullHistoryTsLow API so users can query current full_history_low value of specified column family.
### Performance Improvements
+111 -5
View File
@@ -446,7 +446,83 @@ else
PLATFORM_CXXFLAGS += -isystem $(GTEST_DIR)
endif
include folly.mk
# This provides a Makefile simulation of a Meta-internal folly integration.
# It is not validated for general use.
#
# USE_FOLLY links the build targets with libfolly.a. The latter could be
# built using 'make build_folly', or built externally and specified in
# the CXXFLAGS and EXTRA_LDFLAGS env variables. The build_detect_platform
# script tries to detect if an external folly dependency has been specified.
# If not, it exports FOLLY_PATH to the path of the installed Folly and
# dependency libraries.
#
# USE_FOLLY_LITE cherry picks source files from Folly to include in the
# RocksDB library. Its faster and has fewer dependencies on 3rd party
# libraries, but with limited functionality. For example, coroutine
# functionality is not available.
ifeq ($(USE_FOLLY),1)
ifeq ($(USE_FOLLY_LITE),1)
$(error Please specify only one of USE_FOLLY and USE_FOLLY_LITE)
endif
ifneq ($(strip $(FOLLY_PATH)),)
BOOST_PATH = $(shell (ls -d $(FOLLY_PATH)/../boost*))
DBL_CONV_PATH = $(shell (ls -d $(FOLLY_PATH)/../double-conversion*))
GFLAGS_PATH = $(shell (ls -d $(FOLLY_PATH)/../gflags*))
GLOG_PATH = $(shell (ls -d $(FOLLY_PATH)/../glog*))
LIBEVENT_PATH = $(shell (ls -d $(FOLLY_PATH)/../libevent*))
XZ_PATH = $(shell (ls -d $(FOLLY_PATH)/../xz*))
LIBSODIUM_PATH = $(shell (ls -d $(FOLLY_PATH)/../libsodium*))
FMT_PATH = $(shell (ls -d $(FOLLY_PATH)/../fmt*))
# For some reason, glog and fmt libraries are under either lib or lib64
GLOG_LIB_PATH = $(shell (ls -d $(GLOG_PATH)/lib*))
FMT_LIB_PATH = $(shell (ls -d $(FMT_PATH)/lib*))
# AIX: pre-defined system headers are surrounded by an extern "C" block
ifeq ($(PLATFORM), OS_AIX)
PLATFORM_CCFLAGS += -I$(BOOST_PATH)/include -I$(DBL_CONV_PATH)/include -I$(GLOG_PATH)/include -I$(LIBEVENT_PATH)/include -I$(XZ_PATH)/include -I$(LIBSODIUM_PATH)/include -I$(FOLLY_PATH)/include -I$(FMT_PATH)/include
PLATFORM_CXXFLAGS += -I$(BOOST_PATH)/include -I$(DBL_CONV_PATH)/include -I$(GLOG_PATH)/include -I$(LIBEVENT_PATH)/include -I$(XZ_PATH)/include -I$(LIBSODIUM_PATH)/include -I$(FOLLY_PATH)/include -I$(FMT_PATH)/include
else
PLATFORM_CCFLAGS += -isystem $(BOOST_PATH)/include -isystem $(DBL_CONV_PATH)/include -isystem $(GLOG_PATH)/include -isystem $(LIBEVENT_PATH)/include -isystem $(XZ_PATH)/include -isystem $(LIBSODIUM_PATH)/include -isystem $(FOLLY_PATH)/include -isystem $(FMT_PATH)/include
PLATFORM_CXXFLAGS += -isystem $(BOOST_PATH)/include -isystem $(DBL_CONV_PATH)/include -isystem $(GLOG_PATH)/include -isystem $(LIBEVENT_PATH)/include -isystem $(XZ_PATH)/include -isystem $(LIBSODIUM_PATH)/include -isystem $(FOLLY_PATH)/include -isystem $(FMT_PATH)/include
endif
# Add -ldl at the end as gcc resolves a symbol in a library by searching only in libraries specified later
# in the command line
PLATFORM_LDFLAGS += $(FOLLY_PATH)/lib/libfolly.a $(BOOST_PATH)/lib/libboost_context.a $(BOOST_PATH)/lib/libboost_filesystem.a $(BOOST_PATH)/lib/libboost_atomic.a $(BOOST_PATH)/lib/libboost_program_options.a $(BOOST_PATH)/lib/libboost_regex.a $(BOOST_PATH)/lib/libboost_system.a $(BOOST_PATH)/lib/libboost_thread.a $(DBL_CONV_PATH)/lib/libdouble-conversion.a $(FMT_LIB_PATH)/libfmt.a $(GLOG_LIB_PATH)/libglog.so $(GFLAGS_PATH)/lib/libgflags.so.2.2 $(LIBEVENT_PATH)/lib/libevent-2.1.so -ldl
PLATFORM_LDFLAGS += -Wl,-rpath=$(GFLAGS_PATH)/lib -Wl,-rpath=$(GLOG_LIB_PATH) -Wl,-rpath=$(LIBEVENT_PATH)/lib -Wl,-rpath=$(LIBSODIUM_PATH)/lib -Wl,-rpath=$(LIBEVENT_PATH)/lib
endif
PLATFORM_CCFLAGS += -DUSE_FOLLY -DFOLLY_NO_CONFIG
PLATFORM_CXXFLAGS += -DUSE_FOLLY -DFOLLY_NO_CONFIG
endif
ifeq ($(USE_FOLLY_LITE),1)
# Path to the Folly source code and include files
FOLLY_DIR = ./third-party/folly
ifneq ($(strip $(BOOST_SOURCE_PATH)),)
BOOST_INCLUDE = $(shell (ls -d $(BOOST_SOURCE_PATH)/boost*/))
# AIX: pre-defined system headers are surrounded by an extern "C" block
ifeq ($(PLATFORM), OS_AIX)
PLATFORM_CCFLAGS += -I$(BOOST_INCLUDE)
PLATFORM_CXXFLAGS += -I$(BOOST_INCLUDE)
else
PLATFORM_CCFLAGS += -isystem $(BOOST_INCLUDE)
PLATFORM_CXXFLAGS += -isystem $(BOOST_INCLUDE)
endif
endif # BOOST_SOURCE_PATH
# AIX: pre-defined system headers are surrounded by an extern "C" block
ifeq ($(PLATFORM), OS_AIX)
PLATFORM_CCFLAGS += -I$(FOLLY_DIR)
PLATFORM_CXXFLAGS += -I$(FOLLY_DIR)
else
PLATFORM_CCFLAGS += -isystem $(FOLLY_DIR)
PLATFORM_CXXFLAGS += -isystem $(FOLLY_DIR)
endif
PLATFORM_CCFLAGS += -DUSE_FOLLY -DFOLLY_NO_CONFIG
PLATFORM_CXXFLAGS += -DUSE_FOLLY -DFOLLY_NO_CONFIG
# TODO: fix linking with fbcode compiler config
PLATFORM_LDFLAGS += -lglog
endif
ifdef TEST_CACHE_LINE_SIZE
PLATFORM_CCFLAGS += -DTEST_CACHE_LINE_SIZE=$(TEST_CACHE_LINE_SIZE)
@@ -607,7 +683,6 @@ am__v_CCH_1 =
%.h.pub: %.h # .h.pub not actually created, so re-checked on each invocation
$(AM_V_CCH) cd include/ && echo '#include "$(patsubst include/%,%,$<)"' | \
$(CXX) -std=$(or $(ROCKSDB_CXX_STANDARD),c++20) -I. -DROCKSDB_NAMESPACE=42 -x c++ -c - -o /dev/null
build_tools/check-public-header.sh $<
check-headers: $(HEADER_OK_FILES)
@@ -1421,9 +1496,6 @@ db_test: $(OBJ_DIR)/db/db_test.o $(TEST_LIBRARY) $(LIBRARY)
db_test2: $(OBJ_DIR)/db/db_test2.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
db_etc3_test: $(OBJ_DIR)/db/db_etc3_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
compression_test: $(OBJ_DIR)/util/compression_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
@@ -2420,6 +2492,40 @@ commit_prereq:
false # J=$(J) build_tools/precommit_checker.py unit clang_unit release clang_release tsan asan ubsan lite unit_non_shm
# $(MAKE) clean && $(MAKE) jclean && $(MAKE) rocksdbjava;
# For public CI runs, checkout folly in a way that can build with RocksDB.
# This is mostly intended as a test-only simulation of Meta-internal folly
# integration.
checkout_folly:
if [ -e third-party/folly ]; then \
cd third-party/folly && ${GIT_COMMAND} fetch origin; \
else \
cd third-party && ${GIT_COMMAND} clone https://github.com/facebook/folly.git; \
fi
@# Pin to a particular version for public CI, so that PR authors don't
@# need to worry about folly breaking our integration. Update periodically
cd third-party/folly && git reset --hard e95383b7c8b5b1e46cf47acf2f317d54f93c8268
@# Apparently missing include
perl -pi -e 's/(#include <atomic>)/$$1\n#include <cstring>/' third-party/folly/folly/lang/Exception.h
@# Warning-as-error on memcpy
perl -pi -e 's/memcpy.&ptr/memcpy((void*)&ptr/' third-party/folly/folly/lang/Exception.cpp
@# const mismatch
perl -pi -e 's/: environ/: (const char**)(environ)/' third-party/folly/folly/Subprocess.cpp
@# NOTE: boost source will be needed for any build including `USE_FOLLY_LITE` builds as those depend on boost headers
cd third-party/folly && $(PYTHON) build/fbcode_builder/getdeps.py fetch boost
CXX_M_FLAGS = $(filter -m%, $(CXXFLAGS))
build_folly:
FOLLY_INST_PATH=`cd third-party/folly; $(PYTHON) build/fbcode_builder/getdeps.py show-inst-dir`; \
if [ "$$FOLLY_INST_PATH" ]; then \
rm -rf $${FOLLY_INST_PATH}/../../*; \
else \
echo "Please run checkout_folly first"; \
false; \
fi
cd third-party/folly && \
CXXFLAGS=" $(CXX_M_FLAGS) -DHAVE_CXX11_ATOMIC " $(PYTHON) build/fbcode_builder/getdeps.py build --no-tests
# ---------------------------------------------------------------------------
# Build size testing
# ---------------------------------------------------------------------------
-3
View File
@@ -135,9 +135,6 @@ def generate_buck(repo_path, deps_map):
BUCK = TARGETSBuilder("%s/BUCK" % repo_path, extra_argv)
# Add oncall("rocksdb_point_of_contact") at the top
BUCK.add_oncall("rocksdb_point_of_contact")
# rocksdb_lib
BUCK.add_library(
"rocksdb_lib",
-5
View File
@@ -45,11 +45,6 @@ class TARGETSBuilder:
self.total_bin = 0
self.total_test = 0
self.tests_cfg = ""
def add_oncall(self, oncall):
with open(self.path, "ab") as targets_file:
targets_file.write(targets_cfg.oncall_template.format(name=oncall).encode("utf-8"))
def add_library(
self,
+3 -10
View File
@@ -1,12 +1,10 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
# This source code is licensed under both the GPLv2 (found in the COPYING file in the root directory)
# and the Apache 2.0 License (found in the LICENSE.Apache file in the root directory).
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
rocksdb_target_header_template = """# This file \100generated by:
#$ python3 buckifier/buckify_rocksdb.py{extra_argv}
# --> DO NOT EDIT MANUALLY <--
# This file is a Meta-specific integration for buck builds, so can
# only be validated by Meta employees.
# This file is a Facebook-specific integration for buck builds, so can
# only be validated by Facebook employees.
load("//rocks/buckifier:defs.bzl", "cpp_library_wrapper","rocks_cpp_library_wrapper","cpp_binary_wrapper","cpp_unittest_wrapper","fancy_bench_wrapper","add_c_test_wrapper")
load("@fbcode_macros//build_defs:export_files.bzl", "export_file")
@@ -43,8 +41,3 @@ fancy_bench_wrapper(suite_name="{name}", binary_to_bench_to_metric_list_map={ben
export_file_template = """
export_file(name = "{name}")
"""
oncall_template = """
oncall("{name}")
"""
+2 -6
View File
@@ -311,8 +311,7 @@ EOF
EOF
then
COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=1"
# Hack: don't link extra gflags assuming it comes with folly
[ "$USE_FOLLY" ] || PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
# check if namespace is gflags
elif $CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o 2>/dev/null << EOF
#include <gflags/gflags.h>
@@ -321,8 +320,7 @@ EOF
EOF
then
COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=1 -DGFLAGS_NAMESPACE=gflags"
# Hack: don't link extra gflags assuming it comes with folly
[ "$USE_FOLLY" ] || PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
# check if namespace is google
elif $CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o 2>/dev/null << EOF
#include <gflags/gflags.h>
@@ -774,7 +772,6 @@ fi
if [ "$USE_FOLLY_LITE" ]; then
if [ "$FOLLY_DIR" ]; then
BOOST_SOURCE_PATH=`cd $FOLLY_DIR && $PYTHON build/fbcode_builder/getdeps.py show-source-dir boost`
FMT_SOURCE_PATH=`cd $FOLLY_DIR && $PYTHON build/fbcode_builder/getdeps.py show-source-dir fmt`
fi
fi
@@ -819,7 +816,6 @@ echo "FIND=$FIND" >> "$OUTPUT"
echo "WATCH=$WATCH" >> "$OUTPUT"
echo "FOLLY_PATH=$FOLLY_PATH" >> "$OUTPUT"
echo "BOOST_SOURCE_PATH=$BOOST_SOURCE_PATH" >> "$OUTPUT"
echo "FMT_SOURCE_PATH=$FMT_SOURCE_PATH" >> "$OUTPUT"
# This will enable some related identifiers for the preprocessor
if test -n "$JEMALLOC"; then
-31
View File
@@ -1,31 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) Meta Platforms, Inc. and affiliates. All Rights Reserved.
#
# Check for some simple mistakes in public headers (on the command line)
# that should prevent commit or push
BAD=""
# Look for potential for ODR violations caused by public headers depending on
# build parameters that could vary between RocksDB build and application build.
# * Cases like LUA, ROCKSDB_NAMESPACE, and ROCKSDB_ASSERT_STATUS_CHECKED are
# intentional, hard to avoid. (We expect definitions to change and the user
# should also.)
# * Cases like _WIN32, OS_WIN, and __cplusplus are essentially ODR-safe.
# * Cases like
# #ifdef BLAH // ODR-SAFE
# #undef BLAH
# #endif
# that should not cause ODR violations can be exempted with the ODR-SAFE
# marker recognized here.
grep -nHE '^#if' -- "$@" | grep -vE 'ROCKSDB_NAMESPACE|ROCKSDB_ASSERT_STATUS_CHECKED|LUA|_WIN32|OS_WIN|ODR-SAFE|__cplusplus|ROCKSDB_DLL|ROCKSDB_LIBRARY_EXPORTS'
if [ "$?" != "1" ]; then
echo "^^^^^ #if in public API could cause an ODR violation."
echo " Add // ODR-SAFE if verified safe."
BAD=1
fi
if [ "$BAD" ]; then
exit 1
fi
-64
View File
@@ -148,70 +148,6 @@ else
echo "Checking format of uncommitted changes..."
fi
# Check for missing copyright in new files
echo "Checking for copyright headers in new files..."
# Get list of new files (added, not just modified)
if [ -z "$uncommitted_code" ]; then
# Post-commit: check files added since merge base
new_files=$(git diff --name-only --diff-filter=A "$FORMAT_UPSTREAM_MERGE_BASE" -- '*.h' '*.cc' '*.py' $EXCLUDE)
else
# Pre-commit: check staged new files
new_files=$(git diff --name-only --diff-filter=A --cached HEAD -- '*.h' '*.cc' '*.py' $EXCLUDE)
fi
if [ -n "$new_files" ]; then
files_missing_copyright=""
for file in $new_files; do
if [ -f "$file" ]; then
# Check if file is missing copyright
# For .py files, check for Python-style comment
# For .h and .cc files, check for C++-style comment
if [[ "$file" == *.py ]]; then
if ! grep -q "Copyright (c) Meta Platforms, Inc. and affiliates" "$file"; then
files_missing_copyright="$files_missing_copyright $file"
# Add copyright header to Python file
temp_file=$(mktemp)
{
echo "# Copyright (c) Meta Platforms, Inc. and affiliates."
echo "# This source code is licensed under both the GPLv2 (found in the COPYING file in the root directory)"
echo "# and the Apache 2.0 License (found in the LICENSE.Apache file in the root directory)."
echo
cat "$file"
} > "$temp_file"
mv "$temp_file" "$file"
echo "Added copyright header to $file"
fi
elif [[ "$file" == *.h ]] || [[ "$file" == *.cc ]]; then
if ! grep -q "Copyright (c) Meta Platforms, Inc. and affiliates" "$file"; then
files_missing_copyright="$files_missing_copyright $file"
# Add copyright header to C++ file
temp_file=$(mktemp)
{
echo "// Copyright (c) Meta Platforms, Inc. and affiliates. "
echo "// This source code is licensed under both the GPLv2 (found in the "
echo "// COPYING file in the root directory) and Apache 2.0 License "
echo "// (found in the LICENSE.Apache file in the root directory)."
echo
cat "$file"
} > "$temp_file"
mv "$temp_file" "$file"
echo "Added copyright header to $file"
fi
fi
fi
done
if [ -n "$files_missing_copyright" ]; then
echo "Copyright headers were added to new files."
else
echo "All new files have copyright headers."
fi
else
echo "No new files to check for copyright headers."
fi
if [ -z "$diffs" ]
then
echo "Nothing needs to be reformatted!"
+1 -1
View File
@@ -119,7 +119,7 @@ DEFINE_uint32(seed, 0, "Hashing/random seed to use. 0 = choose at random");
DEFINE_string(secondary_cache_uri, "",
"Full URI for creating a custom secondary cache object");
DEFINE_string(cache_type, "hyper_clock_cache", "Type of block cache.");
DEFINE_string(cache_type, "lru_cache", "Type of block cache.");
DEFINE_bool(use_jemalloc_no_dump_allocator, false,
"Whether to use JemallocNoDumpAllocator");
+4 -4
View File
@@ -33,7 +33,7 @@ const char* kTieredCacheName = "TieredCache";
// proportionally across the primary/secondary caches.
//
// The primary block cache is initially sized to the sum of the primary cache
// budget + the secondary cache budget, as follows -
// budget + teh secondary cache budget, as follows -
// |--------- Primary Cache Configured Capacity -----------|
// |---Secondary Cache Budget----|----Primary Cache Budget-----|
//
@@ -51,7 +51,7 @@ const char* kTieredCacheName = "TieredCache";
// placeholder is counted against the primary cache. To compensate and count
// a portion of it against the secondary cache, the secondary cache Deflate()
// method is called to shrink it. Since the Deflate() causes the secondary
// actual usage to shrink, it is reflected here by releasing an equal amount
// actual usage to shrink, it is refelcted here by releasing an equal amount
// from the pri_cache_res_ reservation. The Deflate() in the secondary cache
// can be, but is not required to be, implemented using its own cache
// reservation manager.
@@ -72,7 +72,7 @@ const char* kTieredCacheName = "TieredCache";
// reservation is increased by an equal amount.
//
// Another way of implementing this would have been to simply split the user
// reservation into primary and secondary components. However, this would
// reservation into primary and seconary components. However, this would
// require allocating a structure to track the associated secondary cache
// reservation, which adds some complexity and overhead.
//
@@ -621,7 +621,7 @@ Status CacheWithSecondaryAdapter::UpdateCacheReservationRatio(
} else {
// We're shrinking the ratio. Try to avoid unnecessary evictions -
// 1. Lower the secondary cache capacity
// 2. Decrease pri_cache_res_ reservation to reflect lower secondary
// 2. Decrease pri_cache_res_ reservation to relect lower secondary
// cache utilization (decrease in capacity - decrease in share of cache
// reservations)
// 3. Inflate the secondary cache to give it back the reduction in its
-1
View File
@@ -1 +0,0 @@
ccache.exe cl.exe %*
+130 -366
View File
@@ -635,11 +635,10 @@ static bool SaveError(char** errptr, const Status& s) {
return true;
}
// Helper function to copy string data to a malloc'd buffer
// Works with std::string, Slice, and PinnableSlice through implicit conversion
static inline char* CopyString(const Slice& slice) {
char* result = reinterpret_cast<char*>(malloc(slice.size()));
memcpy(result, slice.data(), slice.size());
// Copies str to a new malloc()-ed buffer. The buffer is not NUL terminated.
static char* CopyString(const std::string& str) {
char* result = reinterpret_cast<char*>(malloc(sizeof(char) * str.size()));
memcpy(result, str.data(), sizeof(char) * str.size());
return result;
}
@@ -1441,14 +1440,11 @@ char* rocksdb_get(rocksdb_t* db, const rocksdb_readoptions_t* options,
const char* key, size_t keylen, size_t* vallen,
char** errptr) {
char* result = nullptr;
// Use PinnableSlice to avoid unnecessary copy
PinnableSlice pinnable_val;
Status s = db->rep->Get(options->rep, db->rep->DefaultColumnFamily(),
Slice(key, keylen), &pinnable_val);
std::string tmp;
Status s = db->rep->Get(options->rep, Slice(key, keylen), &tmp);
if (s.ok()) {
*vallen = pinnable_val.size();
// Only one copy: from PinnableSlice to malloc'd buffer
result = CopyString(pinnable_val);
*vallen = tmp.size();
result = CopyString(tmp);
} else {
*vallen = 0;
if (!s.IsNotFound()) {
@@ -1463,14 +1459,12 @@ char* rocksdb_get_cf(rocksdb_t* db, const rocksdb_readoptions_t* options,
const char* key, size_t keylen, size_t* vallen,
char** errptr) {
char* result = nullptr;
// Use PinnableSlice to avoid unnecessary copy
PinnableSlice pinnable_val;
Status s = db->rep->Get(options->rep, column_family->rep, Slice(key, keylen),
&pinnable_val);
std::string tmp;
Status s =
db->rep->Get(options->rep, column_family->rep, Slice(key, keylen), &tmp);
if (s.ok()) {
*vallen = pinnable_val.size();
// Only one copy: from PinnableSlice to malloc'd buffer
result = CopyString(pinnable_val);
*vallen = tmp.size();
result = CopyString(tmp);
} else {
*vallen = 0;
if (!s.IsNotFound()) {
@@ -1543,17 +1537,12 @@ void rocksdb_multi_get(rocksdb_t* db, const rocksdb_readoptions_t* options,
size_t num_keys, const char* const* keys_list,
const size_t* keys_list_sizes, char** values_list,
size_t* values_list_sizes, char** errs) {
// Use unique_ptr for efficiency (avoids vector overhead for fixed-size array)
std::unique_ptr<Slice[]> keys(new Slice[num_keys]);
std::vector<Slice> keys(num_keys);
for (size_t i = 0; i < num_keys; i++) {
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
// Use PinnableSlice to avoid unnecessary allocations
auto cfh = db->rep->DefaultColumnFamily();
std::vector<PinnableSlice> values(num_keys);
std::vector<Status> statuses(num_keys);
db->rep->MultiGet(options->rep, cfh, num_keys, keys.get(), values.data(),
statuses.data());
std::vector<std::string> values(num_keys);
std::vector<Status> statuses = db->rep->MultiGet(options->rep, keys, &values);
for (size_t i = 0; i < num_keys; i++) {
if (statuses[i].ok()) {
values_list[i] = CopyString(values[i]);
@@ -1578,13 +1567,10 @@ void rocksdb_multi_get_with_ts(rocksdb_t* db,
char** values_list, size_t* values_list_sizes,
char** timestamp_list,
size_t* timestamp_list_sizes, char** errs) {
// Use unique_ptr for efficiency
std::unique_ptr<Slice[]> keys_arr(new Slice[num_keys]);
std::vector<Slice> keys(num_keys);
for (size_t i = 0; i < num_keys; i++) {
keys_arr[i] = Slice(keys_list[i], keys_list_sizes[i]);
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
// Note: MultiGet with timestamps only has vector-based API
std::vector<Slice> keys(keys_arr.get(), keys_arr.get() + num_keys);
std::vector<std::string> values(num_keys);
std::vector<std::string> timestamps(num_keys);
std::vector<Status> statuses =
@@ -1616,19 +1602,15 @@ void rocksdb_multi_get_cf(
size_t num_keys, const char* const* keys_list,
const size_t* keys_list_sizes, char** values_list,
size_t* values_list_sizes, char** errs) {
// Use unique_ptr for efficiency (avoids vector overhead for fixed-size
// arrays)
std::unique_ptr<Slice[]> keys(new Slice[num_keys]);
std::unique_ptr<ColumnFamilyHandle*[]> cfs(new ColumnFamilyHandle*[num_keys]);
std::vector<Slice> keys(num_keys);
std::vector<ColumnFamilyHandle*> cfs(num_keys);
for (size_t i = 0; i < num_keys; i++) {
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
cfs[i] = column_families[i]->rep;
}
// Use PinnableSlice to avoid unnecessary allocations
std::vector<PinnableSlice> values(num_keys);
std::vector<Status> statuses(num_keys);
db->rep->MultiGet(options->rep, num_keys, cfs.get(), keys.get(),
values.data(), statuses.data());
std::vector<std::string> values(num_keys);
std::vector<Status> statuses =
db->rep->MultiGet(options->rep, cfs, keys, &values);
for (size_t i = 0; i < num_keys; i++) {
if (statuses[i].ok()) {
values_list[i] = CopyString(values[i]);
@@ -1653,20 +1635,16 @@ void rocksdb_multi_get_cf_with_ts(
const size_t* keys_list_sizes, char** values_list,
size_t* values_list_sizes, char** timestamps_list,
size_t* timestamps_list_sizes, char** errs) {
// Use unique_ptr for efficiency (avoids vector overhead for fixed-size
// arrays)
std::unique_ptr<Slice[]> keys(new Slice[num_keys]);
std::unique_ptr<ColumnFamilyHandle*[]> cfs(new ColumnFamilyHandle*[num_keys]);
std::vector<Slice> keys(num_keys);
std::vector<ColumnFamilyHandle*> cfs(num_keys);
for (size_t i = 0; i < num_keys; i++) {
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
cfs[i] = column_families[i]->rep;
}
// Use PinnableSlice to avoid unnecessary allocations
std::vector<PinnableSlice> values(num_keys);
std::vector<std::string> values(num_keys);
std::vector<std::string> timestamps(num_keys);
std::vector<Status> statuses(num_keys);
db->rep->MultiGet(options->rep, num_keys, cfs.get(), keys.get(),
values.data(), timestamps.data(), statuses.data());
std::vector<Status> statuses =
db->rep->MultiGet(options->rep, cfs, keys, &values, &timestamps);
for (size_t i = 0; i < num_keys; i++) {
if (statuses[i].ok()) {
values_list[i] = CopyString(values[i]);
@@ -1725,41 +1703,6 @@ void rocksdb_batched_multi_get_cf(rocksdb_t* db,
delete[] statuses;
}
// Batched MultiGet that takes pre-built Slice array, avoiding key conversion
// overhead
void rocksdb_batched_multi_get_cf_slice(
rocksdb_t* db, const rocksdb_readoptions_t* options,
rocksdb_column_family_handle_t* column_family, size_t num_keys,
const rocksdb_slice_t* keys_list, rocksdb_pinnableslice_t** values,
char** errs, const bool sorted_input) {
PinnableSlice* value_slices = new PinnableSlice[num_keys];
Status* statuses = new Status[num_keys];
// Cast rocksdb_slice_t* to Slice* - they have identical memory layout
const Slice* key_slices = reinterpret_cast<const Slice*>(keys_list);
db->rep->MultiGet(options->rep, column_family->rep, num_keys, key_slices,
value_slices, statuses, sorted_input);
for (size_t i = 0; i < num_keys; ++i) {
if (statuses[i].ok()) {
values[i] = new (rocksdb_pinnableslice_t);
values[i]->rep = std::move(value_slices[i]);
errs[i] = nullptr;
} else {
values[i] = nullptr;
if (!statuses[i].IsNotFound()) {
errs[i] = strdup(statuses[i].ToString().c_str());
} else {
errs[i] = nullptr;
}
}
}
delete[] value_slices;
delete[] statuses;
}
unsigned char rocksdb_key_may_exist(rocksdb_t* db,
const rocksdb_readoptions_t* options,
const char* key, size_t key_len,
@@ -2180,32 +2123,6 @@ void rocksdb_iter_get_error(const rocksdb_iterator_t* iter, char** errptr) {
SaveError(errptr, iter->rep->status());
}
// Iterator functions that return rocksdb_slice_t directly for better
// performance
rocksdb_slice_t rocksdb_iter_key_slice(const rocksdb_iterator_t* iter) {
Slice s = iter->rep->key();
rocksdb_slice_t result;
result.data = s.data();
result.size = s.size();
return result;
}
rocksdb_slice_t rocksdb_iter_value_slice(const rocksdb_iterator_t* iter) {
Slice s = iter->rep->value();
rocksdb_slice_t result;
result.data = s.data();
result.size = s.size();
return result;
}
rocksdb_slice_t rocksdb_iter_timestamp_slice(const rocksdb_iterator_t* iter) {
Slice s = iter->rep->timestamp();
rocksdb_slice_t result;
result.data = s.data();
result.size = s.size();
return result;
}
void rocksdb_iter_refresh(const rocksdb_iterator_t* iter, char** errptr) {
SaveError(errptr, iter->rep->Refresh());
}
@@ -2261,18 +2178,16 @@ void rocksdb_writebatch_putv(rocksdb_writebatch_t* b, int num_keys,
const size_t* keys_list_sizes, int num_values,
const char* const* values_list,
const size_t* values_list_sizes) {
// Use unique_ptr instead of vector to avoid overhead
// Safe because WriteBatch::Put immediately copies the data
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
std::vector<Slice> key_slices(num_keys);
for (int i = 0; i < num_keys; i++) {
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
std::unique_ptr<Slice[]> value_slices(new Slice[num_values]);
std::vector<Slice> value_slices(num_values);
for (int i = 0; i < num_values; i++) {
value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
}
b->rep.Put(SliceParts(key_slices.get(), num_keys),
SliceParts(value_slices.get(), num_values));
b->rep.Put(SliceParts(key_slices.data(), num_keys),
SliceParts(value_slices.data(), num_values));
}
void rocksdb_writebatch_putv_cf(rocksdb_writebatch_t* b,
@@ -2281,18 +2196,16 @@ void rocksdb_writebatch_putv_cf(rocksdb_writebatch_t* b,
const size_t* keys_list_sizes, int num_values,
const char* const* values_list,
const size_t* values_list_sizes) {
// Use unique_ptr instead of vector to avoid overhead
// Safe because WriteBatch::Put immediately copies the data
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
std::vector<Slice> key_slices(num_keys);
for (int i = 0; i < num_keys; i++) {
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
std::unique_ptr<Slice[]> value_slices(new Slice[num_values]);
std::vector<Slice> value_slices(num_values);
for (int i = 0; i < num_values; i++) {
value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
}
b->rep.Put(column_family->rep, SliceParts(key_slices.get(), num_keys),
SliceParts(value_slices.get(), num_values));
b->rep.Put(column_family->rep, SliceParts(key_slices.data(), num_keys),
SliceParts(value_slices.data(), num_values));
}
void rocksdb_writebatch_merge(rocksdb_writebatch_t* b, const char* key,
@@ -2312,18 +2225,16 @@ void rocksdb_writebatch_mergev(rocksdb_writebatch_t* b, int num_keys,
const size_t* keys_list_sizes, int num_values,
const char* const* values_list,
const size_t* values_list_sizes) {
// Use unique_ptr instead of vector to avoid overhead
// Safe because WriteBatch::Merge immediately copies the data
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
std::vector<Slice> key_slices(num_keys);
for (int i = 0; i < num_keys; i++) {
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
std::unique_ptr<Slice[]> value_slices(new Slice[num_values]);
std::vector<Slice> value_slices(num_values);
for (int i = 0; i < num_values; i++) {
value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
}
b->rep.Merge(SliceParts(key_slices.get(), num_keys),
SliceParts(value_slices.get(), num_values));
b->rep.Merge(SliceParts(key_slices.data(), num_keys),
SliceParts(value_slices.data(), num_values));
}
void rocksdb_writebatch_mergev_cf(rocksdb_writebatch_t* b,
@@ -2332,18 +2243,16 @@ void rocksdb_writebatch_mergev_cf(rocksdb_writebatch_t* b,
const size_t* keys_list_sizes, int num_values,
const char* const* values_list,
const size_t* values_list_sizes) {
// Use unique_ptr instead of vector to avoid overhead
// Safe because WriteBatch::Merge immediately copies the data
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
std::vector<Slice> key_slices(num_keys);
for (int i = 0; i < num_keys; i++) {
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
std::unique_ptr<Slice[]> value_slices(new Slice[num_values]);
std::vector<Slice> value_slices(num_values);
for (int i = 0; i < num_values; i++) {
value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
}
b->rep.Merge(column_family->rep, SliceParts(key_slices.get(), num_keys),
SliceParts(value_slices.get(), num_values));
b->rep.Merge(column_family->rep, SliceParts(key_slices.data(), num_keys),
SliceParts(value_slices.data(), num_values));
}
void rocksdb_writebatch_delete(rocksdb_writebatch_t* b, const char* key,
@@ -2383,25 +2292,21 @@ void rocksdb_writebatch_singledelete_cf_with_ts(
void rocksdb_writebatch_deletev(rocksdb_writebatch_t* b, int num_keys,
const char* const* keys_list,
const size_t* keys_list_sizes) {
// Use unique_ptr instead of vector to avoid overhead
// Safe because WriteBatch::Delete immediately copies the data
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
std::vector<Slice> key_slices(num_keys);
for (int i = 0; i < num_keys; i++) {
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
b->rep.Delete(SliceParts(key_slices.get(), num_keys));
b->rep.Delete(SliceParts(key_slices.data(), num_keys));
}
void rocksdb_writebatch_deletev_cf(
rocksdb_writebatch_t* b, rocksdb_column_family_handle_t* column_family,
int num_keys, const char* const* keys_list, const size_t* keys_list_sizes) {
// Use unique_ptr instead of vector to avoid overhead
// Safe because WriteBatch::Delete immediately copies the data
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
std::vector<Slice> key_slices(num_keys);
for (int i = 0; i < num_keys; i++) {
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
b->rep.Delete(column_family->rep, SliceParts(key_slices.get(), num_keys));
b->rep.Delete(column_family->rep, SliceParts(key_slices.data(), num_keys));
}
void rocksdb_writebatch_delete_range(rocksdb_writebatch_t* b,
@@ -2425,16 +2330,14 @@ void rocksdb_writebatch_delete_rangev(rocksdb_writebatch_t* b, int num_keys,
const size_t* start_keys_list_sizes,
const char* const* end_keys_list,
const size_t* end_keys_list_sizes) {
// Use unique_ptr instead of vector to avoid overhead
// Safe because WriteBatch::DeleteRange immediately copies the data
std::unique_ptr<Slice[]> start_key_slices(new Slice[num_keys]);
std::unique_ptr<Slice[]> end_key_slices(new Slice[num_keys]);
std::vector<Slice> start_key_slices(num_keys);
std::vector<Slice> end_key_slices(num_keys);
for (int i = 0; i < num_keys; i++) {
start_key_slices[i] = Slice(start_keys_list[i], start_keys_list_sizes[i]);
end_key_slices[i] = Slice(end_keys_list[i], end_keys_list_sizes[i]);
}
b->rep.DeleteRange(SliceParts(start_key_slices.get(), num_keys),
SliceParts(end_key_slices.get(), num_keys));
b->rep.DeleteRange(SliceParts(start_key_slices.data(), num_keys),
SliceParts(end_key_slices.data(), num_keys));
}
void rocksdb_writebatch_delete_rangev_cf(
@@ -2442,17 +2345,15 @@ void rocksdb_writebatch_delete_rangev_cf(
int num_keys, const char* const* start_keys_list,
const size_t* start_keys_list_sizes, const char* const* end_keys_list,
const size_t* end_keys_list_sizes) {
// Use unique_ptr instead of vector to avoid overhead
// Safe because WriteBatch::DeleteRange immediately copies the data
std::unique_ptr<Slice[]> start_key_slices(new Slice[num_keys]);
std::unique_ptr<Slice[]> end_key_slices(new Slice[num_keys]);
std::vector<Slice> start_key_slices(num_keys);
std::vector<Slice> end_key_slices(num_keys);
for (int i = 0; i < num_keys; i++) {
start_key_slices[i] = Slice(start_keys_list[i], start_keys_list_sizes[i]);
end_key_slices[i] = Slice(end_keys_list[i], end_keys_list_sizes[i]);
}
b->rep.DeleteRange(column_family->rep,
SliceParts(start_key_slices.get(), num_keys),
SliceParts(end_key_slices.get(), num_keys));
SliceParts(start_key_slices.data(), num_keys),
SliceParts(end_key_slices.data(), num_keys));
}
void rocksdb_writebatch_put_log_data(rocksdb_writebatch_t* b, const char* blob,
@@ -2613,17 +2514,16 @@ void rocksdb_writebatch_wi_putv(rocksdb_writebatch_wi_t* b, int num_keys,
const size_t* keys_list_sizes, int num_values,
const char* const* values_list,
const size_t* values_list_sizes) {
// Use unique_ptr for better performance (avoids vector overhead)
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
std::vector<Slice> key_slices(num_keys);
for (int i = 0; i < num_keys; i++) {
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
std::unique_ptr<Slice[]> value_slices(new Slice[num_values]);
std::vector<Slice> value_slices(num_values);
for (int i = 0; i < num_values; i++) {
value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
}
b->rep->Put(SliceParts(key_slices.get(), num_keys),
SliceParts(value_slices.get(), num_values));
b->rep->Put(SliceParts(key_slices.data(), num_keys),
SliceParts(value_slices.data(), num_values));
}
void rocksdb_writebatch_wi_putv_cf(
@@ -2659,17 +2559,16 @@ void rocksdb_writebatch_wi_mergev(rocksdb_writebatch_wi_t* b, int num_keys,
const size_t* keys_list_sizes, int num_values,
const char* const* values_list,
const size_t* values_list_sizes) {
// Use unique_ptr for better performance (avoids vector overhead)
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
std::vector<Slice> key_slices(num_keys);
for (int i = 0; i < num_keys; i++) {
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
std::unique_ptr<Slice[]> value_slices(new Slice[num_values]);
std::vector<Slice> value_slices(num_values);
for (int i = 0; i < num_values; i++) {
value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
}
b->rep->Merge(SliceParts(key_slices.get(), num_keys),
SliceParts(value_slices.get(), num_values));
b->rep->Merge(SliceParts(key_slices.data(), num_keys),
SliceParts(value_slices.data(), num_values));
}
void rocksdb_writebatch_wi_mergev_cf(
@@ -2677,17 +2576,16 @@ void rocksdb_writebatch_wi_mergev_cf(
int num_keys, const char* const* keys_list, const size_t* keys_list_sizes,
int num_values, const char* const* values_list,
const size_t* values_list_sizes) {
// Use unique_ptr for better performance (avoids vector overhead)
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
std::vector<Slice> key_slices(num_keys);
for (int i = 0; i < num_keys; i++) {
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
std::unique_ptr<Slice[]> value_slices(new Slice[num_values]);
std::vector<Slice> value_slices(num_values);
for (int i = 0; i < num_values; i++) {
value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
}
b->rep->Merge(column_family->rep, SliceParts(key_slices.get(), num_keys),
SliceParts(value_slices.get(), num_values));
b->rep->Merge(column_family->rep, SliceParts(key_slices.data(), num_keys),
SliceParts(value_slices.data(), num_values));
}
void rocksdb_writebatch_wi_delete(rocksdb_writebatch_wi_t* b, const char* key,
@@ -2725,12 +2623,11 @@ void rocksdb_writebatch_wi_deletev(rocksdb_writebatch_wi_t* b, int num_keys,
void rocksdb_writebatch_wi_deletev_cf(
rocksdb_writebatch_wi_t* b, rocksdb_column_family_handle_t* column_family,
int num_keys, const char* const* keys_list, const size_t* keys_list_sizes) {
// Use unique_ptr for better performance (avoids vector overhead)
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
std::vector<Slice> key_slices(num_keys);
for (int i = 0; i < num_keys; i++) {
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
b->rep->Delete(column_family->rep, SliceParts(key_slices.get(), num_keys));
b->rep->Delete(column_family->rep, SliceParts(key_slices.data(), num_keys));
}
void rocksdb_writebatch_wi_delete_range(rocksdb_writebatch_wi_t* b,
@@ -2756,15 +2653,14 @@ void rocksdb_writebatch_wi_delete_rangev(rocksdb_writebatch_wi_t* b,
const size_t* start_keys_list_sizes,
const char* const* end_keys_list,
const size_t* end_keys_list_sizes) {
// Use unique_ptr for better performance (avoids vector overhead)
std::unique_ptr<Slice[]> start_key_slices(new Slice[num_keys]);
std::unique_ptr<Slice[]> end_key_slices(new Slice[num_keys]);
std::vector<Slice> start_key_slices(num_keys);
std::vector<Slice> end_key_slices(num_keys);
for (int i = 0; i < num_keys; i++) {
start_key_slices[i] = Slice(start_keys_list[i], start_keys_list_sizes[i]);
end_key_slices[i] = Slice(end_keys_list[i], end_keys_list_sizes[i]);
}
b->rep->DeleteRange(SliceParts(start_key_slices.get(), num_keys),
SliceParts(end_key_slices.get(), num_keys));
b->rep->DeleteRange(SliceParts(start_key_slices.data(), num_keys),
SliceParts(end_key_slices.data(), num_keys));
}
void rocksdb_writebatch_wi_delete_rangev_cf(
@@ -2772,16 +2668,15 @@ void rocksdb_writebatch_wi_delete_rangev_cf(
int num_keys, const char* const* start_keys_list,
const size_t* start_keys_list_sizes, const char* const* end_keys_list,
const size_t* end_keys_list_sizes) {
// Use unique_ptr for better performance (avoids vector overhead)
std::unique_ptr<Slice[]> start_key_slices(new Slice[num_keys]);
std::unique_ptr<Slice[]> end_key_slices(new Slice[num_keys]);
std::vector<Slice> start_key_slices(num_keys);
std::vector<Slice> end_key_slices(num_keys);
for (int i = 0; i < num_keys; i++) {
start_key_slices[i] = Slice(start_keys_list[i], start_keys_list_sizes[i]);
end_key_slices[i] = Slice(end_keys_list[i], end_keys_list_sizes[i]);
}
b->rep->DeleteRange(column_family->rep,
SliceParts(start_key_slices.get(), num_keys),
SliceParts(end_key_slices.get(), num_keys));
SliceParts(start_key_slices.data(), num_keys),
SliceParts(end_key_slices.data(), num_keys));
}
void rocksdb_writebatch_wi_put_log_data(rocksdb_writebatch_wi_t* b,
@@ -2899,13 +2794,12 @@ char* rocksdb_writebatch_wi_get_from_batch_and_db(
const rocksdb_readoptions_t* options, const char* key, size_t keylen,
size_t* vallen, char** errptr) {
char* result = nullptr;
// Use PinnableSlice to avoid unnecessary allocations
PinnableSlice pinnable_val;
std::string tmp;
Status s = wbwi->rep->GetFromBatchAndDB(db->rep, options->rep,
Slice(key, keylen), &pinnable_val);
Slice(key, keylen), &tmp);
if (s.ok()) {
*vallen = pinnable_val.size();
result = CopyString(pinnable_val);
*vallen = tmp.size();
result = CopyString(tmp);
} else {
*vallen = 0;
if (!s.IsNotFound()) {
@@ -2938,14 +2832,12 @@ char* rocksdb_writebatch_wi_get_from_batch_and_db_cf(
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, size_t* vallen, char** errptr) {
char* result = nullptr;
// Use PinnableSlice to avoid unnecessary allocations
PinnableSlice pinnable_val;
Status s =
wbwi->rep->GetFromBatchAndDB(db->rep, options->rep, column_family->rep,
Slice(key, keylen), &pinnable_val);
std::string tmp;
Status s = wbwi->rep->GetFromBatchAndDB(
db->rep, options->rep, column_family->rep, Slice(key, keylen), &tmp);
if (s.ok()) {
*vallen = pinnable_val.size();
result = CopyString(pinnable_val);
*vallen = tmp.size();
result = CopyString(tmp);
} else {
*vallen = 0;
if (!s.IsNotFound()) {
@@ -7030,11 +6922,11 @@ char* rocksdb_transaction_get(rocksdb_transaction_t* txn,
const char* key, size_t klen, size_t* vlen,
char** errptr) {
char* result = nullptr;
PinnableSlice pinnable_val;
Status s = txn->rep->Get(options->rep, Slice(key, klen), &pinnable_val);
std::string tmp;
Status s = txn->rep->Get(options->rep, Slice(key, klen), &tmp);
if (s.ok()) {
*vlen = pinnable_val.size();
result = CopyString(pinnable_val);
*vlen = tmp.size();
result = CopyString(tmp);
} else {
*vlen = 0;
if (!s.IsNotFound()) {
@@ -7065,12 +6957,12 @@ char* rocksdb_transaction_get_cf(rocksdb_transaction_t* txn,
const char* key, size_t klen, size_t* vlen,
char** errptr) {
char* result = nullptr;
PinnableSlice pinnable_val;
Status s = txn->rep->Get(options->rep, column_family->rep, Slice(key, klen),
&pinnable_val);
std::string tmp;
Status s =
txn->rep->Get(options->rep, column_family->rep, Slice(key, klen), &tmp);
if (s.ok()) {
*vlen = pinnable_val.size();
result = CopyString(pinnable_val);
*vlen = tmp.size();
result = CopyString(tmp);
} else {
*vlen = 0;
if (!s.IsNotFound()) {
@@ -7104,12 +6996,12 @@ char* rocksdb_transaction_get_for_update(rocksdb_transaction_t* txn,
size_t* vlen, unsigned char exclusive,
char** errptr) {
char* result = nullptr;
PinnableSlice pinnable_val;
Status s = txn->rep->GetForUpdate(options->rep, Slice(key, klen),
&pinnable_val, exclusive);
std::string tmp;
Status s =
txn->rep->GetForUpdate(options->rep, Slice(key, klen), &tmp, exclusive);
if (s.ok()) {
*vlen = pinnable_val.size();
result = CopyString(pinnable_val);
*vlen = tmp.size();
result = CopyString(tmp);
} else {
*vlen = 0;
if (!s.IsNotFound()) {
@@ -7141,12 +7033,12 @@ char* rocksdb_transaction_get_for_update_cf(
rocksdb_column_family_handle_t* column_family, const char* key, size_t klen,
size_t* vlen, unsigned char exclusive, char** errptr) {
char* result = nullptr;
PinnableSlice pinnable_val;
std::string tmp;
Status s = txn->rep->GetForUpdate(options->rep, column_family->rep,
Slice(key, klen), &pinnable_val, exclusive);
Slice(key, klen), &tmp, exclusive);
if (s.ok()) {
*vlen = pinnable_val.size();
result = CopyString(pinnable_val);
*vlen = tmp.size();
result = CopyString(tmp);
} else {
*vlen = 0;
if (!s.IsNotFound()) {
@@ -7180,13 +7072,10 @@ void rocksdb_transaction_multi_get(rocksdb_transaction_t* txn,
const size_t* keys_list_sizes,
char** values_list,
size_t* values_list_sizes, char** errs) {
// Use unique_ptr for efficiency
std::unique_ptr<Slice[]> keys_arr(new Slice[num_keys]);
std::vector<Slice> keys(num_keys);
for (size_t i = 0; i < num_keys; i++) {
keys_arr[i] = Slice(keys_list[i], keys_list_sizes[i]);
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
// Note: Transaction only has vector-based MultiGet API
std::vector<Slice> keys(keys_arr.get(), keys_arr.get() + num_keys);
std::vector<std::string> values(num_keys);
std::vector<Status> statuses =
txn->rep->MultiGet(options->rep, keys, &values);
@@ -7212,14 +7101,10 @@ void rocksdb_transaction_multi_get_for_update(
size_t num_keys, const char* const* keys_list,
const size_t* keys_list_sizes, char** values_list,
size_t* values_list_sizes, char** errs) {
// Use unique_ptr for efficiency
std::unique_ptr<Slice[]> keys_arr(new Slice[num_keys]);
std::vector<Slice> keys(num_keys);
for (size_t i = 0; i < num_keys; i++) {
keys_arr[i] = Slice(keys_list[i], keys_list_sizes[i]);
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
// Note: GetForUpdate only has vector-based API, no array-based PinnableSlice
// variant
std::vector<Slice> keys(keys_arr.get(), keys_arr.get() + num_keys);
std::vector<std::string> values(num_keys);
std::vector<Status> statuses =
txn->rep->MultiGetForUpdate(options->rep, keys, &values);
@@ -7246,15 +7131,12 @@ void rocksdb_transaction_multi_get_cf(
size_t num_keys, const char* const* keys_list,
const size_t* keys_list_sizes, char** values_list,
size_t* values_list_sizes, char** errs) {
// Use unique_ptr for efficiency
std::unique_ptr<Slice[]> keys_arr(new Slice[num_keys]);
std::vector<Slice> keys(num_keys);
std::vector<ColumnFamilyHandle*> cfs(num_keys);
for (size_t i = 0; i < num_keys; i++) {
keys_arr[i] = Slice(keys_list[i], keys_list_sizes[i]);
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
cfs[i] = column_families[i]->rep;
}
// Note: Transaction only has vector-based MultiGet API
std::vector<Slice> keys(keys_arr.get(), keys_arr.get() + num_keys);
std::vector<std::string> values(num_keys);
std::vector<Status> statuses =
txn->rep->MultiGet(options->rep, cfs, keys, &values);
@@ -7281,16 +7163,12 @@ void rocksdb_transaction_multi_get_for_update_cf(
size_t num_keys, const char* const* keys_list,
const size_t* keys_list_sizes, char** values_list,
size_t* values_list_sizes, char** errs) {
// Use unique_ptr for efficiency
std::unique_ptr<Slice[]> keys_arr(new Slice[num_keys]);
std::vector<Slice> keys(num_keys);
std::vector<ColumnFamilyHandle*> cfs(num_keys);
for (size_t i = 0; i < num_keys; i++) {
keys_arr[i] = Slice(keys_list[i], keys_list_sizes[i]);
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
cfs[i] = column_families[i]->rep;
}
// Note: GetForUpdate only has vector-based API, no array-based PinnableSlice
// variant
std::vector<Slice> keys(keys_arr.get(), keys_arr.get() + num_keys);
std::vector<std::string> values(num_keys);
std::vector<Status> statuses =
txn->rep->MultiGetForUpdate(options->rep, cfs, keys, &values);
@@ -7317,12 +7195,11 @@ char* rocksdb_transactiondb_get(rocksdb_transactiondb_t* txn_db,
const char* key, size_t klen, size_t* vlen,
char** errptr) {
char* result = nullptr;
PinnableSlice pinnable_val;
Status s = txn_db->rep->Get(options->rep, txn_db->rep->DefaultColumnFamily(),
Slice(key, klen), &pinnable_val);
std::string tmp;
Status s = txn_db->rep->Get(options->rep, Slice(key, klen), &tmp);
if (s.ok()) {
*vlen = pinnable_val.size();
result = CopyString(pinnable_val);
*vlen = tmp.size();
result = CopyString(tmp);
} else {
*vlen = 0;
if (!s.IsNotFound()) {
@@ -7353,12 +7230,12 @@ char* rocksdb_transactiondb_get_cf(
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, size_t* vallen, char** errptr) {
char* result = nullptr;
PinnableSlice pinnable_val;
std::string tmp;
Status s = txn_db->rep->Get(options->rep, column_family->rep,
Slice(key, keylen), &pinnable_val);
Slice(key, keylen), &tmp);
if (s.ok()) {
*vallen = pinnable_val.size();
result = CopyString(pinnable_val);
*vallen = tmp.size();
result = CopyString(tmp);
} else {
*vallen = 0;
if (!s.IsNotFound()) {
@@ -7392,17 +7269,13 @@ void rocksdb_transactiondb_multi_get(rocksdb_transactiondb_t* txn_db,
const size_t* keys_list_sizes,
char** values_list,
size_t* values_list_sizes, char** errs) {
// Use unique_ptr for efficiency
std::unique_ptr<Slice[]> keys(new Slice[num_keys]);
std::vector<Slice> keys(num_keys);
for (size_t i = 0; i < num_keys; i++) {
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
// Use PinnableSlice to avoid unnecessary allocations
auto cfh = txn_db->rep->DefaultColumnFamily();
std::vector<PinnableSlice> values(num_keys);
std::vector<Status> statuses(num_keys);
txn_db->rep->MultiGet(options->rep, cfh, num_keys, keys.get(), values.data(),
statuses.data());
std::vector<std::string> values(num_keys);
std::vector<Status> statuses =
txn_db->rep->MultiGet(options->rep, keys, &values);
for (size_t i = 0; i < num_keys; i++) {
if (statuses[i].ok()) {
values_list[i] = CopyString(values[i]);
@@ -7426,18 +7299,15 @@ void rocksdb_transactiondb_multi_get_cf(
size_t num_keys, const char* const* keys_list,
const size_t* keys_list_sizes, char** values_list,
size_t* values_list_sizes, char** errs) {
// Use unique_ptr for efficiency
std::unique_ptr<Slice[]> keys(new Slice[num_keys]);
std::unique_ptr<ColumnFamilyHandle*[]> cfs(new ColumnFamilyHandle*[num_keys]);
std::vector<Slice> keys(num_keys);
std::vector<ColumnFamilyHandle*> cfs(num_keys);
for (size_t i = 0; i < num_keys; i++) {
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
cfs[i] = column_families[i]->rep;
}
// Use PinnableSlice to avoid unnecessary allocations
std::vector<PinnableSlice> values(num_keys);
std::vector<Status> statuses(num_keys);
txn_db->rep->MultiGet(options->rep, num_keys, cfs.get(), keys.get(),
values.data(), statuses.data());
std::vector<std::string> values(num_keys);
std::vector<Status> statuses =
txn_db->rep->MultiGet(options->rep, cfs, keys, &values);
for (size_t i = 0; i < num_keys; i++) {
if (statuses[i].ok()) {
values_list[i] = CopyString(values[i]);
@@ -8030,110 +7900,4 @@ uint64_t rocksdb_wait_for_compact_options_get_timeout(
return opt->rep.timeout.count();
}
/* High-performance zero-copy Get implementations */
struct rocksdb_pinnable_handle_t {
PinnableSlice rep;
};
rocksdb_pinnable_handle_t* rocksdb_get_pinned_v2(
rocksdb_t* db, const rocksdb_readoptions_t* options, const char* key,
size_t keylen, char** errptr) {
rocksdb_pinnable_handle_t* handle = new rocksdb_pinnable_handle_t;
Status s = db->rep->Get(options->rep, db->rep->DefaultColumnFamily(),
Slice(key, keylen), &handle->rep);
if (!s.ok()) {
delete handle;
if (!s.IsNotFound()) {
SaveError(errptr, s);
}
return nullptr;
}
return handle;
}
rocksdb_pinnable_handle_t* rocksdb_get_pinned_cf_v2(
rocksdb_t* db, const rocksdb_readoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, char** errptr) {
rocksdb_pinnable_handle_t* handle = new rocksdb_pinnable_handle_t;
Status s = db->rep->Get(options->rep, column_family->rep, Slice(key, keylen),
&handle->rep);
if (!s.ok()) {
delete handle;
if (!s.IsNotFound()) {
SaveError(errptr, s);
}
return nullptr;
}
return handle;
}
const char* rocksdb_pinnable_handle_get_value(
const rocksdb_pinnable_handle_t* handle, size_t* vallen) {
if (!handle) {
*vallen = 0;
return nullptr;
}
*vallen = handle->rep.size();
return handle->rep.data();
}
void rocksdb_pinnable_handle_destroy(rocksdb_pinnable_handle_t* handle) {
delete handle;
}
unsigned char rocksdb_get_into_buffer(rocksdb_t* db,
const rocksdb_readoptions_t* options,
const char* key, size_t keylen,
char* buffer, size_t buffer_size,
size_t* vallen, unsigned char* found,
char** errptr) {
PinnableSlice pinnable_val;
Status s = db->rep->Get(options->rep, db->rep->DefaultColumnFamily(),
Slice(key, keylen), &pinnable_val);
if (s.ok()) {
*found = 1;
*vallen = pinnable_val.size();
if (buffer_size >= pinnable_val.size()) {
memcpy(buffer, pinnable_val.data(), pinnable_val.size());
return 1; // Success - data copied
}
return 0; // Buffer too small
} else {
*found = 0;
*vallen = 0;
if (!s.IsNotFound()) {
SaveError(errptr, s);
}
return 0;
}
}
unsigned char rocksdb_get_into_buffer_cf(
rocksdb_t* db, const rocksdb_readoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, char* buffer, size_t buffer_size, size_t* vallen,
unsigned char* found, char** errptr) {
PinnableSlice pinnable_val;
Status s = db->rep->Get(options->rep, column_family->rep, Slice(key, keylen),
&pinnable_val);
if (s.ok()) {
*found = 1;
*vallen = pinnable_val.size();
if (buffer_size >= pinnable_val.size()) {
memcpy(buffer, pinnable_val.data(), pinnable_val.size());
return 1; // Success - data copied
}
return 0; // Buffer too small
} else {
*found = 0;
*vallen = 0;
if (!s.IsNotFound()) {
SaveError(errptr, s);
}
return 0;
}
}
} // end extern "C"
-276
View File
@@ -1255,70 +1255,6 @@ int main(int argc, char** argv) {
rocksdb_writebatch_destroy(wb);
}
StartPhase("writebatch_vectors_cf");
{
const char* cf_name = "wb_vectors_cf";
rocksdb_column_family_handle_t* wb_cf =
rocksdb_create_column_family(db, options, cf_name, &err);
CheckNoError(err);
rocksdb_writebatch_t* wb = rocksdb_writebatch_create();
// Test putv_cf: concatenates multiple slices into a single key/value
const char* put_keys[2] = {"k", "ey"};
const size_t put_key_sizes[2] = {1, 2};
const char* put_vals[3] = {"v", "a", "l"};
const size_t put_val_sizes[3] = {1, 1, 1};
rocksdb_writebatch_putv_cf(wb, wb_cf, 2, put_keys, put_key_sizes, 3,
put_vals, put_val_sizes);
rocksdb_write(db, woptions, wb, &err);
CheckNoError(err);
// putv_cf concatenates: key="k"+"ey"="key", value="v"+"a"+"l"="val"
CheckGetCF(db, roptions, wb_cf, "key", "val");
CheckGetCF(db, roptions, wb_cf, "k", NULL);
CheckGetCF(db, roptions, wb_cf, "ey", NULL);
// Test deletev_cf: concatenates multiple slices for key
rocksdb_writebatch_clear(wb);
const char* del_keys[2] = {"k", "ey"};
const size_t del_key_sizes[2] = {1, 2};
rocksdb_writebatch_deletev_cf(wb, wb_cf, 2, del_keys, del_key_sizes);
rocksdb_write(db, woptions, wb, &err);
CheckNoError(err);
CheckGetCF(db, roptions, wb_cf, "key", NULL);
// Test delete_rangev_cf: concatenates slices for range deletion
rocksdb_writebatch_clear(wb);
rocksdb_writebatch_put_cf(wb, wb_cf, "a", 1, "1", 1);
rocksdb_writebatch_put_cf(wb, wb_cf, "b", 1, "2", 1);
rocksdb_writebatch_put_cf(wb, wb_cf, "c", 1, "3", 1);
rocksdb_write(db, woptions, wb, &err);
CheckNoError(err);
CheckGetCF(db, roptions, wb_cf, "a", "1");
CheckGetCF(db, roptions, wb_cf, "b", "2");
CheckGetCF(db, roptions, wb_cf, "c", "3");
rocksdb_writebatch_clear(wb);
const char* range_start[2] = {"a", ""}; // "a" + "" = "a"
const size_t range_start_sizes[2] = {1, 0};
const char* range_end[2] = {"c", ""};
const size_t range_end_sizes[2] = {1, 0};
rocksdb_writebatch_delete_rangev_cf(wb, wb_cf, 2, range_start,
range_start_sizes, range_end,
range_end_sizes);
rocksdb_write(db, woptions, wb, &err);
CheckNoError(err);
// Range [a, c) should delete "a" and "b", but not "c"
CheckGetCF(db, roptions, wb_cf, "a", NULL);
CheckGetCF(db, roptions, wb_cf, "b", NULL);
CheckGetCF(db, roptions, wb_cf, "c", "3");
rocksdb_writebatch_destroy(wb);
rocksdb_drop_column_family(db, wb_cf, &err);
CheckNoError(err);
rocksdb_column_family_handle_destroy(wb_cf);
}
StartPhase("writebatch_vectors");
{
rocksdb_writebatch_t* wb = rocksdb_writebatch_create();
@@ -1484,43 +1420,6 @@ int main(int argc, char** argv) {
rocksdb_iter_destroy(iter);
}
StartPhase("iter_slice");
{
// Test the new slice-based iterator API for better performance
rocksdb_iterator_t* iter = rocksdb_create_iterator(db, roptions);
CheckCondition(!rocksdb_iter_valid(iter));
rocksdb_iter_seek_to_first(iter);
CheckCondition(rocksdb_iter_valid(iter));
// Test rocksdb_iter_key_slice
rocksdb_slice_t key_slice = rocksdb_iter_key_slice(iter);
CheckEqual("box", key_slice.data, key_slice.size);
// Test rocksdb_iter_value_slice
rocksdb_slice_t value_slice = rocksdb_iter_value_slice(iter);
CheckEqual("c", value_slice.data, value_slice.size);
// Move to next entry and test again
rocksdb_iter_next(iter);
CheckCondition(rocksdb_iter_valid(iter));
key_slice = rocksdb_iter_key_slice(iter);
value_slice = rocksdb_iter_value_slice(iter);
CheckEqual("foo", key_slice.data, key_slice.size);
CheckEqual("hello", value_slice.data, value_slice.size);
// Test seeking with slice API
rocksdb_iter_seek(iter, "b", 1);
CheckCondition(rocksdb_iter_valid(iter));
key_slice = rocksdb_iter_key_slice(iter);
value_slice = rocksdb_iter_value_slice(iter);
CheckEqual("box", key_slice.data, key_slice.size);
CheckEqual("c", value_slice.data, value_slice.size);
rocksdb_iter_get_error(iter, &err);
CheckNoError(err);
rocksdb_iter_destroy(iter);
}
StartPhase("wbwi_iter");
{
rocksdb_iterator_t* base_iter = rocksdb_create_iterator(db, roptions);
@@ -1606,53 +1505,6 @@ int main(int argc, char** argv) {
CheckMultiGetValues(3, vals, vals_sizes, errs, expected);
}
StartPhase("zero_copy_get_pinned_v2");
{
// Test new zero-copy get functions
// Test rocksdb_get_pinned_v2
rocksdb_pinnable_handle_t* handle =
rocksdb_get_pinned_v2(db, roptions, "foo", 3, &err);
CheckNoError(err);
CheckCondition(handle != NULL);
size_t val_len;
const char* val = rocksdb_pinnable_handle_get_value(handle, &val_len);
CheckEqual("hello", val, val_len);
rocksdb_pinnable_handle_destroy(handle);
// Test with non-existent key
handle = rocksdb_get_pinned_v2(db, roptions, "notfound", 8, &err);
CheckNoError(err);
CheckCondition(handle == NULL);
// Test rocksdb_get_into_buffer
char buffer[100];
unsigned char found;
unsigned char success = rocksdb_get_into_buffer(
db, roptions, "foo", 3, buffer, sizeof(buffer), &val_len, &found, &err);
CheckNoError(err);
CheckCondition(success == 1);
CheckCondition(found == 1);
CheckCondition(val_len == 5);
CheckCondition(memcmp(buffer, "hello", 5) == 0);
// Test with buffer too small
success = rocksdb_get_into_buffer(db, roptions, "foo", 3, buffer,
2, // Buffer too small
&val_len, &found, &err);
CheckNoError(err);
CheckCondition(success == 0); // Should fail due to small buffer
CheckCondition(found == 1);
CheckCondition(val_len == 5); // Should still report actual size
// Test with non-existent key
success = rocksdb_get_into_buffer(db, roptions, "notfound", 8, buffer,
sizeof(buffer), &val_len, &found, &err);
CheckNoError(err);
CheckCondition(success == 0);
CheckCondition(found == 0);
}
StartPhase("pin_get");
{
CheckPinGet(db, roptions, "box", "c");
@@ -2070,55 +1922,6 @@ int main(int argc, char** argv) {
rocksdb_flush_wal(db, 1, &err);
CheckNoError(err);
// Test column family handle get name
{
size_t name_len;
char* cf_name =
rocksdb_column_family_handle_get_name(handles[1], &name_len);
CheckCondition(name_len == 3);
CheckCondition(memcmp(cf_name, "cf1", 3) == 0);
rocksdb_free(cf_name);
}
// Test zero-copy get with column families
{
rocksdb_pinnable_handle_t* handle =
rocksdb_get_pinned_cf_v2(db, roptions, handles[1], "box", 3, &err);
CheckNoError(err);
CheckCondition(handle != NULL);
size_t val_len;
const char* val = rocksdb_pinnable_handle_get_value(handle, &val_len);
CheckEqual("c", val, val_len);
rocksdb_pinnable_handle_destroy(handle);
// Test with non-existent key
handle = rocksdb_get_pinned_cf_v2(db, roptions, handles[1], "notfound", 8,
&err);
CheckNoError(err);
CheckCondition(handle == NULL);
// Test rocksdb_get_into_buffer_cf
char buffer[100];
unsigned char found;
unsigned char success = rocksdb_get_into_buffer_cf(
db, roptions, handles[1], "buff", 4, buffer, sizeof(buffer), &val_len,
&found, &err);
CheckNoError(err);
CheckCondition(success == 1);
CheckCondition(found == 1);
CheckCondition(val_len == 7);
CheckCondition(memcmp(buffer, "rocksdb", 7) == 0);
// Test with buffer too small
success = rocksdb_get_into_buffer_cf(db, roptions, handles[1], "buff", 4,
buffer, 3, // Buffer too small
&val_len, &found, &err);
CheckNoError(err);
CheckCondition(success == 0); // Should fail due to small buffer
CheckCondition(found == 1);
CheckCondition(val_len == 7); // Should still report actual size
}
// Test WriteBatchWithIndex iteration with Column Family
rocksdb_writebatch_wi_t* wbwi = rocksdb_writebatch_wi_create(0, true);
rocksdb_writebatch_wi_put_cf(wbwi, handles[1], "boat", 4, "row",
@@ -2195,74 +1998,6 @@ int main(int argc, char** argv) {
}
}
{
// Test rocksdb_batched_multi_get_cf_slice for better performance
// Build rocksdb_slice_t array directly to avoid conversion overhead
rocksdb_slice_t batched_key_slices[4];
batched_key_slices[0].data = "box";
batched_key_slices[0].size = 3;
batched_key_slices[1].data = "buff";
batched_key_slices[1].size = 4;
batched_key_slices[2].data = "barfooxx";
batched_key_slices[2].size = 8;
batched_key_slices[3].data = "box";
batched_key_slices[3].size = 3;
const char* expected_value[4] = {"c", "rocksdb", NULL, "c"};
char* batched_errs[4];
rocksdb_pinnableslice_t* pvals[4];
rocksdb_batched_multi_get_cf_slice(db, roptions, handles[1], 4,
batched_key_slices, pvals,
batched_errs, false);
const char* val;
size_t val_len;
for (i = 0; i < 4; ++i) {
CheckNoError(batched_errs[i]);
if (pvals[i] != NULL) {
val = rocksdb_pinnableslice_value(pvals[i], &val_len);
CheckEqual(expected_value[i], val, val_len);
rocksdb_pinnableslice_destroy(pvals[i]);
} else {
CheckEqual(expected_value[i], NULL, 0);
}
}
}
{
// Test rocksdb_batched_multi_get_cf_slice with sorted_input=true
// Keys must be in sorted order for this optimization
rocksdb_slice_t sorted_key_slices[3];
sorted_key_slices[0].data = "box";
sorted_key_slices[0].size = 3;
sorted_key_slices[1].data = "buff";
sorted_key_slices[1].size = 4;
sorted_key_slices[2].data = "notfound";
sorted_key_slices[2].size = 8;
const char* expected_value[3] = {"c", "rocksdb", NULL};
char* batched_errs[3];
rocksdb_pinnableslice_t* pvals[3];
rocksdb_batched_multi_get_cf_slice(db, roptions, handles[1], 3,
sorted_key_slices, pvals, batched_errs,
true);
const char* val;
size_t val_len;
for (i = 0; i < 3; ++i) {
CheckNoError(batched_errs[i]);
if (pvals[i] != NULL) {
val = rocksdb_pinnableslice_value(pvals[i], &val_len);
CheckEqual(expected_value[i], val, val_len);
rocksdb_pinnableslice_destroy(pvals[i]);
} else {
CheckEqual(expected_value[i], NULL, 0);
}
}
}
{
unsigned char value_found = 0;
@@ -3734,17 +3469,6 @@ int main(int argc, char** argv) {
rocksdb_transaction_put(txn, "foo", 3, "hello", 5, &err);
CheckNoError(err);
// test transaction get/set name (before commit)
{
rocksdb_transaction_set_name(txn, "test_txn", 8, &err);
CheckNoError(err);
size_t name_len;
char* txn_name = rocksdb_transaction_get_name(txn, &name_len);
CheckCondition(name_len == 8);
CheckCondition(memcmp(txn_name, "test_txn", 8) == 0);
rocksdb_free(txn_name);
}
// read from outside transaction, before commit
CheckTxnDBGet(txn_db, roptions, "foo", NULL);
CheckTxnDBPinGet(txn_db, roptions, "foo", NULL);
-226
View File
@@ -534,232 +534,6 @@ TEST_F(CompactFilesTest, GetCompactionJobInfo) {
delete db;
}
// Helper function to generate zero-padded keys
// e.g., MakeKey("a", 5) -> "a05", MakeKey("b", 42) -> "b42"
static std::string MakeKey(const std::string& prefix, int index) {
return prefix + (index < 10 ? "0" : "") + std::to_string(index);
}
TEST_F(CompactFilesTest, TrivialMoveNonOverlappingFiles) {
Options options;
options.create_if_missing = true;
options.disable_auto_compactions = true;
options.compression = kNoCompression;
options.level_compaction_dynamic_level_bytes = false;
DB* db = nullptr;
ASSERT_OK(DestroyDB(db_name_, options));
Status s = DB::Open(options, db_name_, &db);
ASSERT_OK(s);
ASSERT_NE(db, nullptr);
// Create 3 non-overlapping files in L0
// File 1: keys [a00-a99]
for (int i = 0; i < 100; i++) {
std::string key = MakeKey("a", i);
ASSERT_OK(db->Put(WriteOptions(), key, "value_" + key));
}
ASSERT_OK(db->Flush(FlushOptions()));
// File 2: keys [b00-b99]
for (int i = 0; i < 100; i++) {
std::string key = MakeKey("b", i);
ASSERT_OK(db->Put(WriteOptions(), key, "value_" + key));
}
ASSERT_OK(db->Flush(FlushOptions()));
// File 3: keys [c00-c99]
for (int i = 0; i < 100; i++) {
std::string key = MakeKey("c", i);
ASSERT_OK(db->Put(WriteOptions(), key, "value_" + key));
}
ASSERT_OK(db->Flush(FlushOptions()));
// Verify files are in L0
ColumnFamilyMetaData meta;
db->GetColumnFamilyMetaData(&meta);
ASSERT_EQ(meta.levels[0].files.size(), 3);
ASSERT_EQ(meta.levels[1].files.size(), 0);
// Get L0 files
std::vector<std::string> l0_files;
for (const auto& file : meta.levels[0].files) {
l0_files.push_back(file.db_path + "/" + file.name);
}
CompactionOptions compact_option;
compact_option.allow_trivial_move = true;
// Compact all L0 files to L1 (non-overlapping in L1)
ASSERT_OK(db->CompactFiles(compact_option, l0_files, 1));
// Verify files are now in L1
db->GetColumnFamilyMetaData(&meta);
ASSERT_EQ(meta.levels[0].files.size(), 0);
ASSERT_EQ(meta.levels[1].files.size(), 3);
// Get the first file from L1 (should be the one with keys a00-a99)
std::string l1_file_to_move;
std::vector<std::string> l1_files_to_move_later;
uint64_t l1_file_number = 0;
for (const auto& file : meta.levels[1].files) {
if (file.smallestkey[0] == 'a') {
l1_file_to_move = file.db_path + "/" + file.name;
l1_file_number = file.file_number;
} else {
l1_files_to_move_later.push_back(file.db_path + "/" + file.name);
}
}
ASSERT_FALSE(l1_file_to_move.empty());
// Set up sync point to verify trivial move path is taken
bool trivial_move_executed = false;
SyncPoint::GetInstance()->SetCallBack(
"DBImpl::CompactFilesImpl:TrivialMove",
[&](void* /*arg*/) { trivial_move_executed = true; });
SyncPoint::GetInstance()->EnableProcessing();
// Move the file from L1 to L6 - this should be a trivial move
// because the file doesn't overlap with anything in L6
std::vector<std::string> files_to_move = {l1_file_to_move};
ASSERT_OK(db->CompactFiles(compact_option, files_to_move, 6));
// Verify trivial move was executed
ASSERT_TRUE(trivial_move_executed);
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
// Verify the file is now in L6
db->GetColumnFamilyMetaData(&meta);
ASSERT_EQ(meta.levels[1].files.size(), 2); // Two files remain in L1
ASSERT_EQ(meta.levels[6].files.size(), 1); // One file in L6
// Verify it's the correct file in L6
bool found_file_in_l6 = false;
for (const auto& file : meta.levels[6].files) {
if (file.file_number == l1_file_number) {
found_file_in_l6 = true;
// Verify key range hasn't changed
ASSERT_EQ(file.smallestkey[0], 'a');
ASSERT_EQ(file.largestkey[0], 'a');
break;
}
}
ASSERT_TRUE(found_file_in_l6);
// Move the other 2 files from L1 to L6, with allow_trivial_move set to false.
// This will trigger a normal compaction, so the 2 files will be compacted
// into a single file in L6.
ASSERT_OK(db->CompactFiles(CompactionOptions(), l1_files_to_move_later, 6));
// Verify files in L6
db->GetColumnFamilyMetaData(&meta);
ASSERT_EQ(meta.levels[1].files.size(), 0); // Zero files remain in L1
ASSERT_EQ(meta.levels[6].files.size(), 2); // Two file in L6
// Verify data integrity - all keys should still be readable
for (int i = 0; i < 100; i++) {
std::string key = MakeKey("a", i);
std::string value;
ASSERT_OK(db->Get(ReadOptions(), key, &value));
ASSERT_EQ(value, "value_" + key);
}
delete db;
}
TEST_F(CompactFilesTest, TrivialMoveBlockedByOverlap) {
Options options;
options.create_if_missing = true;
options.disable_auto_compactions = true;
options.compression = kNoCompression;
options.level_compaction_dynamic_level_bytes = false;
options.num_levels = 7;
DB* db = nullptr;
ASSERT_OK(DestroyDB(db_name_, options));
Status s = DB::Open(options, db_name_, &db);
ASSERT_OK(s);
ASSERT_NE(db, nullptr);
// Create a file in L6 with keys [m00-m99] (wide range)
for (int i = 0; i < 100; i++) {
std::string key = MakeKey("m", i);
ASSERT_OK(db->Put(WriteOptions(), key, "value_" + key));
}
ASSERT_OK(db->Flush(FlushOptions()));
// Get L0 file
ColumnFamilyMetaData meta;
db->GetColumnFamilyMetaData(&meta);
std::vector<std::string> l0_files;
for (const auto& file : meta.levels[0].files) {
l0_files.push_back(file.db_path + "/" + file.name);
}
CompactionOptions compact_option;
compact_option.allow_trivial_move = true;
// Move to L6
ASSERT_OK(db->CompactFiles(compact_option, l0_files, 6));
// Now create a file in L1 with overlapping keys [m50-m60]
for (int i = 50; i <= 60; i++) {
std::string key = "m" + std::to_string(i);
ASSERT_OK(db->Put(WriteOptions(), key, "updated_value_" + key));
}
ASSERT_OK(db->Flush(FlushOptions()));
// Get the L0 file
db->GetColumnFamilyMetaData(&meta);
std::vector<std::string> l0_files_2;
for (const auto& file : meta.levels[0].files) {
l0_files_2.push_back(file.db_path + "/" + file.name);
}
// Move to L1
ASSERT_OK(db->CompactFiles(compact_option, l0_files_2, 1));
// Get the L1 file
db->GetColumnFamilyMetaData(&meta);
ASSERT_EQ(meta.levels[1].files.size(), 1);
std::string l1_file =
meta.levels[1].files[0].db_path + "/" + meta.levels[1].files[0].name;
// Set up sync point to verify full compaction path is taken
bool trivial_move_executed = false;
SyncPoint::GetInstance()->SetCallBack(
"DBImpl::CompactFilesImpl:TrivialMove",
[&](void* /*arg*/) { trivial_move_executed = true; });
SyncPoint::GetInstance()->EnableProcessing();
// Try to move from L1 to L6 - this should NOT be a trivial move
// because the file overlaps with the existing file in L6
ASSERT_OK(db->CompactFiles(compact_option, {l1_file}, 6));
// Verify trivial move was NOT executed (full compaction happened)
ASSERT_FALSE(trivial_move_executed);
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
// Verify the result - should have merged data in L6
db->GetColumnFamilyMetaData(&meta);
ASSERT_EQ(meta.levels[1].files.size(), 0); // L1 should be empty
// L6 should have the merged file (may be 1 file if merged, or 2 if not)
ASSERT_GE(meta.levels[6].files.size(), 1);
// Verify updated values are present
for (int i = 50; i <= 60; i++) {
std::string key = "m" + std::to_string(i);
std::string value;
ASSERT_OK(db->Get(ReadOptions(), key, &value));
ASSERT_EQ(value, "updated_value_" + key);
}
delete db;
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+3 -17
View File
@@ -281,9 +281,8 @@ Compaction::Compaction(
std::vector<CompactionInputFiles> _inputs, int _output_level,
uint64_t _target_file_size, uint64_t _max_compaction_bytes,
uint32_t _output_path_id, CompressionType _compression,
CompressionOptions _compression_opts,
Temperature _output_temperature_override, uint32_t _max_subcompactions,
std::vector<FileMetaData*> _grandparents,
CompressionOptions _compression_opts, Temperature _output_temperature,
uint32_t _max_subcompactions, std::vector<FileMetaData*> _grandparents,
std::optional<SequenceNumber> _earliest_snapshot,
const SnapshotChecker* _snapshot_checker,
CompactionReason _compaction_reason, const std::string& _trim_ts,
@@ -304,7 +303,7 @@ Compaction::Compaction(
output_path_id_(_output_path_id),
output_compression_(_compression),
output_compression_opts_(_compression_opts),
output_temperature_override_(_output_temperature_override),
output_temperature_(_output_temperature),
deletion_compaction_(_compaction_reason == CompactionReason::kFIFOTtl ||
_compaction_reason ==
CompactionReason::kFIFOMaxSize),
@@ -1129,17 +1128,4 @@ void Compaction::FilterInputsForCompactionIterator() {
}
}
Temperature Compaction::GetOutputTemperature(bool is_proximal_level) const {
if (output_temperature_override_ != Temperature::kUnknown) {
return output_temperature_override_;
}
if (is_last_level() && !is_proximal_level &&
mutable_cf_options_.last_level_temperature != Temperature::kUnknown) {
return mutable_cf_options_.last_level_temperature;
}
return mutable_cf_options_.default_write_temperature;
}
} // namespace ROCKSDB_NAMESPACE
+3 -12
View File
@@ -90,8 +90,7 @@ class Compaction {
uint64_t target_file_size, uint64_t max_compaction_bytes,
uint32_t output_path_id, CompressionType compression,
CompressionOptions compression_opts,
Temperature output_temperature_override,
uint32_t max_subcompactions,
Temperature output_temperature, uint32_t max_subcompactions,
std::vector<FileMetaData*> grandparents,
std::optional<SequenceNumber> earliest_snapshot,
const SnapshotChecker* snapshot_checker,
@@ -180,10 +179,6 @@ class Compaction {
const std::vector<CompactionInputFiles>* inputs() { return &inputs_; }
// Returns the LevelFilesBrief of the specified compaction input level.
// Note that if the compaction includes standalone range deletion file,
// this function returns the result after filtering out input files covered
// by the range deletion file.
// Use inputs() if you want to get the original input files.
const LevelFilesBrief* input_levels(size_t compaction_input_level) const {
return &input_levels_[compaction_input_level];
}
@@ -414,11 +409,7 @@ class Compaction {
uint64_t max_compaction_bytes() const { return max_compaction_bytes_; }
// Order of precedence for temperature:
// 1. Override temp if not kUnknown
// 2. Temperature of the last level files if applicable
// 3. Default write temperature
Temperature GetOutputTemperature(bool is_proximal_level = false) const;
Temperature output_temperature() const { return output_temperature_; }
uint32_t max_subcompactions() const { return max_subcompactions_; }
@@ -550,7 +541,7 @@ class Compaction {
const uint32_t output_path_id_;
CompressionType output_compression_;
CompressionOptions output_compression_opts_;
Temperature output_temperature_override_;
Temperature output_temperature_;
// If true, then the compaction can be done by simply deleting input files.
const bool deletion_compaction_;
// should it split the output file using the compact cursor?
-10
View File
@@ -270,16 +270,6 @@ class CompactionIterator {
// true, unless `must_count_input_entries=true` was specified during iterator
// creation (which ensures the count is always accurate).
uint64_t NumInputEntryScanned() const { return input_.NumItered(); }
// Returns true if the current valid key was already scanned/counted during
// a lookahead operation in a previous iteration.
//
// REQUIRED: Valid() must be true
bool IsCurrentKeyAlreadyScanned() const {
assert(Valid());
return at_next_ || merge_out_iter_.Valid();
}
Status InputStatus() const { return input_.status(); }
bool IsDeleteRangeSentinelKey() const { return is_range_del_; }
+91 -589
View File
@@ -51,9 +51,7 @@
#include "rocksdb/status.h"
#include "rocksdb/table.h"
#include "rocksdb/utilities/options_type.h"
#include "table/format.h"
#include "table/merging_iterator.h"
#include "table/meta_blocks.h"
#include "table/table_builder.h"
#include "table/unique_id_impl.h"
#include "test_util/sync_point.h"
@@ -255,9 +253,7 @@ void CompactionJob::ReportStartedCompaction(Compaction* compaction) {
void CompactionJob::Prepare(
std::optional<std::pair<std::optional<Slice>, std::optional<Slice>>>
known_single_subcompact,
const CompactionProgress& compaction_progress,
log::Writer* compaction_progress_writer) {
known_single_subcompact) {
db_mutex_->AssertHeld();
AutoThreadOperationStageUpdater stage_updater(
ThreadStatus::STAGE_COMPACTION_PREPARE);
@@ -307,9 +303,6 @@ void CompactionJob::Prepare(
/*sub_job_id*/ 0);
}
MaybeAssignCompactionProgressAndWriter(compaction_progress,
compaction_progress_writer);
// collect all seqno->time information from the input files which will be used
// to encode seqno->time to the output files.
SequenceNumber preserve_time_min_seqno = kMaxSequenceNumber;
@@ -408,25 +401,6 @@ void CompactionJob::Prepare(
options_file_number_ = versions_->options_file_number();
}
void CompactionJob::MaybeAssignCompactionProgressAndWriter(
const CompactionProgress& compaction_progress,
log::Writer* compaction_progress_writer) {
// LIMITATION: Only supports resuming single subcompaction for now
if (compact_->sub_compact_states.size() != 1) {
return;
}
if (!compaction_progress.empty()) {
assert(compaction_progress.size() == 1);
SubcompactionState* sub_compact = &compact_->sub_compact_states[0];
const SubcompactionProgress& subcompaction_progress =
compaction_progress[0];
sub_compact->SetSubcompactionProgress(subcompaction_progress);
}
compaction_progress_writer_ = compaction_progress_writer;
}
uint64_t CompactionJob::GetSubcompactionsLimit() {
return extra_num_subcompaction_threads_reserved_ +
std::max(
@@ -812,19 +786,20 @@ Status CompactionJob::SyncOutputDirectories() {
Status CompactionJob::VerifyOutputFiles() {
Status status;
std::vector<port::Thread> thread_pool;
ColumnFamilyData* cfd = compact_->compaction->column_family_data();
VerifyOutputFlags verify_output_flags =
compact_->compaction->mutable_cf_options().verify_output_flags;
// For backward compatibility
if (paranoid_file_checks_) {
verify_output_flags |= VerifyOutputFlags::kVerifyIteration;
verify_output_flags |= VerifyOutputFlags::kEnableForLocalCompaction;
verify_output_flags |= VerifyOutputFlags::kEnableForRemoteCompaction;
std::vector<const CompactionOutputs::Output*> files_output;
for (const auto& state : compact_->sub_compact_states) {
for (const auto& output : state.GetOutputs()) {
files_output.emplace_back(&output);
}
}
auto verify_table = [&](SubcompactionState& subcompaction_state) {
for (const auto& output_file : subcompaction_state.GetOutputs()) {
ColumnFamilyData* cfd = compact_->compaction->column_family_data();
std::atomic<size_t> next_file_idx(0);
auto verify_table = [&](Status& output_status) {
while (true) {
size_t file_idx = next_file_idx.fetch_add(1);
if (file_idx >= files_output.size()) {
break;
}
// Verify that the table is usable
// We set for_compaction to false and don't
// OptimizeForCompactionTableRead here because this is a special case
@@ -833,19 +808,13 @@ Status CompactionJob::VerifyOutputFiles() {
// verification as user reads since the goal is to cache it here for
// further user reads
ReadOptions verify_table_read_options(Env::IOActivity::kCompaction);
verify_table_read_options.verify_checksums = true;
verify_table_read_options.readahead_size =
file_options_for_read_.compaction_readahead_size;
std::unique_ptr<TableReader> table_reader_guard;
TableReader* table_reader_ptr = table_reader_guard.get();
verify_table_read_options.rate_limiter_priority =
GetRateLimiterPriority();
InternalIterator* iter = cfd->table_cache()->NewIterator(
verify_table_read_options, file_options_, cfd->internal_comparator(),
output_file.meta,
files_output[file_idx]->meta,
/*range_del_agg=*/nullptr, compact_->compaction->mutable_cf_options(),
/*table_reader_ptr=*/&table_reader_ptr,
/*table_reader_ptr=*/nullptr,
cfd->internal_stats()->GetFileReadHist(
compact_->compaction->output_level()),
TableReaderCaller::kCompactionRefill, /*arena=*/nullptr,
@@ -855,63 +824,38 @@ Status CompactionJob::VerifyOutputFiles() {
/*largest_compaction_key=*/nullptr,
/*allow_unprepared_value=*/false);
auto s = iter->status();
if (s.ok()) {
// Check for remote/local compaction and verify_output_flags flags
const bool should_verify =
(subcompaction_state.compaction_job_stats.is_remote_compaction &&
!!(verify_output_flags &
VerifyOutputFlags::kEnableForRemoteCompaction)) ||
(!subcompaction_state.compaction_job_stats.is_remote_compaction &&
!!(verify_output_flags &
VerifyOutputFlags::kEnableForLocalCompaction));
if (should_verify) {
const bool should_verify_block_checksum =
!!(verify_output_flags & VerifyOutputFlags::kVerifyBlockChecksum);
const bool should_verify_iteration =
!!(verify_output_flags & VerifyOutputFlags::kVerifyIteration);
if (should_verify_block_checksum) {
assert(table_reader_ptr != nullptr);
// If verifying iteration as well, verify meta blocks here only to
// avoid redundant checks on data blocks
s = table_reader_ptr->VerifyChecksum(
verify_table_read_options, TableReaderCaller::kCompaction,
/*meta_blocks_only=*/should_verify_iteration);
}
if (s.ok() && should_verify_iteration) {
OutputValidator validator(cfd->internal_comparator(),
/*_enable_hash=*/true);
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
s = validator.Add(iter->key(), iter->value());
if (!s.ok()) {
break;
}
}
if (s.ok()) {
s = iter->status();
}
if (s.ok() && !validator.CompareValidator(output_file.validator)) {
s = Status::Corruption(
"Key-value checksum of compaction output doesn't match what "
"was computed when written");
}
if (s.ok() && paranoid_file_checks_) {
OutputValidator validator(cfd->internal_comparator(),
/*_enable_hash=*/true);
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
s = validator.Add(iter->key(), iter->value());
if (!s.ok()) {
break;
}
}
if (s.ok()) {
s = iter->status();
}
if (s.ok() &&
!validator.CompareValidator(files_output[file_idx]->validator)) {
s = Status::Corruption("Paranoid checksums do not match");
}
}
delete iter;
if (!s.ok()) {
subcompaction_state.status = s;
output_status = s;
break;
}
}
};
for (size_t i = 1; i < compact_->sub_compact_states.size(); i++) {
thread_pool.emplace_back(verify_table,
std::ref(compact_->sub_compact_states[i]));
std::ref(compact_->sub_compact_states[i].status));
}
verify_table(compact_->sub_compact_states[0]);
verify_table(compact_->sub_compact_states[0].status);
for (auto& thread : thread_pool) {
thread.join();
}
@@ -980,8 +924,7 @@ void CompactionJob::FinalizeCompactionRun(
UpdateCompactionJobInputStatsFromInternalStats(internal_stats_,
num_input_range_del);
}
UpdateCompactionJobOutputStatsFromInternalStats(input_status,
internal_stats_);
UpdateCompactionJobOutputStatsFromInternalStats(internal_stats_);
RecordCompactionIOStats();
LogFlush(db_options_.info_log);
@@ -1306,8 +1249,8 @@ Status CompactionJob::SetupAndValidateCompactionFilter(
return Status::OK();
}
void CompactionJob::InitializeReadOptionsAndBoundaries(
const size_t ts_sz, ReadOptions& read_options,
void CompactionJob::InitializeReadOptions(
ColumnFamilyData* cfd, ReadOptions& read_options,
SubcompactionKeyBoundaries& boundaries) {
read_options.verify_checksums = true;
read_options.fill_cache = false;
@@ -1321,6 +1264,8 @@ void CompactionJob::InitializeReadOptionsAndBoundaries(
// Remove the timestamps from boundaries because boundaries created in
// GenSubcompactionBoundaries doesn't strip away the timestamp.
const size_t ts_sz = cfd->user_comparator()->timestamp_size();
if (boundaries.start.has_value()) {
read_options.iterate_lower_bound = &(*boundaries.start);
if (ts_sz > 0) {
@@ -1337,7 +1282,30 @@ void CompactionJob::InitializeReadOptionsAndBoundaries(
read_options.iterate_upper_bound = &(*boundaries.end_without_ts);
}
}
}
InternalIterator* CompactionJob::CreateInputIterator(
SubcompactionState* sub_compact, ColumnFamilyData* cfd,
SubcompactionInternalIterators& iterators,
SubcompactionKeyBoundaries& boundaries, ReadOptions& read_options) {
// This is assigned after creation of SubcompactionState to simplify that
// creation across both CompactionJob and CompactionServiceCompactionJob
sub_compact->AssignRangeDelAggregator(
std::make_unique<CompactionRangeDelAggregator>(
&cfd->internal_comparator(), job_context_->snapshot_seqs,
&full_history_ts_low_, &trim_ts_));
InitializeReadOptions(cfd, read_options, boundaries);
// Although the v2 aggregator is what the level iterator(s) know about,
// the AddTombstones calls will be propagated down to the v1 aggregator.
iterators.raw_input =
std::unique_ptr<InternalIterator>(versions_->MakeInputIterator(
read_options, sub_compact->compaction, sub_compact->RangeDelAgg(),
file_options_for_read_, boundaries.start, boundaries.end));
InternalIterator* input = iterators.raw_input.get();
const size_t ts_sz = cfd->user_comparator()->timestamp_size();
if (ts_sz > 0) {
if (ts_sz <= strlen(boundaries.kMaxTs)) {
boundaries.ts_slice = Slice(boundaries.kMaxTs, ts_sz);
@@ -1346,6 +1314,7 @@ void CompactionJob::InitializeReadOptionsAndBoundaries(
boundaries.ts_slice = Slice(boundaries.max_ts);
}
}
if (boundaries.start.has_value()) {
boundaries.start_ikey.SetInternalKey(*boundaries.start, kMaxSequenceNumber,
kValueTypeForSeek);
@@ -1366,29 +1335,6 @@ void CompactionJob::InitializeReadOptionsAndBoundaries(
boundaries.end_internal_key = boundaries.end_ikey.GetInternalKey();
boundaries.end_user_key = boundaries.end_ikey.GetUserKey();
}
}
InternalIterator* CompactionJob::CreateInputIterator(
SubcompactionState* sub_compact, ColumnFamilyData* cfd,
SubcompactionInternalIterators& iterators,
SubcompactionKeyBoundaries& boundaries, ReadOptions& read_options) {
const size_t ts_sz = cfd->user_comparator()->timestamp_size();
InitializeReadOptionsAndBoundaries(ts_sz, read_options, boundaries);
// This is assigned after creation of SubcompactionState to simplify that
// creation across both CompactionJob and CompactionServiceCompactionJob
sub_compact->AssignRangeDelAggregator(
std::make_unique<CompactionRangeDelAggregator>(
&cfd->internal_comparator(), job_context_->snapshot_seqs,
&full_history_ts_low_, &trim_ts_));
// Although the v2 aggregator is what the level iterator(s) know about,
// the AddTombstones calls will be propagated down to the v1 aggregator.
iterators.raw_input =
std::unique_ptr<InternalIterator>(versions_->MakeInputIterator(
read_options, sub_compact->compaction, sub_compact->RangeDelAgg(),
file_options_for_read_, boundaries.start, boundaries.end));
InternalIterator* input = iterators.raw_input.get();
if (boundaries.start.has_value() || boundaries.end.has_value()) {
iterators.clip = std::make_unique<ClippingIterator>(
@@ -1458,8 +1404,7 @@ std::unique_ptr<CompactionIterator> CompactionJob::CreateCompactionIterator(
env_, ShouldReportDetailedTime(env_, stats_), sub_compact->RangeDelAgg(),
blob_resources.blob_file_builder.get(), db_options_.allow_data_in_errors,
db_options_.enforce_single_del_contracts, manual_compaction_canceled_,
sub_compact->compaction
->DoesInputReferenceBlobFiles() /* must_count_input_entries */,
sub_compact->compaction->DoesInputReferenceBlobFiles(),
sub_compact->compaction, compaction_filter, shutting_down_,
db_options_.info_log, full_history_ts_low, preserve_seqno_after_);
}
@@ -1479,13 +1424,11 @@ CompactionJob::CreateFileHandlers(SubcompactionState* sub_compact,
const CompactionFileCloseFunc close_file_func =
[this, sub_compact, start_user_key, end_user_key](
const Status& status,
const ParsedInternalKey& prev_iter_output_internal_key,
const Slice& next_table_min_key, const CompactionIterator* c_iter,
CompactionOutputs& outputs) {
return this->FinishCompactionOutputFile(
status, prev_iter_output_internal_key, next_table_min_key,
start_user_key, end_user_key, c_iter, sub_compact, outputs);
CompactionOutputs& outputs, const Status& status,
const Slice& next_table_min_key) {
return this->FinishCompactionOutputFile(status, sub_compact, outputs,
next_table_min_key,
start_user_key, end_user_key);
};
return {open_file_func, close_file_func};
@@ -1499,9 +1442,6 @@ Status CompactionJob::ProcessKeyValue(
const uint64_t kRecordStatsEvery = 1000;
[[maybe_unused]] const std::optional<const Slice> end = sub_compact->end;
IterKey prev_iter_output_key;
ParsedInternalKey prev_iter_output_internal_key;
TEST_SYNC_POINT_CALLBACK(
"CompactionJob::ProcessKeyValueCompaction()::Processing",
static_cast<void*>(const_cast<Compaction*>(sub_compact->compaction)));
@@ -1552,8 +1492,7 @@ Status CompactionJob::ProcessKeyValue(
// TODO: it would be better to have the compaction file open/close moved
// into `CompactionOutputs` which has the output file information.
status = sub_compact->AddToOutput(*c_iter, use_proximal_output,
open_file_func, close_file_func,
prev_iter_output_internal_key);
open_file_func, close_file_func);
if (!status.ok()) {
break;
}
@@ -1561,11 +1500,6 @@ Status CompactionJob::ProcessKeyValue(
TEST_SYNC_POINT_CALLBACK("CompactionJob::Run():PausingManualCompaction:2",
static_cast<void*>(const_cast<std::atomic<bool>*>(
&manual_compaction_canceled_)));
prev_iter_output_key.SetInternalKey(c_iter->key(),
&prev_iter_output_internal_key);
prev_iter_output_internal_key.sequence = ikey.sequence;
prev_iter_output_internal_key.type = ikey.type;
c_iter->Next();
#ifndef NDEBUG
@@ -1750,22 +1684,6 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
ReadOptions read_options;
const WriteOptions write_options(Env::IOPriority::IO_LOW,
Env::IOActivity::kCompaction);
InternalIterator* input_iter = CreateInputIterator(
sub_compact, cfd, iterators, boundaries, read_options);
assert(input_iter);
Status status =
MaybeResumeSubcompactionProgressOnInputIterator(sub_compact, input_iter);
if (status.IsNotFound()) {
input_iter->SeekToFirst();
} else if (!status.ok()) {
sub_compact->status = status;
return;
}
MergeHelper merge(
env_, cfd->user_comparator(), cfd->ioptions().merge_operator.get(),
compaction_filter, db_options_.info_log.get(),
@@ -1774,6 +1692,11 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
compact_->compaction->level(), db_options_.stats);
BlobFileResources blob_resources;
InternalIterator* input_iter = CreateInputIterator(
sub_compact, cfd, iterators, boundaries, read_options);
assert(input_iter);
input_iter->SeekToFirst();
auto c_iter =
CreateCompactionIterator(sub_compact, cfd, input_iter, compaction_filter,
merge, blob_resources, write_options);
@@ -1788,8 +1711,9 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
auto [open_file_func, close_file_func] =
CreateFileHandlers(sub_compact, boundaries);
status = ProcessKeyValue(sub_compact, cfd, c_iter.get(), open_file_func,
close_file_func, prev_cpu_micros);
Status status =
ProcessKeyValue(sub_compact, cfd, c_iter.get(), open_file_func,
close_file_func, prev_cpu_micros);
status = FinalizeProcessKeyValueStatus(cfd, input_iter, c_iter.get(), status);
@@ -1871,11 +1795,9 @@ void CompactionJob::RecordDroppedKeys(
}
Status CompactionJob::FinishCompactionOutputFile(
const Status& input_status,
const ParsedInternalKey& prev_iter_output_internal_key,
const Slice& next_table_min_key, const Slice* comp_start_user_key,
const Slice* comp_end_user_key, const CompactionIterator* c_iter,
SubcompactionState* sub_compact, CompactionOutputs& outputs) {
const Status& input_status, SubcompactionState* sub_compact,
CompactionOutputs& outputs, const Slice& next_table_min_key,
const Slice* comp_start_user_key, const Slice* comp_end_user_key) {
AutoThreadOperationStageUpdater stage_updater(
ThreadStatus::STAGE_COMPACTION_SYNC_FILE);
assert(sub_compact != nullptr);
@@ -2049,99 +1971,10 @@ Status CompactionJob::FinishCompactionOutputFile(
}
}
if (s.ok() && ShouldUpdateSubcompactionProgress(sub_compact, c_iter,
prev_iter_output_internal_key,
next_table_min_key, meta)) {
UpdateSubcompactionProgress(c_iter, next_table_min_key, sub_compact);
s = PersistSubcompactionProgress(sub_compact);
}
outputs.ResetBuilder();
return s;
}
bool CompactionJob::ShouldUpdateSubcompactionProgress(
const SubcompactionState* sub_compact, const CompactionIterator* c_iter,
const ParsedInternalKey& prev_iter_output_internal_key,
const Slice& next_table_min_internal_key, const FileMetaData* meta) const {
const auto* cfd = sub_compact->compaction->column_family_data();
// No need to update when the progress will not get persisted
if (compaction_progress_writer_ == nullptr) {
return false;
}
// No need to update for a new empty output
if (meta == nullptr) {
return false;
}
// TODO(hx235): save progress even on the last output file
if (next_table_min_internal_key.empty()) {
return false;
}
// LIMITATION: Persisting compaction progress with timestamp
// is not supported since the feature of persisting timestamp of the key in
// SST files itself is still experimental
size_t ts_sz = cfd->user_comparator()->timestamp_size();
if (ts_sz > 0) {
return false;
}
// LIMITATION: Compaction progress persistence disabled for file boundaries
// containing range deletions. Range deletions can span file boundaries,
// making it difficult to ensure adjacent output tables have different user
// keys. See the last check for why different users keys of adjacent output
// tables are needed
const ValueType next_table_min_internal_key_type =
ExtractValueType(next_table_min_internal_key);
const ValueType prev_iter_output_internal_key_type =
prev_iter_output_internal_key.user_key.empty()
? ValueType::kTypeValue
: prev_iter_output_internal_key.type;
// Range deletes truncated to align with file boundaries may be output by the
// compaction iterator with `ValueType::kTypeMaxValid` instead of the original
// type.
if ((next_table_min_internal_key_type == ValueType::kTypeRangeDeletion ||
next_table_min_internal_key_type == ValueType::kTypeMaxValid) ||
(prev_iter_output_internal_key_type == ValueType::kTypeRangeDeletion ||
prev_iter_output_internal_key_type == ValueType::kTypeMaxValid)) {
return false;
}
// LIMITATION: Compaction progress persistence disabled when adjacent output
// tables share the same user key at boundaries. This ensures a simple Seek()
// of the next key when resuming can process all versions of a user key
const Slice next_table_min_user_key =
ExtractUserKey(next_table_min_internal_key);
const Slice prev_table_last_user_key =
prev_iter_output_internal_key.user_key.empty()
? Slice()
: prev_iter_output_internal_key.user_key;
if (cfd->user_comparator()->EqualWithoutTimestamp(next_table_min_user_key,
prev_table_last_user_key)) {
return false;
}
// LIMITATION: Don't save progress if the current key has already been scanned
// (looked ahead) in the input but not yet output. This can happen with merge
// operations, single deletes, and deletes at the bottommost level where
// CompactionIterator needs to look ahead to process multiple entries for the
// same user key before outputting a result. If we saved progress and resumed
// at this boundary, the resumed session would see and process the same input
// key again through Seek(), leading to incorrect double-counting in
// number of processed input entries and input count verification failure
//
// TODO(hx235): Offset num_processed_input_records to avoid double counting
// instead of disabling progress persistence.
if (c_iter->IsCurrentKeyAlreadyScanned()) {
return false;
}
return true;
}
Status CompactionJob::InstallCompactionResults(bool* compaction_released) {
assert(compact_);
@@ -2287,8 +2120,15 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
// Pass temperature of the last level files to FileSystem.
FileOptions fo_copy = file_options_;
auto temperature =
sub_compact->compaction->GetOutputTemperature(outputs.IsProximalLevel());
Temperature temperature = sub_compact->compaction->output_temperature();
Temperature last_level_temp =
sub_compact->compaction->mutable_cf_options().last_level_temperature;
// 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.IsProximalLevel()) {
temperature = last_level_temp;
}
fo_copy.temperature = temperature;
fo_copy.write_hint = write_hint_;
@@ -2564,7 +2404,6 @@ void CompactionJob::UpdateCompactionJobInputStatsFromInternalStats(
}
void CompactionJob::UpdateCompactionJobOutputStatsFromInternalStats(
const Status& status,
const InternalStats::CompactionStatsFull& internal_stats) const {
assert(job_stats_);
job_stats_->elapsed_micros = internal_stats.output_level_stats.micros;
@@ -2595,7 +2434,7 @@ void CompactionJob::UpdateCompactionJobOutputStatsFromInternalStats(
internal_stats.proximal_level_stats.num_output_files_blob;
}
if (status.ok() && job_stats_->num_output_files > 0) {
if (job_stats_->num_output_files > 0) {
CopyPrefix(compact_->SmallestUserKey(),
CompactionJobStats::kMaxPrefixLength,
&job_stats_->smallest_output_key_prefix);
@@ -2676,344 +2515,6 @@ Env::IOPriority CompactionJob::GetRateLimiterPriority() {
return Env::IO_LOW;
}
Status CompactionJob::ReadTablePropertiesDirectly(
const ImmutableOptions& ioptions, const MutableCFOptions& moptions,
const FileMetaData* file_meta, const ReadOptions& read_options,
std::shared_ptr<const TableProperties>* tp) {
std::unique_ptr<FSRandomAccessFile> file;
std::string file_name = GetTableFileName(file_meta->fd.GetNumber());
Status s = ioptions.fs->NewRandomAccessFile(file_name, file_options_, &file,
nullptr /* dbg */);
if (!s.ok()) {
return s;
}
std::unique_ptr<RandomAccessFileReader> file_reader(
new RandomAccessFileReader(
std::move(file), file_name, ioptions.clock, io_tracer_,
ioptions.stats, Histograms::SST_READ_MICROS /* hist_type */,
nullptr /* file_read_hist */, ioptions.rate_limiter.get(),
ioptions.listeners));
std::unique_ptr<TableProperties> props;
uint64_t magic_number = kBlockBasedTableMagicNumber;
const auto* table_factory = moptions.table_factory.get();
if (table_factory == nullptr) {
return Status::Incomplete("Table factory is not set");
} else {
const auto& table_factory_name = table_factory->Name();
if (table_factory_name == TableFactory::kPlainTableName()) {
magic_number = kPlainTableMagicNumber;
} else if (table_factory_name == TableFactory::kCuckooTableName()) {
magic_number = kCuckooTableMagicNumber;
}
}
s = ReadTableProperties(file_reader.get(), file_meta->fd.GetFileSize(),
magic_number, ioptions, read_options, &props);
if (!s.ok()) {
return s;
}
*tp = std::move(props);
return s;
}
Status CompactionJob::ReadOutputFilesTableProperties(
const autovector<FileMetaData>& output_files,
const ReadOptions& read_options,
std::vector<std::shared_ptr<const TableProperties>>&
output_files_table_properties,
bool is_proximal_level) {
assert(!output_files.empty());
static const char* level_type =
is_proximal_level ? "proximal output" : "output";
output_files_table_properties.reserve(output_files.size());
Status s;
for (const FileMetaData& metadata : output_files) {
std::shared_ptr<const TableProperties> tp;
s = ReadTablePropertiesDirectly(compact_->compaction->immutable_options(),
compact_->compaction->mutable_cf_options(),
&metadata, read_options, &tp);
if (!s.ok()) {
ROCKS_LOG_ERROR(
db_options_.info_log,
"Failed to read table properties for %s level output file #%" PRIu64
": %s",
level_type, metadata.fd.GetNumber(), s.ToString().c_str());
return s;
}
if (tp == nullptr) {
ROCKS_LOG_ERROR(db_options_.info_log,
"Empty table property for %s level output file #%" PRIu64
"",
level_type, metadata.fd.GetNumber());
s = Status::Corruption("Empty table property for " +
std::string(level_type) +
" level output files during resuming");
return s;
}
output_files_table_properties.push_back(tp);
}
return s;
}
void CompactionJob::RestoreCompactionOutputs(
const ColumnFamilyData* cfd,
const std::vector<std::shared_ptr<const TableProperties>>&
output_files_table_properties,
SubcompactionProgressPerLevel& subcompaction_progress_per_level,
CompactionOutputs* outputs_to_restore) {
assert(outputs_to_restore->GetOutputs().size() == 0);
const auto& output_files = subcompaction_progress_per_level.GetOutputFiles();
for (size_t i = 0; i < output_files.size(); i++) {
FileMetaData file_copy = output_files[i];
outputs_to_restore->AddOutput(std::move(file_copy),
cfd->internal_comparator(),
paranoid_file_checks_, true /* finished */);
outputs_to_restore->UpdateTableProperties(
*output_files_table_properties[i]);
}
outputs_to_restore->SetNumOutputRecords(
subcompaction_progress_per_level.GetNumProcessedOutputRecords());
}
// Attempt to resume compaction from a previously persisted compaction progress.
//
// RETURNS:
// - Status::OK():
// * Input iterator positioned at next unprocessed key
// * CompactionOutputs objects fully restored for both output and proximal
// output levels in SubcompactionState
// * Compaction job statistics accurately reflect input and output records
// processed for record count verification
// * File number generation advanced to prevent conflicts with existing outputs
// - Status::NotFound(): No valid progress to resume from
// - Status::Corruption(): Resume key is invalid, beyond input range, or output
// restoration failed
// - Other non-OK status: Iterator errors or file system issues during
// restoration
//
// The caller must check for Status::IsIncomplete() to distinguish between
// "no resume needed" (proceed with `InternalIterator::SeekToFirst()`) vs
// "resume failed" scenarios.
Status CompactionJob::MaybeResumeSubcompactionProgressOnInputIterator(
SubcompactionState* sub_compact, InternalIterator* input_iter) {
const ReadOptions read_options(Env::IOActivity::kCompaction);
ColumnFamilyData* cfd = sub_compact->compaction->column_family_data();
SubcompactionProgress& subcompaction_progress =
sub_compact->GetSubcompactionProgressRef();
if (subcompaction_progress.output_level_progress
.GetNumProcessedOutputRecords() == 0 &&
subcompaction_progress.proximal_output_level_progress
.GetNumProcessedOutputRecords() == 0) {
return Status::NotFound("No subcompaction progress to resume");
}
ROCKS_LOG_INFO(db_options_.info_log, "[%s] [JOB %d] Resuming compaction : %s",
cfd->GetName().c_str(), job_id_,
subcompaction_progress.ToString().c_str());
input_iter->Seek(subcompaction_progress.next_internal_key_to_compact);
if (!input_iter->Valid()) {
ROCKS_LOG_ERROR(db_options_.info_log,
"[%s] [JOB %d] Iterator is invalid after "
"seeking to the key to resume. This indicates the key is "
"incorrectly beyond the input data range.",
cfd->GetName().c_str(), job_id_);
return Status::Corruption(
"The key to resume is beyond the input data range");
} else if (!input_iter->status().ok()) {
ROCKS_LOG_ERROR(db_options_.info_log,
"[%s] [JOB %d] Iterator has error after seeking to "
"the key to resume: %s",
cfd->GetName().c_str(), job_id_,
input_iter->status().ToString().c_str());
return Status::Corruption(
"Iterator has error status after seeking to the key: " +
input_iter->status().ToString());
}
sub_compact->compaction_job_stats.has_accurate_num_input_records =
subcompaction_progress.num_processed_input_records != 0;
sub_compact->compaction_job_stats.num_input_records =
subcompaction_progress.num_processed_input_records;
for (const bool& is_proximal_level : {false, true}) {
if (is_proximal_level &&
!sub_compact->compaction->SupportsPerKeyPlacement()) {
continue;
}
Status s;
SubcompactionProgressPerLevel& subcompaction_progress_per_level =
is_proximal_level
? subcompaction_progress.proximal_output_level_progress
: subcompaction_progress.output_level_progress;
const auto& output_files =
subcompaction_progress_per_level.GetOutputFiles();
std::vector<std::shared_ptr<const TableProperties>>
output_files_table_properties;
// TODO(hx235): investigate if we can skip reading properties to save read
// IO
s = ReadOutputFilesTableProperties(output_files, read_options,
output_files_table_properties);
if (!s.ok()) {
ROCKS_LOG_ERROR(
db_options_.info_log,
"[%s] [JOB %d] Failed to read table properties for %s output level"
"files "
"during resume: %s.",
cfd->GetName().c_str(), job_id_, is_proximal_level ? "proximal" : "",
s.ToString().c_str());
return Status::Corruption(
"Not able to resume due to table property reading error " +
s.ToString());
}
RestoreCompactionOutputs(cfd, output_files_table_properties,
subcompaction_progress_per_level,
sub_compact->Outputs(is_proximal_level));
// Skip past all the used file numbers to avoid creating new output files
// after resumption that conflict with the existing output files
for (const auto& file_meta : output_files) {
uint64_t file_number = file_meta.fd.GetNumber();
while (versions_->NewFileNumber() <= file_number) {
versions_->FetchAddFileNumber(1);
}
}
}
return Status::OK();
}
void CompactionJob::UpdateSubcompactionProgress(
const CompactionIterator* c_iter, const Slice next_table_min_key,
SubcompactionState* sub_compact) {
assert(c_iter);
SubcompactionProgress& subcompaction_progress =
sub_compact->GetSubcompactionProgressRef();
IterKey next_ikey_to_compact;
next_ikey_to_compact.SetInternalKey(ExtractUserKey(next_table_min_key),
kMaxSequenceNumber, kValueTypeForSeek);
subcompaction_progress.next_internal_key_to_compact =
next_ikey_to_compact.GetInternalKey().ToString();
// Track total processed input records for progress reporting by combining:
// - Resumed count: records already processed before compaction was
// interrupted
// - Current count: records scanned in the current compaction session
// Only update when both tracking mechanisms provide accurate counts to ensure
// reliability.
subcompaction_progress.num_processed_input_records =
c_iter->HasNumInputEntryScanned() &&
sub_compact->compaction_job_stats.has_accurate_num_input_records
? c_iter->NumInputEntryScanned() +
sub_compact->compaction_job_stats.num_input_records
: 0;
UpdateSubcompactionProgressPerLevel(
sub_compact, false /* is_proximal_level */, subcompaction_progress);
if (sub_compact->compaction->SupportsPerKeyPlacement()) {
UpdateSubcompactionProgressPerLevel(
sub_compact, true /* is_proximal_level */, subcompaction_progress);
}
}
void CompactionJob::UpdateSubcompactionProgressPerLevel(
SubcompactionState* sub_compact, bool is_proximal_level,
SubcompactionProgress& subcompaction_progress) {
SubcompactionProgressPerLevel& subcompaction_progress_per_level =
is_proximal_level ? subcompaction_progress.proximal_output_level_progress
: subcompaction_progress.output_level_progress;
subcompaction_progress_per_level.SetNumProcessedOutputRecords(
sub_compact->OutputStats(is_proximal_level)->num_output_records);
const auto& prev_output_files =
subcompaction_progress_per_level.GetOutputFiles();
const auto& current_output_files =
sub_compact->Outputs(is_proximal_level)->GetOutputs();
for (size_t i = prev_output_files.size(); i < current_output_files.size();
i++) {
subcompaction_progress_per_level.AddToOutputFiles(
current_output_files[i].meta);
}
}
Status CompactionJob::PersistSubcompactionProgress(
SubcompactionState* sub_compact) {
SubcompactionProgress& subcompaction_progress =
sub_compact->GetSubcompactionProgressRef();
assert(compaction_progress_writer_);
VersionEdit edit;
edit.SetSubcompactionProgress(subcompaction_progress);
std::string record;
if (!edit.EncodeTo(&record)) {
ROCKS_LOG_ERROR(
db_options_.info_log,
"[%s] [JOB %d] Failed to encode subcompaction "
"progress",
compact_->compaction->column_family_data()->GetName().c_str(), job_id_);
return Status::Corruption("Failed to encode subcompaction progress");
}
WriteOptions write_options(Env::IOActivity::kCompaction);
Status s = compaction_progress_writer_->AddRecord(write_options, record);
IOOptions opts;
if (s.ok()) {
s = WritableFileWriter::PrepareIOOptions(write_options, opts);
}
if (s.ok()) {
s = compaction_progress_writer_->file()->Sync(opts, db_options_.use_fsync);
}
if (!s.ok()) {
ROCKS_LOG_ERROR(
db_options_.info_log,
"[%s] [JOB %d] Failed to persist subcompaction "
"progress: %s",
compact_->compaction->column_family_data()->GetName().c_str(), job_id_,
s.ToString().c_str());
return s;
}
subcompaction_progress.output_level_progress
.UpdateLastPersistedOutputFilesCount();
subcompaction_progress.proximal_output_level_progress
.UpdateLastPersistedOutputFilesCount();
return Status::OK();
}
Status CompactionJob::VerifyInputRecordCount(
uint64_t num_input_range_del) const {
size_t ts_sz = compact_->compaction->column_family_data()
@@ -3084,4 +2585,5 @@ Status CompactionJob::VerifyOutputRecordCount() const {
}
return Status::OK();
}
} // namespace ROCKSDB_NAMESPACE
+10 -69
View File
@@ -176,20 +176,9 @@ class CompactionJob {
// and organizing seqno <-> time info. `known_single_subcompact` is non-null
// if we already have a known single subcompaction, with optional key bounds
// (currently for executing a remote compaction).
//
// @param compaction_progress Previously saved compaction progress
// to resume from. If empty, compaction starts fresh from the
// beginning.
//
// @param compaction_progress_writer Writer for persisting
// subcompaction progress periodically during compaction
// execution. If nullptr, progress tracking is disabled and compaction
// cannot be resumed later.
void Prepare(
std::optional<std::pair<std::optional<Slice>, std::optional<Slice>>>
known_single_subcompact,
const CompactionProgress& compaction_progress = CompactionProgress{},
log::Writer* compaction_progress_writer = nullptr);
known_single_subcompact);
// REQUIRED mutex not held
// Launch threads for each subcompaction and wait for them to finish. After
@@ -208,7 +197,6 @@ class CompactionJob {
protected:
void UpdateCompactionJobOutputStatsFromInternalStats(
const Status& status,
const InternalStats::CompactionStatsFull& internal_stats) const;
void LogCompaction();
@@ -271,10 +259,6 @@ class CompactionJob {
// consecutive groups such that each group has a similar size.
void GenSubcompactionBoundaries();
void MaybeAssignCompactionProgressAndWriter(
const CompactionProgress& compaction_progress,
log::Writer* compaction_progress_writer);
// Get the number of planned subcompactions based on max_subcompactions and
// extra reserved resources
uint64_t GetSubcompactionsLimit();
@@ -375,9 +359,8 @@ class CompactionJob {
const CompactionFilter* configured_compaction_filter,
const CompactionFilter*& compaction_filter,
std::unique_ptr<CompactionFilter>& compaction_filter_from_factory);
void InitializeReadOptionsAndBoundaries(
size_t ts_sz, ReadOptions& read_options,
SubcompactionKeyBoundaries& boundaries);
void InitializeReadOptions(ColumnFamilyData* cfd, ReadOptions& read_options,
SubcompactionKeyBoundaries& boundaries);
InternalIterator* CreateInputIterator(
SubcompactionState* sub_compact, ColumnFamilyData* cfd,
SubcompactionInternalIterators& iterators,
@@ -428,12 +411,12 @@ class CompactionJob {
// update the thread status for starting a compaction.
void ReportStartedCompaction(Compaction* compaction);
Status FinishCompactionOutputFile(
const Status& input_status,
const ParsedInternalKey& prev_iter_output_internal_key,
const Slice& next_table_min_key, const Slice* comp_start_user_key,
const Slice* comp_end_user_key, const CompactionIterator* c_iter,
SubcompactionState* sub_compact, CompactionOutputs& outputs);
Status FinishCompactionOutputFile(const Status& input_status,
SubcompactionState* sub_compact,
CompactionOutputs& outputs,
const Slice& next_table_min_key,
const Slice* comp_start_user_key,
const Slice* comp_end_user_key);
Status InstallCompactionResults(bool* compaction_released);
Status OpenCompactionOutputFile(SubcompactionState* sub_compact,
CompactionOutputs& outputs);
@@ -510,9 +493,6 @@ class CompactionJob {
// Setting this requires DBMutex.
uint64_t options_file_number_ = 0;
// Writer for persisting compaction progress during compaction
log::Writer* compaction_progress_writer_ = nullptr;
// Get table file name in where it's outputting to, which should also be in
// `output_directory_`.
virtual std::string GetTableFileName(uint64_t file_number);
@@ -520,43 +500,6 @@ class CompactionJob {
// The Compaction Read and Write priorities are the same for different
// scenarios, such as write stalled.
Env::IOPriority GetRateLimiterPriority();
Status MaybeResumeSubcompactionProgressOnInputIterator(
SubcompactionState* sub_compact, InternalIterator* input_iter);
Status ReadOutputFilesTableProperties(
const autovector<FileMetaData>& temporary_output_file_allocation,
const ReadOptions& read_options,
std::vector<std::shared_ptr<const TableProperties>>&
output_files_table_properties,
bool is_proximal_level = false);
Status ReadTablePropertiesDirectly(
const ImmutableOptions& ioptions, const MutableCFOptions& moptions,
const FileMetaData* file_meta, const ReadOptions& read_options,
std::shared_ptr<const TableProperties>* tp);
void RestoreCompactionOutputs(
const ColumnFamilyData* cfd,
const std::vector<std::shared_ptr<const TableProperties>>&
output_files_table_properties,
SubcompactionProgressPerLevel& subcompaction_progress_per_level,
CompactionOutputs* outputs_to_restore);
bool ShouldUpdateSubcompactionProgress(
const SubcompactionState* sub_compact, const CompactionIterator* c_iter,
const ParsedInternalKey& prev_iter_output_internal_key,
const Slice& next_table_min_internal_key, const FileMetaData* meta) const;
void UpdateSubcompactionProgress(const CompactionIterator* c_iter,
const Slice next_table_min_key,
SubcompactionState* sub_compact);
Status PersistSubcompactionProgress(SubcompactionState* sub_compact);
void UpdateSubcompactionProgressPerLevel(
SubcompactionState* sub_compact, bool is_proximal_level,
SubcompactionProgress& subcompaction_progress);
};
// CompactionServiceInput is used the pass compaction information between two
@@ -706,9 +649,7 @@ class CompactionServiceCompactionJob : private CompactionJob {
// REQUIRED: mutex held
// Like CompactionJob::Prepare()
void Prepare(
const CompactionProgress& compaction_progress = CompactionProgress{},
log::Writer* compaction_progress_writer = nullptr);
void Prepare();
// Run the compaction in current thread and return the result
Status Run();
+10 -463
View File
@@ -17,7 +17,6 @@
#include "db/db_impl/db_impl.h"
#include "db/error_handler.h"
#include "db/version_set.h"
#include "file/filename.h"
#include "file/random_access_file_reader.h"
#include "file/writable_file_writer.h"
#include "options/options_helper.h"
@@ -211,12 +210,12 @@ class CompactionJobTestBase : public testing::Test {
table_cache_(NewLRUCache(50000, 16)),
write_buffer_manager_(db_options_.db_write_buffer_size),
versions_(new VersionSet(
dbname_, &db_options_, mutable_db_options_, env_options_,
table_cache_.get(), &write_buffer_manager_, &write_controller_,
dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr,
/*io_tracer=*/nullptr, /*db_id=*/"", /*db_session_id=*/"",
/*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr, /*unchanging=*/false)),
/*error_handler=*/nullptr, /*read_only=*/false)),
shutting_down_(false),
mock_table_factory_(new mock::MockTableFactory()),
error_handler_(nullptr, db_options_, &mutex_),
@@ -546,13 +545,13 @@ class CompactionJobTestBase : public testing::Test {
ASSERT_OK(s);
db_options_.info_log = info_log;
versions_.reset(new VersionSet(
dbname_, &db_options_, mutable_db_options_, env_options_,
table_cache_.get(), &write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
test::kUnitTestDbId, /*db_session_id=*/"",
/*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr, /*unchanging=*/false));
versions_.reset(
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
test::kUnitTestDbId, /*db_session_id=*/"",
/*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr, /*read_only=*/false));
compaction_job_stats_.Reset();
VersionEdit new_db;
@@ -2410,458 +2409,6 @@ TEST_F(CompactionJobIOPriorityTest, GetRateLimiterPriority) {
Env::IO_LOW, Env::IO_LOW);
}
class ResumableCompactionJobTest : public CompactionJobTestBase {
public:
ResumableCompactionJobTest()
: CompactionJobTestBase(
test::PerThreadDBPath("allow_resumption_job_test"),
BytewiseComparator(), [](uint64_t /*ts*/) { return ""; },
/*test_io_priority=*/false, TableTypeForTest::kBlockBasedTable) {}
protected:
static constexpr const char* kCancelBeforeThisKey = "cancel_before_this_key";
std::string progress_dir_;
bool enable_cancel_ = false;
std::atomic<int> stop_count_{0};
std::atomic<bool> cancel_{false};
SequenceNumber cancel_before_seqno = kMaxSequenceNumber;
void SetUp() override {
CompactionJobTestBase::SetUp();
SyncPoint::GetInstance()->SetCallBack(
"CompactionOutputs::ShouldStopBefore::manual_decision",
[this](void* p) {
auto* pair = static_cast<std::pair<bool*, const Slice>*>(p);
*(pair->first) = true;
// Cancel after outputting a specific key
if (enable_cancel_) {
ParsedInternalKey parsed_key;
if (ParseInternalKey(pair->second, &parsed_key, true).ok()) {
if (parsed_key.user_key == kCancelBeforeThisKey &&
(cancel_before_seqno == kMaxSequenceNumber ||
parsed_key.sequence == cancel_before_seqno)) {
cancel_.store(true);
}
}
}
});
SyncPoint::GetInstance()->EnableProcessing();
}
void TearDown() override {
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
if (env_->FileExists(progress_dir_).ok()) {
std::vector<std::string> files;
EXPECT_OK(env_->GetChildren(progress_dir_, &files));
for (const auto& file : files) {
if (file != "." && file != "..") {
EXPECT_OK(env_->DeleteFile(progress_dir_ + "/" + file));
}
}
EXPECT_OK(env_->DeleteDir(progress_dir_));
}
CompactionJobTestBase::TearDown();
}
void NewDB() {
if (env_->FileExists(progress_dir_).ok()) {
std::vector<std::string> files;
EXPECT_OK(env_->GetChildren(progress_dir_, &files));
for (const auto& file : files) {
if (file != "." && file != "..") {
EXPECT_OK(env_->DeleteFile(progress_dir_ + "/" + file));
}
}
EXPECT_OK(env_->DeleteDir(progress_dir_));
}
CompactionJobTestBase::NewDB();
progress_dir_ = test::PerThreadDBPath("compaction_progress");
ASSERT_OK(env_->CreateDirIfMissing(progress_dir_));
}
void EnableCompactionCancel() { enable_cancel_ = true; }
void DisableCompactionCancel() {
enable_cancel_ = false;
cancel_.store(false);
}
std::unique_ptr<log::Writer> CreateCompactionProgressWriter(
const std::string& compaction_progress_file) {
std::unique_ptr<FSWritableFile> file;
EXPECT_OK(fs_->NewWritableFile(compaction_progress_file, FileOptions(),
&file, nullptr));
auto file_writer = std::make_unique<WritableFileWriter>(
std::move(file), compaction_progress_file, FileOptions());
auto compaction_progress_writer =
std::make_unique<log::Writer>(std::move(file_writer), 0, false);
return compaction_progress_writer;
}
Status RunCompactionWithProgressTracking(
const CompactionProgress& compaction_progress,
log::Writer* compaction_progress_writer,
std::vector<SequenceNumber> snapshots = {},
std::shared_ptr<Statistics> stats = nullptr) {
mutex_.Lock();
auto cfd = versions_->GetColumnFamilySet()->GetDefault();
auto files = cfd->current()->storage_info()->LevelFiles(0);
db_options_.statistics = stats;
db_options_.stats = db_options_.statistics.get();
std::vector<CompactionInputFiles> compaction_input_files;
CompactionInputFiles level;
level.level = 0;
level.files = files;
compaction_input_files.push_back(level);
Compaction compaction(
cfd->current()->storage_info(), cfd->ioptions(),
cfd->GetLatestMutableCFOptions(), mutable_db_options_,
compaction_input_files, 1, mutable_cf_options_.target_file_size_base,
mutable_cf_options_.max_compaction_bytes, 0, kNoCompression,
cfd->GetLatestMutableCFOptions().compression_opts,
Temperature::kUnknown, 0, {}, std::nullopt, nullptr,
CompactionReason::kManualCompaction);
compaction.FinalizeInputInfo(cfd->current());
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL, db_options_.info_log.get());
EventLogger event_logger(db_options_.info_log.get());
JobContext job_context(1, false);
job_context.InitSnapshotContext(nullptr, nullptr, kMaxSequenceNumber,
std::move(snapshots));
CompactionJobStats job_stats;
CompactionJob compaction_job(
0, &compaction, db_options_, mutable_db_options_, env_options_,
versions_.get(), &shutting_down_, &log_buffer, nullptr, nullptr,
nullptr, stats.get(), &mutex_, &error_handler_, &job_context,
table_cache_, &event_logger, false, false, dbname_, &job_stats,
Env::Priority::USER, nullptr, cancel_, env_->GenerateUniqueId(),
DBImpl::GenerateDbSessionId(nullptr), "");
compaction_job.Prepare(std::nullopt, compaction_progress,
compaction_progress_writer);
mutex_.Unlock();
compaction_job.Run().PermitUncheckedError();
EXPECT_OK(compaction_job.io_status());
mutex_.Lock();
bool compaction_released = false;
Status s = compaction_job.Install(&compaction_released);
mutex_.Unlock();
if (!compaction_released) {
compaction.ReleaseCompactionFiles(s);
}
return s;
}
SubcompactionProgress ReadAndParseProgress(
const std::string& compaction_progress_file) {
std::unique_ptr<FSSequentialFile> seq_file;
EXPECT_OK(fs_->NewSequentialFile(compaction_progress_file, FileOptions(),
&seq_file, nullptr));
auto file_reader = std::make_unique<SequentialFileReader>(
std::move(seq_file), compaction_progress_file, 0, nullptr);
log::Reader reader(nullptr, std::move(file_reader), nullptr, true, 0);
SubcompactionProgressBuilder builder;
std::string record;
Slice slice;
while (reader.ReadRecord(&slice, &record)) {
VersionEdit edit;
if (!edit.DecodeFrom(slice).ok()) {
continue;
}
builder.ProcessVersionEdit(edit);
}
EXPECT_TRUE(builder.HasAccumulatedSubcompactionProgress());
return builder.GetAccumulatedSubcompactionProgress();
}
// Test utility function to verify that compaction progress was correctly
// persisted to the progress file after compaction interruption.
//
// VERIFIES:
// - Progress file exists and has expected size (empty if no progress
// expected)
// - Next internal key to compact matches expected user key with proper format
// - Number of processed input records matches position in ordered input keys
// - Number of processed output records equals number of processed input
// records (by test design to simplify verification)
// - Each output file contains exactly one user key (by test design to
// simplify verification)
void VerifyCompactionProgressPersisted(
const std::string& compaction_progress_file,
const std::string& next_user_key_to_compact,
const std::vector<std::string>& ordered_intput_keys) {
ASSERT_OK(env_->FileExists(compaction_progress_file));
uint64_t file_size;
ASSERT_OK(env_->GetFileSize(compaction_progress_file, &file_size));
if (next_user_key_to_compact.empty()) {
ASSERT_EQ(file_size, 0);
return;
}
const auto& subcompaction_progress =
ReadAndParseProgress(compaction_progress_file);
ASSERT_FALSE(subcompaction_progress.next_internal_key_to_compact.empty());
ParsedInternalKey parsed_next_key;
ASSERT_OK(
ParseInternalKey(subcompaction_progress.next_internal_key_to_compact,
&parsed_next_key, true /* log_err_key */));
ASSERT_EQ(parsed_next_key.user_key, next_user_key_to_compact);
ASSERT_EQ(parsed_next_key.sequence, kMaxSequenceNumber);
ASSERT_EQ(parsed_next_key.type, kValueTypeForSeek);
auto it = std::find(ordered_intput_keys.begin(), ordered_intput_keys.end(),
next_user_key_to_compact);
ASSERT_TRUE(it != ordered_intput_keys.end());
auto next_key_index = std::distance(ordered_intput_keys.begin(), it);
ASSERT_EQ(subcompaction_progress.num_processed_input_records,
next_key_index);
ASSERT_EQ(subcompaction_progress.output_level_progress
.GetNumProcessedOutputRecords(),
next_key_index);
ASSERT_EQ(
subcompaction_progress.output_level_progress.GetOutputFiles().size(),
next_key_index);
for (size_t i = 0;
i <
subcompaction_progress.output_level_progress.GetOutputFiles().size();
++i) {
const auto& output_file =
subcompaction_progress.output_level_progress.GetOutputFiles()[i];
ASSERT_EQ(output_file.smallest.user_key().ToString(),
output_file.largest.user_key().ToString());
ASSERT_EQ(output_file.largest.user_key().ToString(),
ordered_intput_keys[i]);
}
}
void RunCancelAndResumeTest(
const std::initializer_list<mock::KVPair>& input_file_1,
const std::initializer_list<mock::KVPair>& input_file_2,
uint64_t last_sequence, const std::vector<uint64_t>& snapshots,
const std::string& expected_next_key_to_compact,
const std::vector<std::string>& expected_input_keys,
bool cancelled_past_mid_point = false) {
std::shared_ptr<Statistics> stats = ROCKSDB_NAMESPACE::CreateDBStatistics();
auto file1 = mock::MakeMockFile(input_file_1);
AddMockFile(file1);
auto file2 = mock::MakeMockFile(input_file_2);
AddMockFile(file2);
SetLastSequence(last_sequence);
// First compaction (will be cancelled)
std::string compaction_progress_file =
CompactionProgressFileName(progress_dir_, 123);
std::unique_ptr<log::Writer> compaction_progress_writer =
CreateCompactionProgressWriter(compaction_progress_file);
ASSERT_OK(stats->Reset());
EnableCompactionCancel();
Status status = RunCompactionWithProgressTracking(
CompactionProgress{}, compaction_progress_writer.get(), snapshots,
stats);
ASSERT_TRUE(status.IsManualCompactionPaused());
DisableCompactionCancel();
HistogramData cancelled_compaction_stats;
stats->histogramData(FILE_WRITE_COMPACTION_MICROS,
&cancelled_compaction_stats);
VerifyCompactionProgressPersisted(compaction_progress_file,
expected_next_key_to_compact,
expected_input_keys);
// Resume compaction
CompactionProgress compaction_progress;
if (expected_next_key_to_compact != "") {
compaction_progress.push_back(
ReadAndParseProgress(compaction_progress_file));
}
std::string compaction_progress_file_2 =
CompactionProgressFileName(progress_dir_, 234);
std::unique_ptr<log::Writer> compaction_progress_writer_2 =
CreateCompactionProgressWriter(compaction_progress_file_2);
ASSERT_OK(stats->Reset());
status = RunCompactionWithProgressTracking(
compaction_progress, compaction_progress_writer_2.get(),
{} /* snapshots */, stats);
ASSERT_OK(status);
if (cancelled_past_mid_point) {
HistogramData resumed_compaction_stats;
stats->histogramData(FILE_WRITE_COMPACTION_MICROS,
&resumed_compaction_stats);
ASSERT_GT(cancelled_compaction_stats.count,
resumed_compaction_stats.count);
}
}
};
TEST_F(ResumableCompactionJobTest, BasicProgressPersistence) {
NewDB();
auto file1 = mock::MakeMockFile({
{KeyStr("a", 1U, kTypeValue), "val1"},
{KeyStr("b", 2U, kTypeValue), "val2"},
});
AddMockFile(file1);
auto file2 = mock::MakeMockFile({
{KeyStr("c", 3U, kTypeValue), "val3"},
{KeyStr("d", 4U, kTypeValue), "val4"},
});
AddMockFile(file2);
SetLastSequence(4U);
std::string compaction_progress_file =
CompactionProgressFileName(progress_dir_, 123);
std::unique_ptr<log::Writer> compaction_progress_writer =
CreateCompactionProgressWriter(compaction_progress_file);
Status status = RunCompactionWithProgressTracking(
CompactionProgress(), compaction_progress_writer.get());
ASSERT_OK(status);
VerifyCompactionProgressPersisted(
compaction_progress_file, "d" /* next_user_key_to_compact */,
{"a", "b", "c", "d"} /* ordered_intput_keys */);
}
TEST_F(ResumableCompactionJobTest, BasicProgressResume) {
NewDB();
RunCancelAndResumeTest(
{{KeyStr("a", 1U, kTypeValue), "val1"},
{KeyStr("b", 2U, kTypeValue), "val2"}} /* input_file_1 */,
{{KeyStr("bb", 3U, kTypeValue), "val3"},
{KeyStr(kCancelBeforeThisKey, 4U, kTypeValue),
"val4"}} /* input_file_2 */,
4U /* last_sequence */, {} /* snapshots */,
kCancelBeforeThisKey /* expected_next_key_to_compact */,
{"a", "b", "bb", kCancelBeforeThisKey} /* expected_input_keys */,
true /* cancelled_past_mid_point */);
}
TEST_F(ResumableCompactionJobTest, NoProgressResumeOnSameKey) {
NewDB();
// `cancel_before_seqno` is set to 0U to force cancellation after
// `kCancelBeforeThisKey@1` instead of `kCancelBeforeThisKey@2`.
// The seqno is 0 because `kCancelBeforeThisKey@1` will have its sequence
// number zeroed during compaction while `kCancelBeforeThisKey@2` won't be
cancel_before_seqno = 0U;
RunCancelAndResumeTest(
{{KeyStr(kCancelBeforeThisKey, 1U, kTypeValue),
"val1"}} /* input_file_1 */,
{{KeyStr(kCancelBeforeThisKey, 2U, kTypeValue), "val11"},
{KeyStr("d", 3U, kTypeValue), "val2"}} /* input_file_2 */,
3U /* last_sequence */, {1U} /* snapshots */,
"" /* expected_next_key_to_compact */,
{kCancelBeforeThisKey, kCancelBeforeThisKey,
"d"} /* expected_input_keys */);
}
TEST_F(ResumableCompactionJobTest, NoProgressResumeOnDeleteRange) {
NewDB();
RunCancelAndResumeTest(
{{KeyStr("a", 1U, kTypeValue), "val1"},
{KeyStr("b", 2U, kTypeValue), "val2"},
{KeyStr(kCancelBeforeThisKey, 3U, kTypeValue),
"val3"}} /* input_file_1 */,
{{KeyStr(kCancelBeforeThisKey, 4U, kTypeRangeDeletion),
"range_deletion_end_key"},
{KeyStr("d", 5U, kTypeValue), "val4"}} /* input_file_2 */,
5U /* last_sequence */, {3U} /* snapshots */,
"b" /* expected_next_key_to_compact */,
{"a", "b", kCancelBeforeThisKey, kCancelBeforeThisKey,
"d"} /* expected_input_keys */);
}
TEST_F(ResumableCompactionJobTest, NoProgressResumeOnMerge) {
merge_op_ = MergeOperators::CreateStringAppendOperator();
NewDB();
RunCancelAndResumeTest(
{{KeyStr("a", 1U, kTypeValue), "val1"},
{KeyStr("b", 2U, kTypeValue), "val2"}} /* input_file_1 */,
{{KeyStr("bb", 3U, kTypeValue), "val3"},
{KeyStr(kCancelBeforeThisKey, 4U, kTypeMerge),
"val4"}} /* input_file_2 */,
4U /* last_sequence */, {} /* snapshots */,
"bb" /* expected_next_key_to_compact */,
{"a", "b", "bb", kCancelBeforeThisKey} /* expected_input_keys */);
}
TEST_F(ResumableCompactionJobTest, NoProgressResumeOnSingleDelete) {
NewDB();
RunCancelAndResumeTest(
{{KeyStr("a", 1U, kTypeValue), "val1"},
{KeyStr("b", 2U, kTypeValue), "val2"},
{KeyStr(kCancelBeforeThisKey, 3U, kTypeValue),
"val3"}} /* input_file_1 */,
{{KeyStr(kCancelBeforeThisKey, 4U, kTypeSingleDeletion), ""},
{KeyStr("d", 5U, kTypeValue), "val4"}} /* input_file_2 */,
5U /* last_sequence */, {3U} /* snapshots */,
"b" /* expected_next_key_to_compact */,
{"a", "b", kCancelBeforeThisKey, kCancelBeforeThisKey,
"d"} /* expected_input_keys */);
}
TEST_F(ResumableCompactionJobTest, NoProgressResumeOnDeletionAtBottom) {
NewDB();
RunCancelAndResumeTest(
{{KeyStr("a", 1U, kTypeValue), "val1"},
{KeyStr("b", 2U, kTypeValue), "val2"},
{KeyStr(kCancelBeforeThisKey, 3U, kTypeValue),
"val3"}} /* input_file_1 */,
{{KeyStr(kCancelBeforeThisKey, 4U, kTypeDeletion), ""},
{KeyStr("d", 5U, kTypeValue), "val4"}} /* input_file_2 */,
5U /* last_sequence */, {3U} /* snapshots */,
"b" /* expected_next_key_to_compact */,
{"a", "b", kCancelBeforeThisKey, kCancelBeforeThisKey,
"d"} /* expected_input_keys */);
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+3 -9
View File
@@ -278,11 +278,7 @@ bool CompactionOutputs::ShouldStopBefore(const CompactionIterator& c_iter) {
}
// reach the max file size
uint64_t estimated_file_size = current_output_file_size_;
if (compaction_->mutable_cf_options().target_file_size_is_upper_bound) {
estimated_file_size += builder_->EstimatedTailSize();
}
if (estimated_file_size >= compaction_->max_output_file_size()) {
if (current_output_file_size_ >= compaction_->max_output_file_size()) {
return true;
}
@@ -363,8 +359,7 @@ bool CompactionOutputs::ShouldStopBefore(const CompactionIterator& c_iter) {
Status CompactionOutputs::AddToOutput(
const CompactionIterator& c_iter,
const CompactionFileOpenFunc& open_file_func,
const CompactionFileCloseFunc& close_file_func,
const ParsedInternalKey& prev_iter_output_internal_key) {
const CompactionFileCloseFunc& close_file_func) {
Status s;
bool is_range_del = c_iter.IsDeleteRangeSentinelKey();
if (is_range_del && compaction_->bottommost_level()) {
@@ -375,8 +370,7 @@ Status CompactionOutputs::AddToOutput(
}
const Slice& key = c_iter.key();
if (ShouldStopBefore(c_iter) && HasBuilder()) {
s = close_file_func(c_iter.InputStatus(), prev_iter_output_internal_key,
key, &c_iter, *this);
s = close_file_func(*this, c_iter.InputStatus(), key);
if (!s.ok()) {
return s;
}
+3 -14
View File
@@ -21,8 +21,7 @@ namespace ROCKSDB_NAMESPACE {
class CompactionOutputs;
using CompactionFileOpenFunc = std::function<Status(CompactionOutputs&)>;
using CompactionFileCloseFunc =
std::function<Status(const Status&, const ParsedInternalKey&, const Slice&,
const CompactionIterator*, CompactionOutputs&)>;
std::function<Status(CompactionOutputs&, const Status&, const Slice&)>;
// Files produced by subcompaction, most of the functions are used by
// compaction_job Open/Close compaction file functions.
@@ -59,8 +58,6 @@ class CompactionOutputs {
precalculated_hash, is_proximal_level_);
}
const std::vector<Output>& GetOutputs() const { return outputs_; }
// Set new table builder for the current output
void NewBuilder(const TableBuilderOptions& tboptions);
@@ -198,10 +195,6 @@ class CompactionOutputs {
std::pair<SequenceNumber, SequenceNumber> keep_seqno_range,
const Slice& next_table_min_key, const std::string& full_history_ts_low);
void SetNumOutputRecords(uint64_t num_output_records) {
stats_.num_output_records = num_output_records;
}
private:
friend class SubcompactionState;
@@ -261,8 +254,7 @@ class CompactionOutputs {
// close and open new compaction output with the functions provided.
Status AddToOutput(const CompactionIterator& c_iter,
const CompactionFileOpenFunc& open_file_func,
const CompactionFileCloseFunc& close_file_func,
const ParsedInternalKey& prev_iter_output_internal_key);
const CompactionFileCloseFunc& close_file_func);
// Close the current output. `open_file_func` is needed for creating new file
// for range-dels only output file.
@@ -278,12 +270,9 @@ class CompactionOutputs {
!range_del_agg->IsEmpty()) {
status = open_file_func(*this);
}
if (HasBuilder()) {
const ParsedInternalKey empty_internal_key{};
const Slice empty_key{};
Status s = close_file_func(status, empty_internal_key, empty_key,
nullptr /* c_iter */, *this);
Status s = close_file_func(*this, status, empty_key);
if (!s.ok() && status.ok()) {
status = s;
}
+11 -16
View File
@@ -242,7 +242,7 @@ bool CompactionPicker::ExpandInputsToCleanCut(const std::string& /*cf_name*/,
GetRange(*inputs, &smallest, &largest);
inputs->clear();
vstorage->GetOverlappingInputs(level, &smallest, &largest, &inputs->files,
hint_index, &hint_index, true, nullptr,
hint_index, &hint_index, true,
next_smallest);
} while (inputs->size() > old_size);
@@ -375,13 +375,12 @@ Compaction* CompactionPicker::PickCompactionForCompactFiles(
// without configurable `CompressionOptions`, which is inconsistent.
compression_type = compact_options.compression;
}
auto c = new Compaction(
vstorage, ioptions_, mutable_cf_options, mutable_db_options, input_files,
output_level, compact_options.output_file_size_limit,
mutable_cf_options.max_compaction_bytes, output_path_id, compression_type,
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
compact_options.output_temperature_override,
mutable_cf_options.default_write_temperature,
compact_options.max_subcompactions,
/* grandparents */ {}, earliest_snapshot, snapshot_checker,
CompactionReason::kManualCompaction);
@@ -465,8 +464,7 @@ bool CompactionPicker::SetupOtherInputs(
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
VersionStorageInfo* vstorage, CompactionInputFiles* inputs,
CompactionInputFiles* output_level_inputs, int* parent_index,
int base_index, bool only_expand_towards_right,
const FileMetaData* starting_l0_file) {
int base_index, bool only_expand_towards_right) {
assert(!inputs->empty());
assert(output_level_inputs->empty());
const int input_level = inputs->level;
@@ -522,11 +520,11 @@ bool CompactionPicker::SetupOtherInputs(
// Round-robin compaction only allows expansion towards the larger side.
vstorage->GetOverlappingInputs(input_level, &smallest, &all_limit,
&expanded_inputs.files, base_index,
nullptr, true, starting_l0_file);
nullptr);
} else {
vstorage->GetOverlappingInputs(input_level, &all_start, &all_limit,
&expanded_inputs.files, base_index,
nullptr, true, starting_l0_file);
nullptr);
}
uint64_t expanded_inputs_size = TotalFileSize(expanded_inputs.files);
if (!ExpandInputsToCleanCut(cf_name, vstorage, &expanded_inputs)) {
@@ -681,7 +679,8 @@ Compaction* CompactionPicker::PickCompactionForCompactRange(
compact_range_options.target_path_id,
GetCompressionType(vstorage, mutable_cf_options, output_level, 1),
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
Temperature::kUnknown, compact_range_options.max_subcompactions,
mutable_cf_options.default_write_temperature,
compact_range_options.max_subcompactions,
/* grandparents */ {}, /* earliest_snapshot */ std::nullopt,
/* snapshot_checker */ nullptr, CompactionReason::kManualCompaction,
trim_ts, /* score */ -1,
@@ -872,8 +871,8 @@ Compaction* CompactionPicker::PickCompactionForCompactRange(
GetCompressionType(vstorage, mutable_cf_options, output_level,
vstorage->base_level()),
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
Temperature::kUnknown, compact_range_options.max_subcompactions,
std::move(grandparents),
mutable_cf_options.default_write_temperature,
compact_range_options.max_subcompactions, std::move(grandparents),
/* earliest_snapshot */ std::nullopt, /* snapshot_checker */ nullptr,
CompactionReason::kManualCompaction, trim_ts, /* score */ -1,
/* l0_files_might_overlap */ true,
@@ -1232,7 +1231,7 @@ void CompactionPicker::PickFilesMarkedForCompaction(
bool CompactionPicker::GetOverlappingL0Files(
VersionStorageInfo* vstorage, CompactionInputFiles* start_level_inputs,
int output_level, int* parent_index, const FileMetaData* starting_l0_file) {
int output_level, int* parent_index) {
// Two level 0 compaction won't run at the same time, so don't need to worry
// about files on level 0 being compacted.
assert(level0_compactions_in_progress()->empty());
@@ -1243,11 +1242,7 @@ bool CompactionPicker::GetOverlappingL0Files(
// which will include the picked file.
start_level_inputs->files.clear();
vstorage->GetOverlappingInputs(0, &smallest, &largest,
&(start_level_inputs->files),
/*hint_index=*/-1,
/*file_index=*/nullptr,
/*expand_range=*/true,
/*starting_l0_file=*/starting_l0_file);
&(start_level_inputs->files));
// If we include more L0 files in the same compaction run it can
// cause the 'smallest' and 'largest' key to get extended to a
+2 -8
View File
@@ -203,16 +203,13 @@ class CompactionPicker {
const std::vector<CompactionInputFiles>& inputs, int level,
int proximal_level) const;
// @param starting_l0_file If not null, restricts L0 file selection to only
// include files at or older than starting_l0_file.
bool SetupOtherInputs(const std::string& cf_name,
const MutableCFOptions& mutable_cf_options,
VersionStorageInfo* vstorage,
CompactionInputFiles* inputs,
CompactionInputFiles* output_level_inputs,
int* parent_index, int base_index,
bool only_expand_towards_right = false,
const FileMetaData* starting_l0_file = nullptr);
bool only_expand_towards_right = false);
void GetGrandparents(VersionStorageInfo* vstorage,
const CompactionInputFiles& inputs,
@@ -225,12 +222,9 @@ class CompactionPicker {
CompactionInputFiles* start_level_inputs,
std::function<bool(const FileMetaData*)> skip_marked_file);
// @param starting_l0_file If not null, restricts L0 file selection to only
// include files at or older than starting_l0_file.
bool GetOverlappingL0Files(VersionStorageInfo* vstorage,
CompactionInputFiles* start_level_inputs,
int output_level, int* parent_index,
const FileMetaData* starting_l0_file = nullptr);
int output_level, int* parent_index);
// Register this compaction in the set of running compactions
void RegisterCompaction(Compaction* c);
+6 -3
View File
@@ -124,7 +124,8 @@ Compaction* FIFOCompactionPicker::PickTTLCompaction(
Compaction* c = new Compaction(
vstorage, ioptions_, mutable_cf_options, mutable_db_options,
std::move(inputs), 0, 0, 0, 0, kNoCompression,
mutable_cf_options.compression_opts, Temperature::kUnknown,
mutable_cf_options.compression_opts,
mutable_cf_options.default_write_temperature,
/* max_subcompactions */ 0, {}, /* earliest_snapshot */ std::nullopt,
/* snapshot_checker */ nullptr, CompactionReason::kFIFOTtl,
/* trim_ts */ "", vstorage->CompactionScore(0),
@@ -193,7 +194,8 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
{comp_inputs}, 0, 16 * 1024 * 1024 /* output file size limit */,
0 /* max compaction bytes, not applicable */,
0 /* output path ID */, mutable_cf_options.compression,
mutable_cf_options.compression_opts, Temperature::kUnknown,
mutable_cf_options.compression_opts,
mutable_cf_options.default_write_temperature,
0 /* max_subcompactions */, {},
/* earliest_snapshot */ std::nullopt,
/* snapshot_checker */ nullptr,
@@ -292,7 +294,8 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
/* target_file_size */ 0,
/* max_compaction_bytes */ 0,
/* output_path_id */ 0, kNoCompression,
mutable_cf_options.compression_opts, Temperature::kUnknown,
mutable_cf_options.compression_opts,
mutable_cf_options.default_write_temperature,
/* max_subcompactions */ 0, {}, /* earliest_snapshot */ std::nullopt,
/* snapshot_checker */ nullptr, CompactionReason::kFIFOMaxSize,
/* trim_ts */ "", vstorage->CompactionScore(0),
+1 -1
View File
@@ -557,7 +557,7 @@ Compaction* LevelCompactionBuilder::GetCompaction() {
GetCompressionType(vstorage_, mutable_cf_options_, output_level_,
vstorage_->base_level()),
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level_),
Temperature::kUnknown,
mutable_cf_options_.default_write_temperature,
/* max_subcompactions */ 0, std::move(grandparents_),
/* earliest_snapshot */ std::nullopt, /* snapshot_checker */ nullptr,
compaction_reason_,
+5 -75
View File
@@ -1178,7 +1178,7 @@ TEST_F(CompactionPickerTest, FIFOToCold1) {
ASSERT_TRUE(compaction.get() != nullptr);
ASSERT_EQ(compaction->compaction_reason(),
CompactionReason::kChangeTemperature);
ASSERT_EQ(compaction->GetOutputTemperature(), Temperature::kCold);
ASSERT_EQ(compaction->output_temperature(), Temperature::kCold);
ASSERT_EQ(1U, compaction->num_input_files(0));
ASSERT_EQ(3U, compaction->input(0, 0)->fd.GetNumber());
}
@@ -1248,7 +1248,7 @@ TEST_F(CompactionPickerTest, FIFOToColdMaxCompactionSize) {
ASSERT_EQ(compaction->compaction_reason(),
CompactionReason::kChangeTemperature);
// Compaction picker picks older files first and picks one file at a time.
ASSERT_EQ(compaction->GetOutputTemperature(), Temperature::kCold);
ASSERT_EQ(compaction->output_temperature(), Temperature::kCold);
ASSERT_EQ(1U, compaction->num_input_files(0));
ASSERT_EQ(1U, compaction->input(0, 0)->fd.GetNumber());
}
@@ -1316,7 +1316,7 @@ TEST_F(CompactionPickerTest, FIFOToColdWithExistingCold) {
ASSERT_EQ(compaction->compaction_reason(),
CompactionReason::kChangeTemperature);
// Compaction picker picks older files first and picks one file at a time.
ASSERT_EQ(compaction->GetOutputTemperature(), Temperature::kCold);
ASSERT_EQ(compaction->output_temperature(), Temperature::kCold);
ASSERT_EQ(1U, compaction->num_input_files(0));
ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
}
@@ -1383,7 +1383,7 @@ TEST_F(CompactionPickerTest, FIFOToColdWithHotBetweenCold) {
ASSERT_TRUE(compaction.get() != nullptr);
ASSERT_EQ(compaction->compaction_reason(),
CompactionReason::kChangeTemperature);
ASSERT_EQ(compaction->GetOutputTemperature(), Temperature::kCold);
ASSERT_EQ(compaction->output_temperature(), Temperature::kCold);
ASSERT_EQ(1U, compaction->num_input_files(0));
ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
}
@@ -1464,35 +1464,12 @@ TEST_F(CompactionPickerTest, FIFOToHotAndWarm) {
ASSERT_EQ(compaction->compaction_reason(),
CompactionReason::kChangeTemperature);
// Compaction picker picks older files first and picks one file at a time.
ASSERT_EQ(compaction->GetOutputTemperature(), Temperature::kWarm);
ASSERT_EQ(compaction->output_temperature(), Temperature::kWarm);
ASSERT_EQ(1U, compaction->num_input_files(0));
ASSERT_EQ(1U, compaction->input(0, 0)->fd.GetNumber());
}
}
TEST_F(CompactionPickerTest, CompactFilesOutputTemperature) {
NewVersionStorage(6, kCompactionStyleLevel);
auto file_number = 66U;
Add(0, file_number, "150", "200", 1000000000U);
UpdateVersionStorageInfo();
std::unordered_set<uint64_t> input{file_number};
std::vector<CompactionInputFiles> input_files;
ASSERT_OK(level_compaction_picker.GetCompactionInputsFromFileNumbers(
&input_files, &input, vstorage_.get(), CompactionOptions()));
auto compaction_options = CompactionOptions();
compaction_options.output_temperature_override = Temperature::kCold;
std::unique_ptr<Compaction> compaction(
level_compaction_picker.PickCompactionForCompactFiles(
compaction_options, input_files, 1, vstorage_.get(),
mutable_cf_options_, mutable_db_options_, /*output_path_id=*/0));
ASSERT_TRUE(compaction.get() != nullptr);
ASSERT_EQ(compaction->GetOutputTemperature(), Temperature::kCold);
}
TEST_F(CompactionPickerTest, CompactionPriMinOverlapping1) {
NewVersionStorage(6, kCompactionStyleLevel);
ioptions_.compaction_pri = kMinOverlappingRatio;
@@ -4716,53 +4693,6 @@ TEST_F(CompactionPickerTest, UniversalMaxReadAmpSmallDB) {
}
}
TEST_F(CompactionPickerTest, StandaloneRangeDeletionOnlyPicksOlderFiles) {
NewVersionStorage(6, kCompactionStyleUniversal);
// Create L0 files with overlapping ranges
// File 1: newest regular file (epoch 5), keys [100, 200]
Add(0, 1U, "100", "200", 1U, 0, 100, 100, 0, false, Temperature::kUnknown,
kUnknownOldestAncesterTime, kUnknownNewestKeyTime, Slice(), Slice(), 5);
// File 2: standalone range deletion (epoch 4), keys [150, 250]
// This file should be marked as having only range deletions
Add(0, 2U, "150", "250", 1U, 0, 200, 200, 0, true, Temperature::kUnknown,
kUnknownOldestAncesterTime, kUnknownNewestKeyTime, Slice(), Slice(), 4);
// Manually set file 2 as standalone range deletion
FileMetaData* range_del_file = file_map_[2U].first;
range_del_file->num_entries = 1;
range_del_file->num_range_deletions = 1;
ASSERT_TRUE(range_del_file->FileIsStandAloneRangeTombstone());
Add(4, 10U, "000", "400", 1U);
Add(5, 20U, "000", "400", 100);
UpdateVersionStorageInfo();
UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
ASSERT_TRUE(universal_compaction_picker.NeedsCompaction(vstorage_.get()));
std::unique_ptr<Compaction> compaction(
universal_compaction_picker.PickCompaction(
cf_name_, mutable_cf_options_, mutable_db_options_,
/*existing_snapshots=*/{}, /* snapshot_checker */ nullptr,
vstorage_.get(), &log_buffer_));
ASSERT_NE(nullptr, compaction);
ASSERT_EQ(2U, compaction->num_input_levels());
// First input level should be L0 with only the standalone range del file
// (file 2)
ASSERT_EQ(0, compaction->level(0));
ASSERT_EQ(1U, compaction->num_input_files(0));
ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
ASSERT_TRUE(compaction->input(0, 0)->FileIsStandAloneRangeTombstone());
// Second input level should be L4 with file 10
ASSERT_EQ(4, compaction->level(1));
ASSERT_EQ(1U, compaction->num_input_files(1));
ASSERT_EQ(10U, compaction->input(1, 0)->fd.GetNumber());
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+6 -16
View File
@@ -1093,7 +1093,7 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
output_level, 1, enable_compression),
GetCompressionOptions(mutable_cf_options_, vstorage_,
output_level, enable_compression),
Temperature::kUnknown,
mutable_cf_options_.default_write_temperature,
/* max_subcompactions */ 0, grandparents,
/* earliest_snapshot */ std::nullopt,
/* snapshot_checker */ nullptr, compaction_reason,
@@ -1441,7 +1441,7 @@ Compaction* UniversalCompactionBuilder::PickIncrementalForReduceSizeAmp(
true /* enable_compression */),
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level,
true /* enable_compression */),
Temperature::kUnknown,
mutable_cf_options_.default_write_temperature,
/* max_subcompactions */ 0, /* grandparents */ {},
/* earliest_snapshot */ std::nullopt,
/* snapshot_checker */ nullptr,
@@ -1544,18 +1544,9 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
}
if (output_level != 0) {
// For standalone range deletion, we don't want to compact it with newer
// L0 files that it doesn't cover.
const FileMetaData* starting_l0_file =
(start_level == 0 && start_level_inputs.size() == 1 &&
start_level_inputs.files[0]->FileIsStandAloneRangeTombstone())
? start_level_inputs.files[0]
: nullptr;
if (start_level == 0) {
if (!picker_->GetOverlappingL0Files(vstorage_, &start_level_inputs,
output_level, nullptr,
starting_l0_file)) {
output_level, nullptr)) {
return nullptr;
}
}
@@ -1566,8 +1557,7 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
output_level_inputs.level = output_level;
if (!picker_->SetupOtherInputs(cf_name_, mutable_cf_options_, vstorage_,
&start_level_inputs, &output_level_inputs,
&parent_index, -1, false,
starting_l0_file)) {
&parent_index, -1)) {
return nullptr;
}
inputs.push_back(start_level_inputs);
@@ -1604,7 +1594,7 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
/* max_grandparent_overlap_bytes */ GetMaxOverlappingBytes(), path_id,
GetCompressionType(vstorage_, mutable_cf_options_, output_level, 1),
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level),
Temperature::kUnknown,
mutable_cf_options_.default_write_temperature,
/* max_subcompactions */ 0, grandparents, earliest_snapshot_,
snapshot_checker_, CompactionReason::kFilesMarkedForCompaction,
/* trim_ts */ "", score_,
@@ -1700,7 +1690,7 @@ Compaction* UniversalCompactionBuilder::PickCompactionWithSortedRunRange(
true /* enable_compression */),
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level,
true /* enable_compression */),
Temperature::kUnknown,
mutable_cf_options_.default_write_temperature,
/* max_subcompactions */ 0, /* grandparents */ {},
/* earliest_snapshot */ std::nullopt,
/* snapshot_checker */ nullptr, compaction_reason,
+3 -6
View File
@@ -326,9 +326,7 @@ CompactionServiceCompactionJob::CompactionServiceCompactionJob(
compaction_input_(compaction_service_input),
compaction_result_(compaction_service_result) {}
void CompactionServiceCompactionJob::Prepare(
const CompactionProgress& compaction_progress,
log::Writer* compaction_progress_writer) {
void CompactionServiceCompactionJob::Prepare() {
std::optional<Slice> begin;
if (compaction_input_.has_begin) {
begin = compaction_input_.begin;
@@ -337,8 +335,7 @@ void CompactionServiceCompactionJob::Prepare(
if (compaction_input_.has_end) {
end = compaction_input_.end;
}
CompactionJob::Prepare(std::make_pair(begin, end), compaction_progress,
compaction_progress_writer);
CompactionJob::Prepare(std::make_pair(begin, end));
}
Status CompactionServiceCompactionJob::Run() {
@@ -411,7 +408,7 @@ Status CompactionServiceCompactionJob::Run() {
// 2. Update job-level output stats with the aggregated internal_stats_
// Please note that input stats will be updated by primary host when all
// subcompactions are finished
UpdateCompactionJobOutputStatsFromInternalStats(status, internal_stats_);
UpdateCompactionJobOutputStatsFromInternalStats(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();
+102 -541
View File
@@ -4,7 +4,6 @@
// (found in the LICENSE.Apache file in the root directory).
#include "db/db_test_util.h"
#include "file/file_util.h"
#include "port/stack_trace.h"
#include "rocksdb/utilities/options_util.h"
#include "table/unique_id_impl.h"
@@ -17,17 +16,17 @@ class MyTestCompactionService : public CompactionService {
MyTestCompactionService(
std::string db_path, Options& options,
std::shared_ptr<Statistics>& statistics,
std::vector<std::shared_ptr<EventListener>> listeners,
std::vector<std::shared_ptr<EventListener>>& listeners,
std::vector<std::shared_ptr<TablePropertiesCollectorFactory>>
table_properties_collector_factories)
: db_path_(std::move(db_path)),
statistics_(statistics),
options_(options),
statistics_(statistics),
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_(std::move(listeners)),
listeners_(listeners),
table_properties_collector_factories_(
std::move(table_properties_collector_factories)) {}
@@ -73,31 +72,6 @@ class MyTestCompactionService : public CompactionService {
if (is_override_wait_status_) {
return override_wait_status_;
}
CompactionServiceOptionsOverride options_override = GetOptionsOverride();
OpenAndCompactOptions options;
options.canceled = &canceled_;
Status s =
DB::OpenAndCompact(options, db_path_, GetOutputPath(scheduled_job_id),
compaction_input, result, options_override);
{
InstrumentedMutexLock l(&mutex_);
if (is_override_wait_result_) {
*result = override_wait_result_;
}
result_ = *result;
}
compaction_num_.fetch_add(1);
if (s.ok()) {
return CompactionServiceJobStatus::kSuccess;
} else {
return CompactionServiceJobStatus::kFailure;
}
}
CompactionServiceOptionsOverride GetOptionsOverride() {
CompactionServiceOptionsOverride options_override;
options_override.env = options_.env;
options_override.file_checksum_gen_factory =
@@ -120,7 +94,26 @@ class MyTestCompactionService : public CompactionService {
options_override.table_properties_collector_factories =
table_properties_collector_factories_;
}
return options_override;
OpenAndCompactOptions options;
options.canceled = &canceled_;
Status s =
DB::OpenAndCompact(options, db_path_, db_path_ + "/" + scheduled_job_id,
compaction_input, result, options_override);
{
InstrumentedMutexLock l(&mutex_);
if (is_override_wait_result_) {
*result = override_wait_result_;
}
result_ = *result;
}
compaction_num_.fetch_add(1);
if (s.ok()) {
return CompactionServiceJobStatus::kSuccess;
} else {
return CompactionServiceJobStatus::kFailure;
}
}
void CancelAwaitingJobs() override { canceled_ = true; }
@@ -167,21 +160,14 @@ class MyTestCompactionService : public CompactionService {
return final_updated_status_.load();
}
protected:
private:
InstrumentedMutex mutex_;
const std::string db_path_;
std::shared_ptr<Statistics> statistics_;
std::atomic_int compaction_num_{0};
std::map<std::string, std::string> jobs_;
std::map<std::string, CompactionServiceJobInfo> infos_;
std::string result_;
std::string GetOutputPath(const std::string& scheduled_job_id) {
return db_path_ + "/" + scheduled_job_id;
}
private:
std::atomic_int compaction_num_{0};
const std::string db_path_;
Options options_;
std::shared_ptr<Statistics> statistics_;
CompactionServiceJobInfo start_info_;
CompactionServiceJobInfo wait_info_;
bool is_override_start_status_ = false;
@@ -191,6 +177,7 @@ class MyTestCompactionService : public CompactionService {
CompactionServiceJobStatus override_wait_status_ =
CompactionServiceJobStatus::kFailure;
bool is_override_wait_result_ = false;
std::string result_;
std::string override_wait_result_;
std::vector<std::shared_ptr<EventListener>> listeners_;
std::vector<std::shared_ptr<TablePropertiesCollectorFactory>>
@@ -476,6 +463,8 @@ TEST_F(CompactionServiceTest, ManualCompaction) {
TEST_F(CompactionServiceTest, StandaloneDeleteRangeTombstoneOptimization) {
Options options = CurrentOptions();
options.compaction_style = CompactionStyle::kCompactionStyleUniversal;
ReopenWithCompactionService(&options);
size_t num_files_after_filtered = 0;
SyncPoint::GetInstance()->SetCallBack(
@@ -483,97 +472,83 @@ TEST_F(CompactionServiceTest, StandaloneDeleteRangeTombstoneOptimization) {
[&](void* arg) {
num_files_after_filtered = *static_cast<size_t*>(arg);
});
SyncPoint::GetInstance()->EnableProcessing();
for (auto compaction_style : {CompactionStyle::kCompactionStyleLevel,
CompactionStyle::kCompactionStyleUniversal}) {
SCOPED_TRACE("Style: " + std::to_string(compaction_style));
options.compaction_style = compaction_style;
ReopenWithCompactionService(&options);
std::vector<std::string> files;
{
// Writes first version of data in range partitioned files.
SstFileWriter sst_file_writer(EnvOptions(), options);
std::string file1 = dbname_ + "file1.sst";
ASSERT_OK(sst_file_writer.Open(file1));
ASSERT_OK(sst_file_writer.Put("a", "a1"));
ASSERT_OK(sst_file_writer.Put("b", "b1"));
ExternalSstFileInfo file1_info;
ASSERT_OK(sst_file_writer.Finish(&file1_info));
files.push_back(std::move(file1));
num_files_after_filtered = 0;
std::vector<std::string> files;
{
// Writes first version of data in range partitioned files.
SstFileWriter sst_file_writer(EnvOptions(), options);
std::string file1 = dbname_ + "file1.sst";
ASSERT_OK(sst_file_writer.Open(file1));
ASSERT_OK(sst_file_writer.Put("a", "a1"));
ASSERT_OK(sst_file_writer.Put("b", "b1"));
ExternalSstFileInfo file1_info;
ASSERT_OK(sst_file_writer.Finish(&file1_info));
files.push_back(std::move(file1));
std::string file2 = dbname_ + "file2.sst";
ASSERT_OK(sst_file_writer.Open(file2));
ASSERT_OK(sst_file_writer.Put("x", "x1"));
ASSERT_OK(sst_file_writer.Put("y", "y1"));
ExternalSstFileInfo file2_info;
ASSERT_OK(sst_file_writer.Finish(&file2_info));
files.push_back(std::move(file2));
}
IngestExternalFileOptions ifo;
ASSERT_OK(db_->IngestExternalFile(files, ifo));
ASSERT_EQ(Get("a"), "a1");
ASSERT_EQ(Get("b"), "b1");
ASSERT_EQ(Get("x"), "x1");
ASSERT_EQ(Get("y"), "y1");
ASSERT_EQ(2, NumTableFilesAtLevel(6));
auto my_cs = GetCompactionService();
uint64_t comp_num = my_cs->GetCompactionNum();
{
// Atomically delete old version of data with one range delete file.
// And a new batch of range partitioned files with new version of data.
files.clear();
SstFileWriter sst_file_writer(EnvOptions(), options);
std::string file2 = dbname_ + "file2.sst";
ASSERT_OK(sst_file_writer.Open(file2));
ASSERT_OK(sst_file_writer.DeleteRange("a", "z"));
ExternalSstFileInfo file2_info;
ASSERT_OK(sst_file_writer.Finish(&file2_info));
files.push_back(std::move(file2));
std::string file3 = dbname_ + "file3.sst";
ASSERT_OK(sst_file_writer.Open(file3));
ASSERT_OK(sst_file_writer.Put("a", "a2"));
ASSERT_OK(sst_file_writer.Put("b", "b2"));
ExternalSstFileInfo file3_info;
ASSERT_OK(sst_file_writer.Finish(&file3_info));
files.push_back(std::move(file3));
std::string file4 = dbname_ + "file4.sst";
ASSERT_OK(sst_file_writer.Open(file4));
ASSERT_OK(sst_file_writer.Put("x", "x2"));
ASSERT_OK(sst_file_writer.Put("y", "y2"));
ExternalSstFileInfo file4_info;
ASSERT_OK(sst_file_writer.Finish(&file4_info));
files.push_back(std::move(file4));
}
ASSERT_OK(db_->IngestExternalFile(files, ifo));
ASSERT_OK(db_->WaitForCompact(WaitForCompactOptions()));
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
CompactionServiceResult result;
my_cs->GetResult(&result);
ASSERT_OK(result.status);
ASSERT_TRUE(result.stats.is_manual_compaction);
ASSERT_TRUE(result.stats.is_remote_compaction);
if (compaction_style == kCompactionStyleUniversal) {
ASSERT_EQ(num_files_after_filtered, 1);
} else {
// Not filtered
ASSERT_EQ(num_files_after_filtered, 3);
}
Close();
std::string file2 = dbname_ + "file2.sst";
ASSERT_OK(sst_file_writer.Open(file2));
ASSERT_OK(sst_file_writer.Put("x", "x1"));
ASSERT_OK(sst_file_writer.Put("y", "y1"));
ExternalSstFileInfo file2_info;
ASSERT_OK(sst_file_writer.Finish(&file2_info));
files.push_back(std::move(file2));
}
IngestExternalFileOptions ifo;
ASSERT_OK(db_->IngestExternalFile(files, ifo));
ASSERT_EQ(Get("a"), "a1");
ASSERT_EQ(Get("b"), "b1");
ASSERT_EQ(Get("x"), "x1");
ASSERT_EQ(Get("y"), "y1");
ASSERT_EQ(2, NumTableFilesAtLevel(6));
auto my_cs = GetCompactionService();
uint64_t comp_num = my_cs->GetCompactionNum();
{
// Atomically delete old version of data with one range delete file.
// And a new batch of range partitioned files with new version of data.
files.clear();
SstFileWriter sst_file_writer(EnvOptions(), options);
std::string file2 = dbname_ + "file2.sst";
ASSERT_OK(sst_file_writer.Open(file2));
ASSERT_OK(sst_file_writer.DeleteRange("a", "z"));
ExternalSstFileInfo file2_info;
ASSERT_OK(sst_file_writer.Finish(&file2_info));
files.push_back(std::move(file2));
std::string file3 = dbname_ + "file3.sst";
ASSERT_OK(sst_file_writer.Open(file3));
ASSERT_OK(sst_file_writer.Put("a", "a2"));
ASSERT_OK(sst_file_writer.Put("b", "b2"));
ExternalSstFileInfo file3_info;
ASSERT_OK(sst_file_writer.Finish(&file3_info));
files.push_back(std::move(file3));
std::string file4 = dbname_ + "file4.sst";
ASSERT_OK(sst_file_writer.Open(file4));
ASSERT_OK(sst_file_writer.Put("x", "x2"));
ASSERT_OK(sst_file_writer.Put("y", "y2"));
ExternalSstFileInfo file4_info;
ASSERT_OK(sst_file_writer.Finish(&file4_info));
files.push_back(std::move(file4));
}
ASSERT_OK(db_->IngestExternalFile(files, ifo));
ASSERT_OK(db_->WaitForCompact(WaitForCompactOptions()));
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
CompactionServiceResult result;
my_cs->GetResult(&result);
ASSERT_OK(result.status);
ASSERT_TRUE(result.stats.is_manual_compaction);
ASSERT_TRUE(result.stats.is_remote_compaction);
ASSERT_EQ(num_files_after_filtered, 1);
Close();
SyncPoint::GetInstance()->DisableProcessing();
}
@@ -1047,7 +1022,6 @@ TEST_F(CompactionServiceTest, CorruptedOutputParanoidFileCheck) {
Destroy(options);
options.disable_auto_compactions = true;
options.paranoid_file_checks = paranoid_file_check_enabled;
options.verify_output_flags = VerifyOutputFlags::kVerifyNone;
ReopenWithCompactionService(&options);
GenerateTestData();
@@ -1102,87 +1076,6 @@ TEST_F(CompactionServiceTest, CorruptedOutputParanoidFileCheck) {
}
}
TEST_F(CompactionServiceTest, CorruptedOutputVerifyOutputFlags) {
for (VerifyOutputFlags verify_output_flags :
{VerifyOutputFlags::kVerifyNone,
VerifyOutputFlags::kEnableForLocalCompaction |
VerifyOutputFlags::kVerifyBlockChecksum,
VerifyOutputFlags::kEnableForRemoteCompaction |
VerifyOutputFlags::kVerifyBlockChecksum,
VerifyOutputFlags::kEnableForRemoteCompaction |
VerifyOutputFlags::kVerifyIteration,
VerifyOutputFlags::kVerifyAll}) {
SCOPED_TRACE(
"verify_output_flags=" +
std::to_string(static_cast<std::underlying_type_t<VerifyOutputFlags>>(
verify_output_flags)));
Options options = CurrentOptions();
Destroy(options);
options.disable_auto_compactions = true;
options.paranoid_file_checks = false;
options.verify_output_flags = verify_output_flags;
ReopenWithCompactionService(&options);
GenerateTestData();
auto my_cs = GetCompactionService();
std::string start_str = Key(15);
std::string end_str = Key(45);
Slice start(start_str);
Slice end(end_str);
uint64_t comp_num = my_cs->GetCompactionNum();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"CompactionServiceCompactionJob::Run:0", [&](void* arg) {
CompactionServiceResult* compaction_result =
*(static_cast<CompactionServiceResult**>(arg));
ASSERT_TRUE(compaction_result != nullptr &&
!compaction_result->output_files.empty());
// Corrupt files here
for (const auto& output_file : compaction_result->output_files) {
std::string file_name =
compaction_result->output_path + "/" + output_file.file_name;
// Corrupt very small range of bytes. This corruption is so small
// that this isn't caught by default light-weight check
ASSERT_OK(test::CorruptFile(env_, file_name, 0, 1,
false /* verifyChecksum */));
}
});
SyncPoint::GetInstance()->EnableProcessing();
const bool is_enabled_for_remote_compaction =
!!(verify_output_flags & VerifyOutputFlags::kEnableForRemoteCompaction);
const bool should_verify_block_checksum =
!!(verify_output_flags & VerifyOutputFlags::kVerifyBlockChecksum);
const bool should_verify_iteration =
!!(verify_output_flags & VerifyOutputFlags::kVerifyIteration);
Status s = db_->CompactRange(CompactRangeOptions(), &start, &end);
if (is_enabled_for_remote_compaction &&
(should_verify_block_checksum || should_verify_iteration)) {
ASSERT_NOK(s);
ASSERT_TRUE(s.IsCorruption());
} else {
// CompactRange() goes through if block checksum wasn't verified
ASSERT_OK(s);
}
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
// On the worker side, the compaction is considered success
// Verification is done on the primary side
CompactionServiceResult result;
my_cs->GetResult(&result);
ASSERT_OK(result.status);
ASSERT_TRUE(result.stats.is_manual_compaction);
ASSERT_TRUE(result.stats.is_remote_compaction);
}
}
TEST_F(CompactionServiceTest, TruncatedOutput) {
Options options = CurrentOptions();
options.disable_auto_compactions = true;
@@ -2100,338 +1993,6 @@ TEST_F(CompactionServiceTest, TablePropertiesCollector) {
ASSERT_TRUE(has_user_property);
}
class ResumableCompactionService : public MyTestCompactionService {
public:
enum class TestScenario {
// Test scenario 1: Two-phase compaction with resumption
// - Phase 1: Cancel the compaction running with resumption enabled (saves
// progress)
// - Phase 2: Resume from saved progress and complete
// Validates: Resumption reduces redundant work
kCancelThenResume,
// Test scenario 2: Two-phase compaction without resumption
// - Phase 1: Cancel the compaction running with resumption enabled (saves
// progress)
// - Phase 2: Start fresh without resumption (ignores saved progress) and
// complete
// Validates: Disabling resumption causes full reprocessing
kCancelThenFreshStart,
// Test scenario 3: Three-phase compaction toggling resumption on/off/on
// - Phase 1: Cancel the compaction running with resumption enabled (saves
// progress)
// - Phase 2: Start fresh wtihout resumption (ignores saved progress) and
// cancel agains
// - Phase 3: Resume with resumption support (loads Phase 1's progress) and
// complete
// Validates: Resumption state can be toggled;
kMultipleCancelToggleResumption
};
ResumableCompactionService(const std::string& db_path, Options& options,
std::shared_ptr<Statistics> statistics,
TestScenario scenario)
: MyTestCompactionService(db_path, options, statistics,
{} /* listeners */,
{} /* table_properties_collector_factories */),
scenario_(scenario) {}
CompactionServiceJobStatus Wait(const std::string& scheduled_job_id,
std::string* result) override {
std::string compaction_input = ExtractCompactionInput(scheduled_job_id);
EXPECT_FALSE(compaction_input.empty());
OpenAndCompactOptions open_and_compaction_options;
auto override_options = GetOptionsOverride();
// Force creation of one key per output file for test simplicity.
// ASSUMPTION: This makes stats.count directly proportional to keys
// processed.
SyncPoint::GetInstance()->SetCallBack(
"CompactionOutputs::ShouldStopBefore::manual_decision", [](void* p) {
auto* pair = static_cast<std::pair<bool*, const Slice>*>(p);
*(pair->first) = true;
});
// Simulate cancelled compaction by overriding status at completion. So
// compaction processes all keys before this point to make stats.count
// comparison straightforward.
SyncPoint::GetInstance()->SetCallBack(
"DBImplSecondary::CompactWithoutInstallation::End", [&](void* status) {
auto s = static_cast<Status*>(status);
*s = Status::Incomplete(Status::SubCode::kManualCompactionPaused);
});
SyncPoint::GetInstance()->EnableProcessing();
// Phase 1: Run compaction with resumption enabled and cancel it
// - Processes all input keys
// - Creates output files and saves progress
// - Status overridden to "paused"
open_and_compaction_options.allow_resumption = true;
auto phase1_stats =
RunCancelledCompaction(open_and_compaction_options, scheduled_job_id,
compaction_input, override_options);
HistogramData phase2_stats;
if (scenario_ == TestScenario::kMultipleCancelToggleResumption) {
// Phase 2: Run compaction WITHOUT resumption (fresh start) and cancel it
// - Delete all files left behind Phase 1 before calling OpenAndCompact()
// - Processes all input keys again from scratch
// - Creates output files but does NOT save progress
// - Status overridden to "paused"
open_and_compaction_options.allow_resumption = false;
// Clean up output folder for fresh start
std::string output_dir = GetOutputPath(scheduled_job_id);
Status cleanup_status = DestroyDir(override_options.env, output_dir);
EXPECT_TRUE(cleanup_status.ok());
EXPECT_OK(override_options.env->CreateDir(output_dir));
phase2_stats =
RunCancelledCompaction(open_and_compaction_options, scheduled_job_id,
compaction_input, override_options);
// Validation: Phase 2 starts from scratch, so it processes the same
// input keys as Phase 1.
// ASSUMPTION: With fixed input (10 keys) and deterministic cancellation
// (after processing), both phases create the same number of output files.
EXPECT_EQ(phase2_stats.count, phase1_stats.count);
}
SyncPoint::GetInstance()->ClearCallBack(
"DBImplSecondary::CompactWithoutInstallation::End");
// Final phase: Run compaction to completion (no cancellation)
if (scenario_ == TestScenario::kMultipleCancelToggleResumption) {
// Attempt to resume but it ends up starting fresh
open_and_compaction_options.allow_resumption = true;
} else if (scenario_ == TestScenario::kCancelThenResume) {
// Resume from Phase 1's saved progress
open_and_compaction_options.allow_resumption = true;
} else { // kCancelThenFreshStart
// Start fresh without resumption
open_and_compaction_options.allow_resumption = false;
// Clean up output folder for fresh start
std::string output_dir = GetOutputPath(scheduled_job_id);
Status cleanup_status = DestroyDir(override_options.env, output_dir);
EXPECT_TRUE(cleanup_status.ok());
EXPECT_OK(override_options.env->CreateDir(output_dir));
}
auto final_phase_stats =
RunCompaction(open_and_compaction_options, scheduled_job_id,
compaction_input, override_options, result);
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
// Validate statistics based on scenario
if (scenario_ == TestScenario::kMultipleCancelToggleResumption) {
// ASSUMPTION: Phase 1 processes all keys before cancellation
EXPECT_GT(phase1_stats.count, 0);
// ASSUMPTION: Phase 2 runs with allow_resumption=false and an empty
// folder. Phase 2 then creates its own output files (but doesn't save
// progress). When Phase 3 starts with allow_resumption=true, it finds no
// progress file exists, so it cannot resume and must start from scratch,
// processing all input keys again.
// Result: Phase 3 does the same amount of work as Phase 1.
EXPECT_EQ(final_phase_stats.count, phase1_stats.count);
} else if (scenario_ == TestScenario::kCancelThenResume) {
// ASSUMPTION: Phase 1 processes all keys before cancellation
EXPECT_GT(phase1_stats.count, 0);
// ASSUMPTION: Phase 1 processes all keys and saves progress before
// cancellation. Final phase resumes from Phase 1's saved progress.
// Since Phase 1 completed all processing before being cancelled, the
// final phase should do less work than Phase 1.
EXPECT_LT(final_phase_stats.count, phase1_stats.count);
} else { // kCancelThenFreshStart
// ASSUMPTION: Phase 1 processes all keys before cancellation
EXPECT_GT(phase1_stats.count, 0);
// ASSUMPTION: Final phase starts fresh without resumption, so it
// processes all input keys again and creates the same number of files
EXPECT_EQ(final_phase_stats.count, phase1_stats.count);
}
StoreResult(*result);
return CompactionServiceJobStatus::kSuccess;
}
private:
std::string ExtractCompactionInput(const std::string& scheduled_job_id) {
InstrumentedMutexLock l(&mutex_);
auto job_index = jobs_.find(scheduled_job_id);
if (job_index == jobs_.end()) {
return "";
}
std::string compaction_input = std::move(job_index->second);
jobs_.erase(job_index);
auto info_index = infos_.find(scheduled_job_id);
if (info_index == infos_.end()) {
return "";
}
infos_.erase(info_index);
return compaction_input;
}
HistogramData RunCancelledCompaction(
const OpenAndCompactOptions& options, const std::string& scheduled_job_id,
const std::string& compaction_input,
const CompactionServiceOptionsOverride& override_options) {
std::string temp_result;
EXPECT_OK(statistics_->Reset());
Status s =
DB::OpenAndCompact(options, db_path_, GetOutputPath(scheduled_job_id),
compaction_input, &temp_result, override_options);
EXPECT_TRUE(s.IsManualCompactionPaused());
HistogramData stats;
statistics_->histogramData(FILE_WRITE_COMPACTION_MICROS, &stats);
return stats;
}
HistogramData RunCompaction(
const OpenAndCompactOptions& options, const std::string& scheduled_job_id,
const std::string& compaction_input,
const CompactionServiceOptionsOverride& override_options,
std::string* result) {
EXPECT_OK(statistics_->Reset());
Status s =
DB::OpenAndCompact(options, db_path_, GetOutputPath(scheduled_job_id),
compaction_input, result, override_options);
EXPECT_TRUE(s.ok());
HistogramData stats;
statistics_->histogramData(FILE_WRITE_COMPACTION_MICROS, &stats);
return stats;
}
void StoreResult(const std::string& result) {
InstrumentedMutexLock l(&mutex_);
result_ = result;
}
TestScenario scenario_;
};
class ResumableCompactionServiceTest : public CompactionServiceTest {
public:
explicit ResumableCompactionServiceTest() : CompactionServiceTest() {}
void RunCompactionCancelTest(
ResumableCompactionService::TestScenario scenario) {
Options options = CurrentOptions();
options.disable_auto_compactions = true;
std::shared_ptr<Statistics> statistics = CreateDBStatistics();
options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
BlockBasedTableOptions table_options;
table_options.verify_compression = true;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
auto resume_cs = std::make_shared<ResumableCompactionService>(
dbname_, options, statistics, scenario);
options.compaction_service = resume_cs;
DestroyAndReopen(options);
GenerateTestData();
ASSERT_OK(statistics->Reset());
CompactRangeOptions cro;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
Status s = db_->CompactRange(cro, nullptr, nullptr);
ASSERT_OK(s);
VerifyTestData();
s = db_->VerifyChecksum();
ASSERT_OK(s);
s = db_->VerifyFileChecksums(ReadOptions());
ASSERT_OK(s);
CompactionServiceResult result;
resume_cs->GetResult(&result);
ASSERT_OK(result.status);
ASSERT_TRUE(result.stats.is_manual_compaction);
ASSERT_TRUE(result.stats.is_remote_compaction);
ASSERT_GT(result.output_files.size(), 0);
uint64_t resumed_bytes =
statistics->getTickerCount(REMOTE_COMPACT_RESUMED_BYTES);
if (scenario ==
ResumableCompactionService::TestScenario::kCancelThenResume) {
// When resuming compaction, some bytes should be resumed from previous
// progress
ASSERT_GT(resumed_bytes, 0);
} else if (scenario == ResumableCompactionService::TestScenario::
kCancelThenFreshStart) {
// When starting fresh (ignoring existing progress), no bytes should be
// resumed
ASSERT_EQ(resumed_bytes, 0);
} else { // kMultipleCancelToggleResumption
// Phase 2 ran without resumption (fresh start), so Phase 3 has no
// progress to resume from. It processes all keys again from scratch.
ASSERT_EQ(resumed_bytes, 0);
}
}
void GenerateTestData() {
for (int i = 0; i < kNumKeys; ++i) {
ASSERT_OK(Put(Key(i), "value"));
ASSERT_OK(Flush());
if (i % 2 == 0) {
ASSERT_OK(Delete(Key(i)));
ASSERT_OK(Flush());
}
}
}
void VerifyTestData() {
for (int i = 0; i < kNumKeys; ++i) {
if (i % 2 == 0) {
ASSERT_EQ("NOT_FOUND", Get((Key(i))));
} else {
ASSERT_EQ("value", Get((Key(i))));
}
}
}
private:
static constexpr int kNumKeys = 10;
};
TEST_F(ResumableCompactionServiceTest, CompactionCancelThenResume) {
RunCompactionCancelTest(
ResumableCompactionService::TestScenario::kCancelThenResume);
}
TEST_F(ResumableCompactionServiceTest, CompactionCancelThenFreshStart) {
RunCompactionCancelTest(
ResumableCompactionService::TestScenario::kCancelThenFreshStart);
}
TEST_F(ResumableCompactionServiceTest,
CompactionMultipleCancelToggleResumption) {
RunCompactionCancelTest(ResumableCompactionService::TestScenario::
kMultipleCancelToggleResumption);
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+2 -4
View File
@@ -108,13 +108,11 @@ Slice SubcompactionState::LargestUserKey() const {
Status SubcompactionState::AddToOutput(
const CompactionIterator& iter, bool use_proximal_output,
const CompactionFileOpenFunc& open_file_func,
const CompactionFileCloseFunc& close_file_func,
const ParsedInternalKey& prev_iter_output_internal_key) {
const CompactionFileCloseFunc& close_file_func) {
// update target output
current_outputs_ =
use_proximal_output ? &proximal_level_outputs_ : &compaction_outputs_;
return current_outputs_->AddToOutput(iter, open_file_func, close_file_func,
prev_iter_output_internal_key);
return current_outputs_->AddToOutput(iter, open_file_func, close_file_func);
}
} // namespace ROCKSDB_NAMESPACE
+1 -13
View File
@@ -208,20 +208,10 @@ class SubcompactionState {
return range_del_agg_ && !range_del_agg_->IsEmpty();
}
void SetSubcompactionProgress(
const SubcompactionProgress& subcompaction_progress) {
subcompaction_progress_ = subcompaction_progress;
}
SubcompactionProgress& GetSubcompactionProgressRef() {
return subcompaction_progress_;
}
// Add compaction_iterator key/value to the `Current` output group.
Status AddToOutput(const CompactionIterator& iter, bool use_proximal_output,
const CompactionFileOpenFunc& open_file_func,
const CompactionFileCloseFunc& close_file_func,
const ParsedInternalKey& prev_iter_output_internal_key);
const CompactionFileCloseFunc& close_file_func);
// Close all compaction output files, both output_to_proximal_level outputs
// and normal outputs.
@@ -251,8 +241,6 @@ class SubcompactionState {
CompactionOutputs proximal_level_outputs_;
CompactionOutputs* current_outputs_ = &compaction_outputs_;
std::unique_ptr<CompactionRangeDelAggregator> range_del_agg_;
SubcompactionProgress subcompaction_progress_;
};
} // namespace ROCKSDB_NAMESPACE
+24
View File
@@ -675,6 +675,30 @@ TEST_F(DBBasicTest, Flush) {
} while (ChangeCompactOptions());
}
TEST_F(DBBasicTest, ManifestRollOver) {
do {
Options options;
options.max_manifest_file_size = 10; // 10 bytes
options = CurrentOptions(options);
CreateAndReopenWithCF({"pikachu"}, options);
{
ASSERT_OK(Put(1, "manifest_key1", std::string(1000, '1')));
ASSERT_OK(Put(1, "manifest_key2", std::string(1000, '2')));
ASSERT_OK(Put(1, "manifest_key3", std::string(1000, '3')));
uint64_t manifest_before_flush = dbfull()->TEST_Current_Manifest_FileNo();
ASSERT_OK(Flush(1)); // This should trigger LogAndApply.
uint64_t manifest_after_flush = dbfull()->TEST_Current_Manifest_FileNo();
ASSERT_GT(manifest_after_flush, manifest_before_flush);
ReopenWithColumnFamilies({"default", "pikachu"}, options);
ASSERT_GT(dbfull()->TEST_Current_Manifest_FileNo(), manifest_after_flush);
// check if a new manifest file got inserted or not.
ASSERT_EQ(std::string(1000, '1'), Get(1, "manifest_key1"));
ASSERT_EQ(std::string(1000, '2'), Get(1, "manifest_key2"));
ASSERT_EQ(std::string(1000, '3'), Get(1, "manifest_key3"));
}
} while (ChangeCompactOptions());
}
TEST_F(DBBasicTest, IdentityAcrossRestarts) {
constexpr size_t kMinIdSize = 10;
do {
+1 -9
View File
@@ -710,20 +710,12 @@ class AlwaysTrueBitsBuilder : public FilterBitsBuilder {
count_ = 0;
// Interpreted as "always true" filter (0 probes over 1 byte of
// payload, 5 bytes metadata)
return Slice("\0\0\0\0\0\0", kAlwaysTrueFilterBytes);
return Slice("\0\0\0\0\0\0", 6);
}
using FilterBitsBuilder::Finish;
size_t ApproximateNumEntries(size_t) override { return SIZE_MAX; }
size_t CalculateSpace(size_t /* num_entries */) override {
return kAlwaysTrueFilterBytes;
}
double EstimatedFpRate(size_t /* num_entries */,
size_t /* bytes */) override {
return 1.0;
}
private:
static constexpr size_t kAlwaysTrueFilterBytes = 6;
size_t count_ = 0;
};
-65
View File
@@ -11541,71 +11541,6 @@ TEST_F(DBCompactionTest, RecordNewestKeyTimeForTtlCompaction) {
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
}
// Test verifies compaction file cutting logic when using tail size estimation
// maintains output files at or below the target file size.
TEST_F(DBCompactionTest, CompactionRespectsTargetSizeWithTailEstimation) {
const int kInitialKeyCount = 10000; // 10k keys
const int kValueSize = 100; // 100 bytes per key
const int kSeed = 301;
Options options = CurrentOptions();
options.target_file_size_is_upper_bound = true;
options.target_file_size_base = 256 * 1024;
options.write_buffer_size = 2 * 1024 * 1024;
options.level0_file_num_compaction_trigger = 100; // Never trigger L0->L1
options.compression = kNoCompression;
BlockBasedTableOptions table_options;
table_options.partition_filters = true;
table_options.metadata_block_size = 4 * 1024;
table_options.index_type = BlockBasedTableOptions::kBinarySearch;
table_options.filter_policy.reset(NewBloomFilterPolicy(10));
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
DestroyAndReopen(options);
// Generate 2 L0 files
// Generate first file with 10k keys (each ~100 bytes) approx 1.2MB total
Random rnd(kSeed);
for (int i = 0; i < kInitialKeyCount; i++) {
ASSERT_OK(Put(Key(i), rnd.RandomString(kValueSize)));
}
ASSERT_OK(Flush());
// Generate second file with overlapping keys to force compaction (prevent
// trivial move)
for (int i = kInitialKeyCount / 2; i < kInitialKeyCount * 1.5; i++) {
ASSERT_OK(Put(Key(i), rnd.RandomString(kValueSize)));
}
ASSERT_OK(Flush());
// Capture file metadata and assert two L0 files
std::vector<LiveFileMetaData> file_metadata;
db_->GetLiveFilesMetaData(&file_metadata);
ASSERT_EQ(file_metadata.size(), 2);
for (const auto& file : file_metadata) {
ASSERT_EQ(file.level, 0);
};
// Manually compact LO files to L1
CompactRangeOptions cro;
cro.change_level = true;
cro.target_level = 1;
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
ASSERT_OK(dbfull()->TEST_WaitForCompact());
// Verify that compacted output files are under target file size
for (const auto& file : file_metadata) {
if (file.level > 0) {
EXPECT_LE(file.size, options.target_file_size_base)
<< "Output file size exceeds target size: " << " File: " << file.name
<< " level: " << file.level << " File size: " << file.size
<< " Target size: " << options.target_file_size_base;
}
}
}
class PeriodicCompactionListener : public EventListener {
public:
explicit PeriodicCompactionListener() {}
-161
View File
@@ -1,161 +0,0 @@
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#include "db/db_test_util.h"
namespace ROCKSDB_NAMESPACE {
class DBEtc3Test : public DBTestBase {
public:
DBEtc3Test() : DBTestBase("db_etc3_test", /*env_do_fsync=*/true) {}
};
TEST_F(DBEtc3Test, ManifestRollOver) {
do {
Options options;
// Force new manifest on each manifest write
options.max_manifest_file_size = 0;
options.max_manifest_space_amp_pct = 0;
options = CurrentOptions(options);
CreateAndReopenWithCF({"pikachu"}, options);
{
ASSERT_OK(Put(1, "key1", std::string(1000, '1')));
ASSERT_OK(Put(1, "key2", std::string(1000, '2')));
ASSERT_OK(Put(1, "key3", std::string(1000, '3')));
uint64_t manifest_before_flush = dbfull()->TEST_Current_Manifest_FileNo();
ASSERT_OK(Flush(1)); // This should trigger LogAndApply.
uint64_t manifest_after_flush = dbfull()->TEST_Current_Manifest_FileNo();
ASSERT_GT(manifest_after_flush, manifest_before_flush);
// Re-open should always re-create manifest file
ReopenWithColumnFamilies({"default", "pikachu"}, options);
ASSERT_GT(dbfull()->TEST_Current_Manifest_FileNo(), manifest_after_flush);
ASSERT_EQ(std::string(1000, '1'), Get(1, "key1"));
ASSERT_EQ(std::string(1000, '2'), Get(1, "key2"));
ASSERT_EQ(std::string(1000, '3'), Get(1, "key3"));
}
} while (ChangeCompactOptions());
}
TEST_F(DBEtc3Test, AutoTuneManifestSize) {
// Ensure we have auto-tuning beyond max_manifest_file_size by default
ASSERT_EQ(DBOptions{}.max_manifest_space_amp_pct, 500);
Options options = CurrentOptions();
ASSERT_OK(db_->SetOptions({{"level0_file_num_compaction_trigger", "20"}}));
// Use large column family names to essentially control the amount of payload
// data needed for the manifest file. Drop manifest entries don't include the
// CF name so are small.
uint64_t prev_manifest_num = 0, cur_manifest_num = 0;
std::deque<ColumnFamilyHandle*> handles;
int counter = 5;
auto AddCfFn = [&]() {
std::string name = "cf" + std::to_string(counter++);
name.resize(1000, 'a');
ASSERT_OK(db_->CreateColumnFamily(options, name, &handles.emplace_back()));
prev_manifest_num = cur_manifest_num;
cur_manifest_num = dbfull()->TEST_Current_Manifest_FileNo();
};
auto DropCfFn = [&]() {
ASSERT_OK(db_->DropColumnFamily(handles.front()));
ASSERT_OK(db_->DestroyColumnFamilyHandle(handles.front()));
handles.pop_front();
prev_manifest_num = cur_manifest_num;
cur_manifest_num = dbfull()->TEST_Current_Manifest_FileNo();
};
auto TrivialManifestWriteFn = [&]() {
ASSERT_OK(Put("x", std::to_string(counter++)));
ASSERT_OK(Flush());
prev_manifest_num = cur_manifest_num;
cur_manifest_num = dbfull()->TEST_Current_Manifest_FileNo();
};
options.max_manifest_file_size = 1000000;
options.max_manifest_space_amp_pct = 0; // no auto-tuning yet
DestroyAndReopen(options);
// With the generous (minimum) maximum manifest size, should not be rotated
AddCfFn();
AddCfFn();
AddCfFn();
ASSERT_EQ(prev_manifest_num, cur_manifest_num);
// Change options for small max and (still) no auto-tuning
ASSERT_OK(db_->SetDBOptions({{"max_manifest_file_size", "3000"}}));
// Takes effect on the next manifest write
TrivialManifestWriteFn();
ASSERT_LT(prev_manifest_num, cur_manifest_num);
// Now we have to rewrite the whole manifest on each write because the
// compacted size exceeds the "max" size.
AddCfFn();
ASSERT_LT(prev_manifest_num, cur_manifest_num);
DropCfFn();
ASSERT_LT(prev_manifest_num, cur_manifest_num);
AddCfFn();
ASSERT_LT(prev_manifest_num, cur_manifest_num);
TrivialManifestWriteFn();
ASSERT_LT(prev_manifest_num, cur_manifest_num);
// Enabling auto-tuning should fix this, immediately for next manifest writes.
// This will allow up to double-ish the size of the compacted manifest,
// which last should have been 4000 + some bytes.
ASSERT_EQ(handles.size(), 4U);
ASSERT_OK(db_->SetDBOptions({{"max_manifest_space_amp_pct", "105"}}));
// After 9 CF names should be enough to rotate the manifest
for (int i = 1; i <= 5; ++i) {
if ((i % 2) == 1) {
DropCfFn();
}
AddCfFn();
ASSERT_EQ(prev_manifest_num, cur_manifest_num);
}
TrivialManifestWriteFn();
ASSERT_LT(prev_manifest_num, cur_manifest_num);
// We now have a different last compacted manifest size, should be
// able to go beyond 9 CFs named in manifest this time.
ASSERT_EQ(handles.size(), 6U);
DropCfFn();
DropCfFn();
for (int i = 1; i <= 4; ++i) {
DropCfFn();
AddCfFn();
ASSERT_EQ(prev_manifest_num, cur_manifest_num);
}
// We've written 10 named CFs to the manifest. We should be able to
// dynamically change the auto-tuning still based on the last "compacted"
// manifest size of 7000 + some bytes.
ASSERT_OK(db_->SetDBOptions({{"max_manifest_space_amp_pct", "51"}}));
TrivialManifestWriteFn();
ASSERT_LT(prev_manifest_num, cur_manifest_num);
// And the "compacted" manifest size has reset again, so should be changed
// again sooner.
ASSERT_EQ(handles.size(), 4U);
for (int i = 1; i <= 2; ++i) {
AddCfFn();
ASSERT_EQ(prev_manifest_num, cur_manifest_num);
}
// Enough for manifest change
AddCfFn();
ASSERT_LT(prev_manifest_num, cur_manifest_num);
// Wrap up
while (!handles.empty()) {
DropCfFn();
}
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
::testing::InitGoogleTest(&argc, argv);
RegisterCustomObjects(argc, argv);
return RUN_ALL_TESTS();
}
-1
View File
@@ -109,7 +109,6 @@ Status DBImpl::GetSortedWalFilesImpl(VectorWalPtr& files, bool need_seqnos) {
{
InstrumentedMutexLock l(&mutex_);
while (pending_purge_obsolete_files_ > 0 || bg_purge_scheduled_ > 0) {
TEST_SYNC_POINT("DBImpl::GetSortedWalFilesImpl:WaitPurge");
bg_cv_.Wait();
}
-146
View File
@@ -3561,152 +3561,6 @@ TEST_F(DBFlushTest, VerifyOutputRecordCount) {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
}
}
class DBFlushSuperBlockTest
: public DBFlushTest,
public ::testing::WithParamInterface<std::tuple<bool, size_t, size_t>> {
public:
DBFlushSuperBlockTest() : DBFlushTest() {}
std::string formatKey(int i) {
int desired_length = 10;
char buffer[64];
snprintf(buffer, 64, "%0*d", desired_length, i);
return buffer;
}
void VerifyReadWithGet(int key_count) {
for (int i = 0; i < key_count; ++i) {
PinnableSlice value;
ASSERT_OK(Get(formatKey(i), &value));
ASSERT_EQ(value.ToString(), added_data[formatKey(i)]);
}
}
void VerifyReadWithIterator(int key_count) {
{
std::unique_ptr<Iterator> it(db_->NewIterator(ReadOptions()));
int i = 0;
for (it->SeekToFirst(); it->Valid(); it->Next()) {
ASSERT_OK(it->status());
ASSERT_EQ((it->key()).ToString(), formatKey(i));
ASSERT_EQ((it->value()).ToString(), added_data[formatKey(i)]);
i++;
}
ASSERT_OK(it->status());
ASSERT_EQ(i, key_count);
}
}
protected:
Random rnd{123};
std::unordered_map<std::string, std::string> added_data;
};
constexpr size_t kLowSpaceOverheadRatio = 256;
TEST_P(DBFlushSuperBlockTest, SuperBlock) {
constexpr int key_count = 12345;
Options options;
options.env = env_;
options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
options.paranoid_file_checks = true;
options.write_buffer_size = 1024 * 1024;
BlockBasedTableOptions block_options;
block_options.block_align = get<0>(GetParam());
block_options.index_block_restart_interval = 3;
block_options.super_block_alignment_size = get<1>(GetParam());
block_options.super_block_alignment_space_overhead_ratio = get<2>(GetParam());
options.table_factory.reset(NewBlockBasedTableFactory(block_options));
if (block_options.block_align) {
// When block align is enabled, disable compression
options.compression = kNoCompression;
}
ASSERT_OK(options.table_factory->ValidateOptions(
DBOptions(options), ColumnFamilyOptions(options)));
Reopen(options);
int super_block_pad_count = 0;
int super_block_pad_exceed_limit_count = 0;
SyncPoint::GetInstance()->SetCallBack(
"BlockBasedTableBuilder::WriteMaybeCompressedBlock:"
"SuperBlockAlignment",
[&super_block_pad_count](void* /*arg*/) { super_block_pad_count++; });
SyncPoint::GetInstance()->SetCallBack(
"BlockBasedTableBuilder::WriteMaybeCompressedBlock:"
"SuperBlockAlignmentPaddingBytesExceedLimit",
[&super_block_pad_exceed_limit_count](void* /*arg*/) {
super_block_pad_exceed_limit_count++;
});
SyncPoint::GetInstance()->EnableProcessing();
// Add lots of keys
for (int i = 0; i < key_count; ++i) {
added_data[formatKey(i)] = std::string(rnd.RandomString(rnd.Next() % 1000));
ASSERT_OK(Put(formatKey(i), added_data[formatKey(i)]));
}
// flush the data in memory to disk to verify with super block alignment, the
// data could be read back properly
Reopen(options);
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
// When block_align is enabled, super block is always aligned, so there should
// be 0 padding for super block alignment
if (block_options.super_block_alignment_size != 0 &&
!block_options.block_align) {
ASSERT_GT(super_block_pad_count, 0);
} else {
ASSERT_EQ(super_block_pad_count, 0);
}
if (!block_options.block_align &&
block_options.super_block_alignment_size != 0 &&
block_options.super_block_alignment_space_overhead_ratio ==
kLowSpaceOverheadRatio) {
ASSERT_GT(super_block_pad_exceed_limit_count, 0);
}
// verify the values are correct
VerifyReadWithGet(key_count);
Reopen(options);
VerifyReadWithIterator(key_count);
// verify checksum
ASSERT_OK(db_->VerifyFileChecksums(ReadOptions()));
// Reopen options and flip the option of super block configuration, read still
// works. This verifies the forward/backward compatibility
if (block_options.super_block_alignment_size == 0) {
block_options.super_block_alignment_size = 16 * 1024;
} else {
block_options.super_block_alignment_size = 0;
}
options.table_factory.reset(NewBlockBasedTableFactory(block_options));
Reopen(options);
// verify the values are correct
VerifyReadWithGet(key_count);
Reopen(options);
VerifyReadWithIterator(key_count);
// verify checksum
ASSERT_OK(db_->VerifyFileChecksums(ReadOptions()));
}
INSTANTIATE_TEST_CASE_P(
SuperBlockTests, DBFlushSuperBlockTest,
testing::Combine(testing::Bool(), testing::Values(0, 32 * 1024, 16 * 1024),
// Use very low space overhead ratio to test
// the case where required padded bytes is
// larger than the max allowed padding size
testing::Values(4, kLowSpaceOverheadRatio)));
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+2 -2
View File
@@ -370,10 +370,10 @@ TEST_F(DBFollowerTest, RetryCatchupManifestRollover) {
// This test creates 4 L0 files and compacts them. The follower, during catchup,
// successfully instantiates 4 Versions corresponding to the 4 files (but
// doesn't install them yet), followed by deleting those 4 and adding a new
// donesn't install them yet), followed by deleting those 4 and adding a new
// file from compaction. The test verifies that the 4 L0 files are deleted
// correctly by the follower.
// We use the Barrier* functions to ensure that the follower first sees the 4
// We use teh Barrier* functions to ensure that the follower first sees the 4
// L0 files and is able to link them, and then sees the compaction that
// obsoletes those L0 files (so those L0 files are intermediates that it has
// to explicitly delete). Suppose we don't have any barriers, its possible
+7 -26
View File
@@ -258,10 +258,10 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
[this]() { this->TriggerPeriodicCompaction(); });
versions_.reset(new VersionSet(
dbname_, &immutable_db_options_, mutable_db_options_, file_options_,
table_cache_.get(), write_buffer_manager_, &write_controller_,
&block_cache_tracer_, io_tracer_, db_id_, db_session_id_,
options.daily_offpeak_time_utc, &error_handler_, read_only));
dbname_, &immutable_db_options_, file_options_, table_cache_.get(),
write_buffer_manager_, &write_controller_, &block_cache_tracer_,
io_tracer_, db_id_, db_session_id_, options.daily_offpeak_time_utc,
&error_handler_, read_only));
column_family_memtables_.reset(
new ColumnFamilyMemTablesImpl(versions_->GetColumnFamilySet()));
@@ -1412,7 +1412,7 @@ Status DBImpl::SetDBOptions(
file_options_for_compaction_ = FileOptions(new_db_options);
file_options_for_compaction_ = fs_->OptimizeForCompactionTableWrite(
file_options_for_compaction_, immutable_db_options_);
versions_->UpdatedMutableDbOptions(mutable_db_options_, &mutex_);
versions_->ChangeFileOptions(mutable_db_options_);
// TODO(xiez): clarify why apply optimize for read to write options
file_options_for_compaction_ = fs_->OptimizeForCompactionTableRead(
file_options_for_compaction_, immutable_db_options_);
@@ -1480,12 +1480,6 @@ int DBImpl::FindMinimumEmptyLevelFitting(
return minimum_level;
}
Status DBImpl::FlushWAL(const FlushWALOptions& options) {
WriteOptions write_options;
write_options.rate_limiter_priority = options.rate_limiter_priority;
return FlushWAL(write_options, options.sync);
}
Status DBImpl::FlushWAL(const WriteOptions& write_options, bool sync) {
if (manual_wal_flush_) {
IOStatus io_s;
@@ -5047,19 +5041,6 @@ void DBImpl::GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
}
}
void DBImpl::GetColumnFamilyMetaData(
ColumnFamilyHandle* column_family,
const GetColumnFamilyMetaDataOptions& options,
ColumnFamilyMetaData* metadata) {
assert(column_family);
auto* cfd =
static_cast_with_check<ColumnFamilyHandleImpl>(column_family)->cfd();
{
InstrumentedMutexLock l(&mutex_);
cfd->current()->GetColumnFamilyMetaData(options, metadata);
}
}
void DBImpl::GetAllColumnFamilyMetaData(
std::vector<ColumnFamilyMetaData>* metadata) {
InstrumentedMutexLock l(&mutex_);
@@ -5519,7 +5500,7 @@ Status DBImpl::RenameTempFileToOptionsFile(const std::string& file_name,
return s;
}
#ifndef NROCKSDB_THREAD_STATUS
#ifdef ROCKSDB_USING_THREAD_STATUS
void DBImpl::NewThreadStatusCfInfo(ColumnFamilyData* cfd) const {
if (immutable_db_options_.enable_thread_tracking) {
@@ -5546,7 +5527,7 @@ void DBImpl::NewThreadStatusCfInfo(ColumnFamilyData* /*cfd*/) const {}
void DBImpl::EraseThreadStatusCfInfo(ColumnFamilyData* /*cfd*/) const {}
void DBImpl::EraseThreadStatusDbInfo() const {}
#endif // !NROCKSDB_THREAD_STATUS
#endif // ROCKSDB_USING_THREAD_STATUS
//
// A global method that can dump out the build version
+2 -18
View File
@@ -484,13 +484,10 @@ class DBImpl : public DB {
const FlushOptions& options,
const std::vector<ColumnFamilyHandle*>& column_families) override;
Status FlushWAL(bool sync) override {
FlushWALOptions options;
options.sync = sync;
return FlushWAL(options);
// TODO: plumb Env::IOActivity, Env::IOPriority
return FlushWAL(WriteOptions(), sync);
}
Status FlushWAL(const FlushWALOptions& options) override;
virtual Status FlushWAL(const WriteOptions& write_options, bool sync);
bool WALBufferIsEmpty();
Status SyncWAL() override;
@@ -573,11 +570,6 @@ class DBImpl : public DB {
void GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
ColumnFamilyMetaData* metadata) override;
// Get column family metadata with filtering based on key range and level
void GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
const GetColumnFamilyMetaDataOptions& options,
ColumnFamilyMetaData* metadata) override;
void GetAllColumnFamilyMetaData(
std::vector<ColumnFamilyMetaData>* metadata) override;
@@ -2401,14 +2393,6 @@ class DBImpl : public DB {
JobContext* job_context, LogBuffer* log_buffer,
CompactionJobInfo* compaction_job_info);
// Helper function to perform trivial move by updating manifest metadata
// without rewriting data files. This is called when IsTrivialMove() is true.
// REQUIRES: mutex held
// Returns: Status of the trivial move operation
Status PerformTrivialMove(Compaction& c, LogBuffer* log_buffer,
bool& compaction_released, size_t& moved_files,
size_t& moved_bytes);
// REQUIRES: mutex unlocked
void TrackOrUntrackFiles(const std::vector<std::string>& existing_data_files,
bool track);
+58 -132
View File
@@ -1424,56 +1424,6 @@ Status DBImpl::CompactFiles(const CompactionOptions& compact_options,
return s;
}
Status DBImpl::PerformTrivialMove(Compaction& c, LogBuffer* log_buffer,
bool& compaction_released,
size_t& moved_files, size_t& moved_bytes) {
mutex_.AssertHeld();
ROCKS_LOG_BUFFER(log_buffer, "[%s] Moving %d files to level-%d\n",
c.column_family_data()->GetName().c_str(),
static_cast<int>(c.num_input_files(0)), c.output_level());
// Move files to the output level by editing the manifest
for (unsigned int l = 0; l < c.num_input_levels(); l++) {
if (c.level(l) == c.output_level()) {
continue;
}
for (size_t i = 0; i < c.num_input_files(l); i++) {
FileMetaData* f = c.input(l, i);
c.edit()->DeleteFile(c.level(l), f->fd.GetNumber());
c.edit()->AddFile(c.output_level(), f->fd.GetNumber(), f->fd.GetPathId(),
f->fd.GetFileSize(), f->smallest, f->largest,
f->fd.smallest_seqno, f->fd.largest_seqno,
f->marked_for_compaction, f->temperature,
f->oldest_blob_file_number, f->oldest_ancester_time,
f->file_creation_time, f->epoch_number,
f->file_checksum, f->file_checksum_func_name,
f->unique_id, f->compensated_range_deletion_size,
f->tail_size, f->user_defined_timestamps_persisted);
moved_bytes += static_cast<size_t>(c.input(l, i)->fd.GetFileSize());
ROCKS_LOG_BUFFER(
log_buffer, "[%s] Moved #%" PRIu64 " to level-%d %" PRIu64 " bytes\n",
c.column_family_data()->GetName().c_str(), f->fd.GetNumber(),
c.output_level(), f->fd.GetFileSize());
}
moved_files += c.num_input_files(l);
}
// Install the new version
const ReadOptions read_options(Env::IOActivity::kCompaction);
const WriteOptions write_options(Env::IOActivity::kCompaction);
Status status = versions_->LogAndApply(
c.column_family_data(), read_options, write_options, c.edit(), &mutex_,
directories_.GetDbDir(), /*new_descriptor_log=*/false,
/*column_family_options=*/nullptr,
[&c, &compaction_released](const Status& s) {
c.ReleaseCompactionFiles(s);
compaction_released = true;
});
return status;
}
Status DBImpl::CompactFilesImpl(
const CompactionOptions& compact_options, ColumnFamilyData* cfd,
Version* version, const std::vector<std::string>& input_file_names,
@@ -1561,63 +1511,6 @@ Status DBImpl::CompactFilesImpl(
// deletion compaction currently not allowed in CompactFiles.
assert(!c->deletion_compaction());
// Check if this can be a trivial move (metadata-only update)
// Similar to the logic in DBImpl::BackgroundCompaction
// Note: We disable trivial move when compaction_service is present because
// the service expects all compactions to go through CompactionJob for
// tracking
bool is_trivial_move = compact_options.allow_trivial_move &&
c->IsTrivialMove() &&
immutable_db_options().compaction_service == nullptr;
if (is_trivial_move) {
// Perform trivial move: just update manifest without rewriting data
TEST_SYNC_POINT("DBImpl::CompactFilesImpl:TrivialMove");
bool compaction_released = false;
size_t moved_files = 0;
size_t moved_bytes = 0;
Status status = PerformTrivialMove(
*c.get(), log_buffer, compaction_released, moved_files, moved_bytes);
if (status.ok()) {
InstallSuperVersionAndScheduleWork(
c->column_family_data(), job_context->superversion_contexts.data());
// Populate output file names for trivial move
if (output_file_names != nullptr) {
for (const auto& newf : c->edit()->GetNewFiles()) {
output_file_names->push_back(TableFileName(
c->immutable_options().cf_paths, newf.second.fd.GetNumber(),
newf.second.fd.GetPathId()));
}
}
ROCKS_LOG_BUFFER(
log_buffer,
"[%s] Trivial move succeeded for %zu files, %zu bytes total\n",
c->column_family_data()->GetName().c_str(), moved_files, moved_bytes);
} else {
if (!compaction_released) {
c->ReleaseCompactionFiles(status);
}
ROCKS_LOG_BUFFER(log_buffer, "[%s] Trivial move failed: %s\n",
c->column_family_data()->GetName().c_str(),
status.ToString().c_str());
error_handler_.SetBGError(status, BackgroundErrorReason::kCompaction);
}
c.reset();
bg_compaction_scheduled_--;
if (bg_compaction_scheduled_ == 0) {
bg_cv_.SignalAll();
}
MaybeScheduleFlushOrCompaction();
return status;
}
// Not a trivial move, proceed with full compaction
InitSnapshotContext(job_context);
std::unique_ptr<std::list<uint64_t>::iterator> pending_outputs_inserted_elem(
@@ -1950,7 +1843,8 @@ Status DBImpl::ReFitLevel(ColumnFamilyData* cfd, int level, int target_level) {
,
LLONG_MAX /* max compaction bytes, not applicable */,
0 /* output path ID, not applicable */, mutable_cf_options.compression,
mutable_cf_options.compression_opts, Temperature::kUnknown,
mutable_cf_options.compression_opts,
mutable_cf_options.default_write_temperature,
0 /* max_subcompactions, not applicable */,
{} /* grandparents, not applicable */,
std::nullopt /* earliest_snapshot */, nullptr /* snapshot_checker */,
@@ -3967,7 +3861,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
uint64_t out_file_creation_time = static_cast<uint64_t>(tmp_current_time);
FileOptions copied_file_options = file_options_;
copied_file_options.temperature = c->GetOutputTemperature();
copied_file_options.temperature = c->output_temperature();
std::unique_ptr<WritableFileWriter> dest_writer;
{
std::unique_ptr<FSWritableFile> dest_file;
@@ -3985,7 +3879,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
"NewWritableFile %s\n"
" out_fname=%s, temperature=%s, io_status=%s",
c->column_family_data()->GetName().c_str(), out_fname.c_str(),
temperature_to_string[c->GetOutputTemperature()].c_str(),
temperature_to_string[c->output_temperature()].c_str(),
io_s.ToString().c_str());
break;
}
@@ -4007,7 +3901,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
c->column_family_data()->GetName().c_str(), in_fname.c_str(),
temperature_to_string[in_file->temperature].c_str(),
out_fname.c_str(),
temperature_to_string[c->GetOutputTemperature()].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
@@ -4047,7 +3941,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
c->column_family_data()->GetName().c_str(), in_fname.c_str(),
temperature_to_string[in_file->temperature].c_str(),
out_fname.c_str(),
temperature_to_string[c->GetOutputTemperature()].c_str(),
temperature_to_string[c->output_temperature()].c_str(),
io_s.ToString().c_str());
break;
}
@@ -4056,15 +3950,15 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
io_s = copy_file_io_status;
if (!io_s.ok()) {
ROCKS_LOG_BUFFER(
log_buffer,
"[%s] Failed to copy from: %s\n"
" temperature=%s, to=%s, temperature=%s, io_status=%s",
c->column_family_data()->GetName().c_str(), in_fname.c_str(),
temperature_to_string[in_file->temperature].c_str(),
out_fname.c_str(),
temperature_to_string[c->GetOutputTemperature()].c_str(),
io_s.ToString().c_str());
ROCKS_LOG_BUFFER(log_buffer,
"[%s] Failed to copy from: %s\n"
" temperature=%s, to=%s, temperature=%s, io_status=%s",
c->column_family_data()->GetName().c_str(),
in_fname.c_str(),
temperature_to_string[in_file->temperature].c_str(),
out_fname.c_str(),
temperature_to_string[c->output_temperature()].c_str(),
io_s.ToString().c_str());
break;
}
ROCKS_LOG_BUFFER(log_buffer,
@@ -4074,7 +3968,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
in_fname.c_str(),
temperature_to_string[in_file->temperature].c_str(),
out_fname.c_str(),
temperature_to_string[c->GetOutputTemperature()].c_str(),
temperature_to_string[c->output_temperature()].c_str(),
io_s.ToString().c_str());
FileMetaData out_file_metadata{
@@ -4086,7 +3980,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
in_file->fd.smallest_seqno,
in_file->fd.largest_seqno,
false /* marked_for_compact */,
c->GetOutputTemperature() /* temperature */,
c->output_temperature() /* temperature */,
in_file->oldest_blob_file_number,
in_file->oldest_ancester_time,
out_file_creation_time,
@@ -4155,7 +4049,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
" temperature=%s, to temperature=%s, status=%s, io_status=%s",
c->column_family_data()->GetName().c_str(), in_fname.c_str(),
temperature_to_string[in_file->temperature].c_str(),
temperature_to_string[c->GetOutputTemperature()].c_str(),
temperature_to_string[c->output_temperature()].c_str(),
status.ToString().c_str(), io_s.ToString().c_str());
}
}
@@ -4181,6 +4075,35 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
NotifyOnCompactionBegin(c->column_family_data(), c.get(), status,
compaction_job_stats, job_context->job_id);
// Move files to next level
int32_t moved_files = 0;
int64_t moved_bytes = 0;
for (unsigned int l = 0; l < c->num_input_levels(); l++) {
if (c->level(l) == c->output_level()) {
continue;
}
for (size_t i = 0; i < c->num_input_files(l); i++) {
FileMetaData* f = c->input(l, i);
c->edit()->DeleteFile(c->level(l), f->fd.GetNumber());
c->edit()->AddFile(
c->output_level(), f->fd.GetNumber(), f->fd.GetPathId(),
f->fd.GetFileSize(), f->smallest, f->largest, f->fd.smallest_seqno,
f->fd.largest_seqno, f->marked_for_compaction, f->temperature,
f->oldest_blob_file_number, f->oldest_ancester_time,
f->file_creation_time, f->epoch_number, f->file_checksum,
f->file_checksum_func_name, f->unique_id,
f->compensated_range_deletion_size, f->tail_size,
f->user_defined_timestamps_persisted);
ROCKS_LOG_BUFFER(
log_buffer,
"[%s] Moving #%" PRIu64 " to level-%d %" PRIu64 " bytes\n",
c->column_family_data()->GetName().c_str(), f->fd.GetNumber(),
c->output_level(), f->fd.GetFileSize());
++moved_files;
moved_bytes += f->fd.GetFileSize();
}
}
if (c->compaction_reason() == CompactionReason::kLevelMaxLevelSize &&
c->immutable_options().compaction_pri == kRoundRobin) {
int start_level = c->start_level();
@@ -4191,12 +4114,14 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
vstorage->GetNextCompactCursor(start_level, c->num_input_files(0)));
}
}
// Perform the trivial move
size_t moved_files = 0;
size_t moved_bytes = 0;
status = PerformTrivialMove(*c.get(), log_buffer, compaction_released,
moved_files, moved_bytes);
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;
});
io_s = versions_->io_status();
InstallSuperVersionAndScheduleWork(
c->column_family_data(), job_context->superversion_contexts.data());
@@ -4211,7 +4136,8 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
<< "total_files_size" << moved_bytes;
}
ROCKS_LOG_BUFFER(
log_buffer, "[%s] Moved #%d files to level-%zu %zu bytes %s: %s\n",
log_buffer,
"[%s] Moved #%d files to level-%d %" PRIu64 " bytes %s: %s\n",
c->column_family_data()->GetName().c_str(), moved_files,
c->output_level(), moved_bytes, status.ToString().c_str(),
c->column_family_data()->current()->storage_info()->LevelSummary(&tmp));
@@ -4502,7 +4428,7 @@ Compaction* DBImpl::CreateIntendedCompactionForwardedToBottomPriorityPool(
c->output_level(), c->target_output_file_size(),
c->max_compaction_bytes(), c->output_path_id(),
c->output_compression(), c->output_compression_opts(),
c->GetOutputTemperature(), c->max_subcompactions(),
c->output_temperature(), c->max_subcompactions(),
c->grandparents(), std::nullopt /* earliest_snapshot */,
nullptr /* snapshot_checker */, c->compaction_reason());
-8
View File
@@ -267,9 +267,6 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
if (!job_context->HaveSomethingToDelete()) {
mutex_.AssertHeld();
--pending_purge_obsolete_files_;
if (pending_purge_obsolete_files_ == 0) {
bg_cv_.SignalAll();
}
}
});
@@ -617,11 +614,6 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
case kOptionsFile:
keep = (number >= optsfile_num2);
break;
case kCompactionProgressFile:
// Keep compaction progress files - they are managed
// separately by DBImplSecondary for now
keep = true;
break;
case kCurrentFile:
case kDBLockFile:
case kIdentityFile:
+3 -3
View File
@@ -293,9 +293,9 @@ Status DB::OpenAsFollower(
DBImplFollower* impl =
new DBImplFollower(tmp_opts, std::move(new_env), dbname, src_path);
impl->versions_.reset(new ReactiveVersionSet(
dbname, &impl->immutable_db_options_, impl->mutable_db_options_,
impl->file_options_, impl->table_cache_.get(),
impl->write_buffer_manager_, &impl->write_controller_, impl->io_tracer_));
dbname, &impl->immutable_db_options_, impl->file_options_,
impl->table_cache_.get(), impl->write_buffer_manager_,
&impl->write_controller_, impl->io_tracer_));
impl->column_family_memtables_.reset(
new ColumnFamilyMemTablesImpl(impl->versions_->GetColumnFamilySet()));
impl->wal_in_db_path_ = impl->immutable_db_options_.IsWalDirSameAsDBPath();
+1 -1
View File
@@ -329,7 +329,7 @@ Status DBImpl::NewDB(std::vector<std::string>* new_filenames) {
}
FileTypeSet tmp_set = immutable_db_options_.checksum_handoff_file_types;
file->SetPreallocationBlockSize(
mutable_db_options_.manifest_preallocation_size);
immutable_db_options_.manifest_preallocation_size);
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
std::move(file), manifest, file_options, immutable_db_options_.clock,
io_tracer_, nullptr /* stats */,
+19 -684
View File
@@ -8,12 +8,7 @@
#include <cinttypes>
#include "db/arena_wrapped_db_iter.h"
#include "db/log_reader.h"
#include "db/log_writer.h"
#include "db/merge_context.h"
#include "db/version_edit.h"
#include "file/filename.h"
#include "file/writable_file_writer.h"
#include "logging/auto_roll_logger.h"
#include "logging/logging.h"
#include "monitoring/perf_context_imp.h"
@@ -783,9 +778,9 @@ Status DB::OpenAsSecondary(
handles->clear();
DBImplSecondary* impl = new DBImplSecondary(tmp_opts, dbname, secondary_path);
impl->versions_.reset(new ReactiveVersionSet(
dbname, &impl->immutable_db_options_, impl->mutable_db_options_,
impl->file_options_, impl->table_cache_.get(),
impl->write_buffer_manager_, &impl->write_controller_, impl->io_tracer_));
dbname, &impl->immutable_db_options_, impl->file_options_,
impl->table_cache_.get(), impl->write_buffer_manager_,
&impl->write_controller_, impl->io_tracer_));
impl->column_family_memtables_.reset(
new ColumnFamilyMemTablesImpl(impl->versions_->GetColumnFamilySet()));
impl->wal_in_db_path_ = impl->immutable_db_options_.IsWalDirSameAsDBPath();
@@ -828,516 +823,18 @@ Status DB::OpenAsSecondary(
return s;
}
Status DBImplSecondary::ScanCompactionProgressFiles(
CompactionProgressFilesScan* scan_result) {
assert(scan_result != nullptr);
scan_result->Clear();
WriteOptions write_options(Env::IOActivity::kCompaction);
IOOptions opts;
Status s = WritableFileWriter::PrepareIOOptions(write_options, opts);
if (!s.ok()) {
return s;
}
std::vector<std::string> all_filenames;
s = fs_->GetChildren(secondary_path_, opts, &all_filenames, nullptr /* dbg*/);
if (!s.ok()) {
return s;
}
for (const auto& filename : all_filenames) {
if (filename == "." || filename == "..") {
continue;
}
uint64_t number;
FileType type;
if (!ParseFileName(filename, &number, &type)) {
continue;
}
// Categorize compaction progress files
if (type == kCompactionProgressFile) {
if (number > scan_result->latest_progress_timestamp) {
// Found a newer progress file
if (scan_result->HasLatestProgressFile()) {
// Previous "latest" becomes "old"
scan_result->old_progress_filenames.push_back(
scan_result->latest_progress_filename.value());
}
scan_result->latest_progress_timestamp = number;
scan_result->latest_progress_filename = filename;
} else {
// This is an older progress file
scan_result->old_progress_filenames.push_back(filename);
}
} else if (type == kTempFile &&
filename.find(kCompactionProgressFileNamePrefix) == 0) {
// Temporary progress files
scan_result->temp_progress_filenames.push_back(filename);
} else if (type == kTableFile) {
// Collect table file numbers for CleanupPhysicalCompactionOutputFiles
scan_result->table_file_numbers.push_back(number);
}
}
return Status::OK();
}
Status DBImplSecondary::DeleteCompactionProgressFiles(
const std::vector<std::string>& filenames) {
WriteOptions write_options(Env::IOActivity::kCompaction);
IOOptions opts;
Status s = WritableFileWriter::PrepareIOOptions(write_options, opts);
if (!s.ok()) {
return s;
}
for (const auto& filename : filenames) {
std::string file_path = secondary_path_ + "/" + filename;
Status delete_status = fs_->DeleteFile(file_path, opts, nullptr /* dbg */);
if (!delete_status.ok()) {
return delete_status;
}
}
return Status::OK();
}
Status DBImplSecondary::CleanupOldAndTemporaryCompactionProgressFiles(
bool preserve_latest, const CompactionProgressFilesScan& scan_result) {
std::vector<std::string> filenames_to_delete;
// Always delete old progress files
filenames_to_delete.insert(filenames_to_delete.end(),
scan_result.old_progress_filenames.begin(),
scan_result.old_progress_filenames.end());
// Always delete temp files
filenames_to_delete.insert(filenames_to_delete.end(),
scan_result.temp_progress_filenames.begin(),
scan_result.temp_progress_filenames.end());
// Conditionally delete latest file
if (!preserve_latest && scan_result.HasLatestProgressFile()) {
filenames_to_delete.push_back(scan_result.latest_progress_filename.value());
}
return DeleteCompactionProgressFiles(filenames_to_delete);
}
// Loads compaction progress from a file and cleans up extra output
// files. After loading the progress, this function identifies and deletes any
// SST files in the output folder that are NOT tracked in the
// progress. This ensures consistency between the progress file and
// actual output files on disk.
Status DBImplSecondary::LoadCompactionProgressAndCleanupExtraOutputFiles(
const std::string& compaction_progress_file_path,
const CompactionProgressFilesScan& scan_result) {
Status s = ParseCompactionProgressFile(compaction_progress_file_path,
&compaction_progress_);
if (s.ok()) {
s = CleanupPhysicalCompactionOutputFiles(true /* preserve_tracked_files */,
scan_result);
}
return s;
}
Status DBImplSecondary::ParseCompactionProgressFile(
const std::string& compaction_progress_file_path,
CompactionProgress* compaction_progress) {
std::unique_ptr<FSSequentialFile> file;
Status s = fs_->NewSequentialFile(compaction_progress_file_path,
FileOptions(), &file, nullptr /* dbg */);
if (!s.ok()) {
return s;
}
std::unique_ptr<SequentialFileReader> file_reader(new SequentialFileReader(
std::move(file), compaction_progress_file_path,
immutable_db_options_.log_readahead_size, io_tracer_, {} /* listeners */,
immutable_db_options_.rate_limiter.get()));
Status reader_status;
struct CompactionProgressReaderReporter : public log::Reader::Reporter {
Status* status;
explicit CompactionProgressReaderReporter(Status* s) : status(s) {}
void Corruption(size_t /*bytes*/, const Status& s,
uint64_t /*log_number*/) override {
if (status->ok()) {
*status = s;
}
}
void OldLogRecord(size_t /*bytes*/) override {
// Ignore old records
}
} progress_reporter(&reader_status);
log::Reader compaction_progress_reader(
immutable_db_options_.info_log, std::move(file_reader),
&progress_reporter, true /* checksum */, 0 /* log_num */);
// LIMITATION: Only supports resuming single subcompaction
SubcompactionProgressBuilder progress_builder;
Slice slice;
std::string record;
while (compaction_progress_reader.ReadRecord(&slice, &record)) {
if (!reader_status.ok()) {
return reader_status;
}
VersionEdit edit;
s = edit.DecodeFrom(slice);
if (!s.ok()) {
break;
}
bool res = progress_builder.ProcessVersionEdit(edit);
if (!res) {
break;
}
}
if (!s.ok()) {
return s;
}
if (progress_builder.HasAccumulatedSubcompactionProgress()) {
compaction_progress->clear();
compaction_progress->push_back(
progress_builder.GetAccumulatedSubcompactionProgress());
} else {
s = Status::NotFound("No compaction progress was persisted yet");
}
return s;
}
Status DBImplSecondary::RenameCompactionProgressFile(
const std::string& temp_file_path, std::string* final_file_path) {
uint64_t current_time = env_->NowMicros();
*final_file_path = CompactionProgressFileName(secondary_path_, current_time);
WriteOptions write_options(Env::IOActivity::kCompaction);
IOOptions opts;
Status s = WritableFileWriter::PrepareIOOptions(write_options, opts);
if (!s.ok()) {
return s;
}
s = fs_->RenameFile(temp_file_path, *final_file_path, opts,
nullptr /* dbg */);
return s;
}
Status DBImplSecondary::CleanupPhysicalCompactionOutputFiles(
bool preserve_tracked_files,
const CompactionProgressFilesScan& scan_result) {
std::unordered_set<uint64_t> files_to_preserve;
if (preserve_tracked_files) {
for (const auto& subcompaction_progress : compaction_progress_) {
for (const auto& file_metadata :
subcompaction_progress.output_level_progress.GetOutputFiles()) {
files_to_preserve.insert(file_metadata.fd.GetNumber());
}
for (const auto& file_metadata :
subcompaction_progress.proximal_output_level_progress
.GetOutputFiles()) {
files_to_preserve.insert(file_metadata.fd.GetNumber());
}
}
}
WriteOptions write_options(Env::IOActivity::kCompaction);
IOOptions opts;
Status s = WritableFileWriter::PrepareIOOptions(write_options, opts);
if (!s.ok()) {
return s;
}
for (uint64_t file_number : scan_result.table_file_numbers) {
bool should_delete =
!preserve_tracked_files ||
(files_to_preserve.find(file_number) == files_to_preserve.end());
if (should_delete) {
std::string file_path = MakeTableFileName(secondary_path_, file_number);
Status delete_status =
fs_->DeleteFile(file_path, opts, nullptr /* dbg */);
if (!delete_status.ok()) {
return delete_status;
}
}
}
return Status::OK();
}
Status DBImplSecondary::InitializeCompactionWorkspace(
bool allow_resumption, std::unique_ptr<FSDirectory>* output_dir,
std::unique_ptr<log::Writer>* compaction_progress_writer) {
// Create output directory if it doest exist yet
Status s = CreateAndNewDirectory(fs_.get(), secondary_path_, output_dir);
if (!s.ok() || !allow_resumption) {
return s;
}
s = PrepareCompactionProgressState();
if (!s.ok()) {
return s;
}
s = FinalizeCompactionProgressWriter(compaction_progress_writer);
if (!s.ok()) {
return s;
}
return Status::OK();
}
// PrepareCompactionProgressState() manages compaction progress files and output
// files to ensure a clean, consistent state for resuming or starting fresh
// compaction.
//
// PRECONDITION:
// - This function is ONLY called when allow_resumption = true
// - The caller wants resumption support for this compaction attempt
//
// FILE SYSTEM STATE (before entering this function):
// - 0 or more compaction progress files may exist in `secondary_path_`:
// * Latest progress file (from the most recent compaction attempt)
// * Older progress files (left by crashing during a previous
// InitializeCompactionWorkspace() call)
// * Temporary progress files (left by crashing during a previous
// InitializeCompactionWorkspace() call)
// - 0 or more compaction output files may exist in `secondary_path_`
//
// POSTCONDITIONS (after this function):
// - IF the latest progress file exists AND it parses successfully AND
// actually contains valid compaction progress:
// * Exactly one latest progress file remains
// * All older and temporary compaction progress files are deleted
// * All corresponding compaction output files are preserved
// * All extra compaction output files are deleted (files left by
// compaction
// crashing before persisting the progress)
// * Result: Ready to resume compaction from the saved progress
// - OTHERWISE (no latest progress file OR it fails to parse OR it's
// invalid):
// * ALL compaction progress files are deleted (latest + older +
// temporary)
// * ALL compaction output files are deleted
// * Result: Ready to start fresh compaction (despite allow_resumption =
// true, we cannot resume because there's no valid progress to resume from)
//
// ERROR HANDLING:
// - ON ERROR (if any of the postconditions cannot be achieved):
// * Function returns error status
// * File system may be left in a partially modified state
// * Caller should manually clean up secondary_path_ before retrying
// * Subsequent OpenAndCompact() calls to this clean secondary_path_ will
// effectively start fresh compaction
Status DBImplSecondary::PrepareCompactionProgressState() {
Status s;
// STEP 1: Scan directory ONCE (includes progress files + table files)
CompactionProgressFilesScan scan_result;
s = ScanCompactionProgressFiles(&scan_result);
if (!s.ok()) {
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
"Encountered error when scanning for compaction "
"progress files: %s",
s.ToString().c_str());
return s;
}
std::optional<std::string> latest_progress_file =
scan_result.latest_progress_filename;
// STEP 2: Determine if we should resume
bool should_resume = false;
if (latest_progress_file.has_value()) {
should_resume = true;
} else {
ROCKS_LOG_WARN(immutable_db_options_.info_log,
"Did not find any latest compaction progress file. "
"Will perform clean up to start fresh compaction");
}
// STEP 3: Cleanup using pre-scanned results
if (should_resume) {
// Keep latest, delete old/temp
s = CleanupOldAndTemporaryCompactionProgressFiles(
true /* preserve_latest */, scan_result);
} else {
// Delete everything including latest
s = CleanupOldAndTemporaryCompactionProgressFiles(
false /* preserve_latest */, scan_result);
latest_progress_file.reset();
}
if (!s.ok()) {
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
"Failed to clean up compaction progress file(s): %s. "
"Will fail the compaction",
s.ToString().c_str());
return s;
}
// STEP 4: Load progress if resuming
if (latest_progress_file.has_value()) {
uint64_t timestamp = scan_result.latest_progress_timestamp;
std::string compaction_progress_file_path =
CompactionProgressFileName(secondary_path_, timestamp);
s = LoadCompactionProgressAndCleanupExtraOutputFiles(
compaction_progress_file_path, scan_result);
if (!s.ok()) {
ROCKS_LOG_WARN(immutable_db_options_.info_log,
"Failed to load the latest compaction "
"progress from %s: %s. Will perform clean up "
"to start fresh compaction",
latest_progress_file.value().c_str(),
s.ToString().c_str());
return HandleInvalidOrNoCompactionProgress(compaction_progress_file_path,
scan_result);
}
ROCKS_LOG_DEBUG(
immutable_db_options_.info_log,
"Loaded compaction progress with %zu subcompaction(s) from %s",
compaction_progress_.size(), compaction_progress_file_path.c_str());
return s;
} else {
return HandleInvalidOrNoCompactionProgress(
std::nullopt /* compaction_progress_file_path */, scan_result);
}
}
uint64_t DBImplSecondary::CalculateResumedCompactionBytes(
const CompactionProgress& compaction_progress) const {
uint64_t total_resumed_bytes = 0;
for (const auto& subcompaction_progress : compaction_progress) {
for (const auto& file_meta :
subcompaction_progress.output_level_progress.GetOutputFiles()) {
total_resumed_bytes += file_meta.fd.file_size;
}
for (const auto& file_meta :
subcompaction_progress.proximal_output_level_progress
.GetOutputFiles()) {
total_resumed_bytes += file_meta.fd.file_size;
}
}
return total_resumed_bytes;
}
Status DBImplSecondary::HandleInvalidOrNoCompactionProgress(
const std::optional<std::string>& compaction_progress_file_path,
const CompactionProgressFilesScan& scan_result) {
compaction_progress_.clear();
Status s;
if (compaction_progress_file_path.has_value()) {
WriteOptions write_options(Env::IOActivity::kCompaction);
IOOptions opts;
s = WritableFileWriter::PrepareIOOptions(write_options, opts);
if (s.ok()) {
s = fs_->DeleteFile(compaction_progress_file_path.value(), opts,
nullptr /* dbg */);
}
if (!s.ok()) {
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
"Failed to remove invalid progress file: %s",
s.ToString().c_str());
return s;
}
}
s = CleanupPhysicalCompactionOutputFiles(false /* preserve_tracked_files */,
scan_result);
if (!s.ok()) {
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
"Failed to cleanup existing compaction output files: %s",
s.ToString().c_str());
return s;
}
return Status::OK();
}
Status DBImplSecondary::CompactWithoutInstallation(
const OpenAndCompactOptions& options, ColumnFamilyHandle* cfh,
const CompactionServiceInput& input, CompactionServiceResult* result) {
if (options.canceled && options.canceled->load(std::memory_order_acquire)) {
return Status::Incomplete(Status::SubCode::kManualCompactionPaused);
}
std::unique_ptr<FSDirectory> output_dir;
std::unique_ptr<log::Writer> compaction_progress_writer;
InstrumentedMutexLock l(&mutex_);
auto cfd = static_cast_with_check<ColumnFamilyHandleImpl>(cfh)->cfd();
if (!cfd) {
return Status::InvalidArgument("Cannot find column family" +
cfh->GetName());
}
Status s;
const auto& mutable_cf_options = cfd->GetLatestMutableCFOptions();
// TODO(hx235): Resuming compaction is currently incompatible with
// output hash verification (enabled via paranoid_file_checks=true or
// verify_output_flags containing kVerifyIteration) because resumed compaction
// will lose the hash computed before interruption.
// Potential solutions:
// 1. Persist the hash state: Before interruption, save the current hash value
// of each output file to disk, allowing validation to continue correctly
// after resumption.
// 2. Immediate verification: Move output verification to happen
// immediately after each output file is created and closed, eliminating
// the need to maintain hash state across resumption boundaries.
bool output_hash_verification_enabled =
mutable_cf_options.paranoid_file_checks ||
!!(mutable_cf_options.verify_output_flags &
VerifyOutputFlags::kVerifyIteration);
bool allow_resumption =
options.allow_resumption && !output_hash_verification_enabled;
if (options.allow_resumption && output_hash_verification_enabled) {
ROCKS_LOG_WARN(immutable_db_options_.info_log,
"Resume compaction configured but disabled due to "
"incompatibility with output hash verification "
"(paranoid_file_checks=true or verify_output_flags "
"containing kVerifyIteration)");
}
mutex_.Unlock();
s = InitializeCompactionWorkspace(allow_resumption, &output_dir,
&compaction_progress_writer);
mutex_.Lock();
if (!s.ok()) {
return s;
}
std::unordered_set<uint64_t> input_set;
for (const auto& file_name : input.input_files) {
@@ -1354,12 +851,12 @@ Status DBImplSecondary::CompactWithoutInstallation(
CompactionOptions comp_options;
comp_options.compression = kDisableCompressionOption;
comp_options.output_file_size_limit = MaxFileSizeForLevel(
mutable_cf_options, input.output_level, cfd->ioptions().compaction_style,
vstorage->base_level(),
cfd->GetLatestMutableCFOptions(), input.output_level,
cfd->ioptions().compaction_style, vstorage->base_level(),
cfd->ioptions().level_compaction_dynamic_level_bytes);
std::vector<CompactionInputFiles> input_files;
s = cfd->compaction_picker()->GetCompactionInputsFromFileNumbers(
Status s = cfd->compaction_picker()->GetCompactionInputsFromFileNumbers(
&input_files, &input_set, vstorage, comp_options);
if (!s.ok()) {
ROCKS_LOG_ERROR(
@@ -1382,22 +879,23 @@ Status DBImplSecondary::CompactWithoutInstallation(
// input instead of recreating it in the remote worker
std::unique_ptr<Compaction> c;
assert(cfd->compaction_picker());
std::optional<SequenceNumber> earliest_snapshot = std::nullopt;
// Standalone Range Deletion Optimization is only supported in Universal
// Compactions - https://github.com/facebook/rocksdb/pull/13078
if (cfd->GetLatestCFOptions().compaction_style ==
CompactionStyle::kCompactionStyleUniversal) {
earliest_snapshot = !job_context.snapshot_seqs.empty()
? job_context.snapshot_seqs.front()
: kMaxSequenceNumber;
}
c.reset(cfd->compaction_picker()->PickCompactionForCompactFiles(
comp_options, input_files, input.output_level, vstorage,
mutable_cf_options, mutable_db_options_, 0, earliest_snapshot,
cfd->GetLatestMutableCFOptions(), mutable_db_options_, 0,
/*earliest_snapshot=*/job_context.snapshot_seqs.empty()
? kMaxSequenceNumber
: job_context.snapshot_seqs.front(),
job_context.snapshot_checker));
assert(c != nullptr);
c->FinalizeInputInfo(version);
// Create output directory if it's not existed yet
std::unique_ptr<FSDirectory> output_dir;
s = CreateAndNewDirectory(fs_.get(), secondary_path_, &output_dir);
if (!s.ok()) {
return s;
}
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL,
immutable_db_options_.info_log.get());
@@ -1415,15 +913,13 @@ Status DBImplSecondary::CompactWithoutInstallation(
options.canceled ? *options.canceled : kManualCompactionCanceledFalse_,
input.db_id, db_session_id_, secondary_path_, input, result);
compaction_job.Prepare(compaction_progress_,
compaction_progress_writer.get());
compaction_job.Prepare();
mutex_.Unlock();
s = compaction_job.Run();
mutex_.Lock();
// These cleanup functions handle metadata and state cleanup only and
// not the physical files
// clean up
compaction_job.io_status().PermitUncheckedError();
compaction_job.CleanupCompaction();
c->ReleaseCompactionFiles(s);
@@ -1431,18 +927,6 @@ Status DBImplSecondary::CompactWithoutInstallation(
TEST_SYNC_POINT_CALLBACK("DBImplSecondary::CompactWithoutInstallation::End",
&s);
if (!compaction_progress_.empty() && s.ok()) {
uint64_t total_resumed_bytes =
CalculateResumedCompactionBytes(compaction_progress_);
if (total_resumed_bytes > 0 &&
immutable_db_options_.statistics != nullptr) {
RecordTick(immutable_db_options_.statistics.get(),
REMOTE_COMPACT_RESUMED_BYTES, total_resumed_bytes);
}
}
result->status = s;
return s;
}
@@ -1598,153 +1082,4 @@ Status DB::OpenAndCompact(
output, override_options);
}
Status DBImplSecondary::CreateCompactionProgressWriter(
const std::string& file_path,
std::unique_ptr<log::Writer>* compaction_progress_writer) {
std::unique_ptr<FSWritableFile> file;
Status s =
fs_->NewWritableFile(file_path, FileOptions(), &file, nullptr /* dbg */);
if (!s.ok()) {
return s;
}
std::unique_ptr<WritableFileWriter> file_writer(
new WritableFileWriter(std::move(file), file_path, FileOptions()));
compaction_progress_writer->reset(
new log::Writer(std::move(file_writer), 0 /* log_number */,
false /* recycle_log_files */));
return Status::OK();
}
Status DBImplSecondary::PersistInitialCompactionProgress(
log::Writer* compaction_progress_writer,
const CompactionProgress& compaction_progress) {
assert(compaction_progress_writer);
// LIMITATION: Only supports resuming single subcompaction
assert(compaction_progress.size() == 1);
const SubcompactionProgress& subcompaction_progress = compaction_progress[0];
VersionEdit edit;
edit.SetSubcompactionProgress(subcompaction_progress);
std::string record;
if (!edit.EncodeTo(&record)) {
return Status::IOError("Failed to encode the initial compaction progress");
}
WriteOptions write_options(Env::IOActivity::kCompaction);
Status s = compaction_progress_writer->AddRecord(write_options, record);
if (!s.ok()) {
return s;
}
IOOptions opts;
s = WritableFileWriter::PrepareIOOptions(write_options, opts);
if (!s.ok()) {
return s;
}
s = compaction_progress_writer->file()->Sync(opts,
immutable_db_options_.use_fsync);
return s;
}
Status DBImplSecondary::HandleCompactionProgressWriterCreationFailure(
const std::string& temp_file_path, const std::string& final_file_path,
std::unique_ptr<log::Writer>* compaction_progress_writer) {
compaction_progress_writer->reset();
const std::vector<std::string> paths_to_delete = {final_file_path,
temp_file_path};
Status s;
for (const auto& file_path : paths_to_delete) {
WriteOptions write_options(Env::IOActivity::kCompaction);
IOOptions opts;
s = WritableFileWriter::PrepareIOOptions(write_options, opts);
if (s.ok()) {
s = fs_->DeleteFile(file_path, opts, nullptr /* dbg */);
}
if (!s.ok()) {
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
"Failed to cleanup the compaction progress file "
"during writer creation failure: %s",
s.ToString().c_str());
return s;
}
}
return s;
}
Status DBImplSecondary::FinalizeCompactionProgressWriter(
std::unique_ptr<log::Writer>* compaction_progress_writer) {
uint64_t timestamp = env_->NowMicros();
const std::string temp_file_path =
TempCompactionProgressFileName(secondary_path_, timestamp);
Status s = CreateCompactionProgressWriter(temp_file_path,
compaction_progress_writer);
if (!s.ok()) {
ROCKS_LOG_WARN(immutable_db_options_.info_log,
"Failed to create compaction progress writer at "
"temp path %s: %s. Will perform clean up "
"to start compaction without progress persistence",
temp_file_path.c_str(), s.ToString().c_str());
return HandleCompactionProgressWriterCreationFailure(
temp_file_path, "" /* final_file_path */, compaction_progress_writer);
}
if (!compaction_progress_.empty()) {
s = PersistInitialCompactionProgress(compaction_progress_writer->get(),
compaction_progress_);
if (!s.ok()) {
ROCKS_LOG_WARN(immutable_db_options_.info_log,
"Failed to persist the initial copmaction "
"progress: %s. Will perform clean up "
"to start compaction without progress persistence",
s.ToString().c_str());
return HandleCompactionProgressWriterCreationFailure(
temp_file_path, "" /* final_file_path */, compaction_progress_writer);
}
}
compaction_progress_writer->reset();
std::string final_file_path;
s = RenameCompactionProgressFile(temp_file_path, &final_file_path);
if (!s.ok()) {
ROCKS_LOG_WARN(immutable_db_options_.info_log,
"Failed to rename temporary compaction progress "
"file from %s to %s: %s. Will perform clean up "
"to start compaction without progress persistence",
temp_file_path.c_str(), final_file_path.c_str(),
s.ToString().c_str());
return HandleCompactionProgressWriterCreationFailure(
temp_file_path, final_file_path, compaction_progress_writer);
}
s = CreateCompactionProgressWriter(final_file_path,
compaction_progress_writer);
if (!s.ok()) {
ROCKS_LOG_WARN(immutable_db_options_.info_log,
"Failed to create the final compaction progress "
"writer: %s. Will attempt clean to start the compaction "
"without progress persistence",
s.ToString().c_str());
return HandleCompactionProgressWriterCreationFailure(
"" /* temp_file_path */, final_file_path, compaction_progress_writer);
}
ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
"Finalized compaction progress writer onto %s",
final_file_path.c_str());
return Status::OK();
}
} // namespace ROCKSDB_NAMESPACE
-83
View File
@@ -303,87 +303,6 @@ class DBImplSecondary : public DBImpl {
const CompactionServiceInput& input,
CompactionServiceResult* result);
private:
// Holds results of compaction progress files and output files from a single
// directory scan
struct CompactionProgressFilesScan {
// The latest (newest) progress file filename
std::optional<std::string> latest_progress_filename;
uint64_t latest_progress_timestamp = 0;
// Older progress file filenames (to be deleted)
autovector<std::string> old_progress_filenames;
// Temporary progress file filenames (to be deleted)
autovector<std::string> temp_progress_filenames;
// All output file numbers - for cleanup optimization
std::vector<uint64_t> table_file_numbers;
bool HasLatestProgressFile() const {
return latest_progress_filename.has_value();
}
void Clear() {
latest_progress_filename.reset();
latest_progress_timestamp = 0;
old_progress_filenames.clear();
temp_progress_filenames.clear();
table_file_numbers.clear();
}
};
Status InitializeCompactionWorkspace(
bool allow_resumption, std::unique_ptr<FSDirectory>* output_dir,
std::unique_ptr<log::Writer>* compaction_progress_writer);
Status PrepareCompactionProgressState();
Status ScanCompactionProgressFiles(CompactionProgressFilesScan* scan_result);
Status DeleteCompactionProgressFiles(
const std::vector<std::string>& filenames);
Status CleanupOldAndTemporaryCompactionProgressFiles(
bool preserve_latest, const CompactionProgressFilesScan& scan_result);
Status LoadCompactionProgressAndCleanupExtraOutputFiles(
const std::string& compaction_progress_file_path,
const CompactionProgressFilesScan& scan_result);
Status ParseCompactionProgressFile(
const std::string& compaction_progress_file_path,
CompactionProgress* compaction_progress);
Status HandleInvalidOrNoCompactionProgress(
const std::optional<std::string>& compaction_progress_file_path,
const CompactionProgressFilesScan& scan_result);
Status CleanupPhysicalCompactionOutputFiles(
bool preserve_tracked_files,
const CompactionProgressFilesScan& scan_result);
Status FinalizeCompactionProgressWriter(
std::unique_ptr<log::Writer>* compaction_progress_writer);
Status CreateCompactionProgressWriter(
const std::string& file_path,
std::unique_ptr<log::Writer>* compaction_progress_writer);
Status PersistInitialCompactionProgress(
log::Writer* compaction_progress_writer,
const CompactionProgress& compaction_progress);
Status RenameCompactionProgressFile(const std::string& temp_file_path,
std::string* final_file_path);
Status HandleCompactionProgressWriterCreationFailure(
const std::string& temp_file_path, const std::string& final_file_path,
std::unique_ptr<log::Writer>* compaction_progress_writer);
uint64_t CalculateResumedCompactionBytes(
const CompactionProgress& compaction_progress) const;
// Cache log readers for each log number, used for continue WAL replay
// after recovery
std::map<uint64_t, std::unique_ptr<LogReaderContainer>> log_readers_;
@@ -392,8 +311,6 @@ class DBImplSecondary : public DBImpl {
std::unordered_map<ColumnFamilyData*, uint64_t> cfd_to_current_log_;
const std::string secondary_path_;
CompactionProgress compaction_progress_;
};
} // namespace ROCKSDB_NAMESPACE
-429
View File
@@ -4154,9 +4154,6 @@ INSTANTIATE_TEST_CASE_P(DBMultiScanIteratorTest, DBMultiScanIteratorTest,
::testing::Bool());
TEST_P(DBMultiScanIteratorTest, BasicTest) {
auto options = CurrentOptions();
DestroyAndReopen(options);
// Create a file
for (int i = 0; i < 100; ++i) {
std::stringstream ss;
@@ -4199,8 +4196,6 @@ TEST_P(DBMultiScanIteratorTest, BasicTest) {
}
TEST_P(DBMultiScanIteratorTest, MixedBoundsTest) {
auto options = CurrentOptions();
DestroyAndReopen(options);
// Create a file
for (int i = 0; i < 100; ++i) {
std::stringstream ss;
@@ -4389,74 +4384,6 @@ TEST_P(DBMultiScanIteratorTest, FailureTest) {
iter.reset();
}
TEST_P(DBMultiScanIteratorTest, OutOfL0FileRange) {
// Test that prepare does not fail scan when a scan range
// is outside of a L0 file's key range.
auto options = CurrentOptions();
options.compression = kNoCompression;
DestroyAndReopen(options);
Random rnd(301);
// Create a Lmax file
// key01 ~ key99
for (int i = 0; i < 100; ++i) {
std::stringstream ss;
ss << std::setw(2) << std::setfill('0') << i;
ASSERT_OK(Put("k" + ss.str(), rnd.RandomString(1024)));
}
ASSERT_OK(Flush());
CompactRangeOptions cro;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
// Create a L0 file
// key00 ~ key09
for (int i = 0; i < 10; ++i) {
std::stringstream ss;
ss << std::setw(2) << std::setfill('0') << i;
ASSERT_OK(Put("k" + ss.str(), rnd.RandomString(1024)));
}
ASSERT_OK(Flush());
ASSERT_EQ(NumTableFilesAtLevel(0), 1);
// The second range is outside of L0 file's key range
std::vector<std::string> key_ranges({"k04", "k06", "k12", "k14"});
ReadOptions ro;
Slice ub;
ro.iterate_upper_bound = &ub;
ro.fill_cache = GetParam();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.insert(key_ranges[0], key_ranges[1]);
scan_options.insert(key_ranges[2], key_ranges[3]);
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
std::unique_ptr<Iterator> iter(dbfull()->NewIterator(ro, cfh));
ASSERT_NE(iter, nullptr);
iter->Prepare(scan_options);
int count = 0;
ub = key_ranges[1];
iter->Seek(key_ranges[0]);
while (iter->status().ok() && iter->Valid()) {
ASSERT_GE(iter->key().compare(key_ranges[0]), 0);
ASSERT_LT(iter->key().compare(key_ranges[1]), 0);
count++;
iter->Next();
}
ASSERT_OK(iter->status()) << iter->status().ToString();
ASSERT_EQ(count, 2);
ub = key_ranges[3];
count = 0;
iter->Seek(key_ranges[2]);
while (iter->status().ok() && iter->Valid()) {
ASSERT_GE(iter->key().compare(key_ranges[2]), 0);
ASSERT_LT(iter->key().compare(key_ranges[3]), 0);
count++;
iter->Next();
}
ASSERT_OK(iter->status()) << iter->status().ToString();
ASSERT_EQ(count, 2);
}
TEST_P(DBMultiScanIteratorTest, RangeBetweenFiles) {
auto options = CurrentOptions();
options.target_file_size_base = 100 << 10; // 20KB
@@ -4607,11 +4534,6 @@ TEST_P(DBMultiScanIteratorTest, FragmentedRangeTombstones) {
ASSERT_OK(s);
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ColumnFamilyMetaData cf_meta;
dbfull()->GetColumnFamilyMetaData(cfh, &cf_meta);
// Only the L0 with range deletion is compacted.
ASSERT_EQ(1, cf_meta.levels[0].files.size());
ASSERT_EQ(0, cf_meta.levels[0].files[0].num_deletions);
// The first scan range overlaps the DB key range, while the second extends
// beyond but overlaps the delete range
@@ -4676,357 +4598,6 @@ TEST_P(DBMultiScanIteratorTest, FragmentedRangeTombstones) {
iter.reset();
}
TEST_P(DBMultiScanIteratorTest, ReseekAcrossBlocksSameUserKey) {
// This test exposes a bug where multiscan reseeks backwards when
// max_sequential_skip_in_iterations is triggered with the same user key
// spanning multiple data blocks.
auto options = CurrentOptions();
options.max_sequential_skip_in_iterations = 3;
options.compression = kNoCompression;
// Force each internal key into its own block
BlockBasedTableOptions table_options;
table_options.flush_block_policy_factory =
std::make_shared<FlushBlockEveryKeyPolicyFactory>();
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
DestroyAndReopen(options);
// Taking a snapshot after each Put to preserve all versions during flush.
std::vector<const Snapshot*> snapshots;
for (int i = 0; i < 7; ++i) {
ASSERT_OK(Put("key_a", "value_" + std::to_string(i)));
snapshots.push_back(db_->GetSnapshot());
}
ASSERT_OK(Put("key_b", "value_b"));
ASSERT_OK(Flush());
ASSERT_EQ(1, NumTableFilesAtLevel(0));
// Setup multiscan range covering both keys
std::vector<std::string> key_ranges({"key_a", "key_c"});
ReadOptions ro;
Slice ub = key_ranges[1];
ro.iterate_upper_bound = &ub;
ro.fill_cache = GetParam();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.insert(key_ranges[0], key_ranges[1]);
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
std::unique_ptr<Iterator> iter(dbfull()->NewIterator(ro, cfh));
ASSERT_NE(iter, nullptr);
iter->Prepare(scan_options);
std::vector<std::string> seen_keys;
std::vector<std::string> seen_values;
iter->Seek(key_ranges[0]);
while (iter->status().ok() && iter->Valid()) {
seen_keys.push_back(iter->key().ToString());
seen_values.push_back(iter->value().ToString());
iter->Next();
}
ASSERT_OK(iter->status()) << iter->status().ToString();
ASSERT_EQ(seen_keys.size(), 2) << "Should see key_a and key_b";
ASSERT_EQ(seen_keys[0], "key_a");
ASSERT_EQ(seen_keys[1], "key_b");
ASSERT_EQ(seen_values[0], "value_6");
ASSERT_EQ(seen_values[1], "value_b");
for (auto* snapshot : snapshots) {
db_->ReleaseSnapshot(snapshot);
}
}
TEST_P(DBMultiScanIteratorTest, AsyncPrefetchAcrossMultipleFiles) {
// Test async prefetch with multiple ranges within a single file
auto options = CurrentOptions();
options.target_file_size_base = 1 << 15; // 32KiB
options.compaction_style = kCompactionStyleUniversal;
options.num_levels = 50;
options.compression = kNoCompression;
options.statistics = CreateDBStatistics();
DestroyAndReopen(options);
Random rnd(303);
// Create a single large file with many keys
// ~1MiB of data
// Should be lots of files now
for (int i = 0; i < 1000; ++i) {
std::stringstream ss;
ss << "k" << std::setw(5) << std::setfill('0') << i;
// 1KiB values
ASSERT_OK(Put(ss.str(), rnd.RandomString(1 << 10)));
}
ASSERT_OK(Flush());
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
ASSERT_GT(NumTableFilesAtLevel(49), 3);
// Set up multiple non-overlapping ranges in the same file
// Every 32 values should be a file or so
std::vector<std::string> key_ranges(
{"k00000", "k00100", "k00500", "k00600", "k00800", "k00900"});
ReadOptions ro;
ro.fill_cache = GetParam();
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.use_async_io = true;
scan_options.insert(key_ranges[0], key_ranges[1]);
scan_options.insert(key_ranges[2], key_ranges[3]);
scan_options.insert(key_ranges[4], key_ranges[5]);
auto read_count_before =
options.statistics->getTickerCount(NON_LAST_LEVEL_READ_COUNT);
std::unique_ptr<MultiScan> iter =
dbfull()->NewMultiScan(ro, cfh, scan_options);
ASSERT_NE(iter, nullptr);
auto read_count_after =
options.statistics->getTickerCount(NON_LAST_LEVEL_READ_COUNT);
ASSERT_EQ(read_count_after, read_count_before);
// Verify all three ranges can be scanned successfully
try {
for (auto range : *iter) {
for (auto it : range) {
it.first.ToString();
}
}
} catch (MultiScanException& ex) {
// Make sure exception contains the status
ASSERT_NOK(ex.status());
std::cerr << "Iterator returned status " << ex.what();
abort();
} catch (std::logic_error& ex) {
std::cerr << "Iterator returned logic error " << ex.what();
abort();
}
iter.reset();
}
TEST_P(DBMultiScanIteratorTest, AsyncPrefetchMultipleLevels) {
// Test async prefetch with files in L0 and non-L0 levels
// Similar setup to AsyncPrefetchAcrossMultipleFiles but with L0 files
auto options = CurrentOptions();
options.target_file_size_base = 1 << 15; // 32KiB
options.compaction_style = kCompactionStyleUniversal;
options.num_levels = 50;
options.compression = kNoCompression;
options.statistics = CreateDBStatistics();
DestroyAndReopen(options);
Random rnd(304);
// Create base files and compact to bottom level - ~500KiB of data
for (int i = 0; i < 500; ++i) {
std::stringstream ss;
ss << "k" << std::setw(5) << std::setfill('0') << i;
ASSERT_OK(Put(ss.str(), rnd.RandomString(1 << 10)));
}
ASSERT_OK(Flush());
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
// Verify we have files at bottom level
ASSERT_GT(NumTableFilesAtLevel(49), 0);
// Create additional L0 files with overlapping key ranges
for (int i = 100; i < 150; ++i) {
std::stringstream ss;
ss << "k" << std::setw(5) << std::setfill('0') << i;
ASSERT_OK(Put(ss.str(), rnd.RandomString(1 << 10)));
}
ASSERT_OK(Flush());
// Verify we now have files in both L0 and bottom level
ASSERT_GT(NumTableFilesAtLevel(0), 0);
ASSERT_GT(NumTableFilesAtLevel(49), 0);
// Set up multiple non-overlapping ranges
std::vector<std::string> key_ranges(
{"k00000", "k00100", "k00200", "k00300", "k00400", "k00500"});
ReadOptions ro;
ro.fill_cache = GetParam();
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.use_async_io = true;
scan_options.insert(key_ranges[0], key_ranges[1]);
scan_options.insert(key_ranges[2], key_ranges[3]);
scan_options.insert(key_ranges[4], key_ranges[5]);
std::unique_ptr<MultiScan> iter =
dbfull()->NewMultiScan(ro, cfh, scan_options);
ASSERT_NE(iter, nullptr);
// Verify all three ranges can be scanned successfully
int total_keys = 0;
try {
for (auto range : *iter) {
for (auto it : range) {
it.first.ToString();
total_keys++;
}
}
} catch (MultiScanException& ex) {
ASSERT_NOK(ex.status());
std::cerr << "Iterator returned status " << ex.what();
abort();
} catch (std::logic_error& ex) {
std::cerr << "Iterator returned logic error " << ex.what();
abort();
}
// Should have keys from all three ranges
ASSERT_GT(total_keys, 0);
iter.reset();
}
TEST_P(DBMultiScanIteratorTest, AsyncPrefetchWithDeleteRange) {
// Test async prefetch with delete ranges
auto options = CurrentOptions();
options.target_file_size_base = 1 << 15; // 32KiB
options.compaction_style = kCompactionStyleUniversal;
options.num_levels = 50;
options.compression = kNoCompression;
DestroyAndReopen(options);
Random rnd(305);
// Create base data - ~500KiB
for (int i = 0; i < 500; ++i) {
std::stringstream ss;
ss << "k" << std::setw(5) << std::setfill('0') << i;
ASSERT_OK(Put(ss.str(), rnd.RandomString(1 << 10)));
}
ASSERT_OK(Flush());
// Add delete ranges
ASSERT_OK(db_->DeleteRange(WriteOptions(), dbfull()->DefaultColumnFamily(),
"k00100", "k00200"));
ASSERT_OK(Flush());
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
ASSERT_GT(NumTableFilesAtLevel(49), 0);
// Set up scan ranges that interact with delete ranges
std::vector<std::string> key_ranges({"k00000", "k00500"});
ReadOptions ro;
ro.fill_cache = GetParam();
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.use_async_io = true;
scan_options.insert(key_ranges[0], key_ranges[1]);
std::unique_ptr<MultiScan> iter =
dbfull()->NewMultiScan(ro, cfh, scan_options);
ASSERT_NE(iter, nullptr);
// Verify ranges can be scanned successfully
int total_keys = 0;
try {
for (auto range : *iter) {
for (auto it : range) {
std::string key = it.first.ToString();
// Verify deleted keys are not returned
ASSERT_TRUE((key < "k00100" || key >= "k00200"));
total_keys++;
}
}
} catch (MultiScanException& ex) {
ASSERT_NOK(ex.status());
std::cerr << "Iterator returned status " << ex.what();
abort();
} catch (std::logic_error& ex) {
std::cerr << "Iterator returned logic error " << ex.what();
abort();
}
// Should have keys excluding deleted ranges
ASSERT_EQ(total_keys, 400);
iter.reset();
}
TEST_P(DBMultiScanIteratorTest, AsyncPrefetchWithExternalFileIngestion) {
// Test async prefetch with externally ingested files
auto options = CurrentOptions();
options.target_file_size_base = 1 << 15; // 32KiB
options.compaction_style = kCompactionStyleUniversal;
options.num_levels = 50;
options.compression = kNoCompression;
DestroyAndReopen(options);
Random rnd(306);
// Create base data - ~200KiB
for (int i = 0; i < 200; ++i) {
std::stringstream ss;
ss << "k" << std::setw(5) << std::setfill('0') << i;
ASSERT_OK(Put(ss.str(), rnd.RandomString(1 << 10)));
}
ASSERT_OK(Flush());
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
// Create and ingest external SST file with new data
std::string ingest_file = dbname_ + "/test_ingest.sst";
{
std::unique_ptr<SstFileWriter> writer;
writer.reset(new SstFileWriter(EnvOptions(), options));
ASSERT_OK(writer->Open(ingest_file));
for (int i = 300; i < 500; ++i) {
std::stringstream ss;
ss << "k" << std::setw(5) << std::setfill('0') << i;
ASSERT_OK(writer->Put(ss.str(), rnd.RandomString(1 << 10)));
}
ASSERT_OK(writer->Finish());
}
IngestExternalFileOptions ifo;
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
ASSERT_OK(dbfull()->IngestExternalFile(cfh, {ingest_file}, ifo));
// Set up scan ranges that span both regular and ingested files
std::vector<std::string> key_ranges({"k00000", "k00500"});
ReadOptions ro;
ro.fill_cache = GetParam();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.use_async_io = true;
scan_options.insert(key_ranges[0], key_ranges[1]);
std::unique_ptr<MultiScan> iter =
dbfull()->NewMultiScan(ro, cfh, scan_options);
ASSERT_NE(iter, nullptr);
// Verify all ranges can be scanned successfully
int total_keys = 0;
try {
for (auto range : *iter) {
for (auto it : range) {
it.first.ToString();
total_keys++;
}
}
} catch (MultiScanException& ex) {
ASSERT_NOK(ex.status());
std::cerr << "Iterator returned status " << ex.what();
abort();
} catch (std::logic_error& ex) {
std::cerr << "Iterator returned logic error " << ex.what();
abort();
}
ASSERT_EQ(total_keys, 400);
iter.reset();
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
-35
View File
@@ -432,47 +432,12 @@ TEST_F(DBOptionsTest, SetWalBytesPerSync) {
ASSERT_GT(low_bytes_per_sync, counter);
}
TEST_F(DBOptionsTest, MutableManifestOptions) {
// These aren't end-to-end tests, but sufficient to ensure the VersionSet
// receives the updates with SetDBOptions
for (int64_t i : {0, 1, 100, 100000, 10000000}) {
ASSERT_OK(
db_->SetDBOptions({{"max_manifest_file_size", std::to_string(i)}}));
ASSERT_EQ(i,
static_cast<int64_t>(db_->GetDBOptions().max_manifest_file_size));
ASSERT_EQ(i,
static_cast<int64_t>(
dbfull()->GetVersionSet()->TEST_GetMinMaxManifestFileSize()));
if (i > 1) {
++i;
}
ASSERT_OK(
db_->SetDBOptions({{"max_manifest_space_amp_pct", std::to_string(i)}}));
ASSERT_EQ(i, static_cast<int64_t>(
db_->GetDBOptions().max_manifest_space_amp_pct));
ASSERT_EQ(i,
static_cast<int64_t>(
dbfull()->GetVersionSet()->TEST_GetMaxManifestSpaceAmpPct()));
if (i > 1) {
++i;
}
ASSERT_OK(db_->SetDBOptions(
{{"manifest_preallocation_size", std::to_string(i)}}));
ASSERT_EQ(i, static_cast<int64_t>(
db_->GetDBOptions().manifest_preallocation_size));
ASSERT_EQ(
i, static_cast<int64_t>(
dbfull()->GetVersionSet()->TEST_GetManifestPreallocationSize()));
}
}
TEST_F(DBOptionsTest, WritableFileMaxBufferSize) {
Options options;
options.create_if_missing = true;
options.writable_file_max_buffer_size = 1024 * 1024;
options.level0_file_num_compaction_trigger = 3;
options.max_manifest_file_size = 1;
options.max_manifest_space_amp_pct = 0;
options.env = env_;
int buffer_size = 1024 * 1024;
Reopen(options);
-83
View File
@@ -3825,89 +3825,6 @@ TEST_F(DBRangeDelTest, RowCache) {
// and should not turn db into read-only mdoe.
ASSERT_OK(Put(Key(5), "foo"));
}
TEST_F(DBRangeDelTest, SeekForPrevTest) {
// open db
Options options = GetDefaultOptions();
options.create_if_missing = true;
options.compaction_style = kCompactionStyleUniversal;
// add SST partitioner, split sst file with prefix length 2
options.sst_partitioner_factory = NewSstPartitionerFixedPrefixFactory(2);
Reopen(options);
// File uses SST partitioner, so it will be split into 3 files
// SST file 1: ka1, ka2
// SST file 2: kb1
// SST file 3: kc1, kc2
// Delete range covers from ka2 to kc2, which means record ka2 and kb1, kc1
// are covered by the delete range
std::vector<std::pair<std::string, std::string>> kv = {{"ka1", "value_1"},
{"ka2", "value_2"},
{"kb1", "value_3"},
{"kc1", "value_4"},
{"kc2", "value_5"}};
for (auto& p : kv) {
ASSERT_OK(Put(p.first, p.second));
}
ASSERT_OK(Flush());
// Compact to Lmax, it should have seq 0 now.
ASSERT_OK(CompactRange(CompactRangeOptions(), nullptr, nullptr));
// Open an iterator and create a snapshot, so that keys are not deleted
// completely by delete range in SST
ReadOptions read_opts;
read_opts.snapshot = db_->GetSnapshot();
std::unique_ptr<Iterator> iter(db_->NewIterator(read_opts));
iter->SeekToFirst();
// iterate all the keys and validate the value
for (int i = 0; iter->Valid(); iter->Next()) {
ASSERT_EQ(kv[i].first, iter->key().ToString());
ASSERT_EQ(kv[i].second, iter->value().ToString());
i++;
}
// use delete range to delete the record
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "ka2",
"kc2"));
// Flush
ASSERT_OK(Flush());
// Compact to Lmax
ASSERT_OK(CompactRange(CompactRangeOptions(), nullptr, nullptr));
// Close the iterator and release the snapshot.
ASSERT_OK(iter->status());
iter.reset();
db_->ReleaseSnapshot(read_opts.snapshot);
// create second iterator, seek each key and validate result
std::unique_ptr<Iterator> iter2(db_->NewIterator(ReadOptions()));
// Validate keys are deleted
iter2->SeekToFirst();
ASSERT_TRUE(iter2->Valid());
ASSERT_EQ("ka1", iter2->key().ToString());
iter2->Next();
ASSERT_TRUE(iter2->Valid());
ASSERT_EQ("kc2", iter2->key().ToString());
iter2->Next();
ASSERT_FALSE(iter2->Valid());
// Validate seek for prev result
for (auto& p : kv) {
iter2->SeekForPrev(p.first);
ASSERT_TRUE(iter2->Valid());
if (p.first == "kc2") {
ASSERT_EQ("kc2", iter2->key().ToString());
} else {
ASSERT_EQ("ka1", iter2->key().ToString());
}
}
ASSERT_OK(iter2->status());
iter2.reset();
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
-101
View File
@@ -442,107 +442,6 @@ TEST_P(DBRateLimiterOnWriteWALTest, AutoWalFlush) {
EXPECT_EQ(actual_auto_wal_flush_request,
options_.rate_limiter->GetTotalRequests(Env::IO_USER));
}
class DBRateLimiterOnManualWALFlushTest
: public DBRateLimiterOnWriteTest,
public ::testing::WithParamInterface<Env::IOPriority> {
public:
static std::string GetTestNameSuffix(
::testing::TestParamInfo<Env::IOPriority> info) {
std::ostringstream oss;
if (info.param == Env::IO_USER) {
oss << "RateLimitManualWALFlush";
} else if (info.param == Env::IO_TOTAL) {
oss << "NoRateLimitManualWALFlush";
} else if (info.param == Env::IO_HIGH) {
oss << "RateLimitManualWALFlushWithHighPriority";
} else {
oss << "RateLimitManualWALFlushWithLowPriority";
}
return oss.str();
}
explicit DBRateLimiterOnManualWALFlushTest()
: rate_limiter_priority_(GetParam()) {}
void Init() {
options_ = GetOptions();
// Enable manual WAL flush mode
options_.manual_wal_flush = true;
Reopen(options_);
}
WriteOptions GetWriteOptions() {
WriteOptions write_options;
// WAL must be enabled for manual WAL flush to work
write_options.disableWAL = false;
// In manual WAL flush mode, WAL write rate limiting should be done through
// FlushWAL(), not WriteOptions::rate_limiter_priority
write_options.rate_limiter_priority = Env::IO_TOTAL;
return write_options;
}
protected:
Env::IOPriority rate_limiter_priority_;
};
INSTANTIATE_TEST_CASE_P(DBRateLimiterOnManualWALFlushTest,
DBRateLimiterOnManualWALFlushTest,
::testing::Values(Env::IO_TOTAL, Env::IO_USER,
Env::IO_HIGH, Env::IO_LOW),
DBRateLimiterOnManualWALFlushTest::GetTestNameSuffix);
TEST_P(DBRateLimiterOnManualWALFlushTest, ManualWALFlush) {
Init();
const bool no_rate_limit = (rate_limiter_priority_ == Env::IO_TOTAL);
ASSERT_EQ(0, options_.rate_limiter->GetTotalRequests(Env::IO_TOTAL));
for (bool sync : {false, true}) {
std::int64_t prev_total_request =
options_.rate_limiter->GetTotalRequests(Env::IO_TOTAL);
Status put_status = Put("key_" + std::to_string(sync),
"value_" + std::to_string(sync), GetWriteOptions());
EXPECT_TRUE(put_status.ok());
// Since manual_wal_flush is enabled and write_options.rate_limiter_priority
// is IO_TOTAL, no rate limiting should have occurred for this user write
EXPECT_EQ(0, options_.rate_limiter->GetTotalRequests(Env::IO_TOTAL) -
prev_total_request);
// Now explicitly flush the WAL with the test's rate_limiter_priority
prev_total_request = options_.rate_limiter->GetTotalRequests(Env::IO_TOTAL);
std::int64_t prev_priority_request =
options_.rate_limiter->GetTotalRequests(rate_limiter_priority_);
FlushWALOptions flush_options;
flush_options.sync = sync;
flush_options.rate_limiter_priority = rate_limiter_priority_;
Status flush_status = db_->FlushWAL(flush_options);
EXPECT_TRUE(flush_status.ok());
std::int64_t manual_wal_flush_requests_total =
options_.rate_limiter->GetTotalRequests(Env::IO_TOTAL) -
prev_total_request;
std::int64_t manual_wal_flush_requests_for_priority =
options_.rate_limiter->GetTotalRequests(rate_limiter_priority_) -
prev_priority_request;
if (no_rate_limit) {
EXPECT_EQ(0, manual_wal_flush_requests_total);
EXPECT_EQ(0, manual_wal_flush_requests_for_priority);
} else {
EXPECT_EQ(manual_wal_flush_requests_total,
manual_wal_flush_requests_for_priority);
EXPECT_GT(manual_wal_flush_requests_for_priority, 0);
}
}
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+4 -249
View File
@@ -1492,246 +1492,6 @@ TEST_F(DBTest, MetaDataTest) {
CheckLiveFilesMeta(live_file_meta, files_by_level);
}
TEST_F(DBTest, GetColumnFamilyMetaDataWithKeyRangeAndLevel) {
Options options = CurrentOptions();
options.create_if_missing = true;
options.disable_auto_compactions = true;
int64_t temp_time = 0;
ASSERT_OK(options.env->GetCurrentTime(&temp_time));
DestroyAndReopen(options);
Random rnd(301);
int key_index = 0;
for (int i = 0; i < 100; ++i) {
// Add a single blob reference to each file
std::string blob_index;
BlobIndex::EncodeBlob(&blob_index, /* blob_file_number */ i + 1000,
/* offset */ 1234, /* size */ 5678, kNoCompression);
WriteBatch batch;
ASSERT_OK(WriteBatchInternal::PutBlobIndex(&batch, 0, Key(key_index),
blob_index));
ASSERT_OK(dbfull()->Write(WriteOptions(), &batch));
++key_index;
// Fill up the rest of the file with random values.
GenerateNewFile(&rnd, &key_index, /* nowait */ true);
ASSERT_OK(Flush());
}
std::vector<std::vector<FileMetaData>> files_by_level;
dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(), &files_by_level);
ASSERT_OK(options.env->GetCurrentTime(&temp_time));
ColumnFamilyMetaData cf_meta;
// Keys in the SST files are distributed
// (key000000, key000100) ->File 1
// (key000101, key000201) -> File 2
// (key000202, key000302) -> File 3
// (key009999, key010099) -> File 100
// With keySlice (key000050, key000150) => should only pick 2 files(instead of
// default 100 that is in the level)
auto startKey = Slice("key000050");
auto endKey = Slice("key000150");
GetColumnFamilyMetaDataOptions cf_options(startKey, endKey, 0);
db_->GetColumnFamilyMetaData(cf_options, &cf_meta);
ASSERT_EQ(cf_meta.levels.size(), 1);
const auto& level_meta_from_cf = cf_meta.levels[0];
ASSERT_EQ(level_meta_from_cf.files.size(), 2);
ASSERT_LT(level_meta_from_cf.files[1].smallestkey,
std::string(startKey.data()));
ASSERT_GT(level_meta_from_cf.files[0].largestkey, std::string(endKey.data()));
GetColumnFamilyMetaDataOptions cf_option_default;
db_->GetColumnFamilyMetaData(cf_option_default, &cf_meta);
ASSERT_EQ(cf_meta.levels.size(), 1);
ASSERT_EQ(cf_meta.levels[0].files.size(), 100);
// Test with start key valid and end key unbounded
// This should get all files from key000150 onwards (99 files)
auto startKeyUnbounded = Slice("key000150");
GetColumnFamilyMetaDataOptions cf_options_unbounded_end(startKeyUnbounded,
OptSlice(), 0);
db_->GetColumnFamilyMetaData(cf_options_unbounded_end, &cf_meta);
ASSERT_EQ(cf_meta.levels.size(), 1);
ASSERT_EQ(cf_meta.levels[0].files.size(), 99);
// Test with end key valid and start key unbounded
// This should get all files from beginning to key000250 ( 3 files)
auto endKeyUnbounded = Slice("key000250");
GetColumnFamilyMetaDataOptions cf_options_unbounded_start(OptSlice(),
endKeyUnbounded, 0);
db_->GetColumnFamilyMetaData(cf_options_unbounded_start, &cf_meta);
ASSERT_EQ(cf_meta.levels.size(), 1);
ASSERT_EQ(cf_meta.levels[0].files.size(), 3);
}
TEST_F(DBTest, GetColumnFamilyMetaDataBottommostLevel) {
Options options = CurrentOptions();
options.create_if_missing = true;
options.disable_auto_compactions = true;
options.num_levels = 7;
DestroyAndReopen(options);
Random rnd(301);
int key_index = 0;
for (int i = 0; i < 100; ++i) {
GenerateNewFile(&rnd, &key_index, /* nowait */ true);
ASSERT_OK(Flush());
}
CompactRangeOptions compact_options;
compact_options.bottommost_level_compaction =
BottommostLevelCompaction::kForce;
compact_options.change_level = true;
compact_options.target_level = 6;
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
// Nothing on Level 0 after compaction
ColumnFamilyMetaData cf_meta;
GetColumnFamilyMetaDataOptions cf_options_0(OptSlice(), OptSlice(), 0);
db_->GetColumnFamilyMetaData(cf_options_0, &cf_meta);
ASSERT_EQ(cf_meta.levels.size(), 0);
ASSERT_EQ(cf_meta.file_count, 0);
// Data should be in Level 6
GetColumnFamilyMetaDataOptions cf_options(OptSlice(), OptSlice(), 6);
db_->GetColumnFamilyMetaData(cf_options, &cf_meta);
ASSERT_EQ(cf_meta.levels.size(), 1);
ASSERT_EQ(cf_meta.levels[0].level, 6);
ASSERT_GT(cf_meta.levels[0].files.size(), 0);
size_t all_files = cf_meta.levels[0].files.size();
// Keys in the SST files are distributed across level 6
// Test with key range - should only return files within the range
auto startKey = Slice("key000050");
auto endKey = Slice("key000150");
GetColumnFamilyMetaDataOptions cf_options_range(startKey, endKey, 6);
db_->GetColumnFamilyMetaData(cf_options_range, &cf_meta);
ASSERT_EQ(cf_meta.levels.size(), 1);
ASSERT_EQ(cf_meta.levels[0].level, 6);
ASSERT_GT(cf_meta.levels[0].files.size(), 0);
size_t files_in_range = cf_meta.levels[0].files.size();
// Files in range should be less than or equal to all files
ASSERT_LE(files_in_range, all_files);
}
TEST_F(DBTest, GetColumnFamilyMetaDataMultipleLevels) {
Options options = CurrentOptions();
options.create_if_missing = true;
options.disable_auto_compactions = true;
options.num_levels = 7;
DestroyAndReopen(options);
Random rnd(301);
int key_index = 0;
for (int i = 0; i < 50; ++i) {
GenerateNewFile(&rnd, &key_index, /* nowait */ true);
ASSERT_OK(Flush());
}
CompactRangeOptions compact_options;
compact_options.bottommost_level_compaction =
BottommostLevelCompaction::kForce;
compact_options.change_level = true;
compact_options.target_level = 6;
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
for (int i = 0; i < 30; ++i) {
GenerateNewFile(&rnd, &key_index, /* nowait */ true);
ASSERT_OK(Flush());
}
// First verify both levels have files without key range filter
ColumnFamilyMetaData cf_meta_all_no_range;
GetColumnFamilyMetaDataOptions cf_options_all_no_range;
db_->GetColumnFamilyMetaData(cf_options_all_no_range, &cf_meta_all_no_range);
bool has_level_0 = false;
bool has_level_6 = false;
for (const auto& level : cf_meta_all_no_range.levels) {
if (level.level == 0 && level.files.size() > 0) {
has_level_0 = true;
}
if (level.level == 6 && level.files.size() > 0) {
has_level_6 = true;
}
}
ASSERT_TRUE(has_level_0);
ASSERT_TRUE(has_level_6);
// Test querying bottommost level only with key range
// Use a range that should be in the first set of files (now in level 6)
auto startKey = Slice("key000050");
auto endKey = Slice("key000150");
ColumnFamilyMetaData cf_meta_bottommost;
GetColumnFamilyMetaDataOptions cf_options_bottommost(startKey, endKey, 6);
db_->GetColumnFamilyMetaData(cf_options_bottommost, &cf_meta_bottommost);
ASSERT_EQ(cf_meta_bottommost.levels.size(), 1);
ASSERT_EQ(cf_meta_bottommost.levels[0].level, 6);
ASSERT_GT(cf_meta_bottommost.levels[0].files.size(), 0);
size_t level_6_files_in_range = cf_meta_bottommost.levels[0].files.size();
// Test querying all levels with same key range
ColumnFamilyMetaData cf_meta_all;
GetColumnFamilyMetaDataOptions cf_options_all(startKey, endKey);
db_->GetColumnFamilyMetaData(cf_options_all, &cf_meta_all);
size_t level_6_files_in_range_from_all = 0;
for (const auto& level : cf_meta_all.levels) {
if (level.level == 6) {
level_6_files_in_range_from_all = level.files.size();
}
}
ASSERT_GT(level_6_files_in_range_from_all, 0);
ASSERT_EQ(level_6_files_in_range, level_6_files_in_range_from_all);
}
TEST_F(DBTest, GetColumnFamilyMetaDataEmptyDB) {
Options options = CurrentOptions();
options.create_if_missing = true;
options.num_levels = 7;
DestroyAndReopen(options);
// Test on empty database
ColumnFamilyMetaData cf_meta_empty_db;
GetColumnFamilyMetaDataOptions cf_options_empty_db;
db_->GetColumnFamilyMetaData(cf_options_empty_db, &cf_meta_empty_db);
ASSERT_EQ(cf_meta_empty_db.levels.size(), 0);
ASSERT_EQ(cf_meta_empty_db.file_count, 0);
ASSERT_EQ(cf_meta_empty_db.size, 0);
// Test on empty database with key range
auto startKey = Slice("key000050");
auto endKey = Slice("key000150");
ColumnFamilyMetaData cf_meta_empty_range;
GetColumnFamilyMetaDataOptions cf_options_empty_range(startKey, endKey);
db_->GetColumnFamilyMetaData(cf_options_empty_range, &cf_meta_empty_range);
ASSERT_EQ(cf_meta_empty_range.levels.size(), 0);
ASSERT_EQ(cf_meta_empty_range.file_count, 0);
ASSERT_EQ(cf_meta_empty_range.size, 0);
}
TEST_F(DBTest, AllMetaDataTest) {
Options options = CurrentOptions();
options.create_if_missing = true;
@@ -3775,11 +3535,6 @@ class ModelDB : public DB {
void GetColumnFamilyMetaData(ColumnFamilyHandle* /*column_family*/,
ColumnFamilyMetaData* /*metadata*/) override {}
void GetColumnFamilyMetaData(
ColumnFamilyHandle* /*column_family*/,
const GetColumnFamilyMetaDataOptions& /*options*/,
ColumnFamilyMetaData* /*metadata*/) override {}
Status GetDbIdentity(std::string& /*identity*/) const override {
return Status::OK();
}
@@ -5179,7 +4934,7 @@ TEST_F(DBTest, DynamicMemtableOptions) {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
}
#ifndef NROCKSDB_THREAD_STATUS
#ifdef ROCKSDB_USING_THREAD_STATUS
namespace {
bool VerifyOperationCount(Env* env, ThreadStatus::OperationType op_type,
int expected_count) {
@@ -5637,7 +5392,7 @@ TEST_P(DBTestWithParam, PreShutdownCompactionMiddle) {
ASSERT_EQ(operation_count[ThreadStatus::OP_COMPACTION], 0);
}
#endif // !NROCKSDB_THREAD_STATUS
#endif // ROCKSDB_USING_THREAD_STATUS
TEST_F(DBTest, FlushOnDestroy) {
WriteOptions wo;
@@ -6372,9 +6127,9 @@ TEST_F(DBTest, MergeTestTime) {
ASSERT_EQ(1, count);
ASSERT_EQ(4000000, TestGetTickerCount(options, MERGE_OPERATION_TOTAL_TIME));
#ifndef NROCKSDB_THREAD_STATUS
#ifdef ROCKSDB_USING_THREAD_STATUS
ASSERT_GT(TestGetTickerCount(options, FLUSH_WRITE_BYTES), 0);
#endif // !NROCKSDB_THREAD_STATUS
#endif // ROCKSDB_USING_THREAD_STATUS
}
TEST_P(DBTestWithParam, MergeCompactionTimeTest) {
-3
View File
@@ -5205,7 +5205,6 @@ TEST_F(DBTest2, SwitchMemtableRaceWithNewManifest) {
Options options = CurrentOptions();
DestroyAndReopen(options);
options.max_manifest_file_size = 10;
options.max_manifest_space_amp_pct = 0;
options.create_if_missing = true;
CreateAndReopenWithCF({"pikachu"}, options);
ASSERT_EQ(2, handles_.size());
@@ -5897,7 +5896,6 @@ TEST_P(RenameCurrentTest, Flush) {
Destroy(last_options_);
Options options = GetDefaultOptions();
options.max_manifest_file_size = 1;
options.max_manifest_space_amp_pct = 0;
options.create_if_missing = true;
Reopen(options);
ASSERT_OK(Put("key", "value"));
@@ -5917,7 +5915,6 @@ TEST_P(RenameCurrentTest, Compaction) {
Destroy(last_options_);
Options options = GetDefaultOptions();
options.max_manifest_file_size = 1;
options.max_manifest_space_amp_pct = 0;
options.create_if_missing = true;
Reopen(options);
ASSERT_OK(Put("a", "a_value"));
+1 -2
View File
@@ -454,8 +454,7 @@ Options DBTestBase::GetOptions(
options.allow_mmap_reads = can_allow_mmap;
break;
case kManifestFileSize:
options.max_manifest_file_size = 50; // 50 bytes
options.max_manifest_space_amp_pct = 0; // old behavior
options.max_manifest_file_size = 50; // 50 bytes
break;
case kPerfOptions:
options.delayed_write_rate = 8 * 1024 * 1024;
+2 -3
View File
@@ -1746,8 +1746,8 @@ class RecoveryTestHelper {
WriteController write_controller;
versions.reset(new VersionSet(
test->dbname_, &db_options, MutableDBOptions{options}, file_options,
table_cache.get(), &write_buffer_manager, &write_controller,
test->dbname_, &db_options, file_options, table_cache.get(),
&write_buffer_manager, &write_controller,
/*block_cache_tracer=*/nullptr,
/*io_tracer=*/nullptr, /*db_id=*/"", /*db_session_id=*/"",
options.daily_offpeak_time_utc,
@@ -2277,7 +2277,6 @@ TEST_F(DBWALTest, FixSyncWalOnObseletedWalWithNewManifestCausingMissingWAL) {
Options options = CurrentOptions();
// Small size to force manifest creation
options.max_manifest_file_size = 1;
options.max_manifest_space_amp_pct = 0;
options.track_and_verify_wals_in_manifest = true;
DestroyAndReopen(options);
-44
View File
@@ -333,50 +333,6 @@ TEST_F(FormatTest, ReplaceInternalKeyWithMinTimestamp) {
ASSERT_EQ(kTypeValue, new_key.type);
}
TEST(RocksdbVersionTest, Version) {
// Test preprocessor macros for versioning
ASSERT_GT(ROCKSDB_MAJOR, 0);
ASSERT_GE(ROCKSDB_MINOR, 0);
ASSERT_GE(ROCKSDB_PATCH, 0);
ASSERT_LT(ROCKSDB_MAJOR, 1000);
ASSERT_LT(ROCKSDB_MINOR, 1000);
ASSERT_LT(ROCKSDB_PATCH, 1000);
ASSERT_EQ(ROCKSDB_MAKE_VERSION_INT(123, 456, 789), 123456789);
ASSERT_GT(ROCKSDB_VERSION_INT, 9999999);
ASSERT_LT(ROCKSDB_VERSION_INT, 99999999);
static_assert(ROCKSDB_VERSION_GE(9, 8, 7));
static_assert(
ROCKSDB_VERSION_GE(ROCKSDB_MAJOR, ROCKSDB_MINOR, ROCKSDB_PATCH));
static_assert(
ROCKSDB_VERSION_GE(ROCKSDB_MAJOR, ROCKSDB_MINOR, ROCKSDB_PATCH - 1));
static_assert(
ROCKSDB_VERSION_GE(ROCKSDB_MAJOR, ROCKSDB_MINOR, ROCKSDB_PATCH - 100));
static_assert(
ROCKSDB_VERSION_GE(ROCKSDB_MAJOR, ROCKSDB_MINOR - 1, ROCKSDB_PATCH + 1));
static_assert(ROCKSDB_VERSION_GE(ROCKSDB_MAJOR - 1, ROCKSDB_MINOR + 1,
ROCKSDB_PATCH + 1));
static_assert(
!ROCKSDB_VERSION_GE(ROCKSDB_MAJOR, ROCKSDB_MINOR, ROCKSDB_PATCH + 1));
static_assert(
!ROCKSDB_VERSION_GE(ROCKSDB_MAJOR, ROCKSDB_MINOR, ROCKSDB_PATCH + 100));
static_assert(
!ROCKSDB_VERSION_GE(ROCKSDB_MAJOR, ROCKSDB_MINOR + 1, ROCKSDB_PATCH - 1));
static_assert(!ROCKSDB_VERSION_GE(ROCKSDB_MAJOR + 1, ROCKSDB_MINOR - 1,
ROCKSDB_PATCH - 1));
// More typical usage (but with literal numbers based on relevant API
// features)
#if ROCKSDB_VERSION_GE(ROCKSDB_MAJOR, ROCKSDB_MINOR, ROCKSDB_PATCH)
static_assert(true);
#else
static_assert(false);
#endif
#if !ROCKSDB_VERSION_GE(ROCKSDB_MAJOR, ROCKSDB_MINOR, ROCKSDB_PATCH + 1)
static_assert(true);
#else
static_assert(false);
#endif
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+2 -1
View File
@@ -749,7 +749,8 @@ void ExternalSstFileIngestionJob::CreateEquivalentFileIngestingCompactions() {
cfd_->ioptions().compaction_style),
LLONG_MAX /* max compaction bytes, not applicable */,
0 /* output path ID, not applicable */, mutable_cf_options.compression,
mutable_cf_options.compression_opts, Temperature::kUnknown,
mutable_cf_options.compression_opts,
mutable_cf_options.default_write_temperature,
0 /* max_subcompaction, not applicable */,
{} /* grandparents, not applicable */,
std::nullopt /* earliest_snapshot */, nullptr /* snapshot_checker */,
+5 -22
View File
@@ -4094,17 +4094,6 @@ TEST_P(IngestDBGeneratedFileTest2, NonZeroSeqno) {
ingest_opts.fail_if_not_bottommost_level = std::get<3>(GetParam());
ingest_opts.link_files = std::get<4>(GetParam());
Random* rnd = Random::GetTLSInstance();
rnd->Reset(std::random_device{}());
std::ostringstream ingest_opts_trace;
ingest_opts_trace << "ingest_opts params: " << "snapshot_consistency="
<< ingest_opts.snapshot_consistency << ", "
<< "allow_global_seqno=" << ingest_opts.allow_global_seqno
<< ", " << "allow_blocking_flush="
<< ingest_opts.allow_blocking_flush << ", "
<< "fail_if_not_bottommost_level="
<< ingest_opts.fail_if_not_bottommost_level << ", "
<< "link_files=" << ingest_opts.link_files;
SCOPED_TRACE(ingest_opts_trace.str());
do {
SCOPED_TRACE("option_config_ = " + std::to_string(option_config_));
@@ -4274,17 +4263,11 @@ TEST_P(IngestDBGeneratedFileTest2, NonZeroSeqno) {
s = db_->IngestExternalFile(overlap_cf, sst_file_paths, ingest_opts);
ASSERT_NOK(s);
if (ingest_opts.fail_if_not_bottommost_level) {
ASSERT_TRUE(s.ToString().find("Files cannot be ingested to Lmax") !=
std::string::npos)
<< s.ToString();
} else {
ASSERT_TRUE(s.ToString().find("An ingested file overlaps with existing "
"data in the DB and has been "
"assigned a non-zero sequence number") !=
std::string::npos)
<< s.ToString();
}
ASSERT_TRUE(s.ToString().find("An ingested file overlaps with existing "
"data in the DB and has been "
"assigned a non-zero sequence number") !=
std::string::npos)
<< s.ToString();
}
// Cleanup
+7 -7
View File
@@ -142,13 +142,13 @@ class FlushJobTestBase : public testing::Test {
column_families.emplace_back(cf_name, cf_options_);
}
versions_.reset(new VersionSet(
dbname_, &db_options_, MutableDBOptions{options_}, env_options_,
table_cache_.get(), &write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
test::kUnitTestDbId, /*db_session_id=*/"",
/*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr, /*read_only=*/false));
versions_.reset(
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
test::kUnitTestDbId, /*db_session_id=*/"",
/*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr, /*read_only=*/false));
EXPECT_OK(versions_->Recover(column_families, false));
}
+17 -7
View File
@@ -163,7 +163,9 @@ TEST_F(EventListenerTest, OnSingleDBCompactionTest) {
options.max_bytes_for_level_base = options.target_file_size_base * 2;
options.max_bytes_for_level_multiplier = 2;
options.compression = kNoCompression;
options.enable_thread_tracking = ThreadStatus::kEnabled;
#ifdef ROCKSDB_USING_THREAD_STATUS
options.enable_thread_tracking = true;
#endif // ROCKSDB_USING_THREAD_STATUS
options.level0_file_num_compaction_trigger = kNumL0Files;
options.table_properties_collector_factories.push_back(
std::make_shared<TestPropertiesCollectorFactory>());
@@ -227,7 +229,7 @@ class TestFlushListener : public EventListener {
ASSERT_EQ(info.file_checksum, kUnknownFileChecksum);
ASSERT_EQ(info.file_checksum_func_name, kUnknownFileChecksumFuncName);
#ifndef NROCKSDB_THREAD_STATUS
#ifdef ROCKSDB_USING_THREAD_STATUS
// Verify the id of the current thread that created this table
// file matches the id of any active flush or compaction thread.
uint64_t thread_id = env_->GetThreadID();
@@ -244,7 +246,7 @@ class TestFlushListener : public EventListener {
}
}
ASSERT_TRUE(found_match);
#endif // !NROCKSDB_THREAD_STATUS
#endif // ROCKSDB_USING_THREAD_STATUS
}
void OnFlushCompleted(DB* db, const FlushJobInfo& info) override {
@@ -308,7 +310,9 @@ TEST_F(EventListenerTest, OnSingleDBFlushTest) {
Options options;
options.env = CurrentOptions().env;
options.write_buffer_size = k110KB;
options.enable_thread_tracking = ThreadStatus::kEnabled;
#ifdef ROCKSDB_USING_THREAD_STATUS
options.enable_thread_tracking = true;
#endif // ROCKSDB_USING_THREAD_STATUS
TestFlushListener* listener = new TestFlushListener(options.env, this);
options.listeners.emplace_back(listener);
std::vector<std::string> cf_names = {"pikachu", "ilya", "muromec",
@@ -353,7 +357,9 @@ TEST_F(EventListenerTest, MultiCF) {
Options options;
options.env = CurrentOptions().env;
options.write_buffer_size = k110KB;
options.enable_thread_tracking = ThreadStatus::kEnabled;
#ifdef ROCKSDB_USING_THREAD_STATUS
options.enable_thread_tracking = true;
#endif // ROCKSDB_USING_THREAD_STATUS
options.atomic_flush = atomic_flush;
options.create_if_missing = true;
DestroyAndReopen(options);
@@ -401,7 +407,9 @@ TEST_F(EventListenerTest, MultiCF) {
TEST_F(EventListenerTest, MultiDBMultiListeners) {
Options options;
options.env = CurrentOptions().env;
options.enable_thread_tracking = ThreadStatus::kEnabled;
#ifdef ROCKSDB_USING_THREAD_STATUS
options.enable_thread_tracking = true;
#endif // ROCKSDB_USING_THREAD_STATUS
options.table_properties_collector_factories.push_back(
std::make_shared<TestPropertiesCollectorFactory>());
std::vector<TestFlushListener*> listeners;
@@ -489,7 +497,9 @@ TEST_F(EventListenerTest, MultiDBMultiListeners) {
TEST_F(EventListenerTest, DisableBGCompaction) {
Options options;
options.env = CurrentOptions().env;
options.enable_thread_tracking = ThreadStatus::kEnabled;
#ifdef ROCKSDB_USING_THREAD_STATUS
options.enable_thread_tracking = true;
#endif // ROCKSDB_USING_THREAD_STATUS
TestFlushListener* listener = new TestFlushListener(options.env, this);
const int kCompactionTrigger = 1;
const int kSlowdownTrigger = 5;
+4 -4
View File
@@ -59,7 +59,7 @@ class Reader {
// live while this Reader is in use.
//
// If "checksum" is true, verify checksums if available.
// TODO(hx235): separate WAL related parameters from general `Reader`
// TODO(hx235): seperate WAL related parameters from general `Reader`
// parameters
Reader(std::shared_ptr<Logger> info_log,
std::unique_ptr<SequentialFileReader>&& file, Reporter* reporter,
@@ -155,7 +155,7 @@ class Reader {
// which log number this is
uint64_t const log_number_;
// See `Options::track_and_verify_wals`
// See `Optinos::track_and_verify_wals`
bool track_and_verify_wals_;
// Below variables are used for WAL verification
// TODO(hx235): To revise `stop_replay_for_corruption_` inside `LogReader`
@@ -208,8 +208,8 @@ class Reader {
};
// Return type, or one of the preceding special values
// If WAL compression is enabled, fragment_checksum is the checksum of the
// fragment computed from the original buffer containing uncompressed
// If WAL compressioned is enabled, fragment_checksum is the checksum of the
// fragment computed from the orginal buffer containinng uncompressed
// fragment.
uint8_t ReadPhysicalRecord(Slice* result, size_t* drop_size,
uint64_t* fragment_checksum = nullptr);
+1 -1
View File
@@ -77,7 +77,7 @@ class Writer {
// Create a writer that will append data to "*dest".
// "*dest" must be initially empty.
// "*dest" must remain live while this Writer is in use.
// TODO(hx235): separate WAL related parameters from general `Reader`
// TODO(hx235): seperate WAL related parameters from general `Reader`
// parameters
explicit Writer(std::unique_ptr<WritableFileWriter>&& dest,
uint64_t log_number, bool recycle_log_files,
+2 -4
View File
@@ -112,8 +112,7 @@ class MemTableListTest : public testing::Test {
WriteBufferManager write_buffer_manager(db_options.db_write_buffer_size);
WriteController write_controller(10000000u);
VersionSet versions(dbname, &immutable_db_options,
MutableDBOptions{db_options}, env_options,
VersionSet versions(dbname, &immutable_db_options, env_options,
table_cache.get(), &write_buffer_manager,
&write_controller, /*block_cache_tracer=*/nullptr,
/*io_tracer=*/nullptr, /*db_id=*/"",
@@ -164,8 +163,7 @@ class MemTableListTest : public testing::Test {
WriteBufferManager write_buffer_manager(db_options.db_write_buffer_size);
WriteController write_controller(10000000u);
VersionSet versions(dbname, &immutable_db_options,
MutableDBOptions{db_options}, env_options,
VersionSet versions(dbname, &immutable_db_options, env_options,
table_cache.get(), &write_buffer_manager,
&write_controller, /*block_cache_tracer=*/nullptr,
/*io_tracer=*/nullptr, /*db_id=*/"",
+1 -1
View File
@@ -307,7 +307,7 @@ class MergeOutputIterator {
Slice key() { return Slice(*it_keys_); }
Slice value() { return Slice(*it_values_); }
bool Valid() const { return it_keys_ != merge_helper_->keys().rend(); }
bool Valid() { return it_keys_ != merge_helper_->keys().rend(); }
private:
const MergeHelper* merge_helper_;
-41
View File
@@ -303,47 +303,6 @@ TEST_F(ObsoleteFilesTest, BlobFiles) {
ASSERT_EQ(deleted_files, expected_deleted_files);
}
TEST_F(ObsoleteFilesTest, GetSortedWalFilesHangsAfterNoopPurge) {
// This test used to trigger a hang in `DB::GetSortedWalFiles()`, where it
// would wait for a no-op purge that did not signal the CV upon completion.
// Grab an iterator and flush to switch the super version. That way, when the
// iterator is destroyed, it will go through the purge path.
DB* db = db_; // Only using `db` makes it clear we only use DB-level APIs.
ASSERT_OK(db->Put(WriteOptions(), "key", "value"));
std::unique_ptr<Iterator> iter(db->NewIterator(ReadOptions()));
ASSERT_OK(db->Flush(FlushOptions()));
// Sync points ensure `GetSortedWalFiles()` waits for a purge after
// `FindObsoleteFiles()` releases the mutex but before its corresponding purge
// completes.
SyncPoint::GetInstance()->SetCallBack(
"FindObsoleteFiles::PostMutexUnlock", [&](void* /* arg */) {
TEST_SYNC_POINT(
"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:"
"InCallback:1");
TEST_SYNC_POINT(
"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:"
"InCallback:2");
});
SyncPoint::GetInstance()->LoadDependency({
{"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:InCallback:1",
"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:Thread:Begin"},
{"DBImpl::GetSortedWalFilesImpl:WaitPurge",
"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:InCallback:2"},
});
SyncPoint::GetInstance()->EnableProcessing();
port::Thread get_sorted_wal_files_thread([db]() {
TEST_SYNC_POINT(
"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:Thread:Begin");
VectorWalPtr files;
ASSERT_OK(db->GetSortedWalFiles(files));
});
iter.reset();
get_sorted_wal_files_thread.join();
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+2 -2
View File
@@ -120,8 +120,8 @@ class Repairer {
/*io_tracer=*/nullptr, db_session_id_)),
wb_(db_options_.db_write_buffer_size),
wc_(db_options_.delayed_write_rate),
vset_(dbname_, &immutable_db_options_, MutableDBOptions{db_options_},
file_options_, raw_table_cache_.get(), &wb_, &wc_,
vset_(dbname_, &immutable_db_options_, file_options_,
raw_table_cache_.get(), &wb_, &wc_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", db_session_id_, db_options.daily_offpeak_time_utc,
/*error_handler=*/nullptr, /*read_only=*/false),
+139 -477
View File
@@ -112,9 +112,124 @@ bool VersionEdit::EncodeTo(std::string* dst,
f.epoch_number == kUnknownEpochNumber) {
return false;
}
EncodeToNewFile4(f, new_files_[i].first, ts_sz.value(),
has_min_log_number_to_keep_, min_log_number_to_keep_,
min_log_num_written, dst);
PutVarint32(dst, kNewFile4);
PutVarint32Varint64(dst, new_files_[i].first /* level */, f.fd.GetNumber());
PutVarint64(dst, f.fd.GetFileSize());
EncodeFileBoundaries(dst, f, ts_sz.value());
PutVarint64Varint64(dst, f.fd.smallest_seqno, f.fd.largest_seqno);
// Customized fields' format:
// +-----------------------------+
// | 1st field's tag (varint32) |
// +-----------------------------+
// | 1st field's size (varint32) |
// +-----------------------------+
// | bytes for 1st field |
// | (based on size decoded) |
// +-----------------------------+
// | |
// | ...... |
// | |
// +-----------------------------+
// | last field's size (varint32)|
// +-----------------------------+
// | bytes for last field |
// | (based on size decoded) |
// +-----------------------------+
// | terminating tag (varint32) |
// +-----------------------------+
//
// Customized encoding for fields:
// tag kPathId: 1 byte as path_id
// tag kNeedCompaction:
// now only can take one char value 1 indicating need-compaction
//
PutVarint32(dst, NewFileCustomTag::kOldestAncesterTime);
std::string varint_oldest_ancester_time;
PutVarint64(&varint_oldest_ancester_time, f.oldest_ancester_time);
TEST_SYNC_POINT_CALLBACK("VersionEdit::EncodeTo:VarintOldestAncesterTime",
&varint_oldest_ancester_time);
PutLengthPrefixedSlice(dst, Slice(varint_oldest_ancester_time));
PutVarint32(dst, NewFileCustomTag::kFileCreationTime);
std::string varint_file_creation_time;
PutVarint64(&varint_file_creation_time, f.file_creation_time);
TEST_SYNC_POINT_CALLBACK("VersionEdit::EncodeTo:VarintFileCreationTime",
&varint_file_creation_time);
PutLengthPrefixedSlice(dst, Slice(varint_file_creation_time));
PutVarint32(dst, NewFileCustomTag::kEpochNumber);
std::string varint_epoch_number;
PutVarint64(&varint_epoch_number, f.epoch_number);
PutLengthPrefixedSlice(dst, Slice(varint_epoch_number));
if (f.file_checksum_func_name != kUnknownFileChecksumFuncName) {
PutVarint32(dst, NewFileCustomTag::kFileChecksum);
PutLengthPrefixedSlice(dst, Slice(f.file_checksum));
PutVarint32(dst, NewFileCustomTag::kFileChecksumFuncName);
PutLengthPrefixedSlice(dst, Slice(f.file_checksum_func_name));
}
if (f.fd.GetPathId() != 0) {
PutVarint32(dst, NewFileCustomTag::kPathId);
char p = static_cast<char>(f.fd.GetPathId());
PutLengthPrefixedSlice(dst, Slice(&p, 1));
}
if (f.temperature != Temperature::kUnknown) {
PutVarint32(dst, NewFileCustomTag::kTemperature);
char p = static_cast<char>(f.temperature);
PutLengthPrefixedSlice(dst, Slice(&p, 1));
}
if (f.marked_for_compaction) {
PutVarint32(dst, NewFileCustomTag::kNeedCompaction);
char p = static_cast<char>(1);
PutLengthPrefixedSlice(dst, Slice(&p, 1));
}
if (has_min_log_number_to_keep_ && !min_log_num_written) {
PutVarint32(dst, NewFileCustomTag::kMinLogNumberToKeepHack);
std::string varint_log_number;
PutFixed64(&varint_log_number, min_log_number_to_keep_);
PutLengthPrefixedSlice(dst, Slice(varint_log_number));
min_log_num_written = true;
}
if (f.oldest_blob_file_number != kInvalidBlobFileNumber) {
PutVarint32(dst, NewFileCustomTag::kOldestBlobFileNumber);
std::string oldest_blob_file_number;
PutVarint64(&oldest_blob_file_number, f.oldest_blob_file_number);
PutLengthPrefixedSlice(dst, Slice(oldest_blob_file_number));
}
UniqueId64x2 unique_id = f.unique_id;
TEST_SYNC_POINT_CALLBACK("VersionEdit::EncodeTo:UniqueId", &unique_id);
if (unique_id != kNullUniqueId64x2) {
PutVarint32(dst, NewFileCustomTag::kUniqueId);
std::string unique_id_str = EncodeUniqueIdBytes(&unique_id);
PutLengthPrefixedSlice(dst, Slice(unique_id_str));
}
if (f.compensated_range_deletion_size) {
PutVarint32(dst, kCompensatedRangeDeletionSize);
std::string compensated_range_deletion_size;
PutVarint64(&compensated_range_deletion_size,
f.compensated_range_deletion_size);
PutLengthPrefixedSlice(dst, Slice(compensated_range_deletion_size));
}
if (f.tail_size) {
PutVarint32(dst, NewFileCustomTag::kTailSize);
std::string varint_tail_size;
PutVarint64(&varint_tail_size, f.tail_size);
PutLengthPrefixedSlice(dst, Slice(varint_tail_size));
}
if (!f.user_defined_timestamps_persisted) {
// The default value for the flag is true, it's only explicitly persisted
// when it's false. We are putting 0 as the value here to signal false
// (i.e. UDTS not persisted).
PutVarint32(dst, NewFileCustomTag::kUserDefinedTimestampsPersisted);
char p = static_cast<char>(0);
PutLengthPrefixedSlice(dst, Slice(&p, 1));
}
TEST_SYNC_POINT_CALLBACK("VersionEdit::EncodeTo:NewFile4:CustomizeFields",
dst);
PutVarint32(dst, NewFileCustomTag::kTerminate);
}
for (const auto& blob_file_addition : blob_file_additions_) {
@@ -173,142 +288,9 @@ bool VersionEdit::EncodeTo(std::string* dst,
char p = static_cast<char>(persist_user_defined_timestamps_);
PutLengthPrefixedSlice(dst, Slice(&p, 1));
}
if (HasSubcompactionProgress()) {
PutVarint32(dst, kSubcompactionProgress);
std::string progress_data;
subcompaction_progress_.EncodeTo(&progress_data);
PutLengthPrefixedSlice(dst, progress_data);
}
return true;
}
void VersionEdit::EncodeToNewFile4(const FileMetaData& f, int level,
size_t ts_sz,
bool has_min_log_number_to_keep,
uint64_t min_log_number_to_keep,
bool& min_log_num_written,
std::string* dst) {
PutVarint32(dst, kNewFile4);
PutVarint32Varint64(dst, level, f.fd.GetNumber());
PutVarint64(dst, f.fd.GetFileSize());
EncodeFileBoundaries(dst, f, ts_sz);
PutVarint64Varint64(dst, f.fd.smallest_seqno, f.fd.largest_seqno);
// Customized fields' format:
// +-----------------------------+
// | 1st field's tag (varint32) |
// +-----------------------------+
// | 1st field's size (varint32) |
// +-----------------------------+
// | bytes for 1st field |
// | (based on size decoded) |
// +-----------------------------+
// | |
// | ...... |
// | |
// +-----------------------------+
// | last field's size (varint32)|
// +-----------------------------+
// | bytes for last field |
// | (based on size decoded) |
// +-----------------------------+
// | terminating tag (varint32) |
// +-----------------------------+
//
// Customized encoding for fields:
// tag kPathId: 1 byte as path_id
// tag kNeedCompaction:
// now only can take one char value 1 indicating need-compaction
//
PutVarint32(dst, NewFileCustomTag::kOldestAncesterTime);
std::string varint_oldest_ancester_time;
PutVarint64(&varint_oldest_ancester_time, f.oldest_ancester_time);
TEST_SYNC_POINT_CALLBACK("VersionEdit::EncodeTo:VarintOldestAncesterTime",
&varint_oldest_ancester_time);
PutLengthPrefixedSlice(dst, Slice(varint_oldest_ancester_time));
PutVarint32(dst, NewFileCustomTag::kFileCreationTime);
std::string varint_file_creation_time;
PutVarint64(&varint_file_creation_time, f.file_creation_time);
TEST_SYNC_POINT_CALLBACK("VersionEdit::EncodeTo:VarintFileCreationTime",
&varint_file_creation_time);
PutLengthPrefixedSlice(dst, Slice(varint_file_creation_time));
PutVarint32(dst, NewFileCustomTag::kEpochNumber);
std::string varint_epoch_number;
PutVarint64(&varint_epoch_number, f.epoch_number);
PutLengthPrefixedSlice(dst, Slice(varint_epoch_number));
if (f.file_checksum_func_name != kUnknownFileChecksumFuncName) {
PutVarint32(dst, NewFileCustomTag::kFileChecksum);
PutLengthPrefixedSlice(dst, Slice(f.file_checksum));
PutVarint32(dst, NewFileCustomTag::kFileChecksumFuncName);
PutLengthPrefixedSlice(dst, Slice(f.file_checksum_func_name));
}
if (f.fd.GetPathId() != 0) {
PutVarint32(dst, NewFileCustomTag::kPathId);
char p = static_cast<char>(f.fd.GetPathId());
PutLengthPrefixedSlice(dst, Slice(&p, 1));
}
if (f.temperature != Temperature::kUnknown) {
PutVarint32(dst, NewFileCustomTag::kTemperature);
char p = static_cast<char>(f.temperature);
PutLengthPrefixedSlice(dst, Slice(&p, 1));
}
if (f.marked_for_compaction) {
PutVarint32(dst, NewFileCustomTag::kNeedCompaction);
char p = static_cast<char>(1);
PutLengthPrefixedSlice(dst, Slice(&p, 1));
}
if (has_min_log_number_to_keep && !min_log_num_written) {
PutVarint32(dst, NewFileCustomTag::kMinLogNumberToKeepHack);
std::string varint_log_number;
PutFixed64(&varint_log_number, min_log_number_to_keep);
PutLengthPrefixedSlice(dst, Slice(varint_log_number));
min_log_num_written = true;
}
if (f.oldest_blob_file_number != kInvalidBlobFileNumber) {
PutVarint32(dst, NewFileCustomTag::kOldestBlobFileNumber);
std::string oldest_blob_file_number;
PutVarint64(&oldest_blob_file_number, f.oldest_blob_file_number);
PutLengthPrefixedSlice(dst, Slice(oldest_blob_file_number));
}
UniqueId64x2 unique_id = f.unique_id;
TEST_SYNC_POINT_CALLBACK("VersionEdit::EncodeTo:UniqueId", &unique_id);
if (unique_id != kNullUniqueId64x2) {
PutVarint32(dst, NewFileCustomTag::kUniqueId);
std::string unique_id_str = EncodeUniqueIdBytes(&unique_id);
PutLengthPrefixedSlice(dst, Slice(unique_id_str));
}
if (f.compensated_range_deletion_size) {
PutVarint32(dst, NewFileCustomTag::kCompensatedRangeDeletionSize);
std::string compensated_range_deletion_size;
PutVarint64(&compensated_range_deletion_size,
f.compensated_range_deletion_size);
PutLengthPrefixedSlice(dst, Slice(compensated_range_deletion_size));
}
if (f.tail_size) {
PutVarint32(dst, NewFileCustomTag::kTailSize);
std::string varint_tail_size;
PutVarint64(&varint_tail_size, f.tail_size);
PutLengthPrefixedSlice(dst, Slice(varint_tail_size));
}
if (!f.user_defined_timestamps_persisted) {
// The default value for the flag is true, it's only explicitly persisted
// when it's false. We are putting 0 as the value here to signal false
// (i.e. UDTS not persisted).
PutVarint32(dst, NewFileCustomTag::kUserDefinedTimestampsPersisted);
char p = static_cast<char>(0);
PutLengthPrefixedSlice(dst, Slice(&p, 1));
}
TEST_SYNC_POINT_CALLBACK("VersionEdit::EncodeTo:NewFile4:CustomizeFields",
dst);
PutVarint32(dst, NewFileCustomTag::kTerminate);
}
static bool GetInternalKey(Slice* input, InternalKey* dst) {
Slice str;
if (GetLengthPrefixedSlice(input, &str)) {
@@ -319,12 +301,12 @@ static bool GetInternalKey(Slice* input, InternalKey* dst) {
}
}
bool VersionEdit::GetLevel(Slice* input, int* level, int& max_level) {
bool VersionEdit::GetLevel(Slice* input, int* level, const char** /*msg*/) {
uint32_t v = 0;
if (GetVarint32(input, &v)) {
*level = v;
if (max_level < *level) {
max_level = *level;
if (max_level_ < *level) {
max_level_ = *level;
}
return true;
} else {
@@ -332,18 +314,16 @@ bool VersionEdit::GetLevel(Slice* input, int* level, int& max_level) {
}
}
const char* VersionEdit::DecodeNewFile4From(Slice* input, int& max_level,
uint64_t& min_log_number_to_keep,
bool& has_min_log_number_to_keep,
NewFiles& new_files,
FileMetaData& f) {
const char* VersionEdit::DecodeNewFile4From(Slice* input) {
const char* msg = nullptr;
int level = 0;
FileMetaData f;
uint64_t number = 0;
uint32_t path_id = 0;
uint64_t file_size = 0;
SequenceNumber smallest_seqno = 0;
SequenceNumber largest_seqno = kMaxSequenceNumber;
if (GetLevel(input, &level, max_level) && GetVarint64(input, &number) &&
if (GetLevel(input, &level, &msg) && GetVarint64(input, &number) &&
GetVarint64(input, &file_size) && GetInternalKey(input, &f.smallest) &&
GetInternalKey(input, &f.largest) &&
GetVarint64(input, &smallest_seqno) &&
@@ -401,10 +381,10 @@ const char* VersionEdit::DecodeNewFile4From(Slice* input, int& max_level,
case kMinLogNumberToKeepHack:
// This is a hack to encode kMinLogNumberToKeep in a
// forward-compatible fashion.
if (!GetFixed64(&field, &min_log_number_to_keep)) {
if (!GetFixed64(&field, &min_log_number_to_keep_)) {
return "deleted log number malformatted";
}
has_min_log_number_to_keep = true;
has_min_log_number_to_keep_ = true;
break;
case kOldestBlobFileNumber:
if (!GetVarint64(&field, &f.oldest_blob_file_number)) {
@@ -456,12 +436,13 @@ const char* VersionEdit::DecodeNewFile4From(Slice* input, int& max_level,
}
f.fd =
FileDescriptor(number, path_id, file_size, smallest_seqno, largest_seqno);
new_files.emplace_back(level, f);
new_files_.push_back(std::make_pair(level, f));
return nullptr;
}
void VersionEdit::EncodeFileBoundaries(std::string* dst,
const FileMetaData& meta, size_t ts_sz) {
const FileMetaData& meta,
size_t ts_sz) const {
if (ts_sz == 0 || meta.user_defined_timestamps_persisted) {
PutLengthPrefixedSlice(dst, meta.smallest.Encode());
PutLengthPrefixedSlice(dst, meta.largest.Encode());
@@ -564,8 +545,7 @@ Status VersionEdit::DecodeFrom(const Slice& src) {
break;
case kCompactCursor:
if (GetLevel(&input, &level, max_level_) &&
GetInternalKey(&input, &key)) {
if (GetLevel(&input, &level, &msg) && GetInternalKey(&input, &key)) {
// Here we re-use the output format of compact pointer in LevelDB
// to persist compact_cursors_
compact_cursors_.push_back(std::make_pair(level, key));
@@ -578,8 +558,7 @@ Status VersionEdit::DecodeFrom(const Slice& src) {
case kDeletedFile: {
uint64_t number = 0;
if (GetLevel(&input, &level, max_level_) &&
GetVarint64(&input, &number)) {
if (GetLevel(&input, &level, &msg) && GetVarint64(&input, &number)) {
deleted_files_.insert(std::make_pair(level, number));
} else {
if (!msg) {
@@ -592,8 +571,8 @@ Status VersionEdit::DecodeFrom(const Slice& src) {
case kNewFile: {
uint64_t number = 0;
uint64_t file_size = 0;
if (GetLevel(&input, &level, max_level_) &&
GetVarint64(&input, &number) && GetVarint64(&input, &file_size) &&
if (GetLevel(&input, &level, &msg) && GetVarint64(&input, &number) &&
GetVarint64(&input, &file_size) &&
GetInternalKey(&input, &f.smallest) &&
GetInternalKey(&input, &f.largest)) {
f.fd = FileDescriptor(number, 0, file_size);
@@ -610,8 +589,8 @@ Status VersionEdit::DecodeFrom(const Slice& src) {
uint64_t file_size = 0;
SequenceNumber smallest_seqno = 0;
SequenceNumber largest_seqno = kMaxSequenceNumber;
if (GetLevel(&input, &level, max_level_) &&
GetVarint64(&input, &number) && GetVarint64(&input, &file_size) &&
if (GetLevel(&input, &level, &msg) && GetVarint64(&input, &number) &&
GetVarint64(&input, &file_size) &&
GetInternalKey(&input, &f.smallest) &&
GetInternalKey(&input, &f.largest) &&
GetVarint64(&input, &smallest_seqno) &&
@@ -633,9 +612,8 @@ Status VersionEdit::DecodeFrom(const Slice& src) {
uint64_t file_size = 0;
SequenceNumber smallest_seqno = 0;
SequenceNumber largest_seqno = kMaxSequenceNumber;
if (GetLevel(&input, &level, max_level_) &&
GetVarint64(&input, &number) && GetVarint32(&input, &path_id) &&
GetVarint64(&input, &file_size) &&
if (GetLevel(&input, &level, &msg) && GetVarint64(&input, &number) &&
GetVarint32(&input, &path_id) && GetVarint64(&input, &file_size) &&
GetInternalKey(&input, &f.smallest) &&
GetInternalKey(&input, &f.largest) &&
GetVarint64(&input, &smallest_seqno) &&
@@ -652,10 +630,7 @@ Status VersionEdit::DecodeFrom(const Slice& src) {
}
case kNewFile4: {
FileMetaData ignored_file;
msg = DecodeNewFile4From(&input, max_level_, min_log_number_to_keep_,
has_min_log_number_to_keep_, new_files_,
ignored_file);
msg = DecodeNewFile4From(&input);
break;
}
@@ -792,23 +767,6 @@ Status VersionEdit::DecodeFrom(const Slice& src) {
}
break;
case kSubcompactionProgress: {
Slice encoded;
if (!GetLengthPrefixedSlice(&input, &encoded)) {
msg = "SubcompactionProgress not prefixed by length";
break;
}
SubcompactionProgress progress;
Status s = progress.DecodeFrom(&encoded);
if (!s.ok()) {
return s;
}
SetSubcompactionProgress(progress);
break;
}
default:
if (tag & kTagSafeIgnoreMask) {
// Tag from future which can be safely ignored.
@@ -975,10 +933,6 @@ std::string VersionEdit::DebugString(bool hex_key) const {
r.append("\n FullHistoryTsLow: ");
r.append(Slice(full_history_ts_low_).ToString(hex_key));
}
if (HasSubcompactionProgress()) {
r.append("\n SubcompactionProgress: ");
r.append(subcompaction_progress_.ToString());
}
r.append("\n}\n");
return r;
}
@@ -1128,301 +1082,9 @@ std::string VersionEdit::DebugJSON(int edit_num, bool hex_key) const {
jw << "FullHistoryTsLow" << Slice(full_history_ts_low_).ToString(hex_key);
}
if (HasSubcompactionProgress()) {
jw << "SubcompactionProgress" << subcompaction_progress_.ToString();
}
jw.EndObject();
return jw.Get();
}
void SubcompactionProgressPerLevel::EncodeTo(std::string* dst) const {
if (num_processed_output_records_ > 0) {
PutVarint32(
dst,
SubcompactionProgressPerLevelCustomTag::kNumProcessedOutputRecords);
std::string varint_records;
PutVarint64(&varint_records, num_processed_output_records_);
PutLengthPrefixedSlice(dst, varint_records);
}
if (!output_files_.empty()) {
PutVarint32(dst, SubcompactionProgressPerLevelCustomTag::kOutputFilesDelta);
std::string files_data;
EncodeOutputFiles(&files_data);
PutLengthPrefixedSlice(dst, files_data);
}
PutVarint32(dst, SubcompactionProgressPerLevelCustomTag::
kSubcompactionProgressPerLevelTerminate);
}
Status SubcompactionProgressPerLevel::DecodeFrom(Slice* input) {
Clear();
while (true) {
uint32_t tag = 0;
if (!GetVarint32(input, &tag)) {
return Status::Corruption("SubcompactionProgressPerLevel", "tag error");
}
if (tag == SubcompactionProgressPerLevelCustomTag::
kSubcompactionProgressPerLevelTerminate) {
break;
}
Slice field;
if (!GetLengthPrefixedSlice(input, &field)) {
return Status::Corruption("SubcompactionProgressPerLevel",
"field length prefixed slice error");
}
switch (tag) {
case SubcompactionProgressPerLevelCustomTag::kNumProcessedOutputRecords: {
if (!GetVarint64(&field, &num_processed_output_records_)) {
return Status::Corruption("SubcompactionProgressPerLevel",
"invalid num_processed_output_records_");
}
break;
}
case SubcompactionProgressPerLevelCustomTag::kOutputFilesDelta: {
Status s = DecodeOutputFiles(&field, output_files_);
if (!s.ok()) {
return s;
}
break;
}
default:
// Forward compatibility: Handle unknown tags
if ((tag & SubcompactionProgressPerLevelCustomTag::
kSubcompactionProgressPerLevelCustomTagSafeIgnoreMask) !=
0) {
break;
} else {
return Status::NotSupported("SubcompactionProgress",
"unsupported critical custom field");
}
}
}
return Status::OK();
}
void SubcompactionProgressPerLevel::EncodeOutputFiles(std::string* dst) const {
size_t new_files_count =
output_files_.size() > last_persisted_output_files_count_
? output_files_.size() - last_persisted_output_files_count_
: 0;
assert(new_files_count > 0);
PutVarint32(dst, static_cast<uint32_t>(new_files_count));
for (size_t i = last_persisted_output_files_count_; i < output_files_.size();
++i) {
std::string file_dst;
bool ignored_min_log_written = false;
VersionEdit::EncodeToNewFile4(
output_files_[i], -1 /* level */, 0 /* ts_sz */,
false /* has_min_log_number_to_keep */, 0 /* min_log_number_to_keep */,
ignored_min_log_written, &file_dst);
PutLengthPrefixedSlice(dst, file_dst);
}
}
Status SubcompactionProgressPerLevel::DecodeOutputFiles(
Slice* input, autovector<FileMetaData>& output_files) {
uint32_t new_file_count = 0;
if (!GetVarint32(input, &new_file_count)) {
return Status::Corruption("SubcompactionProgressPerLevel",
"new output file count");
}
assert(output_files.size() == 0);
output_files.reserve(new_file_count);
for (uint32_t i = 0; i < new_file_count; ++i) {
Slice file_input;
if (!GetLengthPrefixedSlice(input, &file_input)) {
return Status::Corruption("SubcompactionProgressPerLevel",
"output file metadata");
}
uint32_t tag = 0;
if (!GetVarint32(&file_input, &tag) || tag != kNewFile4) {
return Status::Corruption("SubcompactionProgressPerLevel",
"expected kNewFile4 tag");
}
int ignored_max_level = -1;
uint64_t ignored_min_log_number_to_keep = 0;
bool ignored_has_min_log_number_to_keep = false;
VersionEdit::NewFiles ignored_new_files;
FileMetaData file;
const char* err = VersionEdit::DecodeNewFile4From(
&file_input, ignored_max_level, ignored_min_log_number_to_keep,
ignored_has_min_log_number_to_keep, ignored_new_files, file);
if (err != nullptr) {
return Status::Corruption("SubcompactionProgressPerLevel", err);
}
output_files.push_back(std::move(file));
}
return Status::OK();
}
void SubcompactionProgress::EncodeTo(std::string* dst) const {
if (!next_internal_key_to_compact.empty()) {
PutVarint32(dst, SubcompactionProgressCustomTag::kNextInternalKeyToCompact);
PutLengthPrefixedSlice(dst, next_internal_key_to_compact);
}
PutVarint32(dst, SubcompactionProgressCustomTag::kNumProcessedInputRecords);
std::string varint_records;
PutVarint64(&varint_records, num_processed_input_records);
PutLengthPrefixedSlice(dst, varint_records);
if (output_level_progress.GetOutputFiles().size() >
output_level_progress.GetLastPersistedOutputFilesCount()) {
PutVarint32(dst, SubcompactionProgressCustomTag::kOutputLevelProgress);
std::string level_progress_data;
output_level_progress.EncodeTo(&level_progress_data);
PutLengthPrefixedSlice(dst, level_progress_data);
}
if (proximal_output_level_progress.GetOutputFiles().size() >
proximal_output_level_progress.GetLastPersistedOutputFilesCount()) {
PutVarint32(dst,
SubcompactionProgressCustomTag::kProximalOutputLevelProgress);
std::string level_progress_data;
proximal_output_level_progress.EncodeTo(&level_progress_data);
PutLengthPrefixedSlice(dst, level_progress_data);
}
PutVarint32(dst,
SubcompactionProgressCustomTag::kSubcompactionProgressTerminate);
}
Status SubcompactionProgress::DecodeFrom(Slice* input) {
Clear();
while (true) {
uint32_t custom_tag = 0;
if (!GetVarint32(input, &custom_tag)) {
return Status::Corruption("SubcompactionProgress",
"custom field tag error");
}
if (custom_tag ==
SubcompactionProgressCustomTag::kSubcompactionProgressTerminate) {
break;
}
Slice field;
if (!GetLengthPrefixedSlice(input, &field)) {
return Status::Corruption("SubcompactionProgress",
"custom field length prefixed slice error");
}
switch (custom_tag) {
case SubcompactionProgressCustomTag::kNextInternalKeyToCompact:
next_internal_key_to_compact = field.ToString();
break;
case SubcompactionProgressCustomTag::kNumProcessedInputRecords:
if (!GetVarint64(&field, &num_processed_input_records)) {
return Status::Corruption("SubcompactionProgress",
"invalid num_processed_input_records");
}
break;
case SubcompactionProgressCustomTag::kOutputLevelProgress: {
Status s = output_level_progress.DecodeFrom(&field);
if (!s.ok()) {
return s;
}
break;
}
case SubcompactionProgressCustomTag::kProximalOutputLevelProgress: {
Status s = proximal_output_level_progress.DecodeFrom(&field);
if (!s.ok()) {
return s;
}
break;
}
default:
if ((custom_tag & SubcompactionProgressCustomTag::
kSubcompactionProgressCustomTagSafeIgnoreMask) !=
0) {
break;
} else {
return Status::NotSupported("SubcompactionProgress",
"unsupported critical custom field");
}
}
}
return Status::OK();
}
bool SubcompactionProgressBuilder::ProcessVersionEdit(const VersionEdit& edit) {
if (!edit.HasSubcompactionProgress()) {
return false;
}
const SubcompactionProgress& progress = edit.GetSubcompactionProgress();
MergeDeltaProgress(progress);
has_subcompaction_progress_ = true;
return true;
}
void SubcompactionProgressBuilder::MergeDeltaProgress(
const SubcompactionProgress& delta_progress) {
accumulated_subcompaction_progress_.next_internal_key_to_compact =
delta_progress.next_internal_key_to_compact;
accumulated_subcompaction_progress_.num_processed_input_records =
delta_progress.num_processed_input_records;
MaybeMergeDeltaProgressPerLevel(
accumulated_subcompaction_progress_.output_level_progress,
delta_progress.output_level_progress);
MaybeMergeDeltaProgressPerLevel(
accumulated_subcompaction_progress_.proximal_output_level_progress,
delta_progress.proximal_output_level_progress);
}
void SubcompactionProgressBuilder::MaybeMergeDeltaProgressPerLevel(
SubcompactionProgressPerLevel& accumulated_level_progress,
const SubcompactionProgressPerLevel& delta_level_progress) {
const auto& delta_files = delta_level_progress.GetOutputFiles();
if (delta_files.empty()) {
return;
}
for (const FileMetaData& file : delta_files) {
accumulated_level_progress.AddToOutputFiles(file); // Stored as copy
}
accumulated_level_progress.SetNumProcessedOutputRecords(
delta_level_progress.GetNumProcessedOutputRecords());
}
void SubcompactionProgressBuilder::Clear() {
accumulated_subcompaction_progress_.Clear();
has_subcompaction_progress_ = false;
}
} // namespace ROCKSDB_NAMESPACE
+8 -231
View File
@@ -72,23 +72,6 @@ enum Tag : uint32_t {
kWalAddition2,
kWalDeletion2,
kPersistUserDefinedTimestamps,
kSubcompactionProgress,
};
enum SubcompactionProgressPerLevelCustomTag : uint32_t {
kSubcompactionProgressPerLevelTerminate = 1, // End of fields marker
kOutputFilesDelta = 2,
kNumProcessedOutputRecords = 3,
kSubcompactionProgressPerLevelCustomTagSafeIgnoreMask = 1 << 16,
};
enum SubcompactionProgressCustomTag : uint32_t {
kSubcompactionProgressTerminate = 1, // End of fields marker
kNextInternalKeyToCompact = 2,
kNumProcessedInputRecords = 3,
kOutputLevelProgress = 4,
kProximalOutputLevelProgress = 5,
kSubcompactionProgressCustomTagSafeIgnoreMask = 1 << 16,
};
enum NewFileCustomTag : uint32_t {
@@ -457,194 +440,12 @@ struct LevelFilesBrief {
}
};
struct SubcompactionProgressPerLevel {
uint64_t GetNumProcessedOutputRecords() const {
return num_processed_output_records_;
}
void SetNumProcessedOutputRecords(uint64_t num) {
num_processed_output_records_ = num;
}
const autovector<FileMetaData>& GetOutputFiles() const {
return output_files_;
}
void AddToOutputFiles(const FileMetaData& file) {
output_files_.push_back(file);
}
size_t GetLastPersistedOutputFilesCount() const {
return last_persisted_output_files_count_;
}
void UpdateLastPersistedOutputFilesCount() {
last_persisted_output_files_count_ = output_files_.size();
}
void EncodeTo(std::string* dst) const;
Status DecodeFrom(Slice* input);
void Clear() {
num_processed_output_records_ = 0;
output_files_.clear();
last_persisted_output_files_count_ = 0;
}
std::string ToString() const {
std::ostringstream oss;
oss << "SubcompactionProgressPerLevel{";
oss << " num_processed_output_records=" << num_processed_output_records_;
oss << ", output_files_count=" << output_files_.size();
oss << ", last_persisted_output_files_count="
<< last_persisted_output_files_count_;
oss << " }";
return oss.str();
}
void TEST_ClearOutputFiles() { output_files_.clear(); }
private:
uint64_t num_processed_output_records_ = 0;
autovector<FileMetaData> output_files_ = {};
// Number of files already persisted to help calculate the new output files to
// persist in the future. This is to prevent having to persist all the output
// files metadata so far every time of a "snapshot" of a progress is persisted
// which can lead to O(1+2+...+n) = O(n^2) file metadata being persisted. The
// current approach of persisting only the delta should always persist
// exactly the number (n) of output files in total.
size_t last_persisted_output_files_count_ = 0;
void EncodeOutputFiles(std::string* dst) const;
Status DecodeOutputFiles(Slice* input,
autovector<FileMetaData>& temp_storage);
};
struct SubcompactionProgress {
std::string next_internal_key_to_compact;
uint64_t num_processed_input_records = 0;
SubcompactionProgressPerLevel output_level_progress;
SubcompactionProgressPerLevel proximal_output_level_progress;
SubcompactionProgress() = default;
void Clear() {
next_internal_key_to_compact.clear();
num_processed_input_records = 0;
output_level_progress.Clear();
proximal_output_level_progress.Clear();
}
void EncodeTo(std::string* dst) const;
Status DecodeFrom(Slice* input);
std::string ToString() const {
std::ostringstream oss;
oss << "SubcompactionProgress{";
oss << " next_internal_key_to_compact=";
if (next_internal_key_to_compact.empty()) {
oss << "";
} else {
ParsedInternalKey parsed_key;
Slice key_slice(next_internal_key_to_compact);
if (ParseInternalKey(key_slice, &parsed_key, false /* log_err_key */)
.ok()) {
oss << "user_key(hex)=" << parsed_key.user_key.ToString(true /* hex */);
oss << ", seq=";
if (parsed_key.sequence == kMaxSequenceNumber) {
oss << "kMaxSequenceNumber";
} else {
oss << parsed_key.sequence;
}
oss << ", type=";
if (parsed_key.type == kValueTypeForSeek) {
oss << "kValueTypeForSeek";
} else {
oss << static_cast<int>(parsed_key.type);
}
} else {
oss << "raw=" << key_slice.ToString(true /* hex */);
}
}
oss << ", num_processed_input_records=" << num_processed_input_records;
oss << ", output_level_progress=" << output_level_progress.ToString();
oss << ", proximal_output_level_progress="
<< proximal_output_level_progress.ToString();
oss << " }";
return oss.str();
}
};
class VersionEdit;
// Builder class to reconstruct complete subcompaction progress object
// from multiple decoded VersionEdits containing delta output files information
// of the same subcompaction. See
// `SubcompactionProgressPerLevel::last_persisted_output_files_count_`'s comment
//
// WARNING: This class currently assumes all input VersionEdits contain progress
// information for the SAME subcompaction. It does not validate
// progress data from different subcompactions so mixing progress from
// multiple subcompactions can result in corrupted state silently. The caller is
// responsible for ensuring all VersionEdits processed by a single instance
// of this builder correspond to the same subcompaction.
class SubcompactionProgressBuilder {
public:
SubcompactionProgressBuilder() = default;
bool ProcessVersionEdit(const VersionEdit& edit);
const SubcompactionProgress& GetAccumulatedSubcompactionProgress() const {
return accumulated_subcompaction_progress_;
}
bool HasAccumulatedSubcompactionProgress() const {
return has_subcompaction_progress_;
}
void Clear();
private:
void MergeDeltaProgress(const SubcompactionProgress& delta_progress);
void MaybeMergeDeltaProgressPerLevel(
SubcompactionProgressPerLevel& accumulated_level_progress,
const SubcompactionProgressPerLevel& delta_level_progress);
SubcompactionProgress accumulated_subcompaction_progress_;
bool has_subcompaction_progress_ = false;
};
// Type alias for backward compatibility - vector of subcompaction progress
using CompactionProgress = std::vector<SubcompactionProgress>;
// The state of a DB at any given time is referred to as a Version.
// Any modification to the Version is considered a Version Edit. A Version is
// constructed by joining a sequence of Version Edits. Version Edits are written
// to the MANIFEST file.
class VersionEdit {
public:
// Retrieve the table files added as well as their associated levels.
using NewFiles = std::vector<std::pair<int, FileMetaData>>;
static void EncodeToNewFile4(const FileMetaData& f, int level, size_t ts_sz,
bool has_min_log_number_to_keep,
uint64_t min_log_number_to_keep,
bool& min_log_num_written, std::string* dst);
static const char* DecodeNewFile4From(Slice* input, int& max_level,
uint64_t& min_log_number_to_keep,
bool& has_min_log_number_to_keep,
NewFiles& new_files, FileMetaData& f);
void Clear();
void SetDBId(const std::string& db_id) {
@@ -763,6 +564,8 @@ class VersionEdit {
}
}
// Retrieve the table files added as well as their associated levels.
using NewFiles = std::vector<std::pair<int, FileMetaData>>;
const NewFiles& GetNewFiles() const { return new_files_; }
NewFiles& GetMutableNewFiles() { return new_files_; }
@@ -932,22 +735,6 @@ class VersionEdit {
full_history_ts_low_ = std::move(full_history_ts_low);
}
void SetSubcompactionProgress(const SubcompactionProgress& progress) {
has_subcompaction_progress_ = true;
subcompaction_progress_ = progress;
}
bool HasSubcompactionProgress() const { return has_subcompaction_progress_; }
const SubcompactionProgress& GetSubcompactionProgress() const {
return subcompaction_progress_;
}
void ClearSubcompactionProgress() {
has_subcompaction_progress_ = false;
subcompaction_progress_.Clear();
}
// return true on success.
// `ts_sz` is the size in bytes for the user-defined timestamp contained in
// a user key. This argument is optional because it's only required for
@@ -970,22 +757,15 @@ class VersionEdit {
std::string DebugJSON(int edit_num, bool hex_key = false) const;
private:
// Decode level information from serialized VersionEdit data and and track the
// maximum level seen.
//
// Parameters:
// input: Pointer to serialized data slice
// level: Output parameter for the decoded level value
// max_level: get updated if the decoded level is higher than passed in
// value
//
// Returns: true on successful decode, false on parse error
static bool GetLevel(Slice* input, int* level, int& max_level);
bool GetLevel(Slice* input, int* level, const char** msg);
const char* DecodeNewFile4From(Slice* input);
// Encode file boundaries `FileMetaData.smallest` and `FileMetaData.largest`.
// User-defined timestamps in the user key will be stripped if they shouldn't
// be persisted.
static void EncodeFileBoundaries(std::string* dst, const FileMetaData& meta,
size_t ts_sz);
void EncodeFileBoundaries(std::string* dst, const FileMetaData& meta,
size_t ts_sz) const;
int max_level_ = 0;
std::string db_id_;
@@ -1036,9 +816,6 @@ class VersionEdit {
std::string full_history_ts_low_;
bool persist_user_defined_timestamps_ = true;
bool has_subcompaction_progress_ = false;
SubcompactionProgress subcompaction_progress_;
// Newly created table files and blob files are eligible for deletion if they
// are not registered as live files after the background jobs creating them
// have finished. In case committing the VersionEdit containing such changes
-333
View File
@@ -794,339 +794,6 @@ TEST(FileMetaDataTest, UpdateBoundariesBlobIndex) {
}
}
class SubcompactionProgressTest : public VersionEditTest {
protected:
static constexpr uint64_t kTestFileSize = 1024;
static constexpr SequenceNumber kTestSmallestSeq = 50;
static constexpr SequenceNumber kTestLargestSeq = 150;
static constexpr uint64_t kTestOldestAncesterTime = 12345;
static constexpr uint64_t kTestFileCreationTime = 67890;
static constexpr uint64_t kTestEpochNumber = 10;
static const std::string kTestChecksumFuncName;
FileMetaData CreateTestFile(uint64_t file_number, const std::string& prefix) {
FileMetaData file;
file.fd = FileDescriptor(file_number, 0, kTestFileSize, kTestSmallestSeq,
kTestLargestSeq);
file.smallest = InternalKey(prefix + "a", kTestSmallestSeq, kTypeValue);
file.largest = InternalKey(prefix + "z", kTestLargestSeq, kTypeValue);
file.oldest_ancester_time = kTestOldestAncesterTime;
file.file_creation_time = kTestFileCreationTime;
file.epoch_number = kTestEpochNumber;
file.file_checksum = "checksum_" + std::to_string(file_number);
file.file_checksum_func_name = kTestChecksumFuncName;
file.marked_for_compaction = false;
file.temperature = Temperature::kUnknown;
return file;
}
// Store external file metadata objects for testing
// These simulate files owned by CompactionOutputs
std::vector<FileMetaData> compaction_output_files_;
std::vector<FileMetaData> proximal_level_compaction_output_files_;
SubcompactionProgress CreateSubcompactionProgress(
const std::string& next_key, uint64_t num_processed_input_records,
uint64_t num_processed_output_records,
uint64_t num_processed_proximal_level_output_records,
const std::vector<uint64_t>& output_file_numbers = {},
const std::vector<uint64_t>& proximal_file_numbers = {},
const std::string& file_prefix = "file_") {
SubcompactionProgress progress;
progress.next_internal_key_to_compact = next_key;
progress.num_processed_input_records = num_processed_input_records;
progress.output_level_progress.SetNumProcessedOutputRecords(
num_processed_output_records);
progress.proximal_output_level_progress.SetNumProcessedOutputRecords(
num_processed_proximal_level_output_records);
for (uint64_t file_num : output_file_numbers) {
FileMetaData file = CreateTestFile(file_num, file_prefix + "output_");
progress.output_level_progress.AddToOutputFiles(file);
}
for (uint64_t file_num : proximal_file_numbers) {
FileMetaData file = CreateTestFile(file_num, file_prefix + "proximal_");
progress.proximal_output_level_progress.AddToOutputFiles(file);
}
return progress;
}
std::pair<const VersionEdit, const SubcompactionProgress>
EncodeDecodeProgress(const SubcompactionProgress& progress) {
VersionEdit edit;
edit.SetSubcompactionProgress(progress);
std::string encoded;
EXPECT_TRUE(edit.EncodeTo(&encoded, 0 /* ts_sz */));
VersionEdit decoded_edit;
EXPECT_OK(decoded_edit.DecodeFrom(encoded));
EXPECT_TRUE(decoded_edit.HasSubcompactionProgress());
SubcompactionProgress decoded_progress =
decoded_edit.GetSubcompactionProgress();
return {std::move(decoded_edit), std::move(decoded_progress)};
}
void VerifyFileMetaDataEquality(const FileMetaData& expected,
const FileMetaData& actual) {
// Verify the major fields only
ASSERT_EQ(actual.fd.GetNumber(), expected.fd.GetNumber());
ASSERT_EQ(actual.fd.GetFileSize(), expected.fd.GetFileSize());
ASSERT_EQ(actual.smallest.Encode(), expected.smallest.Encode());
ASSERT_EQ(actual.largest.Encode(), expected.largest.Encode());
ASSERT_EQ(actual.oldest_ancester_time, expected.oldest_ancester_time);
ASSERT_EQ(actual.file_creation_time, expected.file_creation_time);
ASSERT_EQ(actual.epoch_number, expected.epoch_number);
ASSERT_EQ(actual.file_checksum, expected.file_checksum);
ASSERT_EQ(actual.file_checksum_func_name, expected.file_checksum_func_name);
ASSERT_EQ(actual.marked_for_compaction, expected.marked_for_compaction);
ASSERT_EQ(actual.temperature, expected.temperature);
}
void VerifyProgressEquality(const SubcompactionProgress& expected,
const SubcompactionProgress& actual) {
ASSERT_EQ(actual.next_internal_key_to_compact,
expected.next_internal_key_to_compact);
ASSERT_EQ(actual.num_processed_input_records,
expected.num_processed_input_records);
for (const bool is_proximal_level : {false, true}) {
const SubcompactionProgressPerLevel&
actual_subcompaction_progress_by_level =
is_proximal_level ? actual.proximal_output_level_progress
: actual.output_level_progress;
const SubcompactionProgressPerLevel&
expected_subcompaction_progress_by_level =
is_proximal_level ? expected.proximal_output_level_progress
: expected.output_level_progress;
ASSERT_EQ(
actual_subcompaction_progress_by_level.GetNumProcessedOutputRecords(),
expected_subcompaction_progress_by_level
.GetNumProcessedOutputRecords());
ASSERT_EQ(
actual_subcompaction_progress_by_level.GetOutputFiles().size(),
expected_subcompaction_progress_by_level.GetOutputFiles().size());
for (size_t i = 0;
i < expected_subcompaction_progress_by_level.GetOutputFiles().size();
++i) {
VerifyFileMetaDataEquality(
expected_subcompaction_progress_by_level.GetOutputFiles()[i],
actual_subcompaction_progress_by_level.GetOutputFiles()[i]);
}
}
}
};
const std::string SubcompactionProgressTest::kTestChecksumFuncName = "crc32c";
TEST_F(SubcompactionProgressTest, BasicEncodeDecode) {
// Create progress with files for both levels
SubcompactionProgress progress = CreateSubcompactionProgress(
"key_100", // next_internal_key_to_compact
500, // num_processed_input_records
400, // num_processed_output_records
100, // num_processed_proximal_level_output_records
{1}, // output_file_numbers
{2}, // proximal_file_numbers
"test_" // file_prefix
);
auto [ignored, decoded_progress] = EncodeDecodeProgress(progress);
VerifyProgressEquality(progress, decoded_progress);
}
TEST_F(SubcompactionProgressTest, OutputFilesDeltaEncodeDecode) {
// Test Delta Encoding/Decoding
SubcompactionProgress initial_progress = CreateSubcompactionProgress(
"key_100", // next_internal_key_to_compact
100, // num_processed_input_records
40, // num_processed_output_records
60, // num_processed_proximal_level_output_records
{1}, // output_file_numbers
{2}, // proximal_file_numbers
"initial_" // file_prefix
);
auto [initial_decoded_edit, ignored_1] =
EncodeDecodeProgress(initial_progress);
initial_progress.output_level_progress.UpdateLastPersistedOutputFilesCount();
initial_progress.proximal_output_level_progress
.UpdateLastPersistedOutputFilesCount();
// Add one new output file to output and proximal level
SubcompactionProgress updated_progress = initial_progress;
updated_progress.next_internal_key_to_compact = "key_300";
updated_progress.num_processed_input_records = 1000;
updated_progress.output_level_progress.SetNumProcessedOutputRecords(400);
updated_progress.proximal_output_level_progress.SetNumProcessedOutputRecords(
600);
FileMetaData new_file = CreateTestFile(3, "new_");
updated_progress.output_level_progress.AddToOutputFiles(new_file);
FileMetaData new_file_proximal = CreateTestFile(4, "new_");
updated_progress.proximal_output_level_progress.AddToOutputFiles(
new_file_proximal);
auto [delta_decoded_edit, delta_decoded_progress] =
EncodeDecodeProgress(updated_progress);
ASSERT_EQ(delta_decoded_progress.next_internal_key_to_compact,
updated_progress.next_internal_key_to_compact);
ASSERT_EQ(delta_decoded_progress.num_processed_input_records,
updated_progress.num_processed_input_records);
for (const bool& is_proximal_level : {false, true}) {
const SubcompactionProgressPerLevel& delta_progress_per_level =
is_proximal_level
? delta_decoded_progress.proximal_output_level_progress
: delta_decoded_progress.output_level_progress;
const SubcompactionProgressPerLevel& updated_progress_per_level =
is_proximal_level ? updated_progress.proximal_output_level_progress
: updated_progress.output_level_progress;
ASSERT_EQ(delta_progress_per_level.GetNumProcessedOutputRecords(),
updated_progress_per_level.GetNumProcessedOutputRecords());
// Only the newly added file since last persistence should be present
ASSERT_EQ(delta_progress_per_level.GetOutputFiles().size(), 1);
ASSERT_EQ(delta_progress_per_level.GetOutputFiles()[0].fd.GetNumber(),
is_proximal_level ? new_file_proximal.fd.GetNumber()
: new_file.fd.GetNumber());
}
// Test SubcompactionProgressBuilder
SubcompactionProgressBuilder builder;
ASSERT_FALSE(builder.HasAccumulatedSubcompactionProgress());
ASSERT_TRUE(builder.ProcessVersionEdit(initial_decoded_edit));
ASSERT_TRUE(builder.HasAccumulatedSubcompactionProgress());
ASSERT_TRUE(builder.ProcessVersionEdit(delta_decoded_edit));
const auto& accumulated_progress =
builder.GetAccumulatedSubcompactionProgress();
ASSERT_EQ(accumulated_progress.next_internal_key_to_compact,
updated_progress.next_internal_key_to_compact);
ASSERT_EQ(accumulated_progress.num_processed_input_records,
updated_progress.num_processed_input_records);
for (const bool& is_proximal_level : {false, true}) {
const SubcompactionProgressPerLevel& accumulated_progress_per_level =
is_proximal_level ? accumulated_progress.proximal_output_level_progress
: accumulated_progress.output_level_progress;
const SubcompactionProgressPerLevel& updated_progress_per_level =
is_proximal_level ? updated_progress.proximal_output_level_progress
: updated_progress.output_level_progress;
ASSERT_EQ(accumulated_progress_per_level.GetNumProcessedOutputRecords(),
updated_progress_per_level.GetNumProcessedOutputRecords());
ASSERT_EQ(accumulated_progress_per_level.GetOutputFiles().size(),
updated_progress_per_level.GetOutputFiles().size());
std::set<uint64_t> accumulated_file_numbers;
for (const auto& file : accumulated_progress_per_level.GetOutputFiles()) {
accumulated_file_numbers.insert(file.fd.GetNumber());
}
std::set<uint64_t> expected_file_numbers;
for (const auto& file : updated_progress_per_level.GetOutputFiles()) {
expected_file_numbers.insert(file.fd.GetNumber());
}
ASSERT_EQ(accumulated_file_numbers, expected_file_numbers);
}
// ===== PART 3: Test Builder Reset =====
builder.Clear();
ASSERT_FALSE(builder.HasAccumulatedSubcompactionProgress());
}
TEST_F(SubcompactionProgressTest, UnknownTags) {
SubcompactionProgress progress;
std::string encoded;
// 1. Test unknown ignorable tag
progress.next_internal_key_to_compact = "test_key";
progress.num_processed_input_records = 100;
PutVarint32(&encoded,
SubcompactionProgressCustomTag::kNextInternalKeyToCompact);
PutLengthPrefixedSlice(&encoded, progress.next_internal_key_to_compact);
PutVarint32(&encoded,
SubcompactionProgressCustomTag::kNumProcessedInputRecords);
std::string varint_records;
PutVarint64(&varint_records, progress.num_processed_input_records);
PutLengthPrefixedSlice(&encoded, varint_records);
// Manually encode with unknown ignorable tag (has
// SubcompactionProgressCustomTag::kSubcompactionProgressCustomTagSafeIgnoreMask
// bit set)
uint32_t unknown_ignorable_tag =
SubcompactionProgressCustomTag::
kSubcompactionProgressCustomTagSafeIgnoreMask +
1;
PutVarint32(&encoded, unknown_ignorable_tag);
PutLengthPrefixedSlice(&encoded, "future_data");
PutVarint32(&encoded,
SubcompactionProgressCustomTag::kSubcompactionProgressTerminate);
// Test decoding - should succeed and ignore unknown tag
Slice input(encoded);
SubcompactionProgress decoded_progress;
Status s = decoded_progress.DecodeFrom(&input);
ASSERT_OK(s);
// Verify known fields are preserved
ASSERT_EQ(decoded_progress.next_internal_key_to_compact,
progress.next_internal_key_to_compact);
ASSERT_EQ(decoded_progress.num_processed_input_records,
progress.num_processed_input_records);
// 2. Test unknown non-ignorable tag
encoded.clear();
PutVarint32(&encoded,
SubcompactionProgressCustomTag::kNextInternalKeyToCompact);
PutLengthPrefixedSlice(&encoded, "test_key");
// Manually encode with unknown non-ignorable tag (do not have
// SubcompactionProgressCustomTag::kSubcompactionProgressCustomTagSafeIgnoreMask
// bit set)
uint32_t unknown_critical_tag =
SubcompactionProgressCustomTag::
kSubcompactionProgressCustomTagSafeIgnoreMask -
1;
PutVarint32(&encoded, unknown_critical_tag);
PutLengthPrefixedSlice(&encoded, "critical_future_data");
PutVarint32(&encoded,
SubcompactionProgressCustomTag::kSubcompactionProgressTerminate);
// Test decoding - should fail on critical unknown tag
Slice critical_input(encoded);
SubcompactionProgress critical_progress;
Status critical_status = critical_progress.DecodeFrom(&critical_input);
ASSERT_NOK(critical_status);
ASSERT_TRUE(critical_status.IsNotSupported());
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+88 -288
View File
@@ -16,7 +16,6 @@
#include <list>
#include <map>
#include <set>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <vector>
@@ -981,8 +980,7 @@ class LevelIterator final : public InternalIterator {
nullptr,
bool allow_unprepared_value = false,
std::unique_ptr<TruncatedRangeDelIterator>*** range_tombstone_iter_ptr_ =
nullptr,
Statistics* db_statistics = nullptr, SystemClock* clock = nullptr)
nullptr)
: table_cache_(table_cache),
read_options_(read_options),
file_options_(file_options),
@@ -1007,9 +1005,7 @@ class LevelIterator final : public InternalIterator {
allow_unprepared_value_(allow_unprepared_value),
is_next_read_sequential_(false),
to_return_sentinel_(false),
scan_opts_(nullptr),
db_statistics_(db_statistics),
clock_(clock) {
scan_opts_(nullptr) {
// Empty level is not supported.
assert(flevel_ != nullptr && flevel_->num_files > 0);
if (range_tombstone_iter_ptr_) {
@@ -1017,15 +1013,7 @@ class LevelIterator final : public InternalIterator {
}
}
~LevelIterator() override {
delete file_iter_.Set(nullptr);
// Clean up any prepared iterators that weren't used
for (auto& entry : prepared_iters_) {
delete entry.second;
}
prepared_iters_.clear();
assert(prepared_iters_.size() == 0);
}
~LevelIterator() override { delete file_iter_.Set(nullptr); }
// Seek to the first file with a key >= target.
// If range_tombstone_iter_ is not nullptr, then we pretend that file
@@ -1136,12 +1124,10 @@ class LevelIterator final : public InternalIterator {
void Prepare(const MultiScanArgs* so) override {
// We assume here that scan_opts is sorted such that
// scan_opts[0].range.start < scan_opts[1].range.start, and non
// overlapping
// scan_opts[0].range.start < scan_opts[1].range.start, and non overlapping
if (so == nullptr) {
return;
}
scan_opts_ = so;
// Verify comparator is consistent
@@ -1211,35 +1197,9 @@ class LevelIterator final : public InternalIterator {
}
}
}
StopWatch timer(clock_, db_statistics_, MULTISCAN_PREPARE_ITERATORS);
// Propagate multiscan configs
for (auto& file_to_arg : *file_to_scan_opts_) {
file_to_arg.second.CopyConfigFrom(*so);
assert(OverlapRange(*file_to_arg.second.GetScanRanges().begin(),
file_to_arg.first) &&
OverlapRange(*file_to_arg.second.GetScanRanges().rbegin(),
file_to_arg.first));
}
if (so->use_async_io) {
auto before = file_index_;
// Pre-create and prepare only relevant file iterators
for (auto& file_to_arg : *file_to_scan_opts_) {
size_t file_index = file_to_arg.first;
file_index_ = file_index;
// Create iterator for this file
auto iter = NewFileIterator();
if (iter != nullptr) {
// If we have async enabled, lets prepare all our iterators.
iter->Prepare(&file_to_arg.second);
// Store the prepared iterator
prepared_iters_[file_index] = iter;
}
}
file_index_ = before;
}
}
@@ -1316,7 +1276,7 @@ class LevelIterator final : public InternalIterator {
}
#ifndef NDEBUG
bool OverlapRange(const ScanOptions& opts, size_t file_index);
bool OverlapRange(const ScanOptions& opts);
#endif
TableCache* table_cache_;
@@ -1374,15 +1334,9 @@ class LevelIterator final : public InternalIterator {
bool to_return_sentinel_ = false;
const MultiScanArgs* scan_opts_ = nullptr;
Statistics* db_statistics_ = nullptr;
SystemClock* clock_ = nullptr;
// Our stored scan_opts for each prefix
std::unique_ptr<ScanOptionsMap> file_to_scan_opts_ = nullptr;
// Map to store pre-created iterators by file index
std::unordered_map<size_t, InternalIterator*> prepared_iters_;
// Sets flags for if we should return the sentinel key next.
// The condition for returning sentinel is reaching the end of current
// file_iter_: !Valid() && status.().ok().
@@ -1659,19 +1613,8 @@ bool LevelIterator::SkipEmptyFileForward() {
const ScanOptions& opts =
GetMultiScanArgForFile(file_index_).GetScanRanges().front();
if (opts.range.start.has_value()) {
InternalKey target;
const size_t ts_size =
user_comparator_.user_comparator()->timestamp_size();
if (ts_size == 0) {
target = InternalKey(opts.range.start.value(), kMaxSequenceNumber,
kValueTypeForSeek);
} else {
std::string seek_key;
AppendKeyWithMaxTimestamp(&seek_key, opts.range.start.value(),
ts_size);
target =
InternalKey(seek_key, kMaxSequenceNumber, kValueTypeForSeek);
}
InternalKey target(*opts.range.start.AsPtr(), kMaxSequenceNumber,
kValueTypeForSeek);
file_iter_.Seek(target.Encode());
}
} else {
@@ -1719,14 +1662,14 @@ void LevelIterator::SkipEmptyFileBackward() {
}
#ifndef NDEBUG
bool LevelIterator::OverlapRange(const ScanOptions& opts, size_t file_index) {
bool LevelIterator::OverlapRange(const ScanOptions& opts) {
return (user_comparator_.CompareWithoutTimestamp(
opts.range.start.value(), /*a_has_ts=*/false,
ExtractUserKey(flevel_->files[file_index].largest_key),
ExtractUserKey(flevel_->files[file_index_].largest_key),
/*b_has_ts=*/true) <= 0 &&
user_comparator_.CompareWithoutTimestamp(
opts.range.limit.value(), /*a_has_ts=*/false,
ExtractUserKey(flevel_->files[file_index].smallest_key),
ExtractUserKey(flevel_->files[file_index_].smallest_key),
/*b_has_ts=*/true) > 0);
}
#endif
@@ -1737,6 +1680,15 @@ void LevelIterator::SetFileIterator(InternalIterator* iter) {
}
InternalIterator* old_iter = file_iter_.Set(iter);
if (iter && scan_opts_) {
if (FileHasMultiScanArg(file_index_)) {
const MultiScanArgs& new_opts = GetMultiScanArgForFile(file_index_);
assert(OverlapRange(*new_opts.GetScanRanges().begin()) &&
OverlapRange(*new_opts.GetScanRanges().rbegin()));
file_iter_.Prepare(&new_opts);
}
}
// Update the read pattern for PrefetchBuffer.
if (is_next_read_sequential_) {
file_iter_.UpdateReadaheadState(old_iter);
@@ -1766,24 +1718,7 @@ void LevelIterator::InitFileIterator(size_t new_file_index) {
// no need to change anything
} else {
file_index_ = new_file_index;
if (!prepared_iters_.empty()) {
auto prepared_it = prepared_iters_.find(file_index_);
if (prepared_it != prepared_iters_.end()) {
InternalIterator* iter = prepared_it->second;
prepared_iters_.erase(prepared_it);
SetFileIterator(iter);
return;
}
}
InternalIterator* iter = NewFileIterator();
if (FileHasMultiScanArg(file_index_)) {
auto& args = GetMultiScanArgForFile(file_index_);
assert(OverlapRange(*args.GetScanRanges().begin(), file_index_) &&
OverlapRange(*args.GetScanRanges().rbegin(), file_index_));
iter->Prepare(&args);
}
SetFileIterator(iter);
}
}
@@ -2089,79 +2024,6 @@ void Version::GetColumnFamilyMetaData(ColumnFamilyMetaData* cf_meta) {
}
}
void Version::GetColumnFamilyMetaData(
const GetColumnFamilyMetaDataOptions& options,
ColumnFamilyMetaData* cf_meta) {
assert(cf_meta);
assert(cfd_);
cf_meta->name = cfd_->GetName();
cf_meta->size = 0;
cf_meta->file_count = 0;
cf_meta->levels.clear();
cf_meta->blob_file_size = 0;
cf_meta->blob_file_count = 0;
cf_meta->blob_files.clear();
const auto& ioptions = cfd_->ioptions();
auto* vstorage = storage_info();
int first_level = (options.level >= 0) ? options.level : 0;
int last_level =
(options.level >= 0) ? options.level + 1 : cfd_->NumberLevels();
InternalKey ikey_start, ikey_end;
const InternalKey* begin = nullptr;
const InternalKey* end = nullptr;
if (options.range.start.has_value()) {
ikey_start = InternalKey(options.range.start.value(), kMaxSequenceNumber,
kValueTypeForSeek);
begin = &ikey_start;
}
if (options.range.limit.has_value()) {
ikey_end = InternalKey(options.range.limit.value(), kMaxSequenceNumber,
kValueTypeForSeek);
end = &ikey_end;
}
for (int l = first_level; l < last_level; ++l) {
uint64_t level_size = 0;
std::vector<SstFileMetaData> files;
std::vector<FileMetaData*> overlapping_files;
vstorage->GetOverlappingInputs(l, begin, end, &overlapping_files);
for (const auto& file : overlapping_files) {
uint32_t path_id = file->fd.GetPathId();
const auto& file_path = (path_id < ioptions.cf_paths.size())
? ioptions.cf_paths[path_id].path
: ioptions.cf_paths.back().path;
const uint64_t file_number = file->fd.GetNumber();
files.emplace_back(
MakeTableFileName("", file_number), file_number, file_path,
file->fd.GetFileSize(), file->fd.smallest_seqno,
file->fd.largest_seqno, file->smallest.user_key().ToString(),
file->largest.user_key().ToString(),
file->stats.num_reads_sampled.load(std::memory_order_relaxed),
file->being_compacted, file->temperature,
file->oldest_blob_file_number, file->TryGetOldestAncesterTime(),
file->TryGetFileCreationTime(), file->epoch_number,
file->file_checksum, file->file_checksum_func_name);
files.back().num_entries = file->num_entries;
files.back().num_deletions = file->num_deletions;
files.back().smallest = file->smallest.Encode().ToString();
files.back().largest = file->largest.Encode().ToString();
level_size += file->fd.GetFileSize();
cf_meta->file_count++;
}
if (!files.empty()) {
cf_meta->levels.emplace_back(l, level_size, std::move(files));
cf_meta->size += level_size;
}
}
}
uint64_t Version::GetSstFilesSize() {
uint64_t sst_files_size = 0;
for (int level = 0; level < storage_info_.num_levels_; level++) {
@@ -2246,7 +2108,7 @@ InternalIterator* Version::TEST_GetLevelIterator(
cfd_->internal_stats()->GetFileReadHist(level),
TableReaderCaller::kUserIterator, IsFilterSkipped(level), level,
nullptr /* range_del_agg */, nullptr /* compaction_boundaries */,
allow_unprepared_value, &tombstone_iter_ptr, db_statistics_, clock_);
allow_unprepared_value, &tombstone_iter_ptr);
if (read_options.ignore_range_deletions) {
merge_iter_builder->AddIterator(level_iter);
} else {
@@ -2386,7 +2248,7 @@ void Version::AddIteratorsForLevel(const ReadOptions& read_options,
TableReaderCaller::kUserIterator, IsFilterSkipped(level), level,
/*range_del_agg=*/nullptr,
/*compaction_boundaries=*/nullptr, allow_unprepared_value,
&tombstone_iter_ptr, db_statistics_, clock_);
&tombstone_iter_ptr);
if (read_options.ignore_range_deletions) {
merge_iter_builder->AddIterator(level_iter);
} else {
@@ -2443,7 +2305,7 @@ Status Version::OverlapWithLevelIterator(const ReadOptions& read_options,
mutable_cf_options_, should_sample_file_read(),
cfd_->internal_stats()->GetFileReadHist(level),
TableReaderCaller::kUserIterator, IsFilterSkipped(level), level,
&range_del_agg, nullptr, false, nullptr, db_statistics_, clock_));
&range_del_agg, nullptr, false));
status = OverlapWithIterator(ucmp, smallest_user_key, largest_user_key,
iter.get(), overlap);
}
@@ -4625,8 +4487,7 @@ bool VersionStorageInfo::OverlapInLevel(int level,
void VersionStorageInfo::GetOverlappingInputs(
int level, const InternalKey* begin, const InternalKey* end,
std::vector<FileMetaData*>* inputs, int hint_index, int* file_index,
bool expand_range, const FileMetaData* starting_l0_file,
InternalKey** next_smallest) const {
bool expand_range, InternalKey** next_smallest) const {
if (level >= num_non_empty_levels_) {
// this level is empty, no overlapping inputs
return;
@@ -4659,19 +4520,7 @@ void VersionStorageInfo::GetOverlappingInputs(
// index stores the file index need to check.
std::list<size_t> index;
size_t start_index = 0;
if (starting_l0_file != nullptr) {
uint64_t starting_file_number = starting_l0_file->fd.GetNumber();
for (size_t i = 0; i < level_files_brief_[level].num_files; i++) {
if (level_files_brief_[level].files[i].fd.GetNumber() ==
starting_file_number) {
start_index = i;
break;
}
}
assert(start_index < level_files_brief_[level].num_files);
}
for (size_t i = start_index; i < level_files_brief_[level].num_files; i++) {
for (size_t i = 0; i < level_files_brief_[level].num_files; i++) {
index.emplace_back(i);
}
@@ -5458,7 +5307,6 @@ void AtomicGroupReadBuffer::Clear() {
VersionSet::VersionSet(
const std::string& dbname, const ImmutableDBOptions* _db_options,
const MutableDBOptions& mutable_db_options,
const FileOptions& storage_options, Cache* table_cache,
WriteBufferManager* write_buffer_manager, WriteController* write_controller,
BlockCacheTracer* const block_cache_tracer,
@@ -5487,7 +5335,6 @@ VersionSet::VersionSet(
prev_log_number_(0),
current_version_number_(0),
manifest_file_size_(0),
last_compacted_manifest_file_size_(0),
file_options_(storage_options),
block_cache_tracer_(block_cache_tracer),
io_tracer_(io_tracer),
@@ -5495,9 +5342,7 @@ VersionSet::VersionSet(
offpeak_time_option_(OffpeakTimeOption(daily_offpeak_time_utc)),
error_handler_(error_handler),
unchanging_(unchanging),
closed_(false) {
UpdatedMutableDbOptions(mutable_db_options, /*mu=*/nullptr);
}
closed_(false) {}
Status VersionSet::Close(FSDirectory* db_dir, InstrumentedMutex* mu) {
Status s;
@@ -5593,38 +5438,11 @@ void VersionSet::Reset() {
current_version_number_ = 0;
manifest_writers_.clear();
manifest_file_size_ = 0;
last_compacted_manifest_file_size_ = 0;
TuneMaxManifestFileSize();
obsolete_files_.clear();
obsolete_manifests_.clear();
wals_.Reset();
}
void VersionSet::UpdatedMutableDbOptions(
const MutableDBOptions& updated_options, InstrumentedMutex* mu) {
// Must be holding mutex if not called during initialization
if (mu) {
mu->AssertHeld();
} else {
// manifest_file_size_ must be 0 if called from the constructor
assert(manifest_file_size_ == 0);
}
file_options_.writable_file_max_buffer_size =
updated_options.writable_file_max_buffer_size;
min_max_manifest_file_size_ = updated_options.max_manifest_file_size;
max_manifest_space_amp_pct_ = static_cast<unsigned>(
std::max(updated_options.max_manifest_space_amp_pct, 0));
manifest_preallocation_size_ = updated_options.manifest_preallocation_size;
TuneMaxManifestFileSize();
}
void VersionSet::TuneMaxManifestFileSize() {
tuned_max_manifest_file_size_ =
std::max(min_max_manifest_file_size_,
last_compacted_manifest_file_size_ *
(100U + max_manifest_space_amp_pct_) / 100U);
}
void VersionSet::AppendVersion(ColumnFamilyData* column_family_data,
Version* v) {
// compute new compaction score
@@ -5707,8 +5525,8 @@ Status VersionSet::ProcessManifestWrites(
// the preceding version edits in the same atomic group, and update
// their `remaining_entries_` member variable because we are NOT going
// to write the version edits' of dropped CF to the MANIFEST. If we
// don't update, then Recover can report corrupted atomic group
// because the `remaining_entries_` do not match.
// don't update, then Recover can report corrupted atomic group because
// the `remaining_entries_` do not match.
if (!batch_edits.empty()) {
if (batch_edits.back()->IsInAtomicGroup() &&
batch_edits.back()->GetRemainingEntries() > 0) {
@@ -5868,11 +5686,10 @@ Status VersionSet::ProcessManifestWrites(
}
#endif // NDEBUG
uint64_t prev_manifest_file_size = manifest_file_size_;
assert(pending_manifest_file_number_ == 0);
if (!skip_manifest_write &&
(!descriptor_log_ ||
prev_manifest_file_size >= tuned_max_manifest_file_size_)) {
manifest_file_size_ > db_options_->max_manifest_file_size)) {
TEST_SYNC_POINT("VersionSet::ProcessManifestWrites:BeforeNewManifest");
new_descriptor_log = true;
} else {
@@ -5912,8 +5729,6 @@ Status VersionSet::ProcessManifestWrites(
IOStatus manifest_io_status;
manifest_io_status.PermitUncheckedError();
std::unique_ptr<log::Writer> new_desc_log_ptr;
// Save before releasing mu
uint64_t manifest_preallocation_size = manifest_preallocation_size_;
if (skip_manifest_write) {
if (s.ok()) {
constexpr bool update_stats = true;
@@ -5957,13 +5772,16 @@ Status VersionSet::ProcessManifestWrites(
// This is fine because everything inside of this block is serialized --
// only one thread can be here at the same time
// create new manifest file
ROCKS_LOG_INFO(db_options_->info_log, "Creating manifest %" PRIu64 "\n",
pending_manifest_file_number_);
std::string descriptor_fname =
DescriptorFileName(dbname_, pending_manifest_file_number_);
std::unique_ptr<FSWritableFile> descriptor_file;
io_s = NewWritableFile(fs_.get(), descriptor_fname, &descriptor_file,
opt_file_opts);
if (io_s.ok()) {
descriptor_file->SetPreallocationBlockSize(manifest_preallocation_size);
descriptor_file->SetPreallocationBlockSize(
db_options_->manifest_preallocation_size);
FileTypeSet tmp_set = db_options_->checksum_handoff_file_types;
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
std::move(descriptor_file), descriptor_fname, opt_file_opts, clock_,
@@ -6013,12 +5831,10 @@ Status VersionSet::ProcessManifestWrites(
#ifndef NDEBUG
if (batch_edits.size() > 1 && batch_edits.size() - 1 == idx) {
TEST_SYNC_POINT_CALLBACK(
"VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:"
"0",
"VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:0",
nullptr);
TEST_SYNC_POINT(
"VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:"
"1");
"VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:1");
}
++idx;
#endif /* !NDEBUG */
@@ -6055,8 +5871,8 @@ Status VersionSet::ProcessManifestWrites(
file_options_.temperature, dir_contains_current_file);
if (!io_s.ok()) {
s = io_s;
// Quarantine old manifest file in case new manifest file's CURRENT
// file wasn't created successfully and the old manifest is needed.
// Quarantine old manifest file in case new manifest file's CURRENT file
// wasn't created successfully and the old manifest is needed.
limbo_descriptor_log_file_number.push_back(manifest_file_number_);
files_to_quarantine_if_commit_fail.push_back(
&limbo_descriptor_log_file_number);
@@ -6066,13 +5882,6 @@ Status VersionSet::ProcessManifestWrites(
if (s.ok()) {
// find offset in manifest file where this version is stored.
new_manifest_file_size = raw_desc_log_ptr->file()->GetFileSize();
if (new_descriptor_log) {
ROCKS_LOG_INFO(db_options_->info_log,
"Created manifest %" PRIu64
", compacted+appended from %" PRIu64 " to %" PRIu64 "\n",
pending_manifest_file_number_, prev_manifest_file_size,
new_manifest_file_size);
}
}
if (first_writer.edit_list.front()->IsColumnFamilyDrop()) {
@@ -6121,8 +5930,6 @@ Status VersionSet::ProcessManifestWrites(
descriptor_log_ = std::move(new_desc_log_ptr);
obsolete_manifests_.emplace_back(
DescriptorFileName("", manifest_file_number_));
last_compacted_manifest_file_size_ = new_manifest_file_size;
TuneMaxManifestFileSize();
}
// Install the new versions
@@ -6205,21 +6012,21 @@ Status VersionSet::ProcessManifestWrites(
// that renaming tmp file to CURRENT failed.
//
// On local POSIX-compliant FS, the CURRENT must point to the original
// MANIFEST. We can delete the new MANIFEST for simplicity, but we can
// also keep it. Future recovery will ignore this MANIFEST. It's also ok
// for the process not to crash and continue using the db. Any future
// LogAndApply() call will switch to a new MANIFEST and update CURRENT,
// still ignoring this one.
// MANIFEST. We can delete the new MANIFEST for simplicity, but we can also
// keep it. Future recovery will ignore this MANIFEST. It's also ok for the
// process not to crash and continue using the db. Any future LogAndApply()
// call will switch to a new MANIFEST and update CURRENT, still ignoring
// this one.
//
// On non-local FS, it is
// possible that the rename operation succeeded on the server (remote)
// side, but the client somehow returns a non-ok status to RocksDB. Note
// that this does not violate atomicity. Should we delete the new MANIFEST
// successfully, a subsequent recovery attempt will likely see the CURRENT
// pointing to the new MANIFEST, thus fail. We will not be able to open
// the DB again. Therefore, if manifest operations succeed, we should keep
// the the new MANIFEST. If the process proceeds, any future LogAndApply()
// call will switch to a new MANIFEST and update CURRENT. If user tries to
// pointing to the new MANIFEST, thus fail. We will not be able to open the
// DB again. Therefore, if manifest operations succeed, we should keep the
// the new MANIFEST. If the process proceeds, any future LogAndApply() call
// will switch to a new MANIFEST and update CURRENT. If user tries to
// re-open the DB,
// a) CURRENT points to the new MANIFEST, and the new MANIFEST is present.
// b) CURRENT points to the original MANIFEST, and the original MANIFEST
@@ -6348,9 +6155,9 @@ Status VersionSet::LogAndApply(
first_writer.cv.Wait();
}
if (first_writer.done) {
// All non-CF-manipulation operations can be grouped together and
// committed to MANIFEST. They should all have finished. The status code
// is stored in the first manifest writer.
// All non-CF-manipulation operations can be grouped together and committed
// to MANIFEST. They should all have finished. The status code is stored in
// the first manifest writer.
#ifndef NDEBUG
for (const auto& writer : writers) {
assert(writer.done);
@@ -6404,8 +6211,8 @@ void VersionSet::LogAndApplyCFHelper(VersionEdit* edit,
assert(!edit->HasLastSequence());
edit->SetLastSequence(*max_last_sequence);
if (edit->IsColumnFamilyDrop()) {
// if we drop column family, we have to make sure to save max column
// family, so that we don't reuse existing ID
// if we drop column family, we have to make sure to save max column family,
// so that we don't reuse existing ID
edit->SetMaxColumnFamily(column_family_set_->GetMaxColumnFamily());
}
}
@@ -6694,8 +6501,7 @@ void VersionSet::RecoverEpochNumbers() {
Status VersionSet::ListColumnFamilies(std::vector<std::string>* column_families,
const std::string& dbname,
FileSystem* fs) {
// Read "CURRENT" file, which contains a pointer to the current manifest
// file
// Read "CURRENT" file, which contains a pointer to the current manifest file
std::string manifest_path;
uint64_t manifest_file_number;
Status s = GetCurrentManifestPath(dbname, fs, /*is_retry=*/false,
@@ -6757,16 +6563,14 @@ Status VersionSet::ReduceNumberOfLevels(const std::string& dbname,
const ReadOptions read_options;
const WriteOptions write_options;
ImmutableDBOptions imm_db_options(*options);
MutableDBOptions mutable_db_options(*options);
ImmutableDBOptions db_options(*options);
ColumnFamilyOptions cf_options(*options);
std::shared_ptr<Cache> tc(NewLRUCache(options->max_open_files - 10,
options->table_cache_numshardbits));
WriteController wc(options->delayed_write_rate);
WriteBufferManager wb(options->db_write_buffer_size);
VersionSet versions(dbname, &imm_db_options, mutable_db_options, file_options,
tc.get(), &wb, &wc, nullptr /*BlockCacheTracer*/,
nullptr /*IOTracer*/,
VersionSet versions(dbname, &db_options, file_options, tc.get(), &wb, &wc,
nullptr /*BlockCacheTracer*/, nullptr /*IOTracer*/,
/*db_id*/ "",
/*db_session_id*/ "", options->daily_offpeak_time_utc,
/*error_handler_*/ nullptr, /*unchanging=*/false);
@@ -6852,9 +6656,9 @@ Status VersionSet::ReduceNumberOfLevels(const std::string& dbname,
// Get the checksum information including the checksum and checksum function
// name of all SST and blob files in VersionSet. Store the information in
// FileChecksumList which contains a map from file number to its checksum
// info. If DB is not running, make sure call VersionSet::Recover() to load
// the file metadata from Manifest to VersionSet before calling this function.
// FileChecksumList which contains a map from file number to its checksum info.
// If DB is not running, make sure call VersionSet::Recover() to load the file
// metadata from Manifest to VersionSet before calling this function.
Status VersionSet::GetLiveFilesChecksumInfo(FileChecksumList* checksum_list) {
// Clean the previously stored checksum information if any.
Status s;
@@ -6996,8 +6800,8 @@ Status VersionSet::WriteCurrentStateToManifest(
// WARNING: This method doesn't hold a mutex!!
// This is done without DB mutex lock held, but only within single-threaded
// LogAndApply. Column family manipulations can only happen within
// LogAndApply (the same single thread), so we're safe to iterate.
// LogAndApply. Column family manipulations can only happen within LogAndApply
// (the same single thread), so we're safe to iterate.
assert(io_s.ok());
if (db_options_->write_dbid_to_manifest) {
@@ -7031,9 +6835,9 @@ Status VersionSet::WriteCurrentStateToManifest(
}
// New manifest should rollover the WAL deletion record from previous
// manifest. Otherwise, when an addition record of a deleted WAL gets added
// to this new manifest later (which can happens in e.g, SyncWAL()), this
// new manifest creates an illusion that such WAL hasn't been deleted.
// manifest. Otherwise, when an addition record of a deleted WAL gets added to
// this new manifest later (which can happens in e.g, SyncWAL()), this new
// manifest creates an illusion that such WAL hasn't been deleted.
VersionEdit wal_deletions;
wal_deletions.DeleteWalsBefore(min_log_number_to_keep());
std::string wal_deletions_record;
@@ -7165,9 +6969,9 @@ Status VersionSet::WriteCurrentStateToManifest(
// TODO(aekmekji): in CompactionJob::GenSubcompactionBoundaries(), this
// function is called repeatedly with consecutive pairs of slices. For example
// if the slice list is [a, b, c, d] this function is called with arguments
// (a,b) then (b,c) then (c,d). Knowing this, an optimization is possible
// where we avoid doing binary search for the keys b and c twice and instead
// somehow maintain state of where they first appear in the files.
// (a,b) then (b,c) then (c,d). Knowing this, an optimization is possible where
// we avoid doing binary search for the keys b and c twice and instead somehow
// maintain state of where they first appear in the files.
uint64_t VersionSet::ApproximateSize(const SizeApproximationOptions& options,
const ReadOptions& read_options,
Version* v, const Slice& start,
@@ -7188,20 +6992,19 @@ uint64_t VersionSet::ApproximateSize(const SizeApproximationOptions& options,
}
// Outline of the optimization that uses options.files_size_error_margin.
// When approximating the files total size that is used to store a keys
// range, we first sum up the sizes of the files that fully fall into the
// range. Then we sum up the sizes of all the files that may intersect with
// the range (this includes all files in L0 as well). Then, if
// total_intersecting_size is smaller than total_full_size *
// options.files_size_error_margin - we can infer that the intersecting
// files have a sufficiently negligible contribution to the total size, and
// we can approximate the storage required for the keys in range as just
// half of the intersecting_files_size. E.g., if the value of
// files_size_error_margin is 0.1, then the error of the approximation is
// limited to only ~10% of the total size of files that fully fall into the
// keys range. In such case, this helps to avoid a costly process of binary
// searching the intersecting files that is required only for a more precise
// calculation of the total size.
// When approximating the files total size that is used to store a keys range,
// we first sum up the sizes of the files that fully fall into the range.
// Then we sum up the sizes of all the files that may intersect with the range
// (this includes all files in L0 as well). Then, if total_intersecting_size
// is smaller than total_full_size * options.files_size_error_margin - we can
// infer that the intersecting files have a sufficiently negligible
// contribution to the total size, and we can approximate the storage required
// for the keys in range as just half of the intersecting_files_size.
// E.g., if the value of files_size_error_margin is 0.1, then the error of the
// approximation is limited to only ~10% of the total size of files that fully
// fall into the keys range. In such case, this helps to avoid a costly
// process of binary searching the intersecting files that is required only
// for a more precise calculation of the total size.
autovector<FdWithKeyRange*, 32> first_files;
autovector<FdWithKeyRange*, 16> last_files;
@@ -7273,11 +7076,10 @@ uint64_t VersionSet::ApproximateSize(const SizeApproximationOptions& options,
total_intersecting_size += file_ptr->fd.GetFileSize();
}
// Now scan all the first & last files at each level, and estimate their
// size. If the total_intersecting_size is less than X% of the
// total_full_size - we want to approximate the result in order to avoid the
// costly binary search inside ApproximateSize. We use half of file size as
// an approximation below.
// Now scan all the first & last files at each level, and estimate their size.
// If the total_intersecting_size is less than X% of the total_full_size - we
// want to approximate the result in order to avoid the costly binary search
// inside ApproximateSize. We use half of file size as an approximation below.
const double margin = options.files_size_error_margin;
if (margin > 0 && total_intersecting_size <
@@ -7545,8 +7347,7 @@ InternalIterator* VersionSet::MakeInputIterator(
/*no per level latency histogram=*/nullptr,
TableReaderCaller::kCompaction, /*skip_filters=*/false,
/*level=*/static_cast<int>(c->level(which)), range_del_agg,
c->boundaries(which), false, &tombstone_iter_ptr,
db_options_->statistics.get(), clock_);
c->boundaries(which), false, &tombstone_iter_ptr);
range_tombstones.emplace_back(nullptr, tombstone_iter_ptr);
}
}
@@ -7821,13 +7622,12 @@ Status VersionSet::VerifyFileMetadata(const ReadOptions& read_options,
}
ReactiveVersionSet::ReactiveVersionSet(
const std::string& dbname, const ImmutableDBOptions* imm_db_options,
const MutableDBOptions& mutable_db_options,
const std::string& dbname, const ImmutableDBOptions* _db_options,
const FileOptions& _file_options, Cache* table_cache,
WriteBufferManager* write_buffer_manager, WriteController* write_controller,
const std::shared_ptr<IOTracer>& io_tracer)
: VersionSet(dbname, imm_db_options, mutable_db_options, _file_options,
table_cache, write_buffer_manager, write_controller,
: VersionSet(dbname, _db_options, _file_options, table_cache,
write_buffer_manager, write_controller,
/*block_cache_tracer=*/nullptr, io_tracer, /*db_id*/ "",
/*db_session_id*/ "", /*daily_offpeak_time_utc*/ "",
/*error_handler=*/nullptr, /*unchanging=*/false) {}
@@ -7951,8 +7751,8 @@ Status ReactiveVersionSet::MaybeSwitchManifest(
}
} else if (s.IsPathNotFound()) {
// This can happen if the primary switches to a new MANIFEST after the
// secondary reads the CURRENT file but before the secondary actually
// tries to open the MANIFEST.
// secondary reads the CURRENT file but before the secondary actually tries
// to open the MANIFEST.
s = Status::TryAgain(
"The primary may have switched to a new MANIFEST and deleted the old "
"one.");
+9 -56
View File
@@ -268,13 +268,8 @@ class VersionStorageInfo {
bool expand_range = true, // if set, returns files which overlap the
// range and overlap each other. If false,
// then just files intersecting the range
const FileMetaData* starting_l0_file =
nullptr, // If not null, restricts L0 file selection to only include
// files at or older than starting_l0_file.
InternalKey** next_smallest =
nullptr // if non-null, returns the
// smallest key of next file not included
) const;
InternalKey** next_smallest = nullptr) // if non-null, returns the
const; // smallest key of next file not included
void GetCleanInputsWithinInterval(
int level, const InternalKey* begin, // nullptr means before all keys
const InternalKey* end, // nullptr means after all keys
@@ -291,10 +286,8 @@ class VersionStorageInfo {
int hint_index, // index of overlap file
int* file_index, // return index of overlap file
bool within_interval = false, // if set, force the inputs within interval
InternalKey** next_smallest =
nullptr // if non-null, returns the
// smallest key of next file not included
) const;
InternalKey** next_smallest = nullptr) // if non-null, returns the
const; // smallest key of next file not included
// Returns true iff some file in the specified level overlaps
// some part of [*smallest_user_key,*largest_user_key].
@@ -1051,10 +1044,6 @@ class Version {
void GetColumnFamilyMetaData(ColumnFamilyMetaData* cf_meta);
// Get column family metadata with optional filtering by key range and level.
void GetColumnFamilyMetaData(const GetColumnFamilyMetaDataOptions& options,
ColumnFamilyMetaData* cf_meta);
void GetSstFilesBoundaryKeys(Slice* smallest_user_key,
Slice* largest_user_key);
@@ -1197,9 +1186,7 @@ class AtomicGroupReadBuffer {
// but false for secondary instance or writable DB).
class VersionSet {
public:
VersionSet(const std::string& dbname,
const ImmutableDBOptions* imm_db_options,
const MutableDBOptions& mutable_db_options,
VersionSet(const std::string& dbname, const ImmutableDBOptions* db_options,
const FileOptions& file_options, Cache* table_cache,
WriteBufferManager* write_buffer_manager,
WriteController* write_controller,
@@ -1216,13 +1203,6 @@ class VersionSet {
virtual Status Close(FSDirectory* db_dir, InstrumentedMutex* mu);
// Requires: already holding DB mutex `mu`, to ensure
// * Safely read values from `updated_options`
// * Safely update fields on `this` (must be read elsewhere while holding mu)
// except `mu` can be nullptr during initialization
void UpdatedMutableDbOptions(const MutableDBOptions& updated_options,
InstrumentedMutex* mu);
Status LogAndApplyToDefaultColumnFamily(
const ReadOptions& read_options, const WriteOptions& write_options,
VersionEdit* edit, InstrumentedMutex* mu,
@@ -1568,6 +1548,10 @@ class VersionSet {
}
const FileOptions& file_options() { return file_options_; }
void ChangeFileOptions(const MutableDBOptions& new_options) {
file_options_.writable_file_max_buffer_size =
new_options.writable_file_max_buffer_size;
}
// TODO - Consider updating together when file options change in SetDBOptions
const OffpeakTimeOption& offpeak_time_option() {
@@ -1606,16 +1590,6 @@ class VersionSet {
bool& TEST_unchanging() { return const_cast<bool&>(unchanging_); }
uint64_t TEST_GetMinMaxManifestFileSize() {
return min_max_manifest_file_size_;
}
unsigned TEST_GetMaxManifestSpaceAmpPct() {
return max_manifest_space_amp_pct_;
}
size_t TEST_GetManifestPreallocationSize() {
return manifest_preallocation_size_;
}
protected:
struct ManifestWriter;
@@ -1636,7 +1610,6 @@ class VersionSet {
}
};
// Revert back to a post-construction state (keep same options/settings)
void Reset();
// Returns approximated offset of a key in a file for a given version.
@@ -1675,11 +1648,6 @@ class VersionSet {
ColumnFamilyData* cfd, const std::string& fpath,
int level, const FileMetaData& meta);
// Auto-tune next max size for the current manifest file based on its initial
// "compacted" size and other parameters saved in this VersionSet. Must be
// holding DB mutex if outside of DB startup.
void TuneMaxManifestFileSize();
// Protected by DB mutex.
WalSet wals_;
@@ -1731,20 +1699,6 @@ class VersionSet {
// Current size of manifest file
uint64_t manifest_file_size_;
// Size of the populated manifest file last time it was re-written from
// scratch.
uint64_t last_compacted_manifest_file_size_;
// Auto-tuned max allowed size for the current manifest file
uint64_t tuned_max_manifest_file_size_;
// Saved copy of max_manifest_file_size in (Mutable)DBOptions
uint64_t min_max_manifest_file_size_;
// Saved, sanitized copy from (Mutable)DBOptions
unsigned max_manifest_space_amp_pct_;
// Saved copy from (Mutable)DBOptions
size_t manifest_preallocation_size_;
// Obsolete files, or during DB shutdown any files not referenced by what's
// left of the in-memory LSM state.
std::vector<ObsoleteFileInfo> obsolete_files_;
@@ -1797,7 +1751,6 @@ class ReactiveVersionSet : public VersionSet {
public:
ReactiveVersionSet(const std::string& dbname,
const ImmutableDBOptions* _db_options,
const MutableDBOptions& mutable_db_options,
const FileOptions& _file_options, Cache* table_cache,
WriteBufferManager* write_buffer_manager,
WriteController* write_controller,
+39 -41
View File
@@ -1159,12 +1159,12 @@ class VersionSetTestBase {
: env_(nullptr),
dbname_(test::PerThreadDBPath(name)),
options_(),
imm_db_options_(options_),
db_options_(options_),
cf_options_(options_),
immutable_options_(imm_db_options_, cf_options_),
immutable_options_(db_options_, cf_options_),
mutable_cf_options_(cf_options_),
table_cache_(NewLRUCache(50000, 16)),
write_buffer_manager_(imm_db_options_.db_write_buffer_size),
write_buffer_manager_(db_options_.db_write_buffer_size),
shutting_down_(false),
table_factory_(std::make_shared<mock::MockTableFactory>()) {
EXPECT_OK(test::CreateEnvFromSystem(ConfigOptions(), &env_, &env_guard_));
@@ -1178,8 +1178,8 @@ class VersionSetTestBase {
EXPECT_OK(fs_->CreateDirIfMissing(dbname_, IOOptions(), nullptr));
options_.env = env_;
imm_db_options_.env = env_;
imm_db_options_.fs = fs_;
db_options_.env = env_;
db_options_.fs = fs_;
immutable_options_.env = env_;
immutable_options_.fs = fs_;
immutable_options_.clock = env_->GetSystemClock().get();
@@ -1188,17 +1188,16 @@ class VersionSetTestBase {
mutable_cf_options_.table_factory = table_factory_;
versions_.reset(new VersionSet(
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
table_cache_.get(), &write_buffer_manager_, &write_controller_,
dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr, /*read_only=*/false));
reactive_versions_ = std::make_shared<ReactiveVersionSet>(
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
table_cache_.get(), &write_buffer_manager_, &write_controller_,
nullptr);
imm_db_options_.db_paths.emplace_back(dbname_,
std::numeric_limits<uint64_t>::max());
dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_, nullptr);
db_options_.db_paths.emplace_back(dbname_,
std::numeric_limits<uint64_t>::max());
}
virtual ~VersionSetTestBase() {
@@ -1221,7 +1220,7 @@ class VersionSetTestBase {
ASSERT_OK(
SetIdentityFile(WriteOptions(), env_, dbname_, Temperature::kUnknown));
VersionEdit new_db;
if (imm_db_options_.write_dbid_to_manifest) {
if (db_options_.write_dbid_to_manifest) {
DBOptions tmp_db_options;
tmp_db_options.env = env_;
std::unique_ptr<DBImpl> impl(new DBImpl(tmp_db_options, dbname_));
@@ -1382,8 +1381,8 @@ class VersionSetTestBase {
void ReopenDB() {
versions_.reset(new VersionSet(
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
table_cache_.get(), &write_buffer_manager_, &write_controller_,
dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr, /*read_only=*/false));
@@ -1472,8 +1471,7 @@ class VersionSetTestBase {
const std::string dbname_;
EnvOptions env_options_;
Options options_;
ImmutableDBOptions imm_db_options_;
MutableDBOptions mutable_db_options_;
ImmutableDBOptions db_options_;
ColumnFamilyOptions cf_options_;
ImmutableOptions immutable_options_;
MutableCFOptions mutable_cf_options_;
@@ -1904,8 +1902,8 @@ TEST_F(VersionSetTest, WalAddition) {
// Recover a new VersionSet.
{
std::unique_ptr<VersionSet> new_versions(new VersionSet(
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
table_cache_.get(), &write_buffer_manager_, &write_controller_,
dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr, /*unchanging=*/false));
@@ -1972,8 +1970,8 @@ TEST_F(VersionSetTest, WalCloseWithoutSync) {
// Recover a new VersionSet.
{
std::unique_ptr<VersionSet> new_versions(new VersionSet(
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
table_cache_.get(), &write_buffer_manager_, &write_controller_,
dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr, /*unchanging=*/false));
@@ -2026,8 +2024,8 @@ TEST_F(VersionSetTest, WalDeletion) {
// Recover a new VersionSet, only the non-closed WAL should show up.
{
std::unique_ptr<VersionSet> new_versions(new VersionSet(
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
table_cache_.get(), &write_buffer_manager_, &write_controller_,
dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr, /*unchanging=*/false));
@@ -2065,8 +2063,8 @@ TEST_F(VersionSetTest, WalDeletion) {
// Recover from the new MANIFEST, only the non-closed WAL should show up.
{
std::unique_ptr<VersionSet> new_versions(new VersionSet(
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
table_cache_.get(), &write_buffer_manager_, &write_controller_,
dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr, /*unchanging=*/false));
@@ -2186,8 +2184,8 @@ TEST_F(VersionSetTest, DeleteWalsBeforeNonExistingWalNumber) {
// Recover a new VersionSet, WAL0 is deleted, WAL1 is not.
{
std::unique_ptr<VersionSet> new_versions(new VersionSet(
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
table_cache_.get(), &write_buffer_manager_, &write_controller_,
dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr, /*unchanging=*/false));
@@ -2223,8 +2221,8 @@ TEST_F(VersionSetTest, DeleteAllWals) {
// Recover a new VersionSet, all WALs are deleted.
{
std::unique_ptr<VersionSet> new_versions(new VersionSet(
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
table_cache_.get(), &write_buffer_manager_, &write_controller_,
dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr, /*unchanging=*/false));
@@ -2266,8 +2264,8 @@ TEST_F(VersionSetTest, AtomicGroupWithWalEdits) {
// kept.
{
std::unique_ptr<VersionSet> new_versions(new VersionSet(
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
table_cache_.get(), &write_buffer_manager_, &write_controller_,
dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr, /*unchanging=*/false));
@@ -2446,8 +2444,8 @@ class VersionSetWithTimestampTest : public VersionSetTest {
void VerifyFullHistoryTsLow(uint64_t expected_ts_low) {
std::unique_ptr<VersionSet> vset(new VersionSet(
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
table_cache_.get(), &write_buffer_manager_, &write_controller_,
dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr, /*unchanging=*/false));
@@ -3502,7 +3500,7 @@ class VersionSetTestEmptyDb
std::unique_ptr<log::Writer>* log_writer) override {
assert(nullptr != log_writer);
VersionEdit new_db;
if (imm_db_options_.write_dbid_to_manifest) {
if (db_options_.write_dbid_to_manifest) {
ASSERT_OK(SetIdentityFile(WriteOptions(), env_, dbname_,
Temperature::kUnknown));
DBOptions tmp_db_options;
@@ -3534,7 +3532,7 @@ class VersionSetTestEmptyDb
const std::string VersionSetTestEmptyDb::kUnknownColumnFamilyName = "unknown";
TEST_P(VersionSetTestEmptyDb, OpenFromIncompleteManifest0) {
imm_db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
PrepareManifest(nullptr, nullptr, &log_writer_);
log_writer_.reset();
CreateCurrentFile();
@@ -3566,7 +3564,7 @@ TEST_P(VersionSetTestEmptyDb, OpenFromIncompleteManifest0) {
}
TEST_P(VersionSetTestEmptyDb, OpenFromIncompleteManifest1) {
imm_db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
PrepareManifest(nullptr, nullptr, &log_writer_);
// Only a subset of column families in the MANIFEST.
VersionEdit new_cf1;
@@ -3607,7 +3605,7 @@ TEST_P(VersionSetTestEmptyDb, OpenFromIncompleteManifest1) {
}
TEST_P(VersionSetTestEmptyDb, OpenFromInCompleteManifest2) {
imm_db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
PrepareManifest(nullptr, nullptr, &log_writer_);
// Write all column families but no log_number, next_file_number and
// last_sequence.
@@ -3653,7 +3651,7 @@ TEST_P(VersionSetTestEmptyDb, OpenFromInCompleteManifest2) {
}
TEST_P(VersionSetTestEmptyDb, OpenManifestWithUnknownCF) {
imm_db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
PrepareManifest(nullptr, nullptr, &log_writer_);
// Write all column families but no log_number, next_file_number and
// last_sequence.
@@ -3710,7 +3708,7 @@ TEST_P(VersionSetTestEmptyDb, OpenManifestWithUnknownCF) {
}
TEST_P(VersionSetTestEmptyDb, OpenCompleteManifest) {
imm_db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
PrepareManifest(nullptr, nullptr, &log_writer_);
// Write all column families but no log_number, next_file_number and
// last_sequence.
@@ -3830,7 +3828,7 @@ class VersionSetTestMissingFiles : public VersionSetTestBase,
ASSERT_OK(s);
log_writer->reset(new log::Writer(std::move(file_writer), 0, false));
VersionEdit new_db;
if (imm_db_options_.write_dbid_to_manifest) {
if (db_options_.write_dbid_to_manifest) {
DBOptions tmp_db_options;
tmp_db_options.env = env_;
std::unique_ptr<DBImpl> impl(new DBImpl(tmp_db_options, dbname_));
@@ -4090,7 +4088,7 @@ TEST_F(VersionSetTestMissingFiles, NoFileMissing) {
}
TEST_F(VersionSetTestMissingFiles, MinLogNumberToKeep2PC) {
imm_db_options_.allow_2pc = true;
db_options_.allow_2pc = true;
NewDB();
SstInfo sst(100, kDefaultColumnFamilyName, "a", 0 /* level */,
+2 -3
View File
@@ -1,4 +1,4 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
@@ -23,8 +23,7 @@ class OfflineManifestWriter {
immutable_db_options_(WithDbPath(options, db_path)),
tc_(NewLRUCache(1 << 20 /* capacity */,
options.table_cache_numshardbits)),
versions_(db_path, &immutable_db_options_, MutableDBOptions{options},
sopt_, tc_.get(), &wb_, &wc_,
versions_(db_path, &immutable_db_options_, sopt_, tc_.get(), &wb_, &wc_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"",
options.daily_offpeak_time_utc,
+1 -7
View File
@@ -192,13 +192,7 @@ void WalManager::PurgeObsoleteWALFiles() {
s.ToString().c_str());
continue;
}
// Avoid expression `now_seconds - file_m_time` when
// `file_m_time > now_seconds` to prevent unsigned underflow in case
// system clock goes backwards. Both timestamps are based on wall clock
// time, which is not guaranteed to be monotonic.
if (file_m_time <= now_seconds &&
now_seconds - file_m_time > db_options_.WAL_ttl_seconds) {
if (now_seconds - file_m_time > db_options_.WAL_ttl_seconds) {
s = DeleteDBFile(&db_options_, file_path, archival_dir, false,
/*force_fg=*/!wal_in_db_path_);
if (!s.ok()) {
+10 -48
View File
@@ -19,7 +19,6 @@
#include "rocksdb/write_batch.h"
#include "rocksdb/write_buffer_manager.h"
#include "table/mock_table.h"
#include "test_util/mock_time_env.h"
#include "test_util/testharness.h"
#include "test_util/testutil.h"
#include "util/string_util.h"
@@ -40,7 +39,7 @@ class WalManagerTest : public testing::Test {
EXPECT_OK(DestroyDB(dbname_, Options()));
}
void Init(SystemClock* clock_override) {
void Init() {
ASSERT_OK(env_->CreateDirIfMissing(dbname_));
ASSERT_OK(env_->CreateDirIfMissing(ArchivalDirectory(dbname_)));
db_options_.db_paths.emplace_back(dbname_,
@@ -48,15 +47,11 @@ class WalManagerTest : public testing::Test {
db_options_.wal_dir = dbname_;
db_options_.env = env_.get();
db_options_.fs = env_->GetFileSystem();
if (clock_override == nullptr) {
db_options_.clock = env_->GetSystemClock().get();
} else {
db_options_.clock = clock_override;
}
db_options_.clock = env_->GetSystemClock().get();
versions_.reset(new VersionSet(
dbname_, &db_options_, MutableDBOptions{}, env_options_,
table_cache_.get(), &write_buffer_manager_, &write_controller_,
dbname_, &db_options_, env_options_, table_cache_.get(),
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr, /*read_only=*/false));
@@ -129,7 +124,7 @@ class WalManagerTest : public testing::Test {
};
TEST_F(WalManagerTest, ReadFirstRecordCache) {
Init(nullptr /* clock_override */);
Init();
std::string path = dbname_ + "/000001.log";
std::unique_ptr<FSWritableFile> file;
ASSERT_OK(env_->GetFileSystem()->NewWritableFile(path, FileOptions(), &file,
@@ -226,7 +221,7 @@ int CountRecords(TransactionLogIterator* iter) {
TEST_F(WalManagerTest, WALArchivalSizeLimit) {
db_options_.WAL_ttl_seconds = 0;
db_options_.WAL_size_limit_MB = 1000;
Init(nullptr /* clock_override */);
Init();
// TEST : Create WalManager with huge size limit and no ttl.
// Create some archived files and call PurgeObsoleteWALFiles().
@@ -263,7 +258,7 @@ TEST_F(WalManagerTest, WALArchivalSizeLimit) {
TEST_F(WalManagerTest, WALArchivalTtl) {
db_options_.WAL_ttl_seconds = 1000;
Init(nullptr /* clock_override */);
Init();
// TEST : Create WalManager with a ttl and no size limit.
// Create some archived log files and call PurgeObsoleteWALFiles().
@@ -287,41 +282,8 @@ TEST_F(WalManagerTest, WALArchivalTtl) {
ASSERT_TRUE(log_files.empty());
}
TEST_F(WalManagerTest, WALArchivalTtlClockGoesBackwards) {
// This test used to trigger an unsigned underflow bug, where WAL files were
// incorrectly deleted when the system time moved backwards between writing
// to a WAL and running `WalManager::PurgeObsoleteWALFiles()`.
constexpr int kNumLogs = 5;
constexpr int kEntriesPerLog = 100;
db_options_.WAL_ttl_seconds = 86400; // One day
// Configure mock clock to lag one second behind system time. That way, the
// WAL file's mtime will appear to be in the future when
// `WalManager::PurgeObsoleteWALFiles()` runs.
int64_t now_seconds;
ASSERT_OK(env_->GetSystemClock()->GetCurrentTime(&now_seconds));
auto mock_clock = std::make_shared<MockSystemClock>(env_->GetSystemClock());
mock_clock->SetCurrentTime(static_cast<uint64_t>(now_seconds - 1));
db_options_.clock = mock_clock.get();
Init(mock_clock.get() /* clock */);
CreateArchiveLogs(kNumLogs, kEntriesPerLog);
const std::string archive_dir = ArchivalDirectory(dbname_);
ASSERT_EQ(kNumLogs,
ListSpecificFiles(env_.get(), archive_dir, kWalFile).size());
wal_manager_->PurgeObsoleteWALFiles();
// All files must still be present because TTL has not elapsed.
ASSERT_EQ(kNumLogs,
ListSpecificFiles(env_.get(), archive_dir, kWalFile).size());
}
TEST_F(WalManagerTest, TransactionLogIteratorMoveOverZeroFiles) {
Init(nullptr /* clock_override */);
Init();
RollTheLog(false);
Put("key1", std::string(1024, 'a'));
// Create a zero record WAL file.
@@ -335,7 +297,7 @@ TEST_F(WalManagerTest, TransactionLogIteratorMoveOverZeroFiles) {
}
TEST_F(WalManagerTest, TransactionLogIteratorJustEmptyFile) {
Init(nullptr /* clock_override */);
Init();
RollTheLog(false);
auto iter = OpenTransactionLogIter(0);
// Check that an empty iterator is returned
@@ -343,7 +305,7 @@ TEST_F(WalManagerTest, TransactionLogIteratorJustEmptyFile) {
}
TEST_F(WalManagerTest, TransactionLogIteratorNewFileWhileScanning) {
Init(nullptr /* clock_override */);
Init();
CreateArchiveLogs(2, 100);
auto iter = OpenTransactionLogIter(0);
CreateArchiveLogs(1, 100);
+1 -1
View File
@@ -179,7 +179,7 @@ class WriteThread {
PostMemTableCallback* _post_memtable_callback = nullptr,
bool _ingest_wbwi = false)
: batch(_batch),
// TODO: store a copy of WriteOptions instead of its separated data
// TODO: store a copy of WriteOptions instead of its seperated data
// members
sync(write_options.sync),
no_slowdown(write_options.no_slowdown),
-1
View File
@@ -2,7 +2,6 @@ add_executable(db_stress${ARTIFACT_SUFFIX}
batched_ops_stress.cc
cf_consistency_stress.cc
db_stress.cc
db_stress_compaction_service.cc
db_stress_compression_manager.cc
db_stress_common.cc
db_stress_driver.cc
+32 -247
View File
@@ -13,7 +13,6 @@
#include <cmath>
#include "file/file_util.h"
#include "rocksdb/secondary_cache.h"
#include "util/file_checksum_helper.h"
#include "util/xxhash.h"
@@ -229,250 +228,14 @@ void CompressedCacheSetCapacityThread(void* v) {
}
}
#ifndef NDEBUG
static void SetupFaultInjectionForRemoteCompaction(SharedState* shared) {
if (!fault_fs_guard) {
return;
}
fault_fs_guard->SetThreadLocalErrorContext(
FaultInjectionIOType::kRead, shared->GetSeed(), FLAGS_read_fault_one_in,
FLAGS_inject_error_severity == 1 /* retryable */,
FLAGS_inject_error_severity == 2 /* has_data_loss*/);
fault_fs_guard->EnableThreadLocalErrorInjection(FaultInjectionIOType::kRead);
fault_fs_guard->SetThreadLocalErrorContext(
FaultInjectionIOType::kWrite, shared->GetSeed(), FLAGS_write_fault_one_in,
FLAGS_inject_error_severity == 1 /* retryable */,
FLAGS_inject_error_severity == 2 /* has_data_loss*/);
fault_fs_guard->EnableThreadLocalErrorInjection(FaultInjectionIOType::kWrite);
fault_fs_guard->SetThreadLocalErrorContext(
FaultInjectionIOType::kMetadataRead, shared->GetSeed(),
FLAGS_metadata_read_fault_one_in,
FLAGS_inject_error_severity == 1 /* retryable */,
FLAGS_inject_error_severity == 2 /* has_data_loss*/);
fault_fs_guard->EnableThreadLocalErrorInjection(
FaultInjectionIOType::kMetadataRead);
fault_fs_guard->SetThreadLocalErrorContext(
FaultInjectionIOType::kMetadataWrite, shared->GetSeed(),
FLAGS_metadata_write_fault_one_in,
FLAGS_inject_error_severity == 1 /* retryable */,
FLAGS_inject_error_severity == 2 /* has_data_loss*/);
fault_fs_guard->EnableThreadLocalErrorInjection(
FaultInjectionIOType::kMetadataWrite);
}
#endif // NDEBUG
static CompactionServiceOptionsOverride CreateOverrideOptions(
const Options& options, const CompactionServiceJobInfo& job_info) {
CompactionServiceOptionsOverride override_options{
.env = db_stress_env,
.file_checksum_gen_factory = options.file_checksum_gen_factory,
.merge_operator = options.merge_operator,
.compaction_filter = options.compaction_filter,
.compaction_filter_factory = options.compaction_filter_factory,
.prefix_extractor = options.prefix_extractor,
.sst_partitioner_factory = options.sst_partitioner_factory,
.listeners = options.listeners,
.statistics = options.statistics,
.table_properties_collector_factories =
options.table_properties_collector_factories};
// TODO(jaykorean) - create a new compaction filter / merge operator and
// others for remote compactions
//
// Create a new Table Factory
ConfigOptions config_options;
config_options.ignore_unknown_options = false;
config_options.ignore_unsupported_options = false;
Status s = TableFactory::CreateFromString(config_options,
options.table_factory->Name(),
&override_options.table_factory);
if (s.ok()) {
std::string options_str;
s = options.table_factory->GetOptionString(config_options, &options_str);
if (s.ok()) {
s = override_options.table_factory->ConfigureFromString(config_options,
options_str);
}
}
if (!s.ok()) {
fprintf(stdout,
"Failed to set up TableFactory for remote compaction - (%s): %s\n",
job_info.db_name.c_str(), s.ToString().c_str());
}
return override_options;
}
static Status CleanupOutputDirectory(const std::string& output_directory) {
#ifndef NDEBUG
// Temporarily disable fault injection to ensure deletion always succeeds
if (fault_fs_guard) {
fault_fs_guard->DisableAllThreadLocalErrorInjection();
}
#endif // NDEBUG
Status s = DestroyDir(db_stress_env, output_directory);
if (!s.ok()) {
fprintf(stderr,
"Failed to destroy output directory %s when allow_resumption is "
"false: %s\n",
output_directory.c_str(), s.ToString().c_str());
}
if (s.ok()) {
s = db_stress_env->CreateDir(output_directory);
if (!s.ok()) {
fprintf(stderr,
"Failed to recreate output directory %s when allow_resumption is "
"false: %s\n",
output_directory.c_str(), s.ToString().c_str());
}
}
#ifndef NDEBUG
// Re-enable fault injection after deletion
if (fault_fs_guard) {
fault_fs_guard->EnableAllThreadLocalErrorInjection();
}
#endif // NDEBUG
return s;
}
// Set up cancellation mechanism for testing resumable remote compactions.
// Spawns a detached thread to trigger cancellation after a delay (50ms
// initially, or 2/3 of the previous successful compaction time for adaptive
// timing). First-time jobs are always canceled; retries have a 10% chance
// to test consecutive cancellation scenarios.
static std::shared_ptr<std::atomic<bool>> SetupCancellation(
OpenAndCompactOptions& open_compact_options, bool was_canceled,
Random& rand, uint64_t successful_compaction_end_to_end_micros) {
auto canceled = std::make_shared<std::atomic<bool>>(false);
open_compact_options.canceled = canceled.get();
bool should_cancel = !was_canceled || rand.OneIn(10);
if (should_cancel) {
std::thread interruption_thread(
[canceled, successful_compaction_end_to_end_micros]() {
uint64_t sleep_micros =
successful_compaction_end_to_end_micros == 0
? 50000
: successful_compaction_end_to_end_micros * 2 / 3;
std::this_thread::sleep_for(std::chrono::microseconds(sleep_micros));
canceled->store(true);
});
interruption_thread.detach();
}
return canceled;
}
// Process the result of OpenAndCompact operation
static void ProcessCompactionResult(
const Status& s, const std::string& job_id,
const CompactionServiceJobInfo& job_info,
const std::string& serialized_input, const std::string& output_directory,
const std::string& serialized_output, SharedState* shared,
uint64_t& successful_compaction_end_to_end_micros, uint64_t start_micros,
Env* env) {
if (s.IsManualCompactionPaused() && FLAGS_allow_resumption_one_in > 0) {
// Re-enqueue for retry
shared->EnqueueRemoteCompaction(job_id, job_info, serialized_input,
output_directory, true /* was_cancelled */);
return;
}
if (!s.ok()) {
if (!StressTest::IsErrorInjectedAndRetryable(s)) {
// Print in stdout instead of stderr to avoid stress test failure,
// because OpenAndCompact() failure doesn't necessarily mean
// primary db instance failure.
fprintf(stdout, "Failed to run OpenAndCompact(%s): %s\n",
job_info.db_name.c_str(), s.ToString().c_str());
}
} else {
// Track successful completion time
successful_compaction_end_to_end_micros = env->NowMicros() - start_micros;
}
// Add the output regardless of status, so that primary DB doesn't rely
// on the timeout to finish waiting. The actual failure from the
// deserialization can fail the compaction properly
shared->AddRemoteCompactionResult(job_id, s, serialized_output);
}
static void ProcessRemoteCompactionJob(
const std::string& job_id, const CompactionServiceJobInfo& job_info,
const std::string& serialized_input, const std::string& output_directory,
bool was_canceled, SharedState* shared, StressTest* stress_test,
Random& rand, uint64_t& successful_compaction_end_to_end_micros) {
auto options = stress_test->GetOptions(job_info.cf_id);
assert(options.env != nullptr);
auto override_options = CreateOverrideOptions(options, job_info);
OpenAndCompactOptions open_compact_options;
if (FLAGS_allow_resumption_one_in > 0) {
open_compact_options.allow_resumption =
rand.OneIn(FLAGS_allow_resumption_one_in);
} else {
open_compact_options.allow_resumption = false;
}
if (!open_compact_options.allow_resumption) {
CleanupOutputDirectory(output_directory);
}
std::shared_ptr<std::atomic<bool>> canceled = nullptr;
if (FLAGS_allow_resumption_one_in > 0) {
canceled = SetupCancellation(open_compact_options, was_canceled, rand,
successful_compaction_end_to_end_micros);
}
std::string serialized_output;
uint64_t start_micros = options.env->NowMicros();
Status s = DB::OpenAndCompact(open_compact_options, job_info.db_name,
output_directory, serialized_input,
&serialized_output, override_options);
ProcessCompactionResult(s, job_id, job_info, serialized_input,
output_directory, serialized_output, shared,
successful_compaction_end_to_end_micros, start_micros,
options.env);
}
void RemoteCompactionWorkerThread(void* v) {
assert(FLAGS_remote_compaction_worker_threads > 0);
assert(FLAGS_remote_compaction_worker_interval > 0);
auto* thread = static_cast<ThreadState*>(v);
SharedState* shared = thread->shared;
StressTest* stress_test = shared->GetStressTest();
assert(stress_test != nullptr);
#ifndef NDEBUG
SetupFaultInjectionForRemoteCompaction(shared);
#endif // NDEBUG
// Tracks the duration (in microseconds) of the most recent successfully
// completed compaction from start to finish. This value is used in
// SetupCancellation() to adaptively set up cancellation point for a
// compaction
uint64_t successful_compaction_end_to_end_micros = 0;
Random rand(static_cast<uint32_t>(FLAGS_seed));
// Main worker loop
while (true) {
// Check if we should stop
{
MutexLock l(shared->GetMutex());
if (shared->ShouldStopBgThread()) {
@@ -483,20 +246,42 @@ void RemoteCompactionWorkerThread(void* v) {
return;
}
}
std::string job_id;
CompactionServiceJobInfo job_info;
std::string serialized_input;
std::string output_directory;
bool was_canceled;
if (shared->DequeueRemoteCompaction(&job_id, &job_info, &serialized_input,
&output_directory, &was_canceled)) {
ProcessRemoteCompactionJob(
job_id, job_info, serialized_input, output_directory, was_canceled,
shared, stress_test, rand, successful_compaction_end_to_end_micros);
if (shared->DequeueRemoteCompaction(&job_id, &job_info,
&serialized_input)) {
auto options = stress_test->GetOptions(job_info.cf_id);
CompactionServiceOptionsOverride override_options{
.file_checksum_gen_factory = options.file_checksum_gen_factory,
.merge_operator = options.merge_operator,
.compaction_filter = options.compaction_filter,
.compaction_filter_factory = options.compaction_filter_factory,
.prefix_extractor = options.prefix_extractor,
.table_factory = options.table_factory,
.sst_partitioner_factory = options.sst_partitioner_factory,
.listeners = {},
.statistics = options.statistics,
.table_properties_collector_factories =
options.table_properties_collector_factories};
std::string tmp_output_dir = job_info.db_name + "/" + "tmp_output_" +
db_stress_env->GenerateUniqueId();
std::string serialized_output;
Status s = DB::OpenAndCompact(OpenAndCompactOptions{}, job_info.db_name,
tmp_output_dir, serialized_input,
&serialized_output, override_options);
if (!s.ok()) {
// Print in stdout instead of stderr to avoid stress test failure,
// because OpenAndCompact() failure doesn't necessarily mean
// primary db instance failure.
fprintf(stdout, "Failed to run OpenAndCompact(%s): %s\n",
job_info.db_name.c_str(), s.ToString().c_str());
}
// Add the output regardless of status, so that primary DB doesn't rely on
// the timeout to finish waiting. The actual failure from the
// deserialization can fail the compaction properly
shared->AddRemoteCompactionResult(job_id, serialized_output);
}
db_stress_env->SleepForMicroseconds(
thread->rand.Next() % FLAGS_remote_compaction_worker_interval * 1000 +
1);
-4
View File
@@ -248,7 +248,6 @@ DECLARE_string(fs_uri);
DECLARE_uint64(ops_per_thread);
DECLARE_uint64(log2_keys_per_lock);
DECLARE_uint64(max_manifest_file_size);
DECLARE_int32(max_manifest_space_amp_pct);
DECLARE_bool(in_place_update);
DECLARE_string(memtablerep);
DECLARE_int32(prefix_size);
@@ -411,8 +410,6 @@ DECLARE_uint64(max_sequential_skip_in_iterations);
DECLARE_bool(enable_sst_partitioner_factory);
DECLARE_bool(enable_do_not_compress_roles);
DECLARE_bool(block_align);
DECLARE_uint64(super_block_alignment_size);
DECLARE_uint64(super_block_alignment_space_overhead_ratio);
DECLARE_uint32(lowest_used_cache_tier);
DECLARE_bool(enable_custom_split_merge);
DECLARE_uint32(adm_policy);
@@ -430,7 +427,6 @@ DECLARE_bool(track_and_verify_wals);
DECLARE_int32(remote_compaction_worker_threads);
DECLARE_int32(remote_compaction_worker_interval);
DECLARE_bool(remote_compaction_failure_fall_back_to_local);
DECLARE_int32(allow_resumption_one_in);
DECLARE_bool(auto_refresh_iterator_with_snapshot);
DECLARE_uint32(memtable_op_scan_flush_trigger);
DECLARE_uint32(memtable_avg_op_scan_flush_trigger);
@@ -1,61 +0,0 @@
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#ifdef GFLAGS
#include "db_stress_tool/db_stress_compaction_service.h"
#include <string>
#include "db_stress_tool/db_stress_test_base.h"
#include "rocksdb/env.h"
namespace ROCKSDB_NAMESPACE {
CompactionServiceJobStatus DbStressCompactionService::Wait(
const std::string& scheduled_job_id, std::string* result) {
while (true) {
if (aborted_.load()) {
return CompactionServiceJobStatus::kAborted;
}
const auto& maybeResultStatus =
shared_->GetRemoteCompactionResult(scheduled_job_id, result);
if (maybeResultStatus.has_value()) {
auto s = maybeResultStatus.value();
if (s.ok()) {
assert(result);
assert(!result->empty());
return CompactionServiceJobStatus::kSuccess;
} else {
// Remote Compaction failed
if (failure_should_fall_back_to_local_) {
return CompactionServiceJobStatus::kUseLocal;
}
if (StressTest::IsErrorInjectedAndRetryable(s)) {
return CompactionServiceJobStatus::kUseLocal;
}
if (result && result->empty()) {
// If result is empty, set the compaction status in the result so
// that it can be bubbled up to main thread
CompactionServiceResult compaction_result;
compaction_result.status = s;
if (compaction_result.Write(result).ok()) {
assert(result);
assert(!result->empty());
}
}
return CompactionServiceJobStatus::kFailure;
}
} else {
// Remote Compaction is still running
Env::Default()->SleepForMicroseconds(kWaitIntervalInMicros);
}
}
return CompactionServiceJobStatus::kFailure;
}
} // namespace ROCKSDB_NAMESPACE
#endif // GFLAGS
+28 -17
View File
@@ -3,13 +3,11 @@
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#ifdef GFLAGS
#pragma once
#include "db/compaction/compaction_job.h"
#include "db_stress_shared_state.h"
#include "db_stress_tool/db_stress_common.h"
#include "rocksdb/options.h"
#include "utilities/fault_injection_fs.h"
namespace ROCKSDB_NAMESPACE {
@@ -27,33 +25,47 @@ class DbStressCompactionService : public CompactionService {
const char* Name() const override { return kClassName(); }
static constexpr uint64_t kWaitIntervalInMicros = 10 * 1000; // 10ms
static constexpr const char* kTempOutputDirectoryPrefix = "tmp_output_";
static constexpr uint64_t kWaitTimeoutInMicros =
30 * 1000 * 1000; // 30 seconds
CompactionServiceScheduleResponse Schedule(
const CompactionServiceJobInfo& info,
const std::string& compaction_service_input) override {
std::string job_id = info.db_id + "_" + info.db_session_id + "_" +
std::to_string(info.job_id);
if (aborted_.load()) {
return CompactionServiceScheduleResponse(
job_id, CompactionServiceJobStatus::kUseLocal);
}
std::string output_directory = info.db_name + "/" +
kTempOutputDirectoryPrefix +
Env::Default()->GenerateUniqueId();
shared_->EnqueueRemoteCompaction(
job_id, info, compaction_service_input, output_directory,
false /* was_cancelled */); // Not canceled initially
shared_->EnqueueRemoteCompaction(job_id, info, compaction_service_input);
CompactionServiceScheduleResponse response(
job_id, CompactionServiceJobStatus::kSuccess);
return response;
}
CompactionServiceJobStatus Wait(const std::string& scheduled_job_id,
std::string* result) override;
std::string* result) override {
auto start = Env::Default()->NowMicros();
while (Env::Default()->NowMicros() - start < kWaitTimeoutInMicros) {
if (aborted_.load()) {
return CompactionServiceJobStatus::kUseLocal;
}
if (shared_->GetRemoteCompactionResult(scheduled_job_id, result).ok()) {
if (result && result->empty()) {
// Race: Remote worker aborted before client sets aborted_ = true
return CompactionServiceJobStatus::kUseLocal;
}
return CompactionServiceJobStatus::kSuccess;
}
Env::Default()->SleepForMicroseconds(kWaitIntervalInMicros);
}
if (failure_should_fall_back_to_local_) {
fprintf(stdout,
"Remote Compaction failed - fall back to local compaction!\n");
return CompactionServiceJobStatus::kUseLocal;
}
return CompactionServiceJobStatus::kFailure;
}
void OnInstallation(const std::string& scheduled_job_id,
CompactionServiceJobStatus /*status*/) override {
@@ -61,7 +73,7 @@ class DbStressCompactionService : public CompactionService {
std::string serialized;
CompactionServiceResult result;
if (shared_->GetRemoteCompactionResult(scheduled_job_id, &serialized)
.has_value()) {
.ok()) {
if (CompactionServiceResult::Read(serialized, &result).ok()) {
std::vector<std::string> filenames;
Status s = Env::Default()->GetChildren(result.output_path, &filenames);
@@ -88,6 +100,5 @@ class DbStressCompactionService : public CompactionService {
std::atomic_bool aborted_{false};
bool failure_should_fall_back_to_local_;
};
} // namespace ROCKSDB_NAMESPACE
#endif // GFLAGS
} // namespace ROCKSDB_NAMESPACE
+1 -21
View File
@@ -861,11 +861,6 @@ DEFINE_bool(remote_compaction_failure_fall_back_to_local, true,
"If true, remote compaction failures will be ignored and "
"compactions will fall back to local and retried");
DEFINE_int32(allow_resumption_one_in, 0,
"If non-zero, enable resumable compaction with 1/N probability "
"for each OpenAndCompact call.Requires "
"remote_compaction_worker_threads > 0");
DEFINE_uint32(ingest_wbwi_one_in, 0,
"If set, will call"
"IngestWriteBatchWithIndex() instead of regular write operations "
@@ -978,11 +973,7 @@ DEFINE_uint64(log2_keys_per_lock, 2, "Log2 of number of keys per lock");
static const bool FLAGS_log2_keys_per_lock_dummy __attribute__((__unused__)) =
RegisterFlagValidator(&FLAGS_log2_keys_per_lock, &ValidateUint32Range);
DEFINE_uint64(max_manifest_file_size, 16384,
"Maximum size of a MANIFEST file (without auto-tuning)");
DEFINE_int32(max_manifest_space_amp_pct, 500,
"Max manifest space amp percentage for auto-tuning");
DEFINE_uint64(max_manifest_file_size, 16384, "Maximum size of a MANIFEST file");
DEFINE_bool(in_place_update, false, "On true, does inplace update in memtable");
@@ -1441,17 +1432,6 @@ DEFINE_bool(block_align,
ROCKSDB_NAMESPACE::BlockBasedTableOptions().block_align,
"BlockBasedTableOptions.block_align");
DEFINE_uint64(
super_block_alignment_size,
ROCKSDB_NAMESPACE::BlockBasedTableOptions().super_block_alignment_size,
"BlockBasedTableOptions.super_block_alignment_size");
DEFINE_uint64(
super_block_alignment_space_overhead_ratio,
ROCKSDB_NAMESPACE::BlockBasedTableOptions()
.super_block_alignment_space_overhead_ratio,
"BlockBasedTableOptions.super_block_alignment_space_overhead_ratio");
DEFINE_uint32(
lowest_used_cache_tier,
static_cast<uint32_t>(ROCKSDB_NAMESPACE::Options().lowest_used_cache_tier),
+1 -6
View File
@@ -9,7 +9,6 @@
#include <mutex>
#include <unordered_set>
#include "db_stress_tool/db_stress_compaction_service.h"
#include "db_stress_tool/db_stress_shared_state.h"
#include "file/filename.h"
#include "file/writable_file_writer.h"
@@ -22,6 +21,7 @@
#include "util/gflags_compat.h"
#include "util/random.h"
#include "utilities/fault_injection_fs.h"
DECLARE_int32(compact_files_one_in);
extern std::shared_ptr<ROCKSDB_NAMESPACE::FaultInjectionTestFS> fault_fs_guard;
@@ -310,11 +310,6 @@ class DbStressListener : public EventListener {
}
}
}
// We can't do exact matching since remote workers use dynamic temp paths
if (file_dir.find(DbStressCompactionService::kTempOutputDirectoryPrefix) !=
std::string::npos) {
return;
}
assert(false);
#else
(void)file_dir;
+18 -46
View File
@@ -51,24 +51,6 @@ DECLARE_bool(enable_compaction_filter);
namespace ROCKSDB_NAMESPACE {
class StressTest;
struct RemoteCompactionQueueItem {
std::string job_id;
CompactionServiceJobInfo job_info;
std::string serialized_input;
std::string output_directory;
bool canceled;
RemoteCompactionQueueItem(const std::string& id,
const CompactionServiceJobInfo& info,
const std::string& input,
const std::string& output_dir, bool was_canceled)
: job_id(id),
job_info(info),
serialized_input(input),
output_directory(output_dir),
canceled(was_canceled) {}
};
// State shared by all concurrent executions of the same benchmark.
class SharedState {
public:
@@ -296,31 +278,23 @@ class SharedState {
void EnqueueRemoteCompaction(const std::string& job_id,
const CompactionServiceJobInfo& job_info,
const std::string& serialized_input,
const std::string& output_directory,
bool canceled) {
const std::string& serialized_input) {
MutexLock l(&remote_compaction_queue_mu_);
remote_compaction_queue_.emplace(job_id, job_info, serialized_input,
output_directory, canceled);
remote_compaction_queue_.emplace(job_id, job_info, serialized_input);
}
bool DequeueRemoteCompaction(std::string* job_id,
CompactionServiceJobInfo* job_info,
std::string* serialized_input,
std::string* output_directory, bool* canceled) {
std::string* serialized_input) {
assert(job_id);
assert(job_info);
assert(serialized_input);
assert(output_directory);
assert(canceled);
MutexLock l(&remote_compaction_queue_mu_);
if (!remote_compaction_queue_.empty()) {
const RemoteCompactionQueueItem& item = remote_compaction_queue_.front();
*job_id = item.job_id;
*job_info = item.job_info;
*serialized_input = item.serialized_input;
*output_directory = item.output_directory;
*canceled = item.canceled;
const auto [id, info, input] = remote_compaction_queue_.front();
*job_id = id;
*job_info = info;
*serialized_input = input;
remote_compaction_queue_.pop();
return true;
}
@@ -328,23 +302,20 @@ class SharedState {
}
void AddRemoteCompactionResult(const std::string& job_id,
const Status& status,
const std::string& result) {
MutexLock l(&remote_compaction_result_map_mu_);
remote_compaction_result_map_.emplace(
job_id, std::pair<Status, std::string>{status, result});
remote_compaction_result_map_.emplace(job_id, result);
}
std::optional<Status> GetRemoteCompactionResult(const std::string& job_id,
std::string* result) {
Status GetRemoteCompactionResult(const std::string& job_id,
std::string* result) {
MutexLock l(&remote_compaction_result_map_mu_);
if (remote_compaction_result_map_.find(job_id) !=
remote_compaction_result_map_.end()) {
const auto& pair = remote_compaction_result_map_.at(job_id);
*result = pair.second;
return pair.first;
*result = remote_compaction_result_map_.at(job_id);
return Status::OK();
}
return std::nullopt;
return Status::NotFound();
}
void RemoveRemoteCompactionResult(const std::string& job_id) {
@@ -506,14 +477,15 @@ class SharedState {
std::atomic<bool> verification_failure_;
std::atomic<bool> should_stop_test_;
// Queue for the remote compaction.
// Queue for the remote compaction. Tuple of job id, job info and serialized
// compaction_service_input
port::Mutex remote_compaction_queue_mu_;
std::queue<RemoteCompactionQueueItem> remote_compaction_queue_;
std::queue<std::tuple<std::string, CompactionServiceJobInfo, std::string>>
remote_compaction_queue_;
// Result Map for the remote compaciton. Key is the scheduled_job_id and value
// is serialized compaction_service_result
port::Mutex remote_compaction_result_map_mu_;
std::unordered_map<std::string, std::pair<Status, std::string>>
remote_compaction_result_map_;
std::unordered_map<std::string, std::string> remote_compaction_result_map_;
// Keys that should not be overwritten
const std::unordered_set<int64_t> no_overwrite_ids_;
+6 -34
View File
@@ -25,7 +25,6 @@
#include "db_stress_tool/db_stress_filters.h"
#include "db_stress_tool/db_stress_table_properties_collector.h"
#include "db_stress_tool/db_stress_wide_merge_operator.h"
#include "file/file_util.h"
#include "options/options_parser.h"
#include "rocksdb/convenience.h"
#include "rocksdb/filter_policy.h"
@@ -195,10 +194,10 @@ std::shared_ptr<Cache> StressTest::NewCache(size_t capacity,
exit(1);
} else if (EndsWith(cache_type, "hyper_clock_cache")) {
size_t estimated_entry_charge;
if (cache_type == "fixed_hyper_clock_cache") {
if (cache_type == "fixed_hyper_clock_cache" ||
cache_type == "hyper_clock_cache") {
estimated_entry_charge = FLAGS_block_size;
} else if (cache_type == "auto_hyper_clock_cache" ||
cache_type == "hyper_clock_cache") {
} else if (cache_type == "auto_hyper_clock_cache") {
estimated_entry_charge = 0;
} else {
fprintf(stderr, "Cache type not supported.");
@@ -350,6 +349,7 @@ bool StressTest::BuildOptionsTable() {
"1",
"2",
}},
{"max_sequential_skip_in_iterations", {"4", "8", "12"}},
{"block_based_table_factory",
{
keepRibbonFilterPolicyOnly ? "{filter_policy=ribbonfilter:2.35}"
@@ -362,13 +362,6 @@ bool StressTest::BuildOptionsTable() {
std::to_string(FLAGS_block_size + (FLAGS_seed & 0xFFFU)) + "}",
}},
};
if (FLAGS_use_multiscan == 0) {
// TODO: this can fail MultiScan when consecutive data blocks share the
// same user at boundary. MultiScan uses user key to locate the block to
// reach which can move the scan earlier than its current block.
options_tbl.emplace("max_sequential_skip_in_iterations",
std::vector<std::string>{"4", "8", "12"});
}
if (FLAGS_compaction_style == kCompactionStyleUniversal &&
FLAGS_universal_max_read_amp > 0) {
// level0_file_num_compaction_trigger needs to be at most max_read_amp
@@ -1702,10 +1695,7 @@ Status StressTest::TestMultiScan(ThreadState* thread,
std::vector<std::string> end_key_strs;
// TODO support reverse BytewiseComparator in the stress test
MultiScanArgs scan_opts(options_.comparator);
scan_opts.use_async_io =
FLAGS_multiscan_use_async_io &&
CheckFSFeatureSupport(options_.env->GetFileSystem().get(),
FSSupportedOps::kAsyncIO);
scan_opts.use_async_io = FLAGS_multiscan_use_async_io;
start_key_strs.reserve(num_scans);
end_key_strs.reserve(num_scans);
@@ -3685,21 +3675,8 @@ void StressTest::Open(SharedState* shared, bool reopen) {
"Compaction\n");
exit(1);
}
// Each DB open/reopen gets a fresh compaction service instance with a clean
// aborted_ state
auto compaction_service = std::make_shared<DbStressCompactionService>(
options_.compaction_service = std::make_shared<DbStressCompactionService>(
shared, FLAGS_remote_compaction_failure_fall_back_to_local);
options_.compaction_service = compaction_service;
}
if (FLAGS_allow_resumption_one_in > 0) {
if (FLAGS_remote_compaction_worker_threads == 0) {
fprintf(stderr,
"allow_resumption or randomize_allow_resumption requires "
"remote_compaction_worker_threads > 0\n");
exit(1);
}
}
if ((options_.enable_blob_files || options_.enable_blob_garbage_collection ||
@@ -4342,10 +4319,6 @@ void InitializeOptionsFromFlags(
static_cast<BlockBasedTableOptions::IndexShorteningMode>(
FLAGS_index_shortening);
block_based_options.block_align = FLAGS_block_align;
block_based_options.super_block_alignment_size =
fLU64::FLAGS_super_block_alignment_size;
block_based_options.super_block_alignment_space_overhead_ratio =
fLU64::FLAGS_super_block_alignment_space_overhead_ratio;
options.table_factory.reset(NewBlockBasedTableFactory(block_based_options));
options.db_write_buffer_size = FLAGS_db_write_buffer_size;
options.write_buffer_size = FLAGS_write_buffer_size;
@@ -4430,7 +4403,6 @@ void InitializeOptionsFromFlags(
options.compression_opts.checksum = true;
}
options.max_manifest_file_size = FLAGS_max_manifest_file_size;
options.max_manifest_space_amp_pct = FLAGS_max_manifest_space_amp_pct;
options.max_subcompactions = static_cast<uint32_t>(FLAGS_subcompactions);
options.allow_concurrent_memtable_write =
FLAGS_allow_concurrent_memtable_write;
+7 -8
View File
@@ -14,7 +14,6 @@
#include "db_stress_tool/db_stress_common.h"
#include "db_stress_tool/db_stress_shared_state.h"
#include "rocksdb/experimental.h"
#include "utilities/fault_injection_fs.h"
namespace ROCKSDB_NAMESPACE {
class SystemClock;
@@ -26,13 +25,6 @@ using experimental::SstQueryFilterConfigsManager;
class StressTest {
public:
static bool IsErrorInjectedAndRetryable(const Status& error_s) {
assert(!error_s.ok());
return error_s.getState() &&
FaultInjectionTestFS::IsInjectedError(error_s) &&
!status_to_io_status(Status(error_s)).GetDataLoss();
}
StressTest();
virtual ~StressTest() {}
@@ -358,6 +350,13 @@ class StressTest {
return Status::NotSupported("TestCustomOperations() must be overridden");
}
bool IsErrorInjectedAndRetryable(const Status& error_s) const {
assert(!error_s.ok());
return error_s.getState() &&
FaultInjectionTestFS::IsInjectedError(error_s) &&
!status_to_io_status(Status(error_s)).GetDataLoss();
}
void ProcessStatus(SharedState* shared, std::string msg, const Status& s,
bool ignore_injected_error = true) const;
+4 -4
View File
@@ -253,20 +253,20 @@ class PendingExpectedValue {
class ExpectedValueHelper {
public:
// Return whether the key associated with `pre_read_expected_value` and
// `post_read_expected_value` is expected not to exist from beginning till the
// `post_read_expected_value` is expected not to exist from begining till the
// end of the read
//
// The negation of `MustHaveNotExisted()` is "may have not existed".
// To assert some key must have existed, please use `MustHaveExisted()`
// To assert some key must have existsed, please use `MustHaveExisted()`
static bool MustHaveNotExisted(ExpectedValue pre_read_expected_value,
ExpectedValue post_read_expected_value);
// Return whether the key associated with `pre_read_expected_value` and
// `post_read_expected_value` is expected to exist from beginning till the end
// `post_read_expected_value` is expected to exist from begining till the end
// of the read.
//
// The negation of `MustHaveExisted()` is "may have existed".
// To assert some key must have not existed, please use
// To assert some key must have not existsed, please use
// `MustHaveNotExisted()`
static bool MustHaveExisted(ExpectedValue pre_read_expected_value,
ExpectedValue post_read_expected_value);
@@ -1,59 +0,0 @@
---
title: Unified Memory Tracking
layout: post
author: hx235
category: blog
---
## Context / Problem
Modern RocksDB deployments often run in environments with strict memory constraints—cloud VMs, containers, or hosts with hundreds of DB instances. Unpredictable memory usage can lead to out-of-memory (OOM) errors, degraded performance, or even service outages.
Historically, while the block cache was the main source of memory usage, other components—such as memtables, table readers, file metadata, and temporary buffers—could consume significant memory outside the block cache’s control. This made it difficult for users to set a single memory limit and guarantee resource usage stays within expectations.
## Goal
The goal of recent memory tracking work in RocksDB is to enable users to cap the total memory usage of RocksDB instances under a single, configurable limit—the block cache capacity. This is achieved by:
- **Tracking and charging** all major memory consumers (memtables, table readers, file metadata, compression buffers, filter construction) to the block cache.
- **Evicting** data blocks or other memory when the total tracked usage exceeds the configured limit.
- **Providing a fixed memory footprint** for RocksDB, making it easier to run in resource-constrained environments and avoid OOMs.
## Memtable Memory Charging
A major source of memory usage in RocksDB is the memtable. To ensure memtable memory is tracked and capped under a single limit, RocksDB provides the WriteBufferManager (WBM). When WBM is configured with a block cache, memtable memory usage is charged to the block cache. This helps prevent OOM errors and simplifies resource management.
```cpp
std::shared_ptr<Cache> cache = HyperClockCacheOptions(capacity).MakeSharedCache();;
DBOptions db_options;
db_options.write_buffer_manager = std::make_shared<WriteBufferManager>(.., cache);
```
## Other Memory Charging
Beyond memtables, RocksDB allows users to control memory charging for other internal roles using the cache_usage_options API. This provides fine-grained control over how memory is tracked for components like table readers, file metadata, compression dictionary buffers (`CompressionOptions::max_dict_buffer_bytes:`) and filter construction.
```cpp
struct CacheEntryRoleOptions {
enum class Decision {
kEnabled,
kDisabled,
kFallback,
};
Decision charged = Decision::kFallback;
};
struct CacheUsageOptions {
CacheEntryRoleOptions options;
std::map<CacheEntryRole, CacheEntryRoleOptions> options_overrides;
};
...
BlockBasedTableOptions table_options;
table_options.cache_usage_options.options.charged = CacheEntryRoleOptions::Decision::kFallback;
table_options.cache_usage_options.options_overrides[CacheEntryRole::kTableBuilder] = {
.charged = CacheEntryRoleOptions::Decision::kEnabled,
};
```
Default (`Decision::kFallback`) behavior for each memory type:
- `CacheEntryRole::kCompressionDictionaryBuildingBuffer`: `kEnabled`
- `CacheEntryRole::kFilterConstruction`: `kDisabled`
- `CacheEntryRole::kBlockBasedTableReader`: `kDisabled`
- `CacheEntryRole::kFileMetadata`: `kDisabled`
## Monitoring and Observability
RocksDB provides built-in statistics to help users monitor memory usage and cache behavior. The `DB::Properties::kBlockCacheEntryStats` exposes detailed statistics about block cache entries, including breakdowns by each `CacheEntryRole`. These statistics are essential for understanding memory consumption and tuning cache configuration.
@@ -1,74 +0,0 @@
---
title: IO Activity Tagging
layout: post
author: hx235
category: blog
---
## Context
RocksDB performs a variety of IO operations—user reads, background compactions, flushes, database opens, and verification tasks. Treating all these operations the same makes it difficult for file system implementers to optimize performance, prioritize latency-sensitive IOs, and diagnose bottlenecks. To solve that, RocksDB internally tags every IO operation with its activity type using the `IOActivity` enum. This automatic tagging provides precise context for each IO, enabling file systems to make smarter, context-aware decisions for scheduling, caching, and resource management.
## How Internal IO Tagging Works
RocksDB automatically assigns an `IOActivity` tag to each IO operation. This tag is propagated through the storage stack and included in the IO options passed to the file system.
```cpp
enum class IOActivity : uint8_t {
kFlush = 0, // IO for flush operations (background write)
kCompaction = 1, // IO for compaction (background read/write)
kDBOpen = 2, // IO during database open (read/write)
kGet = 3, // User Get() read
kMultiGet = 4, // User MultiGet() read
kDBIterator = 5, // User iterator read
kVerifyDBChecksum = 6, // Verification: DB checksum
kVerifyFileChecksums = 7, // Verification: file checksums
kGetEntity = 8, // Entity Get (e.g., wide-column)
kMultiGetEntity = 9, // Entity MultiGet
kGetFileChecksumsFromCurrentManifest = 10, // Manifest checksum reads
// 0x80–0xFE: Reserved for custom/internal use
kUnknown = 0xFF // Unknown/unspecified activity
};
```
## Access IO Tag in File System
Custom file systems can access the IOActivity tag via the IO options structure provided by RocksDB. This allows them to optimize behavior based on the specific IO activity.
```cpp
Status CustomFileSystem::Append(uint64_t offset, const Slice& data, const IOOptions& io_opts, ...) {
switch (io_opts.io_activity) {
case Env::IOActivity::kGet:
// Prioritize or cache user reads
break;
case Env::IOActivity::kCompaction:
// Throttle or deprioritize background compaction IO
break;
case Env::IOActivity::kDBOpen:
// Track or optimize DB open IO
break;
// ... handle other activities ...
default:
// Default handling
break;
}
}
```
## IO Activity Statistics in RocksDB
RocksDB provides detailed histograms for IO activities, allowing you to analyze both the aggregate time spent (in microseconds) and the count of IOs for each activity type.
```cpp
// Read Histograms
FILE_READ_FLUSH_MICROS
FILE_READ_COMPACTION_MICROS
FILE_READ_DB_OPEN_MICROS
FILE_READ_GET_MICROS
FILE_READ_MULTIGET_MICROS
FILE_READ_DB_ITERATOR_MICROS
FILE_READ_VERIFY_DB_CHECKSUM_MICROS
FILE_READ_VERIFY_FILE_CHECKSUMS_MICROS
// Write Histograms
FILE_WRITE_FLUSH_MICROS
FILE_WRITE_COMPACTION_MICROS
FILE_WRITE_DB_OPEN_MICROS
```
Thanks to Maciej Szeszko and Andrew Chang from the RocksDB team for their contributions in expanding and maintaining the IOActivity enum.
@@ -1,89 +0,0 @@
---
title: "Parallel Compression Revamp: Dramatically Reduced CPU Overhead"
layout: post
author: peterd
category: blog
---
The upcoming RocksDB 10.7 release includes a major revamp of parallel compression that **dramatically reduces the feature's CPU overhead by up to 65%** while maintaining or improving throughput for compression-heavy workloads. We expect this to broaden the set of workloads that could benefit from parallel compression, especially for **bulk SST generation and remote compaction use cases** that are less sensitive to CPU responsiveness.
## Background
Parallel compression in RocksDB (`CompressionOptions::parallel_threads > 1`) allows multiple threads to compress different blocks simultaneously during SST file generation, which can significantly improve compaction throughput for workloads where compression is a bottleneck. However, the original implementation had substantial CPU overhead that often outweighed the benefits, limiting its practical adoption.
## What's New: A Complete Reimplementation
The parallel compression framework has been completely rewritten from the ground up in [pull request #13910](https://github.com/facebook/rocksdb/pull/13910) to address the core inefficiencies:
### Ring Buffer Architecture
Instead of separate compression and write queues with complex thread coordination, the new implementation uses a ring buffer of blocks-in-progress that enables efficient work distribution across threads. This bounds working memory while enabling high throughput with minimal cross-thread synchronization.
![Ring Buffer Architecture](/static/images/parallel-compression/ring-buffer-architecture.svg)
### Work-Stealing Design
Previously, the calling thread could only generate uncompressed blocks, dedicated compression threads could only compress, and a writer thread could only write the SST file to storage. Now, all threads can participate in compression work in a quasi-work-stealing manner, dramatically reducing the need for threads to block waiting for work. While only one thread (the calling thread or "emit thread") can generate uncompressed SST blocks in the new implementation, feeding compression work to other threads and itself, all other threads are compatible with writing compressed blocks to storage.
### Auto-Scaling Thread Management
The ring buffer enables another key feature: auto-scaling of active threads based on ring buffer utilization. The framework intelligently wakes up idle worker threads only when there's sufficient work to justify the overhead, achieving near-maximum throughput while minimizing CPU waste from unnecessary thread wake-ups.
### Lock-Free Synchronization
The entire framework is now lock-free (and wait-free as long as compatible work units are available for each thread), based primarily on atomic operations. To cleanly pack and leverage many data fields into a single atomic value, I've developed a new `BitFields` utility API. This is proving useful for cleaning up the HyperClockCache implementation as well, and will be the topic of a later blog post.
Semaphores are used for lock-free management of idle threads (assuming a lock-free semaphore implementation, which is likely the case with `ROCKSDB_USE_STD_SEMAPHORES` but that is untrustworthy; see below).
## Performance Improvements
The results speak for themselves. Here's a comparison using `db_bench` fillseq benchmarks with various compression configurations:
### ZSTD Compression (Default Level)
Note:
* "throughput" = how quickly a given CPU-bound flush or compaction can complete
* "CPU increase" = total CPU usage in amount of time that each core was used
* "PT" = parallel_threads setting.
**Before:**
- PT=3: ~38% throughput increase for ~73% CPU increase
- PT=6: No throughput increase for ~70% CPU increase
**After:**
- PT=3: ~58% throughput increase for ~25% CPU increase
- PT=6: ~58% throughput increase for ~28% CPU increase
### High Compression Scenarios
For ZSTD compression level 8, the improvements are even more dramatic:
**Before:**
- PT=4: 2.6x throughput increase for 139% CPU increase
- PT=8: 3.6x throughput increase for 135% CPU increase
**After:**
- PT=4: 2.8x throughput increase for 114% CPU increase
- PT=8: 3.7x throughput increase for 116% CPU increase
## Compression Algorithm Optimizations
Alongside the parallel compression revamp, some optimizations have gone into the underlying compression implementations/integrations. Most notably, **LZ4HC received dramatic performance improvements** through better reuse of internal data structures between compression calls (detailed in [pull request #13805](https://github.com/facebook/rocksdb/pull/13805)). A small regression in LZ4 performance from that change was fixed in [pull request #14017](https://github.com/facebook/rocksdb/pull/14017).
While **ZSTD remains the gold standard** for medium-to-high compression ratios in RocksDB, these LZ4HC optimizations make it an increasingly attractive option for read-heavy workloads where LZ4's faster decompression can provide overall performance benefits.
## Production Ready
With these efficiency improvements, parallel compression is now considered **production-ready**. The feature has been thoroughly tested in both unit tests and stress testing, including validation on high-load scenarios with hundreds of concurrent compression jobs and thousands of threads.
Some notes on current limitations:
- Parallel compression is currently incompatible with `UserDefinedIndex` and with the deprecated `decouple_partitioned_filters=false` setting
- Maximum performance is available with `-DROCKSDB_USE_STD_SEMAPHORES` at compile time, though this is not currently recommended due to reported bugs in some implementations of C++20 semaphores
## Configuration Recommendations
The dramatically reduced CPU overhead means parallel compression is now viable for a broader range of workloads, particularly those using higher compression levels or compression-heavy scenarios like time-series data. However, simply enabling parallel compression could result in more *spiky* CPU loads for hosts serving live DB data. **Parallel compression might be most useful for bulk SST file generation and/or remote compaction workloads** because they are less sensitive to CPU responsiveness. In these scenarios there is little danger in setting `parallel_threads=8` even with the possibility of over-subscribing CPU cores, though the potentially safer "sweet spot" is typically around `parallel_threads=3`, depending on compression level, etc.
## Limitations and Future
Although this offers a great improvement in the implementation of an existing option, we recognize that this setup is suboptimal in a number of ways:
* There is no work sharing / thread pooling for these SST compression/writer threads among compactions in the same process, so not well able to fit the workload to available CPU cores and not able to use other SST file compression work to avoid a worker thread going to sleep.
* We are not (yet) using a framework that would allow micro-work sharing with things other than SST generation on a set of threads. That would be a good direction for effective sharing of CPU resources without spikes in usage, but might incur intolerable CPU overhead in managing work. With this "hand optimized" and specialized framework, we can at least evaluate such future endeavors against a perhaps ideal framework in terms of parallelizing with minimal overhead.
## Try It Out
Parallel compression revamp will be available in RocksDB 10.7. As always, we recommend testing in your specific environment to determine the optimal configuration for your workload.
Loaded 100 of 209 files, more files were not shown because too many files have changed in this diff. Show more