Compare commits

..
16 Commits
Author SHA1 Message Date
Richard Barnes fa616f0e79 Fix string-conversion issue in internal_repo_rocksdb/repo/utilities/persistent_cache/volatile_tier_impl.cc +5 (#14296)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14296

This could is triggering `-Wstring-conversion`, which presents as:
```
warning: implicit conversion turns string literal into bool: A to B
```
This is often a bug and what was intended. The most frequent cause is the code was:
```
void foo(bool) { ... }
void foo(std::string) { ... }
foo("this gets interpreted as a bool");
```

It is also possible the issue is innocuous as part of an assert:
```
assert(!"this string is true, so the assertion is false");
EXPECT_FALSE("this string is true, so the expect fails");
```
in these cases the use is to "cute", so we modify the code to make it more obvious.
```
assert(false && "the compiler recognizes and doesn't complain about this pattern");
FAIL() << "much more obvious";
```

Reviewed By: anand1976

Differential Revision: D92013593

fbshipit-source-id: 0b4e00339bef3f76fc5b9ad35e2383c5e4f828f9
2026-02-25 10:30:50 -08:00
Richard Barnes 96db15c6e6 Fix string-conversion issue in internal_repo_rocksdb/repo/table/block_based/index_builder.cc +1
Summary:
This could is triggering `-Wstring-conversion`, which presents as:
```
warning: implicit conversion turns string literal into bool: A to B
```
This is often a bug and what was intended. The most frequent cause is the code was:
```
void foo(bool) { ... }
void foo(std::string) { ... }
foo("this gets interpreted as a bool");
```

It is also possible the issue is innocuous as part of an assert:
```
assert(false && "this string is true, so the assertion is false");
EXPECT_FALSE("this string is true, so the expect fails");
```
in these cases the use is to "cute", so we modify the code to make it more obvious.
```
assert(false && "the compiler recognizes and doesn't complain about this pattern");
FAIL() << "much more obvious";
```

Differential Revision: D92886041

fbshipit-source-id: 6adfaa102f12e293491cc579ec92b48834d1d0a8
2026-02-25 10:30:50 -08:00
Richard Barnes 912596031a Fix string-conversion issue in internal_repo_rocksdb/repo/utilities/persistent_cache/block_cache_tier_file.cc +2 (#14312)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14312

This could is triggering `-Wstring-conversion`, which presents as:
```
warning: implicit conversion turns string literal into bool: A to B
```
This is often a bug and what was intended. The most frequent cause is the code was:
```
void foo(bool) { ... }
void foo(std::string) { ... }
foo("this gets interpreted as a bool");
```

It is also possible the issue is innocuous as part of an assert:
```
assert(!"this string is true, so the assertion is false");
EXPECT_FALSE("this string is true, so the expect fails");
```
in these cases the use is to "cute", so we modify the code to make it more obvious.
```
assert(false && "the compiler recognizes and doesn't complain about this pattern");
FAIL() << "much more obvious";
```

Reviewed By: dmm-fb

Differential Revision: D92528316

fbshipit-source-id: 93fbb624e8731c4cdb559746b44c1aa71d786304
2026-02-25 10:30:50 -08:00
Andrew Chang f4fe40845a Update HISTORY and version to 10.11.2 2026-02-25 10:30:29 -08:00
Andrew Chang 40a5f27f6e Check io_uring probe result in SupportedOps (#14355)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14355

SupportedOps advertised kAsyncIO based only on the IsIOUringEnabled() weak symbol check, without verifying that the constructor's io_uring probe actually succeeded. Add a thread_local_async_read_io_urings_ null check so kAsyncIO is only reported when the probe passed. Also update the constructor to probe with the same IORING_SETUP_SINGLE_ISSUER | IORING_SETUP_DEFER_TASKRUN flags that ReadAsync and MultiRead use at runtime.

Reviewed By: anand1976

Differential Revision: D93780065

fbshipit-source-id: 6f51f544b267cb39d09b49949a9485f55eeae12e
2026-02-24 16:36:14 -08:00
Andrew Chang 9676ce2f84 Propagate file_checksum through FileOptions on NewRandomAccessFile (#14321)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14321

Add file_checksum and file_checksum_func_name fields to FileOptions so that downstream FileSystem implementations can access per-file checksum metadata when SST files are opened. The fields are populated from FileMetaData at all call sites where SST files are opened via NewRandomAccessFile: TableCache::GetTableReader, Version::GetTableProperties, and CompactionJob::ReadTablePropertiesDirectly. Also fixes the fallback path in TableCache::GetTableReader to use the local fopts (with temperature and checksum) instead of the original file_options.

Added a kNoFileChecksumFuncName which is distinct from  kUnknownFileChecksumFuncName:

 - kUnknownFileChecksumFuncName ("Unknown"): We have FileMetaData for this file, and the metadata says no checksum was computed (no factory was configured when the file was written). This is a property of the file itself.
- kNoFileChecksumFuncName ("Unavailable"): We don't even have FileMetaData — we're opening this file in a context where there's no checksum metadata to propagate at all (e.g., SstFileDumper, SstFileReader, checksum generation). It's a property of the call site, not the file.

So the assertion file_checksum.empty() is correct for both, but for different reasons — one says "the file has no checksum," the other says "we have no idea about this file's checksum."

Reviewed By: pdillinger

Differential Revision: D92728944

fbshipit-source-id: 8fd34ea22ca87090b26d0a55c921f354f97f1ffc
2026-02-24 16:36:06 -08:00
Peter Dillinger e506aa7154 Fix format compatibility issues, extend test (#14323)
Summary:
See https://github.com/facebook/rocksdb/issues/14240 which brought this to my attention. Here I've added range deletions and compactions to the format compatible test, and fixed or worked-around compatibility issues (likely longstanding).

The first fix was in Version::MaybeInitializeFileMetaData for an assertion failure simply from adding range deletions from some 5.x version.

The second fix is a broader work-around for older SST files with unreliable num_entries/num_range_deletions/num_deletions statistics in their table properties. We depend on them only for some paranoid checks for compaction, so in my assessment the best way to deal with those files is to exclude the paranoid checks when dealing with the files with unrelaible data. (Details in code comments.) The important part is that compacting old files is exceptionally rare, so we aren't really interefering with the paranoid checks doing thier job on an ongoing basis.

This depends on https://github.com/facebook/rocksdb/issues/14315 (just landed) because there is a remaining undiagnosed problem with some very early releases, but I'm not fixing that because its support is being dropped.

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

Test Plan: test extended (ran locally excluding some releases)

Reviewed By: xingbowang

Differential Revision: D93032653

Pulled By: pdillinger

fbshipit-source-id: f90b32f30ba4764692e68d23705f42c778e0dc1d
2026-02-16 11:13:06 -08:00
Anand Ananthabhotla 6d4c2c3297 Fix racy assertion in AbortIOPartialHandlesBug test (#14319)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14319

The test asserted h1->is_finished and h2->is_finished immediately after AbortIO({H0}), before calling Poll. This is invalid because AbortIO only guarantees that handles in its abort set are finalized. Non-aborted handles' CQEs may or may not be consumed during AbortIO depending on io_uring completion ordering. If H0's two CQEs (original read + cancel) arrive before H1/H2's CQEs, AbortIO breaks out of its wait loop without processing them. Move the H1/H2 is_finished assertions to after Poll, which correctly handles either case. Also remove the racy req_count checks for non-aborted handles since Poll does not increment req_count.

Reviewed By: jaykorean

Differential Revision: D92848827

fbshipit-source-id: 0c09b44ceada99877e8311cff799fa94f1056545
2026-02-16 11:13:06 -08:00
Anand Ananthabhotla 2ceb3ef4d2 Add a cleanup target to crash_test.mk (#14286)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14286

Add the db_c leanup target which can be used by CI test scripts to delete the db on failure. The db_crashtest.py doesn't automatically delete on error.

Reviewed By: jaykorean

Differential Revision: D91912877

fbshipit-source-id: d36ec0896fba64faaafe055d8673e437e85d0c3a
2026-02-06 12:20:35 -08:00
anand76 edc76202a2 Update HISTORY and version to 10.11.1 2026-02-06 12:20:35 -08:00
Andrew Chang b44c70adcf Fix crash in GetLiveFilesStorageInfo on read-only DB (#14306)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14306

GetLiveFilesStorageInfo crashes when called on a read-only RocksDB because
it calls FlushWAL(), which accesses logs_.back() on an empty deque.

Root cause: DBImplReadOnly overrides SyncWAL() to return NotSupported, but
does NOT override FlushWAL(). Read-only DBs have an empty logs_ deque
because they don't create WAL writers during recovery - there's nothing to
write, so no WAL infrastructure is initialized.

The reason SyncWAL was originally marked NotSupported is that these WAL
operations (SyncWAL syncs buffer to disk, FlushWAL flushes to OS buffer)
require an active WAL writer at logs_.back().writer. Since read-only DBs:
1. Cannot perform writes
2. Don't create WAL files for writing
3. Have an empty logs_ deque

...there's no WAL writer to sync or flush. The operations are semantically
meaningless, not just "forbidden write operations."

The fix adds a FlushWAL override matching the SyncWAL pattern. The caller
in db_filesnapshot.cc:403-405 already handles IsNotSupported() gracefully:
  if (s.IsNotSupported()) { s = Status::OK(); }

Reviewed By: pdillinger

Differential Revision: D92419557

fbshipit-source-id: 7079071209b3c7be41a2c98c9b691e68bc031595
2026-02-06 12:20:35 -08:00
Andrew Chang 71eb5a1646 Propagate Poll errors in FilePrefetchBuffer::PollIfNeeded (#14282)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14282

Previously, `PollIfNeeded` returned `void` and silently ignored errors from
`fs_->Poll()` by calling `PermitUncheckedError()`. This could lead to silent
data corruption or unexpected behavior when Poll operations fail.

This diff changes `PollIfNeeded` to return `Status` and properly propagate
Poll errors to callers. When Poll fails:
1. The IO handle is cleaned up via `DestroyAndClearIOHandle`
2. The error status is returned to the caller
3. Callers (`HandleOverlappingAsyncData` and `PrefetchInternal`) now check
   and propagate this error

Also adds a `TEST_SYNC_POINT_CALLBACK` to allow tests to inject Poll errors.

Reviewed By: anand1976

Differential Revision: D91624185

fbshipit-source-id: 8dd0ee6588ed1ce4bf080bcf857b778c5140ccf5
2026-02-06 12:20:35 -08:00
Anand Ananthabhotla 63a256d768 Fix AbortIO consuming completions for non-aborted handles (#14301)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14301

When AbortIO was called with a subset of outstanding async read handles,
it would consume io_uring completions for handles NOT in the abort set
but fail to finalize them. This caused subsequent Poll calls on those
handles to hang forever waiting for completions that had already been
consumed.

The fix adds an `is_being_aborted` flag to Posix_IOHandle that is set
when submitting the cancel request. When processing completions in
AbortIO, handles with this flag wait for req_count==2 (original + cancel),
while handles without the flag are finalized immediately at req_count==1.

Also refactored the completion finalization logic into a shared
FinalizeAsyncRead() helper function used by both Poll and AbortIO.

Reviewed By: mszeszko-meta, archang19

Differential Revision: D92230883

fbshipit-source-id: e6d11e009a4930e5608459771990f6cf7d46d827
2026-02-06 11:47:45 -08:00
mszeszko-meta 87d4b0d579 Merge pull request #14289 from facebook/cherry_pick_14280_to_10_11
Cherry picks into 10.11.fb
2026-02-02 10:47:06 -08:00
Adam Retter 09bc20b16e Fixes the Windows VS 2022 build (#14280)
Summary:
When building a Release on Windows RTTI is not available, so asserts that use dynamic_cast need to be disabled

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

Reviewed By: nmk70

Differential Revision: D91807791

Pulled By: mszeszko-meta

fbshipit-source-id: e29c19c757bcd076a1f09ed40b306bb50ba9e882
2026-01-30 16:36:19 -08:00
Peter Dillinger dd9a806edd Update HISTORY for 10.11.0 2026-01-26 12:42:18 -08:00
338 changed files with 7924 additions and 16434 deletions

No files matched your search

-88
View File
@@ -1,88 +0,0 @@
# When making changes, verify the output of:
# clang-tidy -list-checks
---
Checks: "-*,\
bugprone-argument-comment,\
bugprone-dangling-handle,\
bugprone-fold-init-type,\
bugprone-forward-declaration-namespace,\
bugprone-forwarding-reference-overload,\
bugprone-shadow,\
bugprone-sizeof-*,\
bugprone-string-constructor,\
bugprone-undefined-memory-manipulation,\
bugprone-unused-return-value,\
bugprone-use-after-move,\
cert-env33-c,\
cert-err58-cpp,\
cert-msc30-c,\
cert-msc50-cpp,\
clang-analyzer-core.NullDereference,\
clang-analyzer-core.StackAddressEscape,\
clang-analyzer-deadcode.DeadStores,\
clang-diagnostic-*,\
-clang-diagnostic-missing-designated-field-initializers,\
concurrency-mt-unsafe,\
cppcoreguidelines-avoid-non-const-global-variables,\
cppcoreguidelines-missing-std-forward,\
cppcoreguidelines-pro-type-member-init,\
cppcoreguidelines-special-member-functions,\
cppcoreguidelines-virtual-class-destructor,\
google-build-using-namespace,\
google-explicit-constructor,\
google-readability-avoid-underscore-in-googletest-name,\
misc-definitions-in-headers,\
misc-redundant-expression,\
modernize-make-shared,\
modernize-use-emplace,\
modernize-use-noexcept,\
modernize-use-override,\
modernize-use-using,\
performance-faster-string-find,\
performance-for-range-copy,\
performance-implicit-conversion-in-loop,\
performance-inefficient-algorithm,\
performance-inefficient-string-concatenation,\
performance-inefficient-vector-operation,\
performance-move-const-arg,\
performance-move-constructor-init,\
performance-no-automatic-move,\
performance-no-int-to-ptr,\
performance-noexcept-move-constructor,\
performance-noexcept-swap,\
performance-trivially-destructible,\
performance-type-promotion-in-math-fn,\
performance-unnecessary-copy-initialization,\
performance-unnecessary-value-param,\
readability-braces-around-statements,\
readability-duplicate-include,\
readability-isolate-declaration,\
readability-operators-representation,\
readability-redundant-string-init"
WarningsAsErrors: "bugprone-use-after-move"
CheckOptions:
- key: bugprone-easily-swappable-parameters.MinimumLength
value: 4
- key: cppcoreguidelines-avoid-non-const-global-variables.AllowThreadLocal
value: true
- key: cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor
value: true
- key: cppcoreguidelines-special-member-functions.AllowImplicitlyDeletedCopyOrMove
value: true
- key: modernize-use-using.IgnoreExternC
value: true
- key: performance-move-const-arg.CheckTriviallyCopyableMove
value: false
- key: performance-unnecessary-value-param.AllowedTypes
value: '[Pp]ointer$;[Pp]tr$;[Rr]ef(erence)?$'
- key: performance-unnecessary-copy-initialization.AllowedTypes
value: '[Pp]ointer$;[Pp]tr$;[Rr]ef(erence)?$'
- key: readability-operators-representation.BinaryOperators
value: '&&;&=;&;|;~;!;!=;||;|=;^;^='
- key: readability-redundant-string-init.StringNames
value: '::std::basic_string'
- key: readability-named-parameter.InsertPlainNamesInForwardDecls
value: true
...
+2 -2
View File
@@ -6,6 +6,6 @@ runs:
run: |
make checkout_folly
shell: bash
- name: Install patchelf and libaio
run: apt-get update -y && apt-get install -y patchelf libaio-dev
- name: Install patchelf
run: apt-get update -y && apt-get install -y patchelf
shell: bash
-58
View File
@@ -1,58 +0,0 @@
name: Post clang-tidy PR comment
on:
workflow_run:
workflows: ["clang-tidy"]
types: [completed]
permissions:
pull-requests: write
jobs:
comment:
if: github.event.workflow_run.event == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Download clang-tidy results
id: download
uses: actions/download-artifact@v4.0.0
with:
name: clang-tidy-result
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
continue-on-error: true
- name: Post or update PR comment
if: steps.download.outcome == 'success'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
if (!fs.existsSync('clang-tidy-comment.md') || !fs.existsSync('pr_number.txt')) {
core.info('No clang-tidy results found; skipping.');
return;
}
const body = fs.readFileSync('clang-tidy-comment.md', 'utf8');
const prNumber = parseInt(fs.readFileSync('pr_number.txt', 'utf8').trim());
const marker = '<!-- clang-tidy-bot -->';
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
});
const existing = comments.find(c => c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
core.info(`Updated existing comment ${existing.id}`);
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body,
});
core.info('Created new PR comment');
}
-74
View File
@@ -1,74 +0,0 @@
name: clang-tidy
on:
push:
pull_request:
permissions: {}
jobs:
clang-tidy:
if: github.repository_owner == 'facebook'
runs-on:
labels: 4-core-ubuntu
container:
image: ghcr.io/facebook/rocksdb_ubuntu:24.0
steps:
- uses: actions/checkout@v4.1.0
with:
fetch-depth: 2
- name: Mark workspace as safe for git
run: git config --global --add safe.directory $GITHUB_WORKSPACE
- name: Determine diff base
id: diff-base
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
BASE="${{ github.event.pull_request.base.sha }}"
git fetch --depth=1 origin "$BASE"
else
BASE="${{ github.event.before }}"
if echo "$BASE" | grep -q '^0\{40\}$'; then
echo "skip=true" >> "$GITHUB_OUTPUT"
echo "New branch push; skipping clang-tidy."
exit 0
fi
fi
echo "ref=$BASE" >> "$GITHUB_OUTPUT"
echo "skip=false" >> "$GITHUB_OUTPUT"
- name: Install clang-tidy
if: steps.diff-base.outputs.skip != 'true'
run: apt-get update && apt-get install -y clang-tidy
- name: Generate compile_commands.json
if: steps.diff-base.outputs.skip != 'true'
run: |
mkdir build && cd build
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DCMAKE_C_COMPILER=clang-18 \
-DCMAKE_CXX_COMPILER=clang++-18 ..
cd ..
ln -sf build/compile_commands.json compile_commands.json
- name: Run clang-tidy on changed files
id: clang-tidy
if: steps.diff-base.outputs.skip != 'true'
run: |
python3 tools/run_clang_tidy.py \
-j 4 \
--diff-base ${{ steps.diff-base.outputs.ref }} \
--github-annotations \
--github-step-summary \
--comment-output clang-tidy-comment.md
continue-on-error: true
- name: Save PR number
if: github.event_name == 'pull_request' && always()
run: echo "${{ github.event.pull_request.number }}" > pr_number.txt
- name: Upload clang-tidy results
if: always()
uses: actions/upload-artifact@v4.0.0
with:
name: clang-tidy-result
path: |
clang-tidy-comment.md
pr_number.txt
if-no-files-found: ignore
- name: Fail if clang-tidy found issues
if: steps.clang-tidy.outcome == 'failure'
run: exit 1
+28 -76
View File
@@ -1,18 +1,7 @@
name: facebook/rocksdb/pr-jobs
on: [push, pull_request]
permissions: {}
env:
# Set to a job name to run only that job (on any repo), or leave empty for
# normal behavior (all jobs on facebook repo only).
ONLY_JOB: ''
jobs:
config:
runs-on: ubuntu-latest
outputs:
only_job: ${{ steps.set.outputs.only_job }}
steps:
- id: set
run: echo "only_job=$ONLY_JOB" >> "$GITHUB_OUTPUT"
# NOTE: multiple workflows would be recommended, but the current GHA UI in
# PRs doesn't make it clear when there's an overall error with a workflow,
# making it easy to overlook something broken. Grouping everything into one
@@ -30,10 +19,6 @@ jobs:
# increasing the risk of misconfiguration, especially on forks that might
# want to run with this GHA setup.
#
# SELECTIVE JOB EXECUTION: Set the ONLY_JOB env var at the top of this file
# to a job name (e.g. "build-linux-clang-tidy") to run only that job,
# bypassing the repository owner check. Leave it empty for normal behavior.
#
# DEBUGGING WITH SSH: Temporarily add this as a job step, either before the
# step of interest without the "if:" line or after the failing step with the
# "if:" line. Then use ssh command printed in CI output.
@@ -45,8 +30,7 @@ jobs:
# ======================== Fast Initial Checks ====================== #
check-format-and-targets:
if: needs.config.outputs.only_job == 'check-format-and-targets' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
if: ${{ github.repository_owner == 'facebook' }}
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4.1.0
@@ -60,10 +44,6 @@ jobs:
run: python -m pip install --upgrade pip
- name: Install argparse
run: pip install argparse
- name: Install clang-format
run: |
pip install https://files.pythonhosted.org/packages/fb/ac/3c04772acc0257f5730e83adb542b2603c1a62d1315010ab593a980af404/clang_format-21.1.2-py2.py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
clang-format --version
- name: Download clang-format-diff.py
run: wget https://rocksdb-deps.s3.us-west-2.amazonaws.com/llvm/llvm-project/release/12.x/clang/tools/clang-format/clang-format-diff.py
- name: Check format
@@ -82,8 +62,7 @@ jobs:
SANITY_CHECK=1 LONG_TEST=1 tools/check_format_compatible.sh
# ========================= Linux With Tests ======================== #
build-linux:
if: needs.config.outputs.only_job == 'build-linux' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
@@ -95,8 +74,7 @@ jobs:
- run: make V=1 J=32 -j32 check
- uses: "./.github/actions/post-steps"
build-linux-cmake-mingw:
if: needs.config.outputs.only_job == 'build-linux-cmake-mingw' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 4-core-ubuntu
container:
@@ -115,8 +93,7 @@ jobs:
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-make-with-folly:
if: needs.config.outputs.only_job == 'build-linux-make-with-folly' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
@@ -135,8 +112,7 @@ jobs:
- 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:
if: needs.config.outputs.only_job == 'build-linux-make-with-folly-lite-no-test' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
@@ -150,8 +126,7 @@ jobs:
- run: USE_FOLLY_LITE=1 EXTRA_CXXFLAGS=-DGLOG_USE_GLOG_EXPORT V=1 make -j32 all
- uses: "./.github/actions/post-steps"
build-linux-cmake-with-folly-coroutines:
if: needs.config.outputs.only_job == 'build-linux-cmake-with-folly-coroutines' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
@@ -170,8 +145,7 @@ jobs:
- run: "(mkdir build && cd build && cmake -DUSE_COROUTINES=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 .. && make VERBOSE=1 -j20 && ctest -j20)"
- uses: "./.github/actions/post-steps"
build-linux-cmake-with-benchmark-no-thread-status:
if: needs.config.outputs.only_job == 'build-linux-cmake-with-benchmark-no-thread-status' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
@@ -183,8 +157,7 @@ jobs:
- run: mkdir build && cd build && cmake -DWITH_GFLAGS=1 -DWITH_BENCHMARK=1 -DCMAKE_CXX_FLAGS=-DNROCKSDB_THREAD_STATUS .. && make VERBOSE=1 -j20 && ctest -j20
- uses: "./.github/actions/post-steps"
build-linux-encrypted_env-no_compression:
if: needs.config.outputs.only_job == 'build-linux-encrypted_env-no_compression' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
@@ -198,8 +171,7 @@ jobs:
- uses: "./.github/actions/post-steps"
# ======================== Linux No Test Runs ======================= #
build-linux-release:
if: needs.config.outputs.only_job == 'build-linux-release' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
@@ -225,8 +197,7 @@ jobs:
- run: if ./trace_analyzer --version; then false; else true; fi
- uses: "./.github/actions/post-steps"
build-linux-clang-13-no_test_run:
if: needs.config.outputs.only_job == 'build-linux-clang-13-no_test_run' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 8-core-ubuntu
container:
@@ -242,8 +213,7 @@ jobs:
- run: CC=clang-13 CXX=clang++-13 USE_CLANG=1 EXTRA_CXXFLAGS=-stdlib=libc++ EXTRA_LDFLAGS=-stdlib=libc++ DEBUG_LEVEL=0 make -j32 shared_lib
- uses: "./.github/actions/post-steps"
build-linux-clang-18-no_test_run:
if: needs.config.outputs.only_job == 'build-linux-clang-18-no_test_run' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
@@ -257,8 +227,7 @@ jobs:
- run: CC=clang-18 CXX=clang++-18 USE_CLANG=1 DEBUG_LEVEL=0 make -j32 release
- uses: "./.github/actions/post-steps"
build-linux-gcc-14-no_test_run:
if: needs.config.outputs.only_job == 'build-linux-gcc-14-no_test_run' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
@@ -272,8 +241,7 @@ jobs:
# ======================== Linux Other Checks ======================= #
build-linux-clang18-clang-analyze:
if: needs.config.outputs.only_job == 'build-linux-clang18-clang-analyze' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
@@ -291,10 +259,8 @@ jobs:
with:
name: scan-build-report
path: scan_build_report.tar.gz
build-linux-unity-and-headers:
if: needs.config.outputs.only_job == 'build-linux-unity-and-headers' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 4-core-ubuntu
container:
@@ -308,8 +274,7 @@ jobs:
- run: make V=1 -j8 -k check-headers
- uses: "./.github/actions/post-steps"
build-linux-mini-crashtest:
if: needs.config.outputs.only_job == 'build-linux-mini-crashtest' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 4-core-ubuntu
container:
@@ -322,8 +287,7 @@ jobs:
- uses: "./.github/actions/post-steps"
# ======================= Linux with Sanitizers ===================== #
build-linux-clang18-asan-ubsan:
if: needs.config.outputs.only_job == 'build-linux-clang18-asan-ubsan' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 32-core-ubuntu
container:
@@ -335,8 +299,7 @@ jobs:
- run: COMPILE_WITH_ASAN=1 COMPILE_WITH_UBSAN=1 CC=clang-18 CXX=clang++-18 ROCKSDB_DISABLE_ALIGNED_NEW=1 USE_CLANG=1 make V=1 -j40 check
- uses: "./.github/actions/post-steps"
build-linux-clang18-mini-tsan:
if: needs.config.outputs.only_job == 'build-linux-clang18-mini-tsan' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 32-core-ubuntu
container:
@@ -348,8 +311,7 @@ jobs:
- run: COMPILE_WITH_TSAN=1 CC=clang-18 CXX=clang++-18 ROCKSDB_DISABLE_ALIGNED_NEW=1 USE_CLANG=1 make V=1 -j32 check
- uses: "./.github/actions/post-steps"
build-linux-static_lib-alt_namespace-status_checked:
if: needs.config.outputs.only_job == 'build-linux-static_lib-alt_namespace-status_checked' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
@@ -362,8 +324,7 @@ jobs:
- uses: "./.github/actions/post-steps"
# ========================= MacOS build only ======================== #
build-macos:
if: needs.config.outputs.only_job == 'build-macos' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
if: ${{ github.repository_owner == 'facebook' }}
runs-on: macos-15-xlarge
env:
ROCKSDB_DISABLE_JEMALLOC: 1
@@ -380,8 +341,7 @@ jobs:
- uses: "./.github/actions/post-steps"
# ========================= MacOS with Tests ======================== #
build-macos-cmake:
if: needs.config.outputs.only_job == 'build-macos-cmake' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
if: ${{ github.repository_owner == 'facebook' }}
runs-on: macos-15-xlarge
strategy:
matrix:
@@ -414,8 +374,7 @@ jobs:
# ======================== Windows with Tests ======================= #
# NOTE: some windows jobs are in "nightly" to save resources
build-windows-vs2022:
if: needs.config.outputs.only_job == 'build-windows-vs2022' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
if: ${{ github.repository_owner == 'facebook' }}
runs-on: windows-8-core
env:
CMAKE_GENERATOR: Visual Studio 17 2022
@@ -425,8 +384,7 @@ jobs:
- uses: "./.github/actions/windows-build-steps"
# ============================ Java Jobs ============================ #
build-linux-java:
if: needs.config.outputs.only_job == 'build-linux-java' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 4-core-ubuntu
container:
@@ -457,8 +415,7 @@ jobs:
run: make V=1 J=8 -j8 jtest
# post-steps skipped because of compatibility issues with docker image
build-linux-java-static:
if: needs.config.outputs.only_job == 'build-linux-java-static' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 4-core-ubuntu
container:
@@ -489,8 +446,7 @@ jobs:
run: make V=1 J=8 -j8 rocksdbjavastatic
# post-steps skipped because of compatibility issues with docker image
build-macos-java:
if: needs.config.outputs.only_job == 'build-macos-java' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
if: ${{ github.repository_owner == 'facebook' }}
runs-on: macos-15-xlarge
env:
JAVA_HOME: "/Library/Java/JavaVirtualMachines/liberica-jdk-8.jdk/Contents/Home"
@@ -513,8 +469,7 @@ jobs:
run: make V=1 J=16 -j16 jtest
- uses: "./.github/actions/post-steps"
build-macos-java-static:
if: needs.config.outputs.only_job == 'build-macos-java-static' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
if: ${{ github.repository_owner == 'facebook' }}
runs-on: macos-15-xlarge
env:
JAVA_HOME: "/Library/Java/JavaVirtualMachines/liberica-jdk-8.jdk/Contents/Home"
@@ -536,8 +491,7 @@ jobs:
run: make V=1 J=16 -j16 rocksdbjavastaticosx
- uses: "./.github/actions/post-steps"
build-macos-java-static-universal:
if: needs.config.outputs.only_job == 'build-macos-java-static-universal' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
if: ${{ github.repository_owner == 'facebook' }}
runs-on: macos-15-xlarge
env:
JAVA_HOME: "/Library/Java/JavaVirtualMachines/liberica-jdk-8.jdk/Contents/Home"
@@ -559,8 +513,7 @@ jobs:
run: make V=1 J=16 -j16 rocksdbjavastaticosx_ub
- uses: "./.github/actions/post-steps"
build-linux-java-pmd:
if: needs.config.outputs.only_job == 'build-linux-java-pmd' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 4-core-ubuntu
container:
@@ -586,8 +539,7 @@ jobs:
name: maven-site
path: "${{ github.workspace }}/java/target/site"
build-linux-arm:
if: needs.config.outputs.only_job == 'build-linux-arm' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 4-core-ubuntu-arm
steps:
-12
View File
@@ -4823,12 +4823,6 @@ cpp_unittest_wrapper(name="db_clip_test",
extra_compiler_flags=[])
cpp_unittest_wrapper(name="db_compaction_abort_test",
srcs=["db/db_compaction_abort_test.cc"],
deps=[":rocksdb_test_lib"],
extra_compiler_flags=[])
cpp_unittest_wrapper(name="db_compaction_filter_test",
srcs=["db/db_compaction_filter_test.cc"],
deps=[":rocksdb_test_lib"],
@@ -5731,12 +5725,6 @@ cpp_unittest_wrapper(name="write_prepared_transaction_test",
extra_compiler_flags=[])
cpp_unittest_wrapper(name="write_prepared_transaction_test_seqno",
srcs=["utilities/transactions/write_prepared_transaction_test_seqno.cc"],
deps=[":rocksdb_test_lib"],
extra_compiler_flags=[])
cpp_unittest_wrapper(name="write_unprepared_transaction_test",
srcs=["utilities/transactions/write_unprepared_transaction_test.cc"],
deps=[":rocksdb_test_lib"],
-312
View File
@@ -1,312 +0,0 @@
# RocksDB Code Generation and Review Guidance
This document provides guidance for generating and reviewing code in the RocksDB project, derived from analysis of code review feedback across hundreds of complex merged Pull Requests. Use this as a reference when writing code with AI assistants or conducting code reviews.
---
## General Best Practices
### Code Quality and Maintainability
**Clarity and Readability:** Write clear, self-documenting code. Use meaningful variable names, add comments for complex logic, and structure code to minimize cognitive load. Avoid clever tricks that sacrifice readability for marginal performance gains unless absolutely necessary.
**Consistent Style:** Follow existing code style conventions. RocksDB uses `.clang-format` for formatting, specific naming conventions, and structural patterns. Deviations from these patterns are frequently flagged in reviews.
**Error Handling:** Ensure robust error handling throughout the codebase. Use RocksDB's `Status` type consistently, propagate errors appropriately, and avoid silently ignoring failures. Reviewers pay close attention to edge cases and failure modes.
### Testing Philosophy
**Comprehensive Coverage:** Every change should include appropriate test coverage. This includes unit tests for isolated functionality, integration tests for component interactions, and stress tests for concurrency and performance validation. Reviewers will ask for additional tests if coverage is insufficient.
**Edge Cases and Failure Modes:** Tests should explicitly cover edge cases, boundary conditions, and potential failure scenarios. This is especially important for changes affecting core database operations, compaction, or recovery logic.
**Platform-Specific Testing:** RocksDB supports multiple platforms (Linux, Windows, macOS) and compilers (GCC, Clang, MSVC). Changes should be tested across relevant platforms, particularly when touching platform-specific code or using compiler-specific features.
### Performance Considerations
**⚠️ PERFORMANCE IS CRITICAL:** RocksDB is a high-performance storage engine where every CPU cycle and memory access matters. When writing code, always evaluate from a performance perspective. This is not optional—performance-aware coding is a fundamental requirement for all contributions.
**Benchmarking and Profiling:** Performance claims should be backed by empirical evidence. Use RocksDB's benchmarking tools (e.g., `db_bench`) to validate improvements. Reviewers will request benchmark results for changes that could impact performance.
**Memory Allocation:** Minimize dynamic memory allocations, especially in hot paths. Prefer stack allocation over heap allocation. Reuse buffers when possible. Consider using arena allocators or memory pools for frequent small allocations. Every `new`, `malloc`, or container resize has a cost.
**Memory Copy:** Avoid unnecessary memory copies. Use move semantics, `std::string_view`, `Slice`, and pass-by-reference where appropriate. Be aware of implicit copies in STL containers and function returns. Prefer in-place operations over copy-and-modify patterns.
**CPU Cache Efficiency:** Design data structures and access patterns to be cache-friendly. Keep frequently accessed data together (data locality). Prefer sequential memory access over random access. Be mindful of cache line sizes (typically 64 bytes) and avoid false sharing in concurrent code. Consider struct packing and field ordering to improve cache utilization.
**Loop Optimization:** Look for opportunities to collapse nested loops, reduce loop overhead, and minimize branch mispredictions. Hoist invariant computations out of loops. Consider loop unrolling for tight inner loops. Batch operations when possible to amortize per-operation overhead.
**SIMD and Vectorization:** Leverage SIMD instructions (SSE, AVX) for data-parallel operations when appropriate. Structure data to enable auto-vectorization by the compiler. Consider explicit SIMD intrinsics for critical hot paths like checksum computation, encoding/decoding, and bulk data processing.
**Branch Prediction:** Minimize unpredictable branches in hot paths. Use `LIKELY`/`UNLIKELY` macros to hint branch prediction. Consider branchless alternatives for simple conditionals. Order switch cases and if-else chains by frequency.
**Memory and Resource Management:** Be mindful of memory allocations, especially in hot paths. Use RAII patterns, smart pointers, and RocksDB's memory management utilities appropriately.
**Hot Path Analysis:** When deciding how aggressively to optimize code, consider whether it's on a hot path:
- **Hot path** (executed thousands+ times, e.g., data access, iteration, compaction loops): Performance is paramount. Apply all optimization techniques—loop collapsing, SIMD, cache optimization, pre-allocation, etc. The cost of each operation is multiplied by execution frequency.
- **Cold path** (executed rarely, e.g., DB open, configuration parsing, error handling): Maintainability and clarity are more important. Prefer readable code over micro-optimizations. Complex optimizations here add maintenance burden with negligible performance benefit.
- **Warm path** (moderate frequency): Balance both concerns. Use profiling data to guide optimization decisions.
**Avoid Premature Optimization:** While performance is critical, focus on correctness first, then optimize based on profiling data. However, be performance-aware from the start—choosing the right algorithm and data structure upfront is not premature optimization. Use the hot path analysis above to decide how much optimization effort is warranted.
### API Design and Compatibility
**Backwards Compatibility:** RocksDB maintains strong backwards compatibility guarantees. Breaking changes are rare and require extensive justification. When deprecating features, follow the project's deprecation policy (typically spanning multiple releases).
**API Consistency:** New APIs should be consistent with existing patterns. Use similar naming conventions, parameter ordering, and return types. Reviewers will suggest changes to improve consistency with the broader codebase.
**Documentation:** Public APIs must be thoroughly documented. Include usage examples, parameter descriptions, and notes on thread safety, performance characteristics, and compatibility considerations.
---
## Component-Specific Guidance
### Database Core (`db`)
The database core handles write-ahead logging (WAL), memtables, compaction, and recovery. This component receives the most scrutiny in code reviews.
**Concurrency and Thread Safety:** Database operations are highly concurrent. Reviewers carefully examine locking strategies, atomic operations, and memory ordering. Document synchronization assumptions clearly. Use appropriate memory ordering semantics (`acquire`/`release` vs. `seq_cst`).
**Compaction Logic:** Changes to compaction are complex and high-risk. Ensure that compaction logic respects configured parameters, handles edge cases (empty databases, single-file compactions), and maintains correctness under concurrent operations.
**Error Propagation:** Database operations can fail in many ways (I/O errors, corruption, resource exhaustion). Ensure that errors are properly propagated, logged, and handled. Avoid assertions in production code paths.
**Testing:** Database core changes require extensive testing, including unit tests, integration tests, and stress tests. Test with various configurations, compaction styles, and concurrent workloads.
### Public Headers (`include`)
Public headers define RocksDB's API surface. Changes here have the highest compatibility impact.
**API Design:** New APIs should be intuitive, consistent with existing patterns, and well-documented. Consider how the API will be used in practice and avoid adding unnecessary complexity.
**Backwards Compatibility:** Breaking changes to public APIs require extensive justification and a deprecation plan. Maintain ABI compatibility for bug fixes and patch releases.
**Documentation:** Every public API must be thoroughly documented with usage examples, parameter descriptions, and notes on thread safety and performance characteristics.
**Deprecation:** When deprecating APIs, follow the project's policy. Mark deprecated APIs clearly, provide migration guidance, and maintain support for at least one major release.
### Internal Utilities (`util`)
Internal utilities provide common functionality used throughout the codebase.
**Code Reuse:** Utilities should be general-purpose and reusable. Avoid duplicating functionality that already exists elsewhere in the codebase.
**Error Handling:** Utility functions should handle errors robustly and propagate them appropriately. Consider edge cases like overflow, underflow, and invalid inputs.
**Testing:** Utility functions should have comprehensive test coverage, including edge cases and failure modes. Consider adding death tests for assertions.
**Performance:** Utilities are often used in hot paths. Ensure that implementations are efficient and avoid unnecessary allocations or copies.
### Table Management (`table`)
Table management handles SST file format, block-based tables, and table readers/writers.
**Block Format and Checksums:** Changes to block format require extreme care. Ensure that checksums are computed and verified correctly. Test with various compression algorithms and block sizes.
**Iterator Correctness:** Table iterators are used throughout the codebase. Ensure that iterator semantics (Seek, Next, Prev) are correct, especially at boundaries and with deletions.
**Caching and Prefetching:** Table readers interact with the block cache and prefetching logic. Ensure that cache keys are unique and that prefetching respects configured limits.
**Performance:** Table operations are performance-critical. Benchmark changes that could impact read or write performance.
### Utilities (`utilities`)
Utilities include optional features like transactions, backup engine, and checkpoint.
**Feature Isolation:** Utilities should be self-contained and not introduce unnecessary dependencies on core database internals.
**Deprecation and Cleanup:** Legacy features are being phased out. When removing deprecated code, ensure that migration paths are documented and that users have sufficient warning.
**Cross-Platform Compatibility:** Utilities often interact with OS-specific APIs. Ensure that code works on all supported platforms.
### Options and Configuration (`options`)
Options define RocksDB's configuration system.
**Type Safety:** Use appropriate types for options (e.g., `uint32_t` for flags, scoped enums for enumerated values).
**Deprecation Policy:** When deprecating options, follow the project's policy. Document the deprecation, provide migration guidance, and maintain support for at least one major release.
**Dynamic Configuration:** Some options can be changed dynamically. Ensure that dynamic changes are thread-safe and take effect correctly.
**Validation:** Validate option values and provide clear error messages for invalid configurations.
### Cache (`cache`)
Cache management is critical for RocksDB's performance.
**Concurrency:** Cache operations are highly concurrent. Ensure that implementations are thread-safe and use appropriate synchronization primitives.
**Performance:** Cache operations are in the hot path. Optimize for low latency and high throughput. Benchmark changes carefully.
**Memory Management:** Cache implementations must manage memory carefully to avoid leaks and excessive allocations.
**Eviction Policies:** Changes to eviction policies should be well-tested and benchmarked to ensure they improve overall performance.
---
## Code Review Checklist
When reviewing RocksDB code (or preparing code for review), use this checklist:
### Correctness
- [ ] Does the change preserve database semantics (e.g., snapshot isolation, key ordering)?
- [ ] Are all error cases handled appropriately?
- [ ] Is the change thread-safe? Are synchronization primitives used correctly?
- [ ] Are there any potential data races or deadlocks?
### Testing
- [ ] Does the change include appropriate test coverage?
- [ ] Are edge cases and failure modes tested?
- [ ] Have the tests been run on all supported platforms?
- [ ] Are stress tests passing?
### Performance
- [ ] Are there benchmark results for performance-sensitive changes?
- [ ] Does the change avoid unnecessary allocations or copies?
- [ ] Are hot paths optimized appropriately?
### API and Compatibility
- [ ] Is the change backwards compatible?
- [ ] Are new APIs consistent with existing patterns?
- [ ] Is the public API documented?
- [ ] Are deprecated features handled according to policy?
### Code Quality
- [ ] Does the code follow RocksDB's style conventions?
- [ ] Is the code clear and maintainable?
- [ ] Are comments and documentation sufficient?
- [ ] Are there any code smells or anti-patterns?
---
## Common Review Feedback Patterns
The following patterns emerged as frequent sources of review feedback:
1. **Test Coverage:** Reviewers frequently request additional tests for edge cases, platform-specific behavior, and failure modes. Complex changes require comprehensive test coverage including unit tests, integration tests, and stress tests.
2. **Error Handling:** Ensure proper error propagation using RocksDB's `Status` type. Avoid silent failures and provide clear error messages that include context about what failed and why.
3. **API Design:** New APIs should be consistent with existing patterns. Use descriptive names that follow established conventions. Avoid breaking changes without strong justification and a clear deprecation plan.
4. **Documentation:** Public APIs must be documented with usage examples and notes on thread safety, performance characteristics, and compatibility considerations. Complex internal logic should also be well-commented.
5. **Performance:** Performance-sensitive changes require benchmark results to validate improvements. Use `db_bench` and other profiling tools to measure impact. Avoid premature optimization that adds complexity without measurable benefit.
6. **Concurrency:** Thread safety is critical in RocksDB. Document synchronization assumptions clearly. Use appropriate memory ordering semantics. Consider potential race conditions and deadlocks.
7. **Code Style:** Follow existing conventions for naming, formatting, and structure. Use `.clang-format` for consistent formatting. Prefer scoped enums (`enum class`) over unscoped enums.
8. **Backwards Compatibility:** RocksDB maintains strong compatibility guarantees. Breaking changes require extensive justification. When deprecating features, provide migration guidance and maintain support across multiple releases.
9. **Refactoring:** Reviewers appreciate refactoring that improves code readability and maintainability. Look for opportunities to deduplicate code and simplify complex logic.
10. **Platform Compatibility:** Ensure changes work correctly on all supported platforms (Linux, Windows, macOS) and with all supported compilers (GCC, Clang, MSVC).
---
## Important tips
### Build system
* There are 3 build system. Make, CMake, BUCK(meta internal).
* When a new .cc file is added, update Makefile, CMakeLists.txt, src.mk, BUCK.
* Don't manually edit BUCK file, after updating src.mk, run
/usr/local/bin/python3 buckifier/buckify_rocksdb.py to update it
* Use make to build and run the test. CMake and BUCK are not used locally.
* Use `make dbg` command to build all of the unit test in debug mode.
* For -j in make command, use the number of CPU cores to decide it.
### Unit Test
* After all of the unit tests are added, review them and try to extract common
reusable utility functions to reduce code duplication due to copy past between
unit tests. This should be done every time unit test is updated.
* Don't use sleep to wait for certain events to happen. This will cause test to
be flaky. Instead, use sync point to synchronize thread progress.
* Cap unit test execution with 60 seconds timeout.
* When there are multiple unit tests need to be executed, try to use
gtest_parallel.py if available. E.g.
python3 ${GTEST_PARALLEL}/gtest_parallel.py ./table_test
### Unit test dedup guidelines
* Extract helper functions for repeated patterns such as object
construction, round-trip (encode → decode → verify), and common
assertion sequences.
* Use table-driven tests (struct array + loop) when multiple test cases
share the same logic but differ only in input/expected data.
* Prefer randomized tests over exhaustive parameter permutations. Use
`Random` from `util/random.h` (not `std::mt19937`). Use a time-based
seed with `SCOPED_TRACE("seed=" + std::to_string(seed))` so failures
are reproducible.
* Keep deterministic edge-case tests separate from randomized tests
(error paths, boundary conditions, format verification).
* Methods only used in tests should be private with `friend class` +
`TEST_F` fixture wrappers. In wrappers, always fully qualify the
target method to avoid infinite recursion.
### Adding new public API
Refer to claude_md/add_public_api.md
### Adding new option
Refer to claude_md/add_option.md
### Metrics
* When adding a new feature, evaluate whether there is opportunity to add
metrics. Try to avoid causing performance regression on hot path when adding
metrics.
### Stress test
* When adding a new feature, make sure stress test covers the new option.
### DB bench update
* When adding a performance related feature, support it in db_bench
### Adding release note
* Release note should be kept short at high level for external user consumption.
### Blog posts (docs/_posts)
* Blog post authors must be defined in `docs/_data/authors.yml` to be displayed
### Final verification of the change
* Execute make clean to clean all of the changes.
* Execute make check to build all of the changes and execute all of the tests.
Note that executing all of the tests could take multiple minutes.
### Monitoring make check progress
* Use `make check-progress` to get machine-parseable JSON progress while
`make check` is running. This is useful for Claude Code to monitor long
builds without timeout issues.
* Run `make check` in background, then poll progress:
```bash
make check &
# Poll periodically:
make check-progress
```
* The output shows current phase and progress:
```json
{"status":"running","phase":"compiling","completed":300,"total":919,...}
{"status":"running","phase":"testing","completed":1500,"total":29962,"failed":0,"percent":5,...}
{"status":"completed","phase":"testing","completed":29962,"total":29962,"failed":0,"percent":100,...}
```
* Phases: `compiling` -> `linking` -> `generating` -> `testing` -> `completed`
* Key fields: `status`, `phase`, `completed`, `total`, `failed`, `percent`
* When tests fail, `failed_tests` array shows details (up to 10 failures):
```json
{"status":"running",...,"failed":3,"failed_tests":[
{"test":"cache_test-CacheTest.Usage","exit_code":1,"signal":0,"output":"...test log..."},
{"test":"env_test-EnvTest.Open","exit_code":0,"signal":11,"output":"...Segmentation fault..."}
]}
```
* `exit_code`: non-zero means test assertion failed
* `signal`: non-zero means test was killed (e.g., 9=SIGKILL, 6=SIGABRT, 11=SIGSEGV)
* `output`: last 50 lines of test log including error messages and stack traces
### Executing benchmark using db_bench
* Since the goal is to measure performance, we need to build a release binary
using `make clean && DEBUG_LEVEL=0 make db_bench`. If there is an engine
crash due to bug, we need to switch back to debug build. Make sure to run
`make clean` before running `make dbg`.
### Formatting code
* After making change, use `make format-auto` to auto-apply formatting without
interactive prompts (Claude Code friendly).
-3
View File
@@ -918,7 +918,6 @@ set(SOURCES
util/data_structure.cc
util/dynamic_bloom.cc
util/hash.cc
util/io_dispatcher_imp.cc
util/murmurhash.cc
util/random.cc
util/rate_limiter.cc
@@ -1382,7 +1381,6 @@ if(WITH_TESTS)
db/db_bloom_filter_test.cc
db/db_compaction_filter_test.cc
db/db_compaction_test.cc
db/db_compaction_abort_test.cc
db/db_clip_test.cc
db/db_dynamic_level_test.cc
db/db_encryption_test.cc
@@ -1548,7 +1546,6 @@ if(WITH_TESTS)
utilities/transactions/lock/point/point_lock_manager_stress_test.cc
utilities/transactions/write_committed_transaction_ts_test.cc
utilities/transactions/write_prepared_transaction_test.cc
utilities/transactions/write_prepared_transaction_test_seqno.cc
utilities/transactions/write_unprepared_transaction_test.cc
utilities/transactions/lock/range/range_locking_test.cc
utilities/transactions/timestamped_snapshot_test.cc
+4 -35
View File
@@ -1,44 +1,13 @@
# Rocksdb Change Log
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
## 11.0.4 (03/11/2026)
### Public API Changes
* Re-remove deprecated DB::Open raw pointer variants in db.h
## 11.0.3 (03/03/2026)
### Public API Changes
* Relax option sanitization for kv ratio compaction
## 11.0.2 (02/26/2026)
### Public API Changes
* Revert removing deprecated DB::Open raw pointer variants in db.h
## 11.0.0 (02/23/2026)
### New Features
* Added support for storing wide-column entity column values in blob files. When `min_blob_size` is configured, large column values in wide-column entities will be stored in blob files, reducing SST file size and improving read performance.
* Added `CompactionOptionsFIFO::max_data_files_size` to support FIFO compaction trimming based on combined SST and blob file sizes. Added `CompactionOptionsFIFO::use_kv_ratio_compaction` to enable a capacity-derived intra-L0 compaction strategy optimized for BlobDB workloads, producing uniform-sized compacted files for predictable FIFO trimming.
* Include interpolation search as an alternative to binary search, which typically performs better when keys are uniformly distributed. This is exposed as a new table option `index_block_search_type`. The default is `binary_search`.
### Public API Changes
* Added new virtual methods `AbortAllCompactions()` and `ResumeAllCompactions()` to the `DB` class. Added new `Status::SubCode::kCompactionAborted` to indicate a compaction was aborted. Added `Status::IsCompactionAborted()` helper method to check if a status represents an aborted compaction.
* Drop support for reading (and writing) SST files using `BlockBasedTableOptions.format_version` < 2, which hasn't been the default format for about 10 years. An upgrade path is still possible with full compaction using a RocksDB version >= 4.6.0 and < 11.0.0 and then using the newer version.
* Remove deprecated raw `DB*` variants of `DB::Open` and related functions. Some other minor public APIs were updated as a result
* Remove deprecated `DB::MaxMemCompactionLevel()`
* Remove useless option `CompressedSecondaryCacheOptions::compress_format_version`
* Remove deprecated DB option `skip_checking_sst_file_sizes_on_db_open`. The option was deprecated in 10.5.0 and has been a no-op since then. File size validation is now always performed in parallel during DB open.
* Remove deprecated `SliceTransform::InRange()` virtual method and the `in_range` callback parameter from `rocksdb_slicetransform_create()` in the C API. `InRange()` was never called by RocksDB and existed only for backward compatibility.
* Remove deprecated, unused APIs and options: `ReadOptions::managed` and `ColumnFamilyOptions::snap_refresh_nanos`. Corresponding C and Java APIs are also removed.
* Remove deprecated `SstFileWriter::Add()` method (use `Put()` instead) and the deprecated `skip_filters` parameter from `SstFileWriter` constructors (use `BlockBasedTableOptions::filter_policy` set to `nullptr` to skip filter generation instead).
### Behavior Changes
* Change the default value of `CompactionOptionsUniversal::reduce_file_locking` from `false` to `true` to improve write stall and reduce read regression
## 10.11.2 (02/25/2026)
### Bug Fixes
* Fix longstanding failures that can arise from reading and/or compacting old DB dirs with range deletions (likely from version < 5.19.0) in many newer versions.
* Fix a bug where WritePrepared/WriteUnprepared TransactionDB with two_write_queues=true could experience "sequence number going backwards" corruption during recovery from a background error, due to allocated-but-not-published sequence numbers not being synced before creating new WAL files.
### Performance Improvements
* Add a new table option `separate_key_value_in_data_block`. When set to true keys and values will be stored separately in the data block, which can result in higher cpu cache hit rate and better compression. Works best with data blocks with sufficient restart intervals and large values. Previous versions of RocksDB will reject files written using this option.
## 10.11.1 (02/06/2026)
### Bug Fixes
* Fixed a bug in PosixFS AbortIO that could cause it to ignore an IO completion and a subsequent Poll to hang
## 10.11.0 (01/23/2026)
### Public API Changes
+2 -26
View File
@@ -805,7 +805,7 @@ endif # PLATFORM_SHARED_EXT
.PHONY: check clean coverage ldb_tests package dbg gen-pc build_size \
release tags tags0 valgrind_check format static_lib shared_lib all \
rocksdbjavastatic rocksdbjava install install-static install-shared \
uninstall analyze tools tools_lib check-headers checkout_folly clang-tidy
uninstall analyze tools tools_lib check-headers checkout_folly
all: $(LIBRARY) $(BENCHMARKS) tools tools_lib test_libs $(TESTS)
@@ -993,11 +993,6 @@ watch-log:
dump-log:
bash -c '$(quoted_perl_command)' < LOG
# Machine-parseable progress output for automated monitoring (e.g., Claude Code)
# Outputs JSON: {"status":"running","completed":45,"total":100,"failed":0,"percent":45,"eta_seconds":120}
check-progress:
@build_tools/check_progress.sh
# If J != 1 and GNU parallel is installed, run the tests in parallel,
# via the check_0 rule above. Otherwise, run them sequentially.
check: all
@@ -1209,10 +1204,6 @@ tags0:
format:
build_tools/format-diff.sh
# Non-interactive format (auto-apply without prompts, for CI/automation/Claude Code)
format-auto:
build_tools/format-diff.sh -y
check-format:
build_tools/format-diff.sh -c
@@ -1222,15 +1213,6 @@ check-buck-targets:
check-sources:
build_tools/check-sources.sh
# Run clang-tidy on locally changed files, filtered to changed lines only.
# Requires compile_commands.json (generate with cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON).
# Override CLANG_TIDY_BINARY and CLANG_TIDY_JOBS as needed:
# make clang-tidy CLANG_TIDY_BINARY=/usr/bin/clang-tidy CLANG_TIDY_JOBS=8
CLANG_TIDY_BINARY ?= /opt/homebrew/opt/llvm/bin/clang-tidy
CLANG_TIDY_JOBS ?= $(shell nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)
clang-tidy:
python3 tools/run_clang_tidy.py --clang-tidy-binary $(CLANG_TIDY_BINARY) -j $(CLANG_TIDY_JOBS)
package:
bash build_tools/make_package.sh $(SHARED_MAJOR).$(SHARED_MINOR)
@@ -1460,9 +1442,6 @@ db_compaction_filter_test: $(OBJ_DIR)/db/db_compaction_filter_test.o $(TEST_LIBR
db_compaction_test: $(OBJ_DIR)/db/db_compaction_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
db_compaction_abort_test: $(OBJ_DIR)/db/db_compaction_abort_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
db_clip_test: $(OBJ_DIR)/db/db_clip_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
@@ -1838,9 +1817,6 @@ write_committed_transaction_ts_test: $(OBJ_DIR)/utilities/transactions/write_com
write_prepared_transaction_test: $(OBJ_DIR)/utilities/transactions/write_prepared_transaction_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
write_prepared_transaction_test_seqno: $(OBJ_DIR)/utilities/transactions/write_prepared_transaction_test_seqno.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
write_unprepared_transaction_test: $(OBJ_DIR)/utilities/transactions/write_unprepared_transaction_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
@@ -2561,7 +2537,7 @@ list_all_tests:
# Remove the rules for which dependencies should not be generated and see if any are left.
#If so, include the dependencies; if not, do not include the dependency files
ROCKS_DEP_RULES=$(filter-out clean format check-format check-buck-targets check-headers check-sources clang-tidy jclean jtest package analyze tags rocksdbjavastatic% unity.% unity_test checkout_folly, $(MAKECMDGOALS))
ROCKS_DEP_RULES=$(filter-out clean format check-format check-buck-targets check-headers check-sources jclean jtest package analyze tags rocksdbjavastatic% unity.% unity_test checkout_folly, $(MAKECMDGOALS))
ifneq ("$(ROCKS_DEP_RULES)", "")
-include $(DEPFILES)
endif
-231
View File
@@ -1,231 +0,0 @@
#!/bin/bash
# Output test progress in JSON format for machine parsing
# Usage: build_tools/check_progress.sh
LOG_FILE="LOG"
T_DIR="t"
SRC_MK="src.mk"
# Maximum lines of test output to include per failed test
MAX_OUTPUT_LINES=50
# Helper to escape string for JSON (handles newlines, quotes, backslashes, tabs)
json_escape() {
local str="$1"
# Use python for reliable JSON escaping if available, otherwise use sed
if command -v python3 &>/dev/null; then
printf '%s' "$str" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read())[1:-1], end="")'
else
printf '%s' "$str" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g; s/\r/\\r/g' | awk '{printf "%s\\n", $0}' | sed 's/\\n$//'
fi
}
# Helper to output JSON and exit
output_json() {
local status="$1"
local completed="${2:-0}"
local total="${3:-0}"
local failed="${4:-0}"
local percent="${5:-0}"
local eta="${6:-0}"
local avg_time="${7:-0}"
local last_item="${8:-}"
local phase="${9:-}"
local failed_tests="${10:-}"
# Build JSON output
local json="{\"status\":\"$status\""
if [[ -n "$phase" ]]; then
json="$json,\"phase\":\"$phase\""
fi
json="$json,\"completed\":$completed,\"total\":$total,\"failed\":$failed,\"percent\":$percent"
json="$json,\"eta_seconds\":$eta,\"avg_time\":\"$avg_time\",\"last_item\":\"$(json_escape "$last_item")\""
if [[ -n "$failed_tests" ]]; then
json="$json,\"failed_tests\":[$failed_tests]"
fi
json="$json}"
echo "$json"
}
# Get failed test info with log output
get_failed_tests_json() {
local log_file="$1"
local t_dir="$2"
local max_failures=10
local count=0
local first=true
# Get failed tests from LOG file
while IFS=$'\t' read -r seq host starttime runtime send recv exitval signal cmd; do
# Skip header line
[[ "$seq" == "Seq" ]] && continue
# Check if failed (exitval != 0 or signal != 0)
if [[ "$exitval" != "0" || "$signal" != "0" ]]; then
# Extract test name from command
test_name=$(echo "$cmd" | sed 's,.*/run-,,;s, .*,,')
# Get log file path
log_path="$t_dir/log-run-$test_name"
# Read test output (last N lines)
if [[ -f "$log_path" ]]; then
output=$(tail -n "$MAX_OUTPUT_LINES" "$log_path" 2>/dev/null)
else
output="(log file not found: $log_path)"
fi
# Escape output for JSON
escaped_output=$(json_escape "$output")
# Build JSON object for this failure
if [[ "$first" == "true" ]]; then
first=false
else
printf ","
fi
printf '{"test":"%s","exit_code":%d,"signal":%d,"output":"%s"}' \
"$test_name" "$exitval" "$signal" "$escaped_output"
((count++))
if [[ $count -ge $max_failures ]]; then
break
fi
fi
done < "$log_file"
}
# Check if tests are running (LOG file exists)
if [[ -f "$LOG_FILE" ]]; then
# Count total tests from t/run-* files
if [[ -d "$T_DIR" ]]; then
total=$(find "$T_DIR" -name 'run-*' -type f 2>/dev/null | wc -l)
else
total=0
fi
# If no parallel tests generated yet
if [[ "$total" -eq 0 ]]; then
output_json "running" 0 0 0 0 0 "0" "" "generating"
exit 0
fi
# Parse LOG file (skip header line)
# LOG format: Seq Host Starttime JobRuntime Send Receive Exitval Signal Command
completed=$(tail -n +2 "$LOG_FILE" 2>/dev/null | wc -l)
# Count failures
failed=$(awk -F'\t' 'NR>1 && ($7 != 0 || $8 != 0) {count++} END {print count+0}' "$LOG_FILE" 2>/dev/null)
# Get failed tests JSON with output (only if there are failures)
if [[ "$failed" -gt 0 ]]; then
failed_tests=$(get_failed_tests_json "$LOG_FILE" "$T_DIR")
else
failed_tests=""
fi
# Calculate percentage
if [[ "$total" -gt 0 ]]; then
percent=$((completed * 100 / total))
else
percent=0
fi
# Get last completed test name (extract from command column)
last_test=$(tail -1 "$LOG_FILE" 2>/dev/null | awk -F'\t' '{print $9}' | sed 's,.*/run-,,;s, .*,,;s,^./,,')
# Calculate ETA based on average time
if [[ "$completed" -gt 0 ]]; then
avg_time=$(awk -F'\t' 'NR>1 {sum+=$4; count++} END {if(count>0) printf "%.1f", sum/count; else print "0"}' "$LOG_FILE")
remaining=$((total - completed))
eta=$(awk "BEGIN {printf \"%.0f\", $avg_time * $remaining}")
else
avg_time="0"
eta="0"
fi
# Determine status
if [[ "$completed" -ge "$total" ]]; then
status="completed"
elif [[ "$completed" -gt 0 ]]; then
status="running"
else
status="starting"
fi
output_json "$status" "$completed" "$total" "$failed" "$percent" "$eta" "$avg_time" "$last_test" "testing" "$failed_tests"
exit 0
fi
# No LOG file - check if we're in compilation/linking phase
# Count expected source files from src.mk
if [[ -f "$SRC_MK" ]]; then
# Count LIB_SOURCES (library object files to compile)
expected_lib_objects=$(grep -E '\.cc\s*\\?$' "$SRC_MK" | grep -v '^#' | wc -l)
# Count TEST_MAIN_SOURCES (test binaries to link)
expected_test_binaries=$(sed -n '/^TEST_MAIN_SOURCES =/,/^[^ ]/p' "$SRC_MK" | grep -cE '\.cc\s*\\?$' 2>/dev/null || echo 0)
else
expected_lib_objects=0
expected_test_binaries=0
fi
# Check for test generation phase (t/ directory being created)
if [[ -d "$T_DIR" ]]; then
total=$(find "$T_DIR" -name 'run-*' -type f 2>/dev/null | wc -l)
if [[ "$total" -gt 0 ]]; then
output_json "running" 0 "$total" 0 0 0 "0" "" "generating"
exit 0
fi
fi
# Count compiled object files (in subdirectories matching source structure)
# Object files are created as dir/file.o (e.g., cache/cache.o, db/db_impl.o)
compiled_objects=0
if [[ "$expected_lib_objects" -gt 0 ]]; then
# Count .o files in source directories
compiled_objects=$(find cache db env file logging memory memtable monitoring options port table test_util trace_replay util utilities -name '*.o' -type f 2>/dev/null | wc -l)
fi
# Count linked test binaries (test binaries are in current directory with _test suffix)
linked_tests=0
if [[ "$expected_test_binaries" -gt 0 ]]; then
linked_tests=$(find . -maxdepth 1 -name '*_test' -type f -executable 2>/dev/null | wc -l)
fi
# Determine phase based on what exists
if [[ "$compiled_objects" -eq 0 && "$linked_tests" -eq 0 ]]; then
# Nothing compiled yet - not started or just beginning
output_json "not_started" 0 0 0 0 0 "0" ""
exit 0
fi
# Calculate total work units: compiling + linking
total_work=$((expected_lib_objects + expected_test_binaries))
completed_work=$((compiled_objects + linked_tests))
if [[ "$total_work" -gt 0 ]]; then
percent=$((completed_work * 100 / total_work))
else
percent=0
fi
# Determine phase
if [[ "$compiled_objects" -lt "$expected_lib_objects" ]]; then
phase="compiling"
# Get most recently modified .o file as last_item
last_item=$(find cache db env file logging memory memtable monitoring options port table test_util trace_replay util utilities -name '*.o' -type f -printf '%T@ %p\n' 2>/dev/null | sort -rn | head -1 | cut -d' ' -f2- | sed 's,^\./,,;s,\.o$,,')
elif [[ "$linked_tests" -lt "$expected_test_binaries" ]]; then
phase="linking"
# Get most recently modified test binary as last_item
last_item=$(find . -maxdepth 1 -name '*_test' -type f -executable -printf '%T@ %p\n' 2>/dev/null | sort -rn | head -1 | cut -d' ' -f2- | sed 's,^\./,,')
else
phase="generating"
last_item=""
fi
output_json "running" "$completed_work" "$total_work" 0 "$percent" 0 "0" "$last_item" "$phase"
+6 -16
View File
@@ -7,18 +7,14 @@ print_usage () {
echo "Usage:"
echo "format-diff.sh [OPTIONS]"
echo "-c: check only."
echo "-y: auto-apply formatting without prompts (non-interactive mode)."
echo "-h: print this message."
}
while getopts ':cyh' OPTION; do
while getopts ':ch' OPTION; do
case "$OPTION" in
c)
CHECK_ONLY=1
;;
y)
AUTO_APPLY=1
;;
h)
print_usage
exit 1
@@ -244,16 +240,11 @@ echo "$diffs" |
sed -e "s/\(^-.*$\)/`echo -e \"$COLOR_RED\1$COLOR_END\"`/" |
sed -e "s/\(^+.*$\)/`echo -e \"$COLOR_GREEN\1$COLOR_END\"`/"
# Handle auto-apply mode (non-interactive)
if [ "$AUTO_APPLY" ]; then
to_fix="y"
else
echo -e "Would you like to fix the format automatically (y/n): \c"
echo -e "Would you like to fix the format automatically (y/n): \c"
# Make sure under any mode, we can read user input.
exec < /dev/tty
read to_fix
fi
# Make sure under any mode, we can read user input.
exec < /dev/tty
read to_fix
if [ "$to_fix" != "y" ]
then
@@ -270,8 +261,7 @@ fi
echo "Files reformatted!"
# Amend to last commit if user do the post-commit format check
# Skip amend prompt in auto-apply mode (user can amend manually if desired)
if [ -z "$uncommitted_code" ] && [ -z "$AUTO_APPLY" ]; then
if [ -z "$uncommitted_code" ]; then
echo -e "Would you like to amend the changes to last commit (`git log HEAD --oneline | head -1`)? (y/n): \c"
read to_amend
+5
View File
@@ -54,6 +54,11 @@ static std::unordered_map<std::string, OptionTypeInfo>
{offsetof(struct CompressedSecondaryCacheOptions, compression_type),
OptionType::kCompressionType, OptionVerificationType::kNormal,
OptionTypeFlags::kMutable}},
{"compress_format_version",
{offsetof(struct CompressedSecondaryCacheOptions,
compress_format_version),
OptionType::kUInt32T, OptionVerificationType::kNormal,
OptionTypeFlags::kMutable}},
{"enable_custom_split_merge",
{offsetof(struct CompressedSecondaryCacheOptions,
enable_custom_split_merge),
+9 -9
View File
@@ -101,23 +101,23 @@ class CacheEntryStatsCollector {
}
// Gets saved stats, regardless of age
void GetStats(Stats* stats) {
void GetStats(Stats *stats) {
std::lock_guard<std::mutex> lock(saved_mutex_);
*stats = saved_stats_;
}
Cache* GetCache() const { return cache_; }
Cache *GetCache() const { return cache_; }
// Gets or creates a shared instance of CacheEntryStatsCollector in the
// cache itself, and saves into `ptr`. This shared_ptr will hold the
// entry in cache until all refs are destroyed.
static Status GetShared(Cache* raw_cache, SystemClock* clock,
std::shared_ptr<CacheEntryStatsCollector>* ptr) {
static Status GetShared(Cache *raw_cache, SystemClock *clock,
std::shared_ptr<CacheEntryStatsCollector> *ptr) {
assert(raw_cache);
BasicTypedCacheInterface<CacheEntryStatsCollector, CacheEntryRole::kMisc>
cache{raw_cache};
const Slice& cache_key = GetCacheKey();
const Slice &cache_key = GetCacheKey();
auto h = cache.Lookup(cache_key);
if (h == nullptr) {
// Not yet in cache, but Cache doesn't provide a built-in way to
@@ -152,7 +152,7 @@ class CacheEntryStatsCollector {
}
private:
explicit CacheEntryStatsCollector(Cache* cache, SystemClock* clock)
explicit CacheEntryStatsCollector(Cache *cache, SystemClock *clock)
: saved_stats_(),
working_stats_(),
last_start_time_micros_(0),
@@ -160,7 +160,7 @@ class CacheEntryStatsCollector {
cache_(cache),
clock_(clock) {}
static const Slice& GetCacheKey() {
static const Slice &GetCacheKey() {
// For each template instantiation
static CacheKey ckey = CacheKey::CreateUniqueForProcessLifetime();
static Slice ckey_slice = ckey.AsSlice();
@@ -175,8 +175,8 @@ class CacheEntryStatsCollector {
uint64_t last_start_time_micros_;
uint64_t last_end_time_micros_;
Cache* const cache_;
SystemClock* const clock_;
Cache *const cache_;
SystemClock *const clock_;
};
} // namespace ROCKSDB_NAMESPACE
+3 -3
View File
@@ -24,7 +24,7 @@ namespace ROCKSDB_NAMESPACE {
// 0 | >= 1<<63 | CreateUniqueForProcessLifetime
// > 0 | any | OffsetableCacheKey.WithOffset
CacheKey CacheKey::CreateUniqueForCacheLifetime(Cache* cache) {
CacheKey CacheKey::CreateUniqueForCacheLifetime(Cache *cache) {
// +1 so that we can reserve all zeros for "unset" cache key
uint64_t id = cache->NewId() + 1;
// Ensure we don't collide with CreateUniqueForProcessLifetime
@@ -297,8 +297,8 @@ CacheKey CacheKey::CreateUniqueForProcessLifetime() {
//
// TODO: Nevertheless / regardless, an efficient way to detect (and thus
// quantify) block cache corruptions, including collisions, should be added.
OffsetableCacheKey::OffsetableCacheKey(const std::string& db_id,
const std::string& db_session_id,
OffsetableCacheKey::OffsetableCacheKey(const std::string &db_id,
const std::string &db_session_id,
uint64_t file_number) {
UniqueId64x2 internal_id;
Status s = GetSstInternalUniqueId(db_id, db_session_id, file_number,
+5 -5
View File
@@ -44,13 +44,13 @@ class CacheKey {
inline Slice AsSlice() const {
static_assert(sizeof(*this) == 16, "Standardized on 16-byte cache key");
assert(!IsEmpty());
return Slice(reinterpret_cast<const char*>(this), sizeof(*this));
return Slice(reinterpret_cast<const char *>(this), sizeof(*this));
}
// Create a CacheKey that is unique among others associated with this Cache
// instance. Depends on Cache::NewId. This is useful for block cache
// "reservations".
static CacheKey CreateUniqueForCacheLifetime(Cache* cache);
static CacheKey CreateUniqueForCacheLifetime(Cache *cache);
// Create a CacheKey that is unique among others for the lifetime of this
// process. This is useful for saving in a static data member so that
@@ -87,7 +87,7 @@ class OffsetableCacheKey : private CacheKey {
// Constructs an OffsetableCacheKey with the given information about a file.
// This constructor never generates an "empty" base key.
OffsetableCacheKey(const std::string& db_id, const std::string& db_session_id,
OffsetableCacheKey(const std::string &db_id, const std::string &db_session_id,
uint64_t file_number);
// Creates an OffsetableCacheKey from an SST unique ID, so that cache keys
@@ -134,9 +134,9 @@ class OffsetableCacheKey : private CacheKey {
static_assert(sizeof(file_num_etc64_) == kCommonPrefixSize,
"8 byte common prefix expected");
assert(!IsEmpty());
assert(&this->file_num_etc64_ == static_cast<const void*>(this));
assert(&this->file_num_etc64_ == static_cast<const void *>(this));
return Slice(reinterpret_cast<const char*>(this), kCommonPrefixSize);
return Slice(reinterpret_cast<const char *>(this), kCommonPrefixSize);
}
};
+16 -16
View File
@@ -44,8 +44,8 @@ class CacheReservationManager {
bool increase) = 0;
virtual Status MakeCacheReservation(
std::size_t incremental_memory_used,
std::unique_ptr<CacheReservationManager::CacheReservationHandle>*
handle) = 0;
std::unique_ptr<CacheReservationManager::CacheReservationHandle>
*handle) = 0;
virtual std::size_t GetTotalReservedCacheSize() = 0;
virtual std::size_t GetTotalMemoryUsed() = 0;
};
@@ -90,11 +90,11 @@ class CacheReservationManagerImpl
bool delayed_decrease = false);
// no copy constructor, copy assignment, move constructor, move assignment
CacheReservationManagerImpl(const CacheReservationManagerImpl&) = delete;
CacheReservationManagerImpl& operator=(const CacheReservationManagerImpl&) =
CacheReservationManagerImpl(const CacheReservationManagerImpl &) = delete;
CacheReservationManagerImpl &operator=(const CacheReservationManagerImpl &) =
delete;
CacheReservationManagerImpl(CacheReservationManagerImpl&&) = delete;
CacheReservationManagerImpl& operator=(CacheReservationManagerImpl&&) =
CacheReservationManagerImpl(CacheReservationManagerImpl &&) = delete;
CacheReservationManagerImpl &operator=(CacheReservationManagerImpl &&) =
delete;
~CacheReservationManagerImpl() override;
@@ -178,7 +178,7 @@ class CacheReservationManagerImpl
// REQUIRES: handle != nullptr
Status MakeCacheReservation(
std::size_t incremental_memory_used,
std::unique_ptr<CacheReservationManager::CacheReservationHandle>* handle)
std::unique_ptr<CacheReservationManager::CacheReservationHandle> *handle)
override;
// Return the size of the cache (which is a multiple of kSizeDummyEntry)
@@ -200,7 +200,7 @@ class CacheReservationManagerImpl
// For testing only - it is to help ensure the CacheItemHelperForRole<R>
// accessed from CacheReservationManagerImpl and the one accessed from the
// test are from the same translation units
static const Cache::CacheItemHelper* TEST_GetCacheItemHelperForRole();
static const Cache::CacheItemHelper *TEST_GetCacheItemHelperForRole();
private:
static constexpr std::size_t kSizeDummyEntry = 256 * 1024;
@@ -216,7 +216,7 @@ class CacheReservationManagerImpl
bool delayed_decrease_;
std::atomic<std::size_t> cache_allocated_size_;
std::size_t memory_used_;
std::vector<Cache::Handle*> dummy_handles_;
std::vector<Cache::Handle *> dummy_handles_;
CacheKey cache_key_;
};
@@ -251,14 +251,14 @@ class ConcurrentCacheReservationManager
std::shared_ptr<CacheReservationManager> cache_res_mgr) {
cache_res_mgr_ = std::move(cache_res_mgr);
}
ConcurrentCacheReservationManager(const ConcurrentCacheReservationManager&) =
ConcurrentCacheReservationManager(const ConcurrentCacheReservationManager &) =
delete;
ConcurrentCacheReservationManager& operator=(
const ConcurrentCacheReservationManager&) = delete;
ConcurrentCacheReservationManager(ConcurrentCacheReservationManager&&) =
ConcurrentCacheReservationManager &operator=(
const ConcurrentCacheReservationManager &) = delete;
ConcurrentCacheReservationManager(ConcurrentCacheReservationManager &&) =
delete;
ConcurrentCacheReservationManager& operator=(
ConcurrentCacheReservationManager&&) = delete;
ConcurrentCacheReservationManager &operator=(
ConcurrentCacheReservationManager &&) = delete;
~ConcurrentCacheReservationManager() override {}
@@ -286,7 +286,7 @@ class ConcurrentCacheReservationManager
inline Status MakeCacheReservation(
std::size_t incremental_memory_used,
std::unique_ptr<CacheReservationManager::CacheReservationHandle>* handle)
std::unique_ptr<CacheReservationManager::CacheReservationHandle> *handle)
override {
std::unique_ptr<CacheReservationManager::CacheReservationHandle>
wrapped_handle;
+5 -1
View File
@@ -50,7 +50,8 @@ CompressedSecondaryCache::CompressedSecondaryCache(
std::make_shared<CacheReservationManagerImpl<CacheEntryRole::kMisc>>(
cache_))),
disable_cache_(opts.capacity == 0) {
auto mgr = GetBuiltinV2CompressionManager();
auto mgr =
GetBuiltinCompressionManager(cache_options_.compress_format_version);
compressor_ = mgr->GetCompressor(cache_options_.compression_opts,
cache_options_.compression_type);
decompressor_ =
@@ -355,6 +356,9 @@ std::string CompressedSecondaryCache::GetPrintableOptions() const {
const_cast<CompressionOptions&>(cache_options_.compression_opts))
.c_str());
ret.append(buffer);
snprintf(buffer, kBufferSize, " compress_format_version : %d\n",
cache_options_.compress_format_version);
ret.append(buffer);
return ret;
}
+3 -2
View File
@@ -856,7 +856,8 @@ TEST_P(CompressedSecondaryCacheTestWithCompressionParam, BasicTestFromString) {
if (LZ4_Supported()) {
sec_cache_uri =
"compressed_secondary_cache://"
"capacity=2048;num_shard_bits=0;compression_type=kLZ4Compression";
"capacity=2048;num_shard_bits=0;compression_type=kLZ4Compression;"
"compress_format_version=2";
} else {
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
sec_cache_uri =
@@ -887,7 +888,7 @@ TEST_P(CompressedSecondaryCacheTestWithCompressionParam,
sec_cache_uri =
"compressed_secondary_cache://"
"capacity=2048;num_shard_bits=0;compression_type=kLZ4Compression;"
"enable_custom_split_merge=true";
"compress_format_version=2;enable_custom_split_merge=true";
} else {
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
sec_cache_uri =
+11 -11
View File
@@ -2179,7 +2179,7 @@ TEST_P(DBSecondaryCacheTest, LRUCacheDumpLoadBasic) {
&cache_dumper);
ASSERT_OK(s);
std::vector<DB*> db_list;
db_list.push_back(db_.get());
db_list.push_back(db_);
s = cache_dumper->SetDumpFilter(db_list);
ASSERT_OK(s);
s = cache_dumper->DumpCacheEntriesToWriter();
@@ -2263,11 +2263,11 @@ TEST_P(DBSecondaryCacheTest, LRUCacheDumpLoadWithFilter) {
options.env = fault_env_.get();
std::string dbname1 = test::PerThreadDBPath("db_1");
ASSERT_OK(DestroyDB(dbname1, options));
std::unique_ptr<DB> db1;
DB* db1 = nullptr;
ASSERT_OK(DB::Open(options, dbname1, &db1));
std::string dbname2 = test::PerThreadDBPath("db_2");
ASSERT_OK(DestroyDB(dbname2, options));
std::unique_ptr<DB> db2;
DB* db2 = nullptr;
ASSERT_OK(DB::Open(options, dbname2, &db2));
fault_fs_->SetFailGetUniqueId(true);
@@ -2335,7 +2335,7 @@ TEST_P(DBSecondaryCacheTest, LRUCacheDumpLoadWithFilter) {
&cache_dumper);
ASSERT_OK(s);
std::vector<DB*> db_list;
db_list.push_back(db1.get());
db_list.push_back(db1);
s = cache_dumper->SetDumpFilter(db_list);
ASSERT_OK(s);
s = cache_dumper->DumpCacheEntriesToWriter();
@@ -2377,7 +2377,7 @@ TEST_P(DBSecondaryCacheTest, LRUCacheDumpLoadWithFilter) {
ASSERT_OK(s);
ASSERT_OK(db1->Close());
db1.reset();
delete db1;
ASSERT_OK(DB::Open(options, dbname1, &db1));
// After load, we do the Get again. To validate the cache, we do not allow any
@@ -2406,8 +2406,8 @@ TEST_P(DBSecondaryCacheTest, LRUCacheDumpLoadWithFilter) {
ASSERT_EQ(256, static_cast<int>(block_lookup));
fault_fs_->SetFailGetUniqueId(false);
fault_fs_->SetFilesystemActive(true);
db1.reset();
db2.reset();
delete db1;
delete db2;
ASSERT_OK(DestroyDB(dbname1, options));
ASSERT_OK(DestroyDB(dbname2, options));
}
@@ -2619,11 +2619,11 @@ TEST_P(DBSecondaryCacheTest, TestSecondaryCacheOptionTwoDB) {
options.paranoid_file_checks = true;
std::string dbname1 = test::PerThreadDBPath("db_t_1");
ASSERT_OK(DestroyDB(dbname1, options));
std::unique_ptr<DB> db1;
DB* db1 = nullptr;
ASSERT_OK(DB::Open(options, dbname1, &db1));
std::string dbname2 = test::PerThreadDBPath("db_t_2");
ASSERT_OK(DestroyDB(dbname2, options));
std::unique_ptr<DB> db2;
DB* db2 = nullptr;
Options options2 = options;
options2.lowest_used_cache_tier = CacheTier::kVolatileTier;
ASSERT_OK(DB::Open(options2, dbname2, &db2));
@@ -2700,8 +2700,8 @@ TEST_P(DBSecondaryCacheTest, TestSecondaryCacheOptionTwoDB) {
fault_fs_->SetFailGetUniqueId(false);
fault_fs_->SetFilesystemActive(true);
db1.reset();
db2.reset();
delete db1;
delete db2;
ASSERT_OK(DestroyDB(dbname1, options));
ASSERT_OK(DestroyDB(dbname2, options));
}
-512
View File
@@ -1,512 +0,0 @@
# Adding New Options to RocksDB Public API
This document provides guidance on how to add new options to RocksDB's public API. There are two main categories of options:
1. **Standard Column Family Options** (Options/DBOptions/AdvancedColumnFamilyOptions)
2. **BlockBasedTableOptions** (options specific to block-based table format)
## Overview of Files to Modify
### For Standard Column Family Options
| File | Purpose |
|------|---------|
| `include/rocksdb/advanced_options.h` | Define the option with documentation |
| `include/rocksdb/options.h` | Add reference in related option groups if needed |
| `options/cf_options.h` | Add to `MutableCFOptions` or `ImmutableCFOptions` struct |
| `options/cf_options.cc` | Register option for serialization/deserialization and logging |
| `options/options_helper.cc` | Add to `UpdateColumnFamilyOptions()` for mutable options |
| `options/options_settable_test.cc` | Add to test string for option parsing |
| `db_stress_tool/db_stress_common.h` | Declare gflag |
| `db_stress_tool/db_stress_gflags.cc` | Define gflag with default value |
| `db_stress_tool/db_stress_test_base.cc` | Apply flag to options |
| `tools/db_bench_tool.cc` | Add flag definition and apply to options |
| `tools/db_crashtest.py` | Add randomized values for stress testing |
| `unreleased_history/new_features/` | Add release note markdown file |
### For BlockBasedTableOptions
| File | Purpose |
|------|---------|
| `include/rocksdb/table.h` | Define the option in `BlockBasedTableOptions` struct |
| `table/block_based/block_based_table_factory.cc` | Register for serialization, validation, and printing |
| `options/options_settable_test.cc` | Add to `BlockBasedTableOptionsAllFieldsSettable` test |
| `options/options_test.cc` | Add to `MutableCFOptions` test if applicable |
| `db_stress_tool/db_stress_common.h` | Declare gflag |
| `db_stress_tool/db_stress_gflags.cc` | Define gflag |
| `db_stress_tool/db_stress_test_base.cc` | Apply flag to `block_based_options` |
| `tools/db_bench_tool.cc` | Add flag definition and apply to `block_based_options` |
| `tools/db_crashtest.py` | Add randomized values |
| `java/src/main/java/org/rocksdb/BlockBasedTableConfig.java` | Java API |
| `java/rocksjni/portal.h` | JNI portal for Java bindings |
| `java/rocksjni/table.cc` | JNI implementation |
| `java/src/test/java/org/rocksdb/BlockBasedTableConfigTest.java` | Java unit test |
---
## Pattern 1: Adding a Standard Column Family Option
Example reference: commit `94e65a2e0b4f817aa4bfa4c96cdf867e7980d7bc` (memtable_veirfy_per_key_checksum_on_seek)
### Step 1: Define the Option in Public Header
**File: `include/rocksdb/advanced_options.h`**
Add the option with documentation in `AdvancedColumnFamilyOptions` struct:
```cpp
// Enables additional integrity checks during seek.
// Specifically, for skiplist-based memtables, key checksum validation could
// be enabled during seek optionally. This is helpful to detect corrupted
// memtable keys during reads. Enabling this feature incurs a performance
// overhead due to additional key checksum validation during memtable seek
// operation.
// This option depends on memtable_protection_bytes_per_key to be non zero.
// If memtable_protection_bytes_per_key is zero, no validation is performed.
bool memtable_veirfy_per_key_checksum_on_seek = false;
```
### Step 2: Add to Internal Options Structs
**File: `options/cf_options.h`**
Add to `MutableCFOptions` struct (or `ImmutableCFOptions` for immutable options):
```cpp
// In MutableCFOptions constructor from Options:
memtable_veirfy_per_key_checksum_on_seek(
options.memtable_veirfy_per_key_checksum_on_seek),
// In MutableCFOptions default constructor:
memtable_veirfy_per_key_checksum_on_seek(false),
// In MutableCFOptions struct member declarations:
bool memtable_veirfy_per_key_checksum_on_seek;
```
### Step 3: Register for Serialization/Deserialization
**File: `options/cf_options.cc`**
Add to the options type info map for serialization:
```cpp
{"memtable_veirfy_per_key_checksum_on_seek",
{offsetof(struct MutableCFOptions,
memtable_veirfy_per_key_checksum_on_seek),
OptionType::kBoolean, OptionVerificationType::kNormal,
OptionTypeFlags::kMutable}},
```
Add logging in `MutableCFOptions::Dump()`:
```cpp
ROCKS_LOG_INFO(log, "memtable_veirfy_per_key_checksum_on_seek: %d",
memtable_veirfy_per_key_checksum_on_seek);
```
### Step 4: Update Options Helper
**File: `options/options_helper.cc`**
Add to `UpdateColumnFamilyOptions()`:
```cpp
cf_opts->memtable_veirfy_per_key_checksum_on_seek =
moptions.memtable_veirfy_per_key_checksum_on_seek;
```
### Step 5: Add to Options Settable Test
**File: `options/options_settable_test.cc`**
Add to the test string in `ColumnFamilyOptionsAllFieldsSettable`:
```cpp
"memtable_veirfy_per_key_checksum_on_seek=1;"
```
### Step 6: Add db_stress Support
**File: `db_stress_tool/db_stress_common.h`**
```cpp
DECLARE_bool(memtable_veirfy_per_key_checksum_on_seek);
```
**File: `db_stress_tool/db_stress_gflags.cc`**
```cpp
DEFINE_bool(
memtable_veirfy_per_key_checksum_on_seek,
ROCKSDB_NAMESPACE::Options().memtable_veirfy_per_key_checksum_on_seek,
"Sets CF option memtable_veirfy_per_key_checksum_on_seek.");
```
**File: `db_stress_tool/db_stress_test_base.cc`**
```cpp
options.memtable_veirfy_per_key_checksum_on_seek =
FLAGS_memtable_veirfy_per_key_checksum_on_seek;
```
### Step 7: Add db_bench Support
**File: `tools/db_bench_tool.cc`**
```cpp
// Flag definition (near related flags):
DEFINE_bool(memtable_veirfy_per_key_checksum_on_seek, false,
"Sets CF option memtable_veirfy_per_key_checksum_on_seek");
// Apply flag to options (in InitializeOptionsFromFlags or similar):
options.memtable_veirfy_per_key_checksum_on_seek =
FLAGS_memtable_veirfy_per_key_checksum_on_seek;
```
### Step 8: Add Crash Test Support
**File: `tools/db_crashtest.py`**
```python
"memtable_veirfy_per_key_checksum_on_seek": lambda: random.choice([0] * 7 + [1]),
```
Also add constraint handling in `finalize_and_sanitize()` if needed:
```python
# only skip list memtable representation supports paranoid memory checks
if dest_params.get("memtablerep") != "skip_list":
dest_params["memtable_veirfy_per_key_checksum_on_seek"] = 0
```
### Step 9: Add Release Note
**File: `unreleased_history/new_features/<descriptive_name>.md`**
```markdown
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.
```
---
## Pattern 2: Adding a BlockBasedTableOptions Option
Example reference: commit `742741b175c5f238374c1714f9db3340d49de569` (super_block_alignment_size)
### Step 1: Define the Option in Public Header
**File: `include/rocksdb/table.h`**
Add to `BlockBasedTableOptions` struct with documentation:
```cpp
// Align data blocks on super block alignment. Avoid a data block split across
// super block boundaries. Works with/without compression.
//
// Here a "super block" refers to an aligned unit of underlying Filesystem
// storage for which there is an extra cost when a random read involves two
// such super blocks instead of just one. Configuring that size here suggests
// inserting padding in the SST file to avoid a single SST block splitting
// across two super blocks. Only power-of-two sizes are supported. See also
// super_block_alignment_space_overhead_ratio. Default to 0, which means super
// block alignment is disabled.
size_t super_block_alignment_size = 0;
// This option controls the storage space overhead of super block alignment.
// It is used to calculate the max padding size allowed for super block
// alignment. It is calculated in this way. If super_block_alignment_size is
// 2MB, and super_block_alignment_overhead_ratio is 128, then the max padding
// size allowed for super block alignment is 2MB / 128 = 16KB.
// Note that, when it is set to 0, super block alignment is disabled.
size_t super_block_alignment_space_overhead_ratio = 128;
```
### Step 2: Register for Serialization in Table Factory
**File: `table/block_based/block_based_table_factory.cc`**
Add to the type info map:
```cpp
{"super_block_alignment_size",
{offsetof(struct BlockBasedTableOptions, super_block_alignment_size),
OptionType::kSizeT, OptionVerificationType::kNormal}},
{"super_block_alignment_space_overhead_ratio",
{offsetof(struct BlockBasedTableOptions,
super_block_alignment_space_overhead_ratio),
OptionType::kSizeT, OptionVerificationType::kNormal}},
```
Add validation in `ValidateOptions()`:
```cpp
if ((table_options_.super_block_alignment_size &
(table_options_.super_block_alignment_size - 1))) {
return Status::InvalidArgument(
"Super Block alignment requested but super block alignment size is not "
"a power of 2");
}
if (table_options_.super_block_alignment_size >
std::numeric_limits<uint32_t>::max()) {
return Status::InvalidArgument(
"Super block alignment size exceeds maximum number (4GiB) allowed");
}
```
Add printing in `GetPrintableOptions()`:
```cpp
snprintf(buffer, kBufferSize,
" super_block_alignment_size: %" ROCKSDB_PRIszt "\n",
table_options_.super_block_alignment_size);
ret.append(buffer);
```
### Step 3: Add to Options Settable Test
**File: `options/options_settable_test.cc`**
Add to `BlockBasedTableOptionsAllFieldsSettable` test:
```cpp
"super_block_alignment_size=65536;"
"super_block_alignment_space_overhead_ratio=4096;"
```
### Step 4: Add to Options Test
**File: `options/options_test.cc`**
```cpp
ASSERT_OK(GetColumnFamilyOptionsFromString(
config_options, cf_opts,
"block_based_table_factory.super_block_alignment_size=65536; "
"block_based_table_factory.super_block_alignment_space_overhead_ratio=4096;",
&cf_opts));
ASSERT_EQ(bbto->super_block_alignment_size, 65536);
ASSERT_EQ(bbto->super_block_alignment_space_overhead_ratio, 4096);
```
### Step 5: Add db_stress Support
**File: `db_stress_tool/db_stress_common.h`**
```cpp
DECLARE_uint64(super_block_alignment_size);
DECLARE_uint64(super_block_alignment_space_overhead_ratio);
```
**File: `db_stress_tool/db_stress_gflags.cc`**
```cpp
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");
```
**File: `db_stress_tool/db_stress_test_base.cc`**
```cpp
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;
```
### Step 6: Add db_bench Support
**File: `tools/db_bench_tool.cc`**
```cpp
// Flag definitions:
DEFINE_uint64(
super_block_alignment_size,
ROCKSDB_NAMESPACE::BlockBasedTableOptions().super_block_alignment_size,
"Configure super block size");
DEFINE_uint64(super_block_alignment_space_overhead_ratio,
ROCKSDB_NAMESPACE::BlockBasedTableOptions()
.super_block_alignment_space_overhead_ratio,
"Configure space overhead for super block alignment");
// Apply to block_based_options (in the block where other options are set):
block_based_options.super_block_alignment_size = FLAGS_super_block_alignment_size;
block_based_options.super_block_alignment_space_overhead_ratio =
FLAGS_super_block_alignment_space_overhead_ratio;
```
### Step 7: Add Crash Test Support
**File: `tools/db_crashtest.py`**
```python
"super_block_alignment_size": lambda: random.choice(
[0, 128 * 1024, 512 * 1024, 2 * 1024 * 1024]
),
"super_block_alignment_space_overhead_ratio": lambda: random.choice([0, 32, 4096]),
```
### Step 8: Add Java API Support
**File: `java/src/main/java/org/rocksdb/BlockBasedTableConfig.java`**
Add getter and setter methods:
```java
/**
* Get the super block alignment size.
*
* @return the super block alignment size.
*/
public long superBlockAlignmentSize() {
return superBlockAlignmentSize;
}
/**
* Set the super block alignment size.
* When set to 0, super block alignment is disabled.
*
* @param superBlockAlignmentSize the super block alignment size.
*
* @return the reference to the current option.
*/
public BlockBasedTableConfig setSuperBlockAlignmentSize(final long superBlockAlignmentSize) {
this.superBlockAlignmentSize = superBlockAlignmentSize;
return this;
}
```
Add member variable:
```java
private long superBlockAlignmentSize;
```
Update constructor and native method signature.
**File: `java/rocksjni/portal.h`**
Update `GetMethodID` signature and add fields to Java object construction.
**File: `java/rocksjni/table.cc`**
Add parameters to JNI function and apply to options.
**File: `java/src/test/java/org/rocksdb/BlockBasedTableConfigTest.java`**
Add unit tests:
```java
@Test
public void superBlockAlignmentSize() {
final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig();
blockBasedTableConfig.setSuperBlockAlignmentSize(1024 * 1024);
assertThat(blockBasedTableConfig.superBlockAlignmentSize()).isEqualTo(1024 * 1024);
}
```
---
## Pattern 3: Adding C API for Existing Option
Example reference: commit `429b36c22d76403d275dd0e6877b08d4cea2bc90` (block_align C API)
If an option already exists but needs C API support:
**File: `db/c.cc`**
```cpp
void rocksdb_block_based_options_set_block_align(
rocksdb_block_based_table_options_t* options, unsigned char v) {
options->rep.block_align = v;
}
```
**File: `include/rocksdb/c.h`**
```cpp
extern ROCKSDB_LIBRARY_API void rocksdb_block_based_options_set_block_align(
rocksdb_block_based_table_options_t*, unsigned char);
```
---
## Unit Testing Guidelines
### For Standard Options
Add tests in appropriate test files (e.g., `db/db_memtable_test.cc`, `db/db_options_test.cc`):
```cpp
TEST_F(DBMemTableTest, YourOptionTest) {
Options options;
options.your_new_option = true;
Reopen(options);
// Test the behavior
}
```
### For BlockBasedTableOptions
Add tests in `db/db_flush_test.cc`, `table/block_based/block_based_table_reader_test.cc`, or `table/table_test.cc`:
```cpp
TEST_P(DBFlushYourFeatureTest, YourFeature) {
Options options;
BlockBasedTableOptions block_options;
block_options.your_new_option = some_value;
options.table_factory.reset(NewBlockBasedTableFactory(block_options));
ASSERT_OK(options.table_factory->ValidateOptions(
DBOptions(options), ColumnFamilyOptions(options)));
Reopen(options);
// Test the behavior
}
```
---
## Option Type Reference
Common option types used in serialization:
| OptionType | C++ Type | Example |
|------------|----------|---------|
| `kBoolean` | `bool` | `paranoid_memory_checks` |
| `kInt` | `int` | `max_write_buffer_number` |
| `kInt32T` | `int32_t` | `level0_file_num_compaction_trigger` |
| `kUInt32T` | `uint32_t` | `memtable_protection_bytes_per_key` |
| `kUInt64T` | `uint64_t` | `target_file_size_base` |
| `kSizeT` | `size_t` | `block_size` |
| `kDouble` | `double` | `compression_ratio` |
| `kString` | `std::string` | `db_log_dir` |
---
## Checklist Summary
- [ ] Public header file with option definition and documentation
- [ ] Internal options struct (MutableCFOptions or ImmutableCFOptions)
- [ ] Options serialization/deserialization registration
- [ ] Options logging in Dump() method
- [ ] UpdateColumnFamilyOptions() for mutable options
- [ ] options_settable_test.cc
- [ ] db_stress_common.h (DECLARE)
- [ ] db_stress_gflags.cc (DEFINE)
- [ ] db_stress_test_base.cc (apply flag)
- [ ] db_bench_tool.cc (DEFINE and apply)
- [ ] db_crashtest.py (randomized values)
- [ ] Unit tests
- [ ] unreleased_history markdown file
- [ ] Java API (for BlockBasedTableOptions)
- [ ] C API (if needed)
-504
View File
@@ -1,504 +0,0 @@
# RocksDB API Development Guide
This document provides guidance for adding new public APIs to RocksDB, following the established patterns used by existing APIs like `CompactRange`.
## API Layer Architecture
RocksDB exposes public APIs through multiple layers. Users can access RocksDB through any of the three public APIs: C++ headers, C headers, or Java bindings.
Here is an example for public header db.h:
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ Level 1: Public APIs (User Entry Points) │
├───────────────────────┬─────────────────────────┬───────────────────────────┤
│ C++ Public API │ C API Bindings │ Java/JNI API │
│ include/rocksdb/db.h │ include/rocksdb/c.h │ java/src/.../RocksDB.java │
│ include/rocksdb/*.h │ │ java/src/.../*.java │
└───────────────────────┴────────────┬────────────┴───────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ Level 2: C++ Implementation (Internal Core) │
│ db/db_impl/db_impl*.cc, db/c.cc, java/rocksjni/*.cc │
└─────────────────────────────────────────────────────────────────────────────┘
```
## Step-by-Step Guide: Adding a New Public API
### Step 1: Define the C++ Public Interface
**File:** `include/rocksdb/db.h`
Add the virtual method declaration in the `DB` class:
\`\`\`cpp
// Pure virtual - must be implemented by DBImpl
virtual Status YourNewAPI(const YourAPIOptions& options,
ColumnFamilyHandle* column_family,
/* other params */) = 0;
// Convenience overload for default column family
virtual Status YourNewAPI(const YourAPIOptions& options,
/* other params */) {
return YourNewAPI(options, DefaultColumnFamily(), /* other params */);
}
\`\`\`
**Key Patterns:**
- Use `Status` return type for error handling
- Use `OptSlice` to avoid unnecessary levels of indirection and use of raw pointers.
- Use `ColumnFamilyHandle*` for column family support
- Provide convenience overloads for the default column family
### Step 2: Define Options Struct (If Needed)
**File:** `include/rocksdb/options.h`
If your API has multiple configuration options, define an options struct:
\`\`\`cpp
struct YourAPIOptions {
// Document each option with clear comments
bool some_boolean_option = false;
// Default value explanation
int some_int_option = -1;
// Pointer options require careful lifetime management
std::atomic<bool>* canceled = nullptr;
// Enum options for multi-choice settings
YourEnumType some_enum = YourEnumType::kDefault;
};
\`\`\`
**Key Patterns:**
- Use sensible default values specified inline (e.g., `= false`, `= -1`)
- Do NOT redundantly document the default value in comments; instead, document the rationale (why this default), historical context, and how different values are interpreted
- Group related options logically
- Consider thread-safety for pointer options
### Step 3: Implement in DBImpl
**Header:** `db/db_impl/db_impl.h`
\`\`\`cpp
using DB::YourNewAPI;
Status YourNewAPI(const YourAPIOptions& options,
ColumnFamilyHandle* column_family,
/* other params */) override;
// Private internal implementation if needed
Status YourNewAPIInternal(const YourAPIOptions& options,
ColumnFamilyHandle* column_family,
/* other params */);
\`\`\`
**Implementation:** `db/db_impl/db_impl_<category>.cc`
Choose the appropriate implementation file based on functionality:
- `db_impl_compaction_flush.cc` - Compaction and flush operations
- `db_impl_write.cc` - Write operations
- `db_impl_open.cc` - DB opening/closing
- `db_impl_files.cc` - File operations
- `db_impl.cc` - General operations
\`\`\`cpp
Status DBImpl::YourNewAPI(const YourAPIOptions& options,
ColumnFamilyHandle* column_family,
/* other params */) {
// 1. Input validation
if (/* invalid input */) {
return Status::InvalidArgument("Error message");
}
// 2. Check for cancellation/abort conditions
if (options.canceled && options.canceled->load(std::memory_order_acquire)) {
return Status::Incomplete(Status::SubCode::kManualCompactionPaused);
}
// 3. Get column family data
auto cfh = static_cast<ColumnFamilyHandleImpl*>(column_family);
auto cfd = cfh->cfd();
// 4. Core implementation logic
// ...
return Status::OK();
}
\`\`\`
### Step 4: Handle Special DB Types
**StackableDB (Wrapper DBs):**
**File:** `include/rocksdb/utilities/stackable_db.h`
\`\`\`cpp
using DB::YourNewAPI;
Status YourNewAPI(const YourAPIOptions& options,
ColumnFamilyHandle* column_family,
/* other params */) override {
return db_->YourNewAPI(options, column_family, /* other params */);
}
\`\`\`
**Secondary DB (Read-Only):**
**File:** `db/db_impl/db_impl_secondary.h`
\`\`\`cpp
using DBImpl::YourNewAPI;
Status YourNewAPI(const YourAPIOptions& /*options*/,
ColumnFamilyHandle* /*column_family*/,
/* other params */) override {
return Status::NotSupported("Not supported in secondary DB");
}
\`\`\`
**CompactedDB (Read-Only):**
**File:** `db/db_impl/compacted_db_impl.h`
\`\`\`cpp
using DBImpl::YourNewAPI;
Status YourNewAPI(const YourAPIOptions& /*options*/,
ColumnFamilyHandle* /*column_family*/,
/* other params */) override {
return Status::NotSupported("Not supported for read-only DB");
}
\`\`\`
### Step 5: Add C API Bindings
**Header:** `include/rocksdb/c.h`
\`\`\`c
// Basic version
extern ROCKSDB_LIBRARY_API void rocksdb_your_new_api(
rocksdb_t* db,
const char* start_key, size_t start_key_len,
const char* limit_key, size_t limit_key_len);
// Column family version
extern ROCKSDB_LIBRARY_API void rocksdb_your_new_api_cf(
rocksdb_t* db, rocksdb_column_family_handle_t* column_family,
const char* start_key, size_t start_key_len,
const char* limit_key, size_t limit_key_len);
// With options and error handling
extern ROCKSDB_LIBRARY_API void rocksdb_your_new_api_opt(
rocksdb_t* db, rocksdb_your_api_options_t* opt,
const char* start_key, size_t start_key_len,
const char* limit_key, size_t limit_key_len,
char** errptr);
\`\`\`
**Implementation:** `db/c.cc`
\`\`\`cpp
void rocksdb_your_new_api(rocksdb_t* db, const char* start_key,
size_t start_key_len, const char* limit_key,
size_t limit_key_len) {
Slice a, b;
db->rep->YourNewAPI(
YourAPIOptions(), // Default options
(start_key ? (a = Slice(start_key, start_key_len), &a) : nullptr),
(limit_key ? (b = Slice(limit_key, limit_key_len), &b) : nullptr));
}
void rocksdb_your_new_api_cf(rocksdb_t* db,
rocksdb_column_family_handle_t* column_family,
const char* start_key, size_t start_key_len,
const char* limit_key, size_t limit_key_len) {
Slice a, b;
db->rep->YourNewAPI(
YourAPIOptions(),
column_family->rep,
(start_key ? (a = Slice(start_key, start_key_len), &a) : nullptr),
(limit_key ? (b = Slice(limit_key, limit_key_len), &b) : nullptr));
}
\`\`\`
**If you have options, also add:**
\`\`\`cpp
// Options struct wrapper
struct rocksdb_your_api_options_t {
YourAPIOptions rep;
};
rocksdb_your_api_options_t* rocksdb_your_api_options_create() {
return new rocksdb_your_api_options_t;
}
void rocksdb_your_api_options_destroy(rocksdb_your_api_options_t* opt) {
delete opt;
}
void rocksdb_your_api_options_set_some_option(
rocksdb_your_api_options_t* opt, unsigned char value) {
opt->rep.some_boolean_option = value;
}
\`\`\`
### Step 6: Add Java Bindings
**Java API:** `java/src/main/java/org/rocksdb/RocksDB.java`
\`\`\`java
// Basic version
public void yourNewAPI() throws RocksDBException {
yourNewAPI(null);
}
// Column family version
public void yourNewAPI(ColumnFamilyHandle columnFamilyHandle)
throws RocksDBException {
yourNewAPI(nativeHandle_, null, -1, null, -1, 0,
columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_);
}
// Range version
public void yourNewAPI(final byte[] begin, final byte[] end)
throws RocksDBException {
yourNewAPI(null, begin, end);
}
// Full-featured version with options
public void yourNewAPI(ColumnFamilyHandle columnFamilyHandle,
final byte[] begin, final byte[] end,
final YourAPIOptions options)
throws RocksDBException {
yourNewAPI(nativeHandle_,
begin, begin == null ? -1 : begin.length,
end, end == null ? -1 : end.length,
options.nativeHandle_,
columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_);
}
// Native method declaration
private static native void yourNewAPI(final long handle,
/* @Nullable */ final byte[] begin, final int beginLen,
/* @Nullable */ final byte[] end, final int endLen,
final long optionsHandle,
final long cfHandle);
\`\`\`
**Options Class:** `java/src/main/java/org/rocksdb/YourAPIOptions.java`
\`\`\`java
public class YourAPIOptions extends RocksObject {
public YourAPIOptions() {
super(newYourAPIOptions());
}
// Builder pattern setters
public YourAPIOptions setSomeBooleanOption(boolean value) {
setSomeBooleanOption(nativeHandle_, value);
return this;
}
// Getters
public boolean someBooleanOption() {
return someBooleanOption(nativeHandle_);
}
// Native method declarations
private static native long newYourAPIOptions();
private static native void disposeInternalJni(long handle);
private static native void setSomeBooleanOption(long handle, boolean value);
private static native boolean someBooleanOption(long handle);
@Override
protected final void disposeInternal(final long handle) {
disposeInternalJni(handle);
}
}
\`\`\`
**JNI Implementation:** `java/rocksjni/rocksjni.cc`
\`\`\`cpp
void Java_org_rocksdb_RocksDB_yourNewAPI(
JNIEnv* env, jclass,
jlong jdb_handle, jbyteArray jbegin, jint jbegin_len,
jbyteArray jend, jint jend_len,
jlong joptions_handle, jlong jcf_handle) {
// 1. Convert Java byte arrays to C++ strings
jboolean has_exception = JNI_FALSE;
std::string str_begin;
if (jbegin_len > 0) {
str_begin = ROCKSDB_NAMESPACE::JniUtil::byteString<std::string>(
env, jbegin, jbegin_len,
[](const char* str, const size_t len) { return std::string(str, len); },
&has_exception);
if (has_exception == JNI_TRUE) return;
}
std::string str_end;
if (jend_len > 0) {
str_end = ROCKSDB_NAMESPACE::JniUtil::byteString<std::string>(
env, jend, jend_len,
[](const char* str, const size_t len) { return std::string(str, len); },
&has_exception);
if (has_exception == JNI_TRUE) return;
}
// 2. Get or create options
ROCKSDB_NAMESPACE::YourAPIOptions* options = nullptr;
if (joptions_handle == 0) {
options = new ROCKSDB_NAMESPACE::YourAPIOptions();
} else {
options = reinterpret_cast<ROCKSDB_NAMESPACE::YourAPIOptions*>(joptions_handle);
}
// 3. Unwrap handles
auto* db = reinterpret_cast<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle =
jcf_handle == 0 ? db->DefaultColumnFamily()
: reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jcf_handle);
// 4. Create Slices
std::unique_ptr<ROCKSDB_NAMESPACE::Slice> begin;
std::unique_ptr<ROCKSDB_NAMESPACE::Slice> end;
if (jbegin_len > 0) begin.reset(new ROCKSDB_NAMESPACE::Slice(str_begin));
if (jend_len > 0) end.reset(new ROCKSDB_NAMESPACE::Slice(str_end));
// 5. Call C++ API
ROCKSDB_NAMESPACE::Status s = db->YourNewAPI(*options, cf_handle, begin.get(), end.get());
// 6. Cleanup if we created options
if (joptions_handle == 0) delete options;
// 7. Throw Java exception on error
ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s);
}
\`\`\`
**Options JNI:** `java/rocksjni/your_api_options.cc`
\`\`\`cpp
jlong Java_org_rocksdb_YourAPIOptions_newYourAPIOptions(JNIEnv*, jclass) {
auto* options = new ROCKSDB_NAMESPACE::YourAPIOptions();
return GET_CPLUSPLUS_POINTER(options);
}
void Java_org_rocksdb_YourAPIOptions_disposeInternalJni(JNIEnv*, jclass, jlong jhandle) {
auto* options = reinterpret_cast<ROCKSDB_NAMESPACE::YourAPIOptions*>(jhandle);
delete options;
}
void Java_org_rocksdb_YourAPIOptions_setSomeBooleanOption(
JNIEnv*, jclass, jlong jhandle, jboolean value) {
auto* options = reinterpret_cast<ROCKSDB_NAMESPACE::YourAPIOptions*>(jhandle);
options->some_boolean_option = static_cast<bool>(value);
}
jboolean Java_org_rocksdb_YourAPIOptions_someBooleanOption(JNIEnv*, jclass, jlong jhandle) {
auto* options = reinterpret_cast<ROCKSDB_NAMESPACE::YourAPIOptions*>(jhandle);
return static_cast<jboolean>(options->some_boolean_option);
}
\`\`\`
### Step 7: Update Build Files
**Java CMakeLists.txt:** `java/CMakeLists.txt`
Add your new Java source files:
\`\`\`cmake
src/main/java/org/rocksdb/YourAPIOptions.java
src/test/java/org/rocksdb/YourAPIOptionsTest.java
\`\`\`
### Step 8: Add Release Notes
**Directory:** `unreleased_history/`
RocksDB uses individual files in the `unreleased_history/` directory rather than directly editing `HISTORY.md`. This avoids merge conflicts and ensures changes are attributed to the correct release version.
Add a file to the appropriate subdirectory:
- `unreleased_history/new_features/` - For new functionality
- `unreleased_history/public_api_changes/` - For API changes
- `unreleased_history/behavior_changes/` - For behavior modifications
- `unreleased_history/bug_fixes/` - For bug fixes
**Example:** `unreleased_history/new_features/your_new_api.md`
\`\`\`markdown
Added `YourNewAPI()` to support [describe functionality]. See `YourAPIOptions` for configuration.
\`\`\`
**Example:** `unreleased_history/public_api_changes/your_api_options.md`
**Note:** Files should contain one line of markdown. The "* " prefix is automatically added if not included. These files are compiled into `HISTORY.md` during the release process.
### Step 9: Add Tests
**C++ Unit Tests:** `db/db_your_api_test.cc` or add to existing test file
\`\`\`cpp
TEST_F(DBTest, YourNewAPIBasic) {
Options options = CurrentOptions();
CreateAndReopenWithCF({"pikachu"}, options);
// Setup test data
ASSERT_OK(Put(1, "key1", "value1"));
ASSERT_OK(Put(1, "key2", "value2"));
// Test your API
YourAPIOptions api_options;
api_options.some_boolean_option = true;
ASSERT_OK(db_->YourNewAPI(api_options, handles_[1], nullptr, nullptr));
// Verify results
// ...
}
\`\`\`
**Java Tests:** `java/src/test/java/org/rocksdb/YourAPIOptionsTest.java`
\`\`\`java
public class YourAPIOptionsTest {
@Test
public void yourAPIOptions() {
try (final YourAPIOptions options = new YourAPIOptions()) {
assertFalse(options.someBooleanOption());
options.setSomeBooleanOption(true);
assertTrue(options.someBooleanOption());
}
}
}
\`\`\`
## File Summary Checklist
| Component | File(s) | Required |
|-----------|---------|----------|
| C++ Public Interface | `include/rocksdb/db.h` | ✓ |
| Options Struct | `include/rocksdb/options.h` | If needed |
| DBImpl Declaration | `db/db_impl/db_impl.h` | ✓ |
| DBImpl Implementation | `db/db_impl/db_impl_*.cc` | ✓ |
| StackableDB | `include/rocksdb/utilities/stackable_db.h` | ✓ |
| Secondary DB | `db/db_impl/db_impl_secondary.h` | If not supported |
| Compacted DB | `db/db_impl/compacted_db_impl.h` | If not supported |
| C API Header | `include/rocksdb/c.h` | ✓ |
| C API Implementation | `db/c.cc` | ✓ |
| Java API | `java/src/main/java/org/rocksdb/RocksDB.java` | ✓ |
| Java Options | `java/src/main/java/org/rocksdb/YourAPIOptions.java` | If needed |
| JNI Implementation | `java/rocksjni/rocksjni.cc` | ✓ |
| JNI Options | `java/rocksjni/your_api_options.cc` | If needed |
| Java CMake | `java/CMakeLists.txt` | If new files |
| Changelog | `unreleased_history/*.md` | ✓ |
| C++ Tests | `db/db_*_test.cc` | ✓ |
| Java Tests | `java/src/test/java/org/rocksdb/*Test.java` | ✓ |
## Best Practices
1. **Error Handling**: Always return `Status` objects in C++, throw exceptions in Java
2. **Default Values**: Provide sensible defaults for all options
3. **Documentation**: Add clear comments for all public methods and options
4. **Column Family Support**: Always support column family operations
5. **Thread Safety**: Document thread-safety guarantees
6. **Backward Compatibility**: Avoid breaking existing API contracts
7. **Testing**: Add comprehensive unit tests for all code paths
+10 -7
View File
@@ -403,19 +403,22 @@ TEST_F(BlobFileBuilderTest, Compression) {
ASSERT_EQ(blob_file_addition.GetBlobFileNumber(), blob_file_number);
ASSERT_EQ(blob_file_addition.GetTotalBlobCount(), 1);
auto compressor =
GetBuiltinV2CompressionManager()->GetCompressor({}, kSnappyCompression);
GrowableBuffer compressed_value;
ASSERT_OK(LegacyForceBuiltinCompression(*compressor, /*working_area=*/nullptr,
uncompressed_value,
&compressed_value));
CompressionOptions opts;
CompressionContext context(kSnappyCompression, opts);
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
kSnappyCompression);
std::string compressed_value;
ASSERT_TRUE(Snappy_Compress(info, uncompressed_value.data(),
uncompressed_value.size(), &compressed_value));
ASSERT_EQ(blob_file_addition.GetTotalBlobBytes(),
BlobLogRecord::kHeaderSize + key_size + compressed_value.size());
// Verify the contents of the new blob file as well as the blob reference
std::vector<std::pair<std::string, std::string>> expected_key_value_pairs{
{key, compressed_value.AsSlice().ToString()}};
{key, compressed_value}};
std::vector<std::string> blob_indexes{blob_index};
VerifyBlobFile(blob_file_number, blob_file_path, column_family_id,
-12
View File
@@ -137,18 +137,6 @@ class BlobIndex {
return oss.str();
}
// Encode this blob index into dst based on its type.
void EncodeTo(std::string* dst) const {
if (IsInlined()) {
EncodeInlinedTTL(dst, expiration_, value_);
} else if (HasTTL()) {
EncodeBlobTTL(dst, expiration_, file_number_, offset_, size_,
compression_);
} else {
EncodeBlob(dst, file_number_, offset_, size_, compression_);
}
}
static void EncodeInlinedTTL(std::string* dst, uint64_t expiration,
const Slice& value) {
assert(dst != nullptr);
+51 -116
View File
@@ -11,7 +11,6 @@
#include <cstdlib>
#include <map>
#include <memory>
#include <unordered_set>
#include <vector>
@@ -596,6 +595,7 @@ struct rocksdb_slicetransform_t : public SliceTransform {
char* (*transform_)(void*, const char* key, size_t length,
size_t* dst_length);
unsigned char (*in_domain_)(void*, const char* key, size_t length);
unsigned char (*in_range_)(void*, const char* key, size_t length);
~rocksdb_slicetransform_t() override { (*destructor_)(state_); }
@@ -610,6 +610,10 @@ struct rocksdb_slicetransform_t : public SliceTransform {
bool InDomain(const Slice& src) const override {
return (*in_domain_)(state_, src.data(), src.size());
}
bool InRange(const Slice& src) const override {
return (*in_range_)(state_, src.data(), src.size());
}
};
struct rocksdb_universal_compaction_options_t {
@@ -1218,12 +1222,12 @@ char* rocksdb_open_and_compact_with_options(
rocksdb_t* rocksdb_open(const rocksdb_options_t* options, const char* name,
char** errptr) {
std::unique_ptr<DB> dbptr;
if (SaveError(errptr, DB::Open(options->rep, std::string(name), &dbptr))) {
DB* db;
if (SaveError(errptr, DB::Open(options->rep, std::string(name), &db))) {
return nullptr;
}
rocksdb_t* result = new rocksdb_t;
result->rep = dbptr.release();
result->rep = db;
return result;
}
@@ -1243,14 +1247,13 @@ rocksdb_t* rocksdb_open_for_read_only(const rocksdb_options_t* options,
const char* name,
unsigned char error_if_wal_file_exists,
char** errptr) {
std::unique_ptr<DB> dbptr;
if (SaveError(errptr,
DB::OpenForReadOnly(options->rep, std::string(name), &dbptr,
error_if_wal_file_exists))) {
DB* db;
if (SaveError(errptr, DB::OpenForReadOnly(options->rep, std::string(name),
&db, error_if_wal_file_exists))) {
return nullptr;
}
rocksdb_t* result = new rocksdb_t;
result->rep = dbptr.release();
result->rep = db;
return result;
}
@@ -1258,14 +1261,14 @@ rocksdb_t* rocksdb_open_as_secondary(const rocksdb_options_t* options,
const char* name,
const char* secondary_path,
char** errptr) {
std::unique_ptr<DB> dbptr;
DB* db;
if (SaveError(errptr,
DB::OpenAsSecondary(options->rep, std::string(name),
std::string(secondary_path), &dbptr))) {
std::string(secondary_path), &db))) {
return nullptr;
}
rocksdb_t* result = new rocksdb_t;
result->rep = dbptr.release();
result->rep = db;
return result;
}
@@ -1579,11 +1582,11 @@ rocksdb_t* rocksdb_open_and_trim_history(
std::string trim_ts_(trim_ts, trim_tslen);
std::unique_ptr<DB> dbptr;
DB* db;
std::vector<ColumnFamilyHandle*> handles;
if (SaveError(errptr, DB::OpenAndTrimHistory(
DBOptions(db_options->rep), std::string(name),
column_families, &handles, &dbptr, trim_ts_))) {
column_families, &handles, &db, trim_ts_))) {
return nullptr;
}
@@ -1595,7 +1598,7 @@ rocksdb_t* rocksdb_open_and_trim_history(
column_family_handles[i] = c_handle;
}
rocksdb_t* result = new rocksdb_t;
result->rep = dbptr.release();
result->rep = db;
return result;
}
@@ -1611,10 +1614,10 @@ rocksdb_t* rocksdb_open_column_families(
ColumnFamilyOptions(column_family_options[i]->rep));
}
std::unique_ptr<DB> dbptr;
DB* db;
std::vector<ColumnFamilyHandle*> handles;
if (SaveError(errptr, DB::Open(DBOptions(db_options->rep), std::string(name),
column_families, &handles, &dbptr))) {
column_families, &handles, &db))) {
return nullptr;
}
@@ -1626,7 +1629,7 @@ rocksdb_t* rocksdb_open_column_families(
column_family_handles[i] = c_handle;
}
rocksdb_t* result = new rocksdb_t;
result->rep = dbptr.release();
result->rep = db;
return result;
}
@@ -1679,12 +1682,12 @@ rocksdb_t* rocksdb_open_for_read_only_column_families(
ColumnFamilyOptions(column_family_options[i]->rep));
}
std::unique_ptr<DB> dbptr;
DB* db;
std::vector<ColumnFamilyHandle*> handles;
if (SaveError(errptr, DB::OpenForReadOnly(DBOptions(db_options->rep),
std::string(name), column_families,
&handles, &dbptr,
error_if_wal_file_exists))) {
if (SaveError(errptr,
DB::OpenForReadOnly(DBOptions(db_options->rep),
std::string(name), column_families,
&handles, &db, error_if_wal_file_exists))) {
return nullptr;
}
@@ -1696,7 +1699,7 @@ rocksdb_t* rocksdb_open_for_read_only_column_families(
column_family_handles[i] = c_handle;
}
rocksdb_t* result = new rocksdb_t;
result->rep = dbptr.release();
result->rep = db;
return result;
}
@@ -1712,12 +1715,12 @@ rocksdb_t* rocksdb_open_as_secondary_column_families(
std::string(column_family_names[i]),
ColumnFamilyOptions(column_family_options[i]->rep));
}
std::unique_ptr<DB> dbptr;
DB* db;
std::vector<ColumnFamilyHandle*> handles;
if (SaveError(errptr, DB::OpenAsSecondary(
DBOptions(db_options->rep), std::string(name),
std::string(secondary_path), column_families,
&handles, &dbptr))) {
if (SaveError(errptr, DB::OpenAsSecondary(DBOptions(db_options->rep),
std::string(name),
std::string(secondary_path),
column_families, &handles, &db))) {
return nullptr;
}
for (size_t i = 0; i != handles.size(); ++i) {
@@ -1728,7 +1731,7 @@ rocksdb_t* rocksdb_open_as_secondary_column_families(
column_family_handles[i] = c_handle;
}
rocksdb_t* result = new rocksdb_t;
result->rep = dbptr.release();
result->rep = db;
return result;
}
@@ -3039,19 +3042,12 @@ class H : public WriteBatch::Handler {
void* state_;
void (*put_)(void*, const char* k, size_t klen, const char* v, size_t vlen);
void (*deleted_)(void*, const char* k, size_t klen);
void (*log_data_)(void*, const char* blob, size_t blob_len);
void Put(const Slice& key, const Slice& value) override {
(*put_)(state_, key.data(), key.size(), value.data(), value.size());
}
void Delete(const Slice& key) override {
(*deleted_)(state_, key.data(), key.size());
}
void LogData(const Slice& blob) override {
if (log_data_) {
(*log_data_)(state_, blob.data(), blob.size());
}
}
};
class HCF : public WriteBatch::Handler {
@@ -3062,8 +3058,6 @@ class HCF : public WriteBatch::Handler {
void (*deleted_cf_)(void*, uint32_t cfid, const char* k, size_t klen);
void (*merge_cf_)(void*, uint32_t cfid, const char* k, size_t klen,
const char* v, size_t vlen);
void (*log_data_)(void*, const char* blob, size_t blob_len);
Status PutCF(uint32_t column_family_id, const Slice& key,
const Slice& value) override {
(*put_cf_)(state_, column_family_id, key.data(), key.size(), value.data(),
@@ -3080,11 +3074,6 @@ class HCF : public WriteBatch::Handler {
value.size());
return Status::OK();
}
void LogData(const Slice& blob) override {
if (log_data_) {
(*log_data_)(state_, blob.data(), blob.size());
}
}
};
void rocksdb_writebatch_iterate(rocksdb_writebatch_t* b, void* state,
@@ -3096,20 +3085,6 @@ void rocksdb_writebatch_iterate(rocksdb_writebatch_t* b, void* state,
handler.state_ = state;
handler.put_ = put;
handler.deleted_ = deleted;
handler.log_data_ = nullptr;
b->rep.Iterate(&handler);
}
void rocksdb_writebatch_iterate_ld(
rocksdb_writebatch_t* b, void* state,
void (*put)(void*, const char* k, size_t klen, const char* v, size_t vlen),
void (*deleted)(void*, const char* k, size_t klen),
void (*log_data)(void*, const char* blob, size_t blob_len)) {
H handler;
handler.state_ = state;
handler.put_ = put;
handler.deleted_ = deleted;
handler.log_data_ = log_data;
b->rep.Iterate(&handler);
}
@@ -3125,24 +3100,6 @@ void rocksdb_writebatch_iterate_cf(
handler.put_cf_ = put_cf;
handler.deleted_cf_ = deleted_cf;
handler.merge_cf_ = merge_cf;
handler.log_data_ = nullptr;
b->rep.Iterate(&handler);
}
void rocksdb_writebatch_iterate_cf_ld(
rocksdb_writebatch_t* b, void* state,
void (*put_cf)(void*, uint32_t cfid, const char* k, size_t klen,
const char* v, size_t vlen),
void (*deleted_cf)(void*, uint32_t cfid, const char* k, size_t klen),
void (*merge_cf)(void*, uint32_t cfid, const char* k, size_t klen,
const char* v, size_t vlen),
void (*log_data)(void*, const char* blob, size_t blob_len)) {
HCF handler;
handler.state_ = state;
handler.put_cf_ = put_cf;
handler.deleted_cf_ = deleted_cf;
handler.merge_cf_ = merge_cf;
handler.log_data_ = log_data;
b->rep.Iterate(&handler);
}
@@ -3743,11 +3700,6 @@ void rocksdb_block_based_options_set_format_version(
options->rep.format_version = v;
}
void rocksdb_block_based_options_set_separate_key_value_in_data_block(
rocksdb_block_based_table_options_t* options, unsigned char v) {
options->rep.separate_key_value_in_data_block = v;
}
void rocksdb_block_based_options_set_index_type(
rocksdb_block_based_table_options_t* options, int v) {
options->rep.index_type = static_cast<BlockBasedTableOptions::IndexType>(v);
@@ -3759,12 +3711,6 @@ void rocksdb_block_based_options_set_data_block_index_type(
static_cast<BlockBasedTableOptions::DataBlockIndexType>(v);
}
void rocksdb_block_based_options_set_index_block_search_type(
rocksdb_block_based_table_options_t* options, int v) {
options->rep.index_block_search_type =
static_cast<BlockBasedTableOptions::BlockSearchType>(v);
}
void rocksdb_block_based_options_set_data_block_hash_ratio(
rocksdb_block_based_table_options_t* options, double v) {
options->rep.data_block_hash_table_util_ratio = v;
@@ -4672,6 +4618,16 @@ unsigned char rocksdb_options_get_skip_stats_update_on_db_open(
return opt->rep.skip_stats_update_on_db_open;
}
void rocksdb_options_set_skip_checking_sst_file_sizes_on_db_open(
rocksdb_options_t* opt, unsigned char val) {
opt->rep.skip_checking_sst_file_sizes_on_db_open = val;
}
unsigned char rocksdb_options_get_skip_checking_sst_file_sizes_on_db_open(
rocksdb_options_t* opt) {
return opt->rep.skip_checking_sst_file_sizes_on_db_open;
}
/* Blob Options Settings */
void rocksdb_options_set_enable_blob_files(rocksdb_options_t* opt,
unsigned char val) {
@@ -6087,6 +6043,11 @@ unsigned char rocksdb_readoptions_get_tailing(rocksdb_readoptions_t* opt) {
return opt->rep.tailing;
}
void rocksdb_readoptions_set_managed(rocksdb_readoptions_t* opt,
unsigned char v) {
opt->rep.managed = v;
}
void rocksdb_readoptions_set_readahead_size(rocksdb_readoptions_t* opt,
size_t v) {
opt->rep.readahead_size = v;
@@ -6887,12 +6848,14 @@ rocksdb_slicetransform_t* rocksdb_slicetransform_create(
char* (*transform)(void*, const char* key, size_t length,
size_t* dst_length),
unsigned char (*in_domain)(void*, const char* key, size_t length),
unsigned char (*in_range)(void*, const char* key, size_t length),
const char* (*name)(void*)) {
rocksdb_slicetransform_t* result = new rocksdb_slicetransform_t;
result->state_ = state;
result->destructor_ = destructor;
result->transform_ = transform;
result->in_domain_ = in_domain;
result->in_range_ = in_range;
result->name_ = name;
return result;
}
@@ -6908,6 +6871,7 @@ struct SliceTransformWrapper : public rocksdb_slicetransform_t {
return rep_->Transform(src);
}
bool InDomain(const Slice& src) const override { return rep_->InDomain(src); }
bool InRange(const Slice& src) const override { return rep_->InRange(src); }
static void DoNothing(void*) {}
};
@@ -7031,27 +6995,6 @@ uint64_t rocksdb_fifo_compaction_options_get_max_table_files_size(
return fifo_opts->rep.max_table_files_size;
}
void rocksdb_fifo_compaction_options_set_max_data_files_size(
rocksdb_fifo_compaction_options_t* fifo_opts, uint64_t size) {
fifo_opts->rep.max_data_files_size = size;
}
uint64_t rocksdb_fifo_compaction_options_get_max_data_files_size(
rocksdb_fifo_compaction_options_t* fifo_opts) {
return fifo_opts->rep.max_data_files_size;
}
void rocksdb_fifo_compaction_options_set_use_kv_ratio_compaction(
rocksdb_fifo_compaction_options_t* fifo_opts,
unsigned char use_kv_ratio_compaction) {
fifo_opts->rep.use_kv_ratio_compaction = use_kv_ratio_compaction;
}
unsigned char rocksdb_fifo_compaction_options_get_use_kv_ratio_compaction(
rocksdb_fifo_compaction_options_t* fifo_opts) {
return fifo_opts->rep.use_kv_ratio_compaction;
}
void rocksdb_fifo_compaction_options_destroy(
rocksdb_fifo_compaction_options_t* fifo_opts) {
delete fifo_opts;
@@ -8627,14 +8570,6 @@ void rocksdb_enable_manual_compaction(rocksdb_t* db) {
db->rep->EnableManualCompaction();
}
void rocksdb_abort_all_compactions(rocksdb_t* db) {
db->rep->AbortAllCompactions();
}
void rocksdb_resume_all_compactions(rocksdb_t* db) {
db->rep->ResumeAllCompactions();
}
rocksdb_statistics_histogram_data_t*
rocksdb_statistics_histogram_data_create() {
return new rocksdb_statistics_histogram_data_t{};
+1 -9
View File
@@ -3596,14 +3596,6 @@ int main(int argc, char** argv) {
100000 ==
rocksdb_fifo_compaction_options_get_max_table_files_size(fco));
rocksdb_fifo_compaction_options_set_max_data_files_size(fco, 200000);
CheckCondition(
200000 == rocksdb_fifo_compaction_options_get_max_data_files_size(fco));
rocksdb_fifo_compaction_options_set_use_kv_ratio_compaction(fco, 1);
CheckCondition(
1 == rocksdb_fifo_compaction_options_get_use_kv_ratio_compaction(fco));
rocksdb_fifo_compaction_options_destroy(fco);
}
@@ -4455,7 +4447,7 @@ int main(int argc, char** argv) {
StartPhase("statistics");
{
const uint32_t BYTES_WRITTEN_TICKER = 61;
const uint32_t BYTES_WRITTEN_TICKER = 60;
const uint32_t DB_WRITE_HIST = 1;
rocksdb_statistics_histogram_data_t* hist =
+1 -7
View File
@@ -401,13 +401,7 @@ ColumnFamilyOptions SanitizeCfOptions(const ImmutableDBOptions& db_options,
}
if (result.max_compaction_bytes == 0) {
// For FIFO with use_kv_ratio_compaction, leave max_compaction_bytes as 0
// to signal "auto-calculate target from capacity and SST/blob ratio."
// When explicitly set by the user, it overrides the auto-calculated target.
if (result.compaction_style != kCompactionStyleFIFO ||
!result.compaction_options_fifo.use_kv_ratio_compaction) {
result.max_compaction_bytes = result.target_file_size_base * 25;
}
result.max_compaction_bytes = result.target_file_size_base * 25;
}
bool is_block_based_table = (result.table_factory->IsInstanceOf(
+13 -10
View File
@@ -118,7 +118,8 @@ class ColumnFamilyTestBase : public testing::Test {
for (int i = 0; i < n; i++) {
if (flush_every != 0 && i != 0 && i % flush_every == 0) {
dbfull()->TEST_FlushMemTable();
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
dbi->TEST_FlushMemTable();
}
int keyi = base + i;
@@ -176,7 +177,8 @@ class ColumnFamilyTestBase : public testing::Test {
}
handles_.clear();
names_.clear();
db_.reset();
delete db_;
db_ = nullptr;
}
Status TryOpen(std::vector<std::string> cf,
@@ -216,7 +218,7 @@ class ColumnFamilyTestBase : public testing::Test {
void Open() { Open({"default"}); }
DBImpl* dbfull() { return static_cast_with_check<DBImpl>(db_.get()); }
DBImpl* dbfull() { return static_cast_with_check<DBImpl>(db_); }
int GetProperty(int cf, std::string property) {
std::string value;
@@ -498,7 +500,7 @@ class ColumnFamilyTestBase : public testing::Test {
ColumnFamilyOptions column_family_options_;
DBOptions db_options_;
std::string dbname_;
std::unique_ptr<DB> db_;
DB* db_ = nullptr;
EnvCounter* env_;
std::shared_ptr<Env> env_guard_;
Random rnd_;
@@ -515,7 +517,7 @@ class ColumnFamilyTest
INSTANTIATE_TEST_CASE_P(FormatDef, ColumnFamilyTest,
testing::Values(test::kDefaultFormatVersion));
INSTANTIATE_TEST_CASE_P(FormatLatest, ColumnFamilyTest,
testing::Values(kLatestBbtFormatVersion));
testing::Values(kLatestFormatVersion));
TEST_P(ColumnFamilyTest, DontReuseColumnFamilyID) {
for (int iter = 0; iter < 3; ++iter) {
@@ -705,8 +707,8 @@ INSTANTIATE_TEST_CASE_P(
std::make_tuple(test::kDefaultFormatVersion, false)));
INSTANTIATE_TEST_CASE_P(
FormatLatest, FlushEmptyCFTestWithParam,
testing::Values(std::make_tuple(kLatestBbtFormatVersion, true),
std::make_tuple(kLatestBbtFormatVersion, false)));
testing::Values(std::make_tuple(kLatestFormatVersion, true),
std::make_tuple(kLatestFormatVersion, false)));
TEST_P(ColumnFamilyTest, AddDrop) {
Open();
@@ -3540,10 +3542,11 @@ TEST_P(ColumnFamilyTest, MultipleCFPathsTest) {
// Re-open and verify the keys.
Reopen({ColumnFamilyOptions(), cf_opt1, cf_opt2});
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
for (int cf = 1; cf != 3; ++cf) {
ReadOptions read_options;
read_options.readahead_size = 0;
auto it = db_->NewIterator(read_options, handles_[cf]);
auto it = dbi->NewIterator(read_options, handles_[cf]);
for (it->SeekToFirst(); it->Valid(); it->Next()) {
ASSERT_OK(it->status());
Slice key(it->key());
@@ -3633,7 +3636,7 @@ TEST(ColumnFamilyTest, ValidateMemtableKVChecksumOption) {
// the behavior of manual flush is that it skips retaining UDTs.
class ColumnFamilyRetainUDTTest : public ColumnFamilyTestBase {
public:
ColumnFamilyRetainUDTTest() : ColumnFamilyTestBase(kLatestBbtFormatVersion) {}
ColumnFamilyRetainUDTTest() : ColumnFamilyTestBase(kLatestFormatVersion) {}
void SetUp() override {
db_options_.allow_concurrent_memtable_write = false;
@@ -3883,7 +3886,7 @@ TEST_F(ManualFlushSkipRetainUDTTest, FlushRemovesStaleEntries) {
static_cast_with_check<ColumnFamilyHandleImpl>(cfh)->cfd();
for (int version = 0; version < 100; version++) {
if (version == 50) {
ASSERT_OK(dbfull()->TEST_SwitchMemtable(cfd));
ASSERT_OK(static_cast_with_check<DBImpl>(db_)->TEST_SwitchMemtable(cfd));
}
ASSERT_OK(
Put(0, "foo", EncodeAsUint64(version), "v" + std::to_string(version)));
+46 -32
View File
@@ -75,9 +75,10 @@ TEST_F(CompactFilesTest, L0ConflictsFiles) {
options.level0_file_num_compaction_trigger = kLevel0Trigger;
options.compression = kNoCompression;
std::unique_ptr<DB> db;
DB* db = nullptr;
ASSERT_OK(DestroyDB(db_name_, options));
ASSERT_OK(DB::Open(options, db_name_, &db));
Status s = DB::Open(options, db_name_, &db);
assert(s.ok());
assert(db);
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({
@@ -113,6 +114,7 @@ TEST_F(CompactFilesTest, L0ConflictsFiles) {
}
}
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
delete db;
}
TEST_F(CompactFilesTest, MultipleLevel) {
@@ -126,11 +128,11 @@ TEST_F(CompactFilesTest, MultipleLevel) {
FlushedFileCollector* collector = new FlushedFileCollector();
options.listeners.emplace_back(collector);
std::unique_ptr<DB> db;
DB* db = nullptr;
ASSERT_OK(DestroyDB(db_name_, options));
Status s = DB::Open(options, db_name_, &db);
ASSERT_OK(s);
ASSERT_NE(db.get(), nullptr);
ASSERT_NE(db, nullptr);
// create couple files in L0, L3, L4 and L5
for (int i = 5; i > 2; --i) {
@@ -139,8 +141,7 @@ TEST_F(CompactFilesTest, MultipleLevel) {
ASSERT_OK(db->Flush(FlushOptions()));
// Ensure background work is fully finished including listener callbacks
// before accessing listener state.
ASSERT_OK(
static_cast_with_check<DBImpl>(db.get())->TEST_WaitForBackgroundWork());
ASSERT_OK(static_cast_with_check<DBImpl>(db)->TEST_WaitForBackgroundWork());
auto l0_files = collector->GetFlushedFiles();
ASSERT_OK(db->CompactFiles(CompactionOptions(), l0_files, i));
@@ -190,6 +191,8 @@ TEST_F(CompactFilesTest, MultipleLevel) {
ASSERT_OK(db->CompactFiles(CompactionOptions(), files, 5));
SyncPoint::GetInstance()->DisableProcessing();
thread.join();
delete db;
}
TEST_F(CompactFilesTest, ObsoleteFiles) {
@@ -209,11 +212,11 @@ TEST_F(CompactFilesTest, ObsoleteFiles) {
FlushedFileCollector* collector = new FlushedFileCollector();
options.listeners.emplace_back(collector);
std::unique_ptr<DB> db;
DB* db = nullptr;
ASSERT_OK(DestroyDB(db_name_, options));
Status s = DB::Open(options, db_name_, &db);
ASSERT_OK(s);
ASSERT_NE(db.get(), nullptr);
ASSERT_NE(db, nullptr);
// create couple files
for (int i = 1000; i < 2000; ++i) {
@@ -223,12 +226,13 @@ TEST_F(CompactFilesTest, ObsoleteFiles) {
auto l0_files = collector->GetFlushedFiles();
ASSERT_OK(db->CompactFiles(CompactionOptions(), l0_files, 1));
ASSERT_OK(static_cast_with_check<DBImpl>(db.get())->TEST_WaitForCompact());
ASSERT_OK(static_cast_with_check<DBImpl>(db)->TEST_WaitForCompact());
// verify all compaction input files are deleted
for (const auto& fname : l0_files) {
ASSERT_EQ(Status::NotFound(), env_->FileExists(fname));
}
delete db;
}
TEST_F(CompactFilesTest, NotCutOutputOnLevel0) {
@@ -247,9 +251,10 @@ TEST_F(CompactFilesTest, NotCutOutputOnLevel0) {
FlushedFileCollector* collector = new FlushedFileCollector();
options.listeners.emplace_back(collector);
std::unique_ptr<DB> db;
DB* db = nullptr;
ASSERT_OK(DestroyDB(db_name_, options));
ASSERT_OK(DB::Open(options, db_name_, &db));
Status s = DB::Open(options, db_name_, &db);
assert(s.ok());
assert(db);
// create couple files
@@ -257,20 +262,19 @@ TEST_F(CompactFilesTest, NotCutOutputOnLevel0) {
ASSERT_OK(db->Put(WriteOptions(), std::to_string(i),
std::string(1000, 'a' + (i % 26))));
}
ASSERT_OK(
static_cast_with_check<DBImpl>(db.get())->TEST_WaitForFlushMemTable());
ASSERT_OK(static_cast_with_check<DBImpl>(db)->TEST_WaitForFlushMemTable());
auto l0_files_1 = collector->GetFlushedFiles();
collector->ClearFlushedFiles();
for (int i = 0; i < 500; ++i) {
ASSERT_OK(db->Put(WriteOptions(), std::to_string(i),
std::string(1000, 'a' + (i % 26))));
}
ASSERT_OK(
static_cast_with_check<DBImpl>(db.get())->TEST_WaitForFlushMemTable());
ASSERT_OK(static_cast_with_check<DBImpl>(db)->TEST_WaitForFlushMemTable());
auto l0_files_2 = collector->GetFlushedFiles();
ASSERT_OK(db->CompactFiles(CompactionOptions(), l0_files_1, 0));
ASSERT_OK(db->CompactFiles(CompactionOptions(), l0_files_2, 0));
// no assertion failure
delete db;
}
TEST_F(CompactFilesTest, CapturingPendingFiles) {
@@ -285,7 +289,7 @@ TEST_F(CompactFilesTest, CapturingPendingFiles) {
FlushedFileCollector* collector = new FlushedFileCollector();
options.listeners.emplace_back(collector);
std::unique_ptr<DB> db;
DB* db = nullptr;
ASSERT_OK(DestroyDB(db_name_, options));
Status s = DB::Open(options, db_name_, &db);
ASSERT_OK(s);
@@ -299,8 +303,7 @@ TEST_F(CompactFilesTest, CapturingPendingFiles) {
// Ensure background work is fully finished including listener callbacks
// before accessing listener state.
ASSERT_OK(
static_cast_with_check<DBImpl>(db.get())->TEST_WaitForBackgroundWork());
ASSERT_OK(static_cast_with_check<DBImpl>(db)->TEST_WaitForBackgroundWork());
auto l0_files = collector->GetFlushedFiles();
EXPECT_EQ(5, l0_files.size());
@@ -324,12 +327,13 @@ TEST_F(CompactFilesTest, CapturingPendingFiles) {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
db.reset();
delete db;
// Make sure we can reopen the DB.
s = DB::Open(options, db_name_, &db);
ASSERT_OK(s);
assert(db);
delete db;
}
TEST_F(CompactFilesTest, CompactionFilterWithGetSv) {
@@ -361,12 +365,12 @@ TEST_F(CompactFilesTest, CompactionFilterWithGetSv) {
options.create_if_missing = true;
options.compaction_filter = cf.get();
std::unique_ptr<DB> db;
DB* db = nullptr;
ASSERT_OK(DestroyDB(db_name_, options));
Status s = DB::Open(options, db_name_, &db);
ASSERT_OK(s);
cf->SetDB(db.get());
cf->SetDB(db);
// Write one L0 file
ASSERT_OK(db->Put(WriteOptions(), "K1", "V1"));
@@ -380,6 +384,8 @@ TEST_F(CompactFilesTest, CompactionFilterWithGetSv) {
ASSERT_OK(
db->CompactFiles(ROCKSDB_NAMESPACE::CompactionOptions(), {fname}, 0));
}
delete db;
}
TEST_F(CompactFilesTest, SentinelCompressionType) {
@@ -407,7 +413,7 @@ TEST_F(CompactFilesTest, SentinelCompressionType) {
options.create_if_missing = true;
FlushedFileCollector* collector = new FlushedFileCollector();
options.listeners.emplace_back(collector);
std::unique_ptr<DB> db;
DB* db = nullptr;
ASSERT_OK(DB::Open(options, db_name_, &db));
ASSERT_OK(db->Put(WriteOptions(), "key", "val"));
@@ -415,8 +421,7 @@ TEST_F(CompactFilesTest, SentinelCompressionType) {
// Ensure background work is fully finished including listener callbacks
// before accessing listener state.
ASSERT_OK(
static_cast_with_check<DBImpl>(db.get())->TEST_WaitForBackgroundWork());
ASSERT_OK(static_cast_with_check<DBImpl>(db)->TEST_WaitForBackgroundWork());
auto l0_files = collector->GetFlushedFiles();
ASSERT_EQ(1, l0_files.size());
@@ -432,6 +437,7 @@ TEST_F(CompactFilesTest, SentinelCompressionType) {
// compression_name property
ASSERT_EQ("BuiltinV2;02;", name_and_table_props.second->compression_name);
}
delete db;
}
}
@@ -456,7 +462,11 @@ TEST_F(CompactFilesTest, CompressionWithBlockAlign) {
}
std::unique_ptr<DB> db;
ASSERT_OK(DB::Open(options, db_name_, &db));
{
DB* _db = nullptr;
ASSERT_OK(DB::Open(options, db_name_, &_db));
db.reset(_db);
}
ASSERT_OK(db->Put(WriteOptions(), "key", "val"));
ASSERT_OK(db->Flush(FlushOptions()));
@@ -495,7 +505,7 @@ TEST_F(CompactFilesTest, GetCompactionJobInfo) {
FlushedFileCollector* collector = new FlushedFileCollector();
options.listeners.emplace_back(collector);
std::unique_ptr<DB> db;
DB* db = nullptr;
ASSERT_OK(DestroyDB(db_name_, options));
Status s = DB::Open(options, db_name_, &db);
ASSERT_OK(s);
@@ -506,8 +516,7 @@ TEST_F(CompactFilesTest, GetCompactionJobInfo) {
ASSERT_OK(db->Put(WriteOptions(), std::to_string(i),
std::string(1000, 'a' + (i % 26))));
}
ASSERT_OK(
static_cast_with_check<DBImpl>(db.get())->TEST_WaitForFlushMemTable());
ASSERT_OK(static_cast_with_check<DBImpl>(db)->TEST_WaitForFlushMemTable());
auto l0_files_1 = collector->GetFlushedFiles();
CompactionOptions co;
co.compression = CompressionType::kLZ4Compression;
@@ -523,6 +532,7 @@ TEST_F(CompactFilesTest, GetCompactionJobInfo) {
ASSERT_EQ(compaction_job_info.output_level, 0);
ASSERT_OK(compaction_job_info.status);
// no assertion failure
delete db;
}
// Helper function to generate zero-padded keys
@@ -538,11 +548,11 @@ TEST_F(CompactFilesTest, TrivialMoveNonOverlappingFiles) {
options.compression = kNoCompression;
options.level_compaction_dynamic_level_bytes = false;
std::unique_ptr<DB> db;
DB* db = nullptr;
ASSERT_OK(DestroyDB(db_name_, options));
Status s = DB::Open(options, db_name_, &db);
ASSERT_OK(s);
ASSERT_NE(db.get(), nullptr);
ASSERT_NE(db, nullptr);
// Create 3 non-overlapping files in L0
// File 1: keys [a00-a99]
@@ -655,6 +665,8 @@ TEST_F(CompactFilesTest, TrivialMoveNonOverlappingFiles) {
ASSERT_OK(db->Get(ReadOptions(), key, &value));
ASSERT_EQ(value, "value_" + key);
}
delete db;
}
TEST_F(CompactFilesTest, TrivialMoveBlockedByOverlap) {
@@ -665,11 +677,11 @@ TEST_F(CompactFilesTest, TrivialMoveBlockedByOverlap) {
options.level_compaction_dynamic_level_bytes = false;
options.num_levels = 7;
std::unique_ptr<DB> db;
DB* db = nullptr;
ASSERT_OK(DestroyDB(db_name_, options));
Status s = DB::Open(options, db_name_, &db);
ASSERT_OK(s);
ASSERT_NE(db.get(), nullptr);
ASSERT_NE(db, nullptr);
// Create a file in L6 with keys [m00-m99] (wide range)
for (int i = 0; i < 100; i++) {
@@ -745,6 +757,8 @@ TEST_F(CompactFilesTest, TrivialMoveBlockedByOverlap) {
ASSERT_OK(db->Get(ReadOptions(), key, &value));
ASSERT_EQ(value, "updated_value_" + key);
}
delete db;
}
} // namespace ROCKSDB_NAMESPACE
+29 -122
View File
@@ -128,10 +128,6 @@ const char* GetCompactionProximalOutputRangeTypeString(
}
}
// Static constant for compaction abort flag - always false, used for
// compaction service jobs that don't support abort signaling
const std::atomic<int> CompactionJob::kCompactionAbortedFalse{0};
CompactionJob::CompactionJob(
int job_id, Compaction* compaction, const ImmutableDBOptions& db_options,
const MutableDBOptions& mutable_db_options, const FileOptions& file_options,
@@ -145,10 +141,10 @@ CompactionJob::CompactionJob(
CompactionJobStats* compaction_job_stats, Env::Priority thread_pri,
const std::shared_ptr<IOTracer>& io_tracer,
const std::atomic<bool>& manual_compaction_canceled,
const std::atomic<int>& compaction_aborted, const std::string& db_id,
const std::string& db_session_id, std::string full_history_ts_low,
std::string trim_ts, BlobFileCompletionCallback* blob_callback,
int* bg_compaction_scheduled, int* bg_bottom_compaction_scheduled)
const std::string& db_id, const std::string& db_session_id,
std::string full_history_ts_low, std::string trim_ts,
BlobFileCompletionCallback* blob_callback, int* bg_compaction_scheduled,
int* bg_bottom_compaction_scheduled)
: compact_(new CompactionState(compaction)),
internal_stats_(compaction->compaction_reason(), 1),
db_options_(db_options),
@@ -172,7 +168,6 @@ CompactionJob::CompactionJob(
versions_(versions),
shutting_down_(shutting_down),
manual_compaction_canceled_(manual_compaction_canceled),
compaction_aborted_(compaction_aborted),
db_directory_(db_directory),
blob_output_directory_(blob_output_directory),
db_mutex_(db_mutex),
@@ -713,7 +708,6 @@ void CompactionJob::InitializeCompactionRun() {
}
void CompactionJob::RunSubcompactions() {
TEST_SYNC_POINT("CompactionJob::RunSubcompactions:BeforeStart");
const size_t num_threads = compact_->sub_compact_states.size();
assert(num_threads > 0);
compact_->compaction->GetOrInitInputTableProperties();
@@ -759,71 +753,6 @@ void CompactionJob::RemoveEmptyOutputs() {
}
}
void CompactionJob::CleanupAbortedSubcompactions() {
ColumnFamilyData* cfd = compact_->compaction->column_family_data();
uint64_t total_sst_files_deleted = 0;
uint64_t total_blob_files_deleted = 0;
// Track the first file deletion error to report at the end
Status first_error;
int deletion_errors = 0;
// Mark all subcompactions as aborted and delete their output files
for (auto& sub_compact : compact_->sub_compact_states) {
// Mark this subcompaction as aborted
sub_compact.status =
Status::Incomplete(Status::SubCode::kCompactionAborted);
// Delete all files (SST and blob) tracked during compaction.
// GetOutputFilePaths() contains ALL file paths created, including
// in-progress files that may have been removed from outputs_ or
// blob_file_additions_.
for (const bool is_proximal_level : {false, true}) {
if (is_proximal_level &&
!compact_->compaction->SupportsPerKeyPlacement()) {
continue;
}
for (const std::string& file_path :
sub_compact.Outputs(is_proximal_level)->GetOutputFilePaths()) {
Status s = env_->DeleteFile(file_path);
if (s.ok()) {
// Count SST vs blob files by checking extension
if (file_path.find(".sst") != std::string::npos) {
total_sst_files_deleted++;
} else if (file_path.find(".blob") != std::string::npos) {
total_blob_files_deleted++;
}
} else if (!s.IsNotFound()) {
if (first_error.ok()) {
first_error = s;
}
deletion_errors++;
}
}
}
sub_compact.CleanupOutputs();
}
if (stats_) {
RecordTick(stats_, COMPACTION_ABORTED);
}
ROCKS_LOG_INFO(db_options_.info_log,
"[%s] [JOB %d] Compaction aborted: deleted %" PRIu64
" SST files and %" PRIu64 " blob files",
cfd->GetName().c_str(), job_id_, total_sst_files_deleted,
total_blob_files_deleted);
if (!first_error.ok()) {
ROCKS_LOG_ERROR(db_options_.info_log,
"[%s] [JOB %d] Cleanup completed with %d file deletion "
"errors. First error: %s",
cfd->GetName().c_str(), job_id_, deletion_errors,
first_error.ToString().c_str());
}
}
bool CompactionJob::HasNewBlobFiles() const {
for (const auto& state : compact_->sub_compact_states) {
if (state.Current().HasBlobFileAdditions()) {
@@ -1075,15 +1004,6 @@ Status CompactionJob::Run() {
Status status = CollectSubcompactionErrors();
// If compaction was aborted or manually paused, clean up any output files
// from completed subcompactions to prevent orphaned files on disk.
// Skip cleanup for resumable compaction (when progress writer is set)
// because the output files are needed for resumption.
if ((status.IsCompactionAborted() || status.IsManualCompactionPaused()) &&
compaction_progress_writer_ == nullptr) {
CleanupAbortedSubcompactions();
}
if (status.ok()) {
status = SyncOutputDirectories();
}
@@ -1495,10 +1415,10 @@ InternalIterator* CompactionJob::CreateInputIterator(
return input;
}
void CompactionJob::CreateBlobFileBuilder(
SubcompactionState* sub_compact, ColumnFamilyData* cfd,
std::unique_ptr<BlobFileBuilder>& blob_file_builder,
const WriteOptions& write_options) {
void CompactionJob::CreateBlobFileBuilder(SubcompactionState* sub_compact,
ColumnFamilyData* cfd,
BlobFileResources& blob_resources,
const WriteOptions& write_options) {
const auto& mutable_cf_options =
sub_compact->compaction->mutable_cf_options();
@@ -1507,24 +1427,24 @@ void CompactionJob::CreateBlobFileBuilder(
if (mutable_cf_options.enable_blob_files &&
sub_compact->compaction->output_level() >=
mutable_cf_options.blob_file_starting_level) {
blob_file_builder = std::make_unique<BlobFileBuilder>(
blob_resources.blob_file_builder = std::make_unique<BlobFileBuilder>(
versions_, fs_.get(), &sub_compact->compaction->immutable_options(),
&mutable_cf_options, &file_options_, &write_options, db_id_,
db_session_id_, job_id_, cfd->GetID(), cfd->GetName(), write_hint_,
io_tracer_, blob_callback_, BlobFileCreationReason::kCompaction,
sub_compact->Current().GetOutputFilePathsPtr(),
&blob_resources.blob_file_paths,
sub_compact->Current().GetBlobFileAdditionsPtr());
} else {
blob_file_builder = nullptr;
blob_resources.blob_file_builder = nullptr;
}
}
std::unique_ptr<CompactionIterator> CompactionJob::CreateCompactionIterator(
SubcompactionState* sub_compact, ColumnFamilyData* cfd,
InternalIterator* input, const CompactionFilter* compaction_filter,
MergeHelper& merge, std::unique_ptr<BlobFileBuilder>& blob_file_builder,
MergeHelper& merge, BlobFileResources& blob_resources,
const WriteOptions& write_options) {
CreateBlobFileBuilder(sub_compact, cfd, blob_file_builder, write_options);
CreateBlobFileBuilder(sub_compact, cfd, blob_resources, write_options);
const std::string* const full_history_ts_low =
full_history_ts_low_.empty() ? nullptr : &full_history_ts_low_;
@@ -1536,7 +1456,7 @@ std::unique_ptr<CompactionIterator> CompactionJob::CreateCompactionIterator(
job_context_->earliest_write_conflict_snapshot,
job_context_->GetJobSnapshotSequence(), job_context_->snapshot_checker,
env_, ShouldReportDetailedTime(env_, stats_), sub_compact->RangeDelAgg(),
blob_file_builder.get(), db_options_.allow_data_in_errors,
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 */,
@@ -1575,17 +1495,10 @@ Status CompactionJob::ProcessKeyValue(
SubcompactionState* sub_compact, ColumnFamilyData* cfd,
CompactionIterator* c_iter, const CompactionFileOpenFunc& open_file_func,
const CompactionFileCloseFunc& close_file_func, uint64_t& prev_cpu_micros) {
// Cron interval for periodic operations: stats update, abort check,
// and sync points. Uses 1024 (power of 2) for efficient bitwise check.
const uint64_t kCronEveryMask = (1 << 10) - 1;
Status status;
const uint64_t kRecordStatsEvery = 1000;
[[maybe_unused]] const std::optional<const Slice> end = sub_compact->end;
// Check for abort signal before starting key processing
if (compaction_aborted_.load(std::memory_order_acquire) > 0) {
return Status::Incomplete(Status::SubCode::kCompactionAborted);
}
Status status;
IterKey prev_iter_output_key;
ParsedInternalKey prev_iter_output_internal_key;
@@ -1598,16 +1511,8 @@ Status CompactionJob::ProcessKeyValue(
assert(!end.has_value() ||
cfd->user_comparator()->Compare(c_iter->user_key(), *end) < 0);
const uint64_t num_records = c_iter->iter_stats().num_input_records;
// Periodic cron operations: stats update, abort check.
if ((num_records & kCronEveryMask) == kCronEveryMask) {
// Check for abort signal periodically
if (compaction_aborted_.load(std::memory_order_acquire) > 0) {
status = Status::Incomplete(Status::SubCode::kCompactionAborted);
break;
}
if (c_iter->iter_stats().num_input_records % kRecordStatsEvery ==
kRecordStatsEvery - 1) {
UpdateSubcompactionJobStatsIncrementally(
c_iter, &sub_compact->compaction_job_stats,
db_options_.clock->CPUMicros(), prev_cpu_micros);
@@ -1814,7 +1719,6 @@ Status CompactionJob::FinalizeBlobFiles(SubcompactionState* sub_compact,
}
void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
TEST_SYNC_POINT("CompactionJob::ProcessKeyValueCompaction:Start");
assert(sub_compact);
assert(sub_compact->compaction);
@@ -1868,11 +1772,11 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
false /* internal key corruption is expected */,
job_context_->GetLatestSnapshotSequence(), job_context_->snapshot_checker,
compact_->compaction->level(), db_options_.stats);
std::unique_ptr<BlobFileBuilder> blob_file_builder;
BlobFileResources blob_resources;
auto c_iter =
CreateCompactionIterator(sub_compact, cfd, input_iter, compaction_filter,
merge, blob_file_builder, write_options);
merge, blob_resources, write_options);
assert(c_iter);
c_iter->SeekToFirst();
@@ -1890,8 +1794,9 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
status = FinalizeProcessKeyValueStatus(cfd, input_iter, c_iter.get(), status);
FinalizeSubcompaction(sub_compact, status, open_file_func, close_file_func,
blob_file_builder.get(), c_iter.get(), input_iter,
start_cpu_micros, prev_cpu_micros, io_stats);
blob_resources.blob_file_builder.get(), c_iter.get(),
input_iter, start_cpu_micros, prev_cpu_micros,
io_stats);
NotifyOnSubcompactionCompleted(sub_compact);
}
@@ -2390,10 +2295,6 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
Status s;
IOStatus io_s = NewWritableFile(fs_.get(), fname, &writable_file, fo_copy);
s = io_s;
if (io_s.ok()) {
// Track the SST file path for cleanup on abort.
outputs.AddOutputFilePath(fname);
}
if (sub_compact->io_status.ok()) {
sub_compact->io_status = io_s;
// Since this error is really a copy of the io_s that is checked below as s,
@@ -2932,6 +2833,12 @@ void CompactionJob::RestoreCompactionOutputs(
// - 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);
+10 -11
View File
@@ -142,9 +142,6 @@ class SubcompactionState;
class CompactionJob {
public:
// Constant false aborted flag, used for compaction service jobs
static const std::atomic<int> kCompactionAbortedFalse;
CompactionJob(int job_id, Compaction* compaction,
const ImmutableDBOptions& db_options,
const MutableDBOptions& mutable_db_options,
@@ -160,7 +157,6 @@ class CompactionJob {
Env::Priority thread_pri,
const std::shared_ptr<IOTracer>& io_tracer,
const std::atomic<bool>& manual_compaction_canceled,
const std::atomic<int>& compaction_aborted,
const std::string& db_id = "",
const std::string& db_session_id = "",
std::string full_history_ts_low = "", std::string trim_ts = "",
@@ -307,7 +303,6 @@ class CompactionJob {
void RunSubcompactions();
void UpdateTimingStats(uint64_t start_micros);
void RemoveEmptyOutputs();
void CleanupAbortedSubcompactions();
bool HasNewBlobFiles() const;
Status CollectSubcompactionErrors();
Status SyncOutputDirectories();
@@ -372,6 +367,11 @@ class CompactionJob {
std::unique_ptr<InternalIterator> trim_history_iter;
};
struct BlobFileResources {
std::vector<std::string> blob_file_paths;
std::unique_ptr<BlobFileBuilder> blob_file_builder;
};
bool ShouldUseLocalCompaction(SubcompactionState* sub_compact);
CompactionIOStatsSnapshot InitializeIOStats();
Status SetupAndValidateCompactionFilter(
@@ -386,14 +386,14 @@ class CompactionJob {
SubcompactionState* sub_compact, ColumnFamilyData* cfd,
SubcompactionInternalIterators& iterators,
SubcompactionKeyBoundaries& boundaries, ReadOptions& read_options);
void CreateBlobFileBuilder(
SubcompactionState* sub_compact, ColumnFamilyData* cfd,
std::unique_ptr<BlobFileBuilder>& blob_file_builder,
const WriteOptions& write_options);
void CreateBlobFileBuilder(SubcompactionState* sub_compact,
ColumnFamilyData* cfd,
BlobFileResources& blob_resources,
const WriteOptions& write_options);
std::unique_ptr<CompactionIterator> CreateCompactionIterator(
SubcompactionState* sub_compact, ColumnFamilyData* cfd,
InternalIterator* input_iter, const CompactionFilter* compaction_filter,
MergeHelper& merge, std::unique_ptr<BlobFileBuilder>& blob_file_builder,
MergeHelper& merge, BlobFileResources& blob_resources,
const WriteOptions& write_options);
std::pair<CompactionFileOpenFunc, CompactionFileCloseFunc> CreateFileHandlers(
SubcompactionState* sub_compact, SubcompactionKeyBoundaries& boundaries);
@@ -465,7 +465,6 @@ class CompactionJob {
VersionSet* versions_;
const std::atomic<bool>* shutting_down_;
const std::atomic<bool>& manual_compaction_canceled_;
const std::atomic<int>& compaction_aborted_;
FSDirectory* db_directory_;
FSDirectory* blob_output_directory_;
InstrumentedMutex* db_mutex_;
+7 -7
View File
@@ -82,7 +82,7 @@ class CompactionJobStatsTest : public testing::Test,
std::string dbname_;
std::string alternative_wal_dir_;
Env* env_;
std::unique_ptr<DB> db_;
DB* db_;
std::vector<ColumnFamilyHandle*> handles_;
uint32_t max_subcompactions_;
@@ -123,7 +123,7 @@ class CompactionJobStatsTest : public testing::Test,
static void SetUpTestCase() {}
static void TearDownTestCase() {}
DBImpl* dbfull() { return static_cast_with_check<DBImpl>(db_.get()); }
DBImpl* dbfull() { return static_cast_with_check<DBImpl>(db_); }
void CreateColumnFamilies(const std::vector<std::string>& cfs,
const Options& options) {
@@ -162,8 +162,7 @@ class CompactionJobStatsTest : public testing::Test,
column_families.emplace_back(cfs[i], options[i]);
}
DBOptions db_opts = DBOptions(options[0]);
auto s = DB::Open(db_opts, dbname_, column_families, &handles_, &db_);
return s;
return DB::Open(db_opts, dbname_, column_families, &handles_, &db_);
}
Status TryReopenWithColumnFamilies(const std::vector<std::string>& cfs,
@@ -180,7 +179,8 @@ class CompactionJobStatsTest : public testing::Test,
delete h;
}
handles_.clear();
db_.reset();
delete db_;
db_ = nullptr;
}
void DestroyAndReopen(const Options& options) {
@@ -743,7 +743,7 @@ TEST_P(CompactionJobStatsTest, CompactionJobStatsTest) {
}
ASSERT_OK(Flush(1));
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ASSERT_OK(static_cast_with_check<DBImpl>(db_)->TEST_WaitForCompact());
stats_checker->set_verify_next_comp_io_stats(true);
std::atomic<bool> first_prepare_write(true);
@@ -944,7 +944,7 @@ TEST_P(CompactionJobStatsTest, UniversalCompactionTest) {
start_key += key_base) {
MakeTableWithKeyValues(&rnd, start_key, start_key + key_base - 1, kKeySize,
kValueSize, key_interval, compression_ratio, 1);
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ASSERT_OK(static_cast_with_check<DBImpl>(db_)->TEST_WaitForCompact());
}
ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 0U);
}
+455 -2
View File
@@ -676,8 +676,8 @@ class CompactionJobTestBase : public testing::Test {
&event_logger, false, false, dbname_, &compaction_job_stats_,
Env::Priority::USER, nullptr /* IOTracer */,
/*manual_compaction_canceled=*/kManualCompactionCanceledFalse,
CompactionJob::kCompactionAbortedFalse, env_->GenerateUniqueId(),
DBImpl::GenerateDbSessionId(nullptr), full_history_ts_low_);
env_->GenerateUniqueId(), DBImpl::GenerateDbSessionId(nullptr),
full_history_ts_low_);
VerifyInitializationOfCompactionJobStats(compaction_job_stats_);
compaction_job.Prepare(std::nullopt /*subcompact to be computed*/);
@@ -2409,6 +2409,459 @@ TEST_F(CompactionJobIOPriorityTest, GetRateLimiterPriority) {
kMaxSequenceNumber, 1, false, {kInvalidBlobFileNumber}, true,
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) {
-19
View File
@@ -84,19 +84,6 @@ class CompactionOutputs {
bool HasBlobFileAdditions() const { return !blob_file_additions_.empty(); }
// Get all file paths (SST and blob) created during compaction.
const std::vector<std::string>& GetOutputFilePaths() const {
return output_file_paths_;
}
std::vector<std::string>* GetOutputFilePathsPtr() {
return &output_file_paths_;
}
void AddOutputFilePath(const std::string& path) {
output_file_paths_.push_back(path);
}
BlobGarbageMeter* CreateBlobGarbageMeter() {
assert(!is_proximal_level_);
blob_garbage_meter_ = std::make_unique<BlobGarbageMeter>();
@@ -334,12 +321,6 @@ class CompactionOutputs {
std::vector<BlobFileAddition> blob_file_additions_;
std::unique_ptr<BlobGarbageMeter> blob_garbage_meter_;
// All file paths (SST and blob) created during compaction.
// Used for cleanup on abort - ensures orphan files are deleted even if
// they were removed from outputs_ or blob_file_additions_ (e.g., by
// RemoveLastEmptyOutput when file_size is 0 because builder was abandoned).
std::vector<std::string> output_file_paths_;
// Per level's output stat
InternalStats::CompactionStats stats_;
+6 -66
View File
@@ -27,68 +27,12 @@
namespace ROCKSDB_NAMESPACE {
#ifndef NDEBUG
static void AssertCleanCut(const InternalKeyComparator* icmp,
VersionStorageInfo* vstorage,
CompactionInputFiles* inputs, int level,
Logger* logger) {
const std::vector<FileMetaData*>& level_files = vstorage->LevelFiles(level);
if (inputs->files.empty() || level_files.empty()) {
return;
}
const Comparator* ucmp = icmp->user_comparator();
// Find first and last input file indices in level
int first_input_idx = -1;
int last_input_idx = -1;
for (size_t i = 0; i < level_files.size(); i++) {
if (level_files[i] == inputs->files.front()) {
first_input_idx = static_cast<int>(i);
}
if (level_files[i] == inputs->files.back()) {
last_input_idx = static_cast<int>(i);
}
}
// Check file before first input
if (first_input_idx > 0) {
const FileMetaData* prev_file = level_files[first_input_idx - 1];
const FileMetaData* first_file = inputs->files.front();
int cmp = sstableKeyCompare(ucmp, prev_file->largest, first_file->smallest);
if (cmp == 0) {
ROCKS_LOG_ERROR(logger,
"Clean cut violated: L%d unselected file %" PRIu64
" adjacent to first selected file %" PRIu64,
level, prev_file->fd.GetNumber(),
first_file->fd.GetNumber());
assert(false);
}
}
// Check file after last input
if (last_input_idx >= 0 &&
static_cast<size_t>(last_input_idx) < level_files.size() - 1) {
const FileMetaData* last_file = inputs->files.back();
const FileMetaData* next_file = level_files[last_input_idx + 1];
int cmp = sstableKeyCompare(ucmp, last_file->largest, next_file->smallest);
if (cmp == 0) {
ROCKS_LOG_ERROR(logger,
"Clean cut violated: L%d unselected file %" PRIu64
" adjacent to last selected file %" PRIu64,
level, next_file->fd.GetNumber(),
last_file->fd.GetNumber());
assert(false);
}
}
}
#endif // NDEBUG
bool PickCostBasedIntraL0Compaction(
const std::vector<FileMetaData*>& level_files, size_t min_files_to_compact,
uint64_t max_compact_bytes_per_del_file, uint64_t max_compaction_bytes,
CompactionInputFiles* comp_inputs) {
TEST_SYNC_POINT("PickCostBasedIntraL0Compaction");
bool FindIntraL0Compaction(const std::vector<FileMetaData*>& level_files,
size_t min_files_to_compact,
uint64_t max_compact_bytes_per_del_file,
uint64_t max_compaction_bytes,
CompactionInputFiles* comp_inputs) {
TEST_SYNC_POINT("FindIntraL0Compaction");
size_t start = 0;
@@ -306,10 +250,6 @@ bool CompactionPicker::ExpandInputsToCleanCut(const std::string& /*cf_name*/,
// inputs. thus, inputs should be non-empty here
assert(!inputs->empty());
#ifndef NDEBUG
AssertCleanCut(icmp_, vstorage, inputs, level, ioptions_.logger);
#endif // NDEBUG
// If, after the expansion, there are files that are already under
// compaction, then we must drop/cancel this compaction.
if (AreFilesInCompaction(inputs->files)) {
+5 -4
View File
@@ -328,10 +328,11 @@ class NullCompactionPicker : public CompactionPicker {
// files. Cannot be nullptr.
//
// @return true iff compaction was found.
bool PickCostBasedIntraL0Compaction(
const std::vector<FileMetaData*>& level_files, size_t min_files_to_compact,
uint64_t max_compact_bytes_per_del_file, uint64_t max_compaction_bytes,
CompactionInputFiles* comp_inputs);
bool FindIntraL0Compaction(const std::vector<FileMetaData*>& level_files,
size_t min_files_to_compact,
uint64_t max_compact_bytes_per_del_file,
uint64_t max_compaction_bytes,
CompactionInputFiles* comp_inputs);
CompressionType GetCompressionType(const VersionStorageInfo* vstorage,
const MutableCFOptions& mutable_cf_options,
+71 -384
View File
@@ -9,7 +9,6 @@
#include "db/compaction/compaction_picker_fifo.h"
#include <algorithm>
#include <cinttypes>
#include <string>
#include <vector>
@@ -32,29 +31,6 @@ uint64_t GetTotalFilesSize(const std::vector<FileMetaData*>& files) {
}
return total_size;
}
// Compute effective data size and capacity limit for FIFO compaction.
// When max_data_files_size > 0 (blob-aware mode), the effective size includes
// both SST and blob file sizes, and the limit is max_data_files_size.
// Otherwise, only SST sizes are used with max_table_files_size as the limit.
void GetEffectiveSizeAndLimit(const CompactionOptionsFIFO& fifo_opts,
uint64_t total_sst_size, uint64_t total_blob_size,
uint64_t* effective_size,
uint64_t* effective_max) {
*effective_size = total_sst_size;
*effective_max = fifo_opts.max_table_files_size;
if (fifo_opts.max_data_files_size > 0) {
*effective_size += total_blob_size;
*effective_max = fifo_opts.max_data_files_size;
}
}
// Return the effective capacity limit for FIFO compaction.
// Convenience wrapper when only the limit is needed (e.g., PickTTLCompaction).
uint64_t GetEffectiveMax(const CompactionOptionsFIFO& fifo_opts) {
return fifo_opts.max_data_files_size > 0 ? fifo_opts.max_data_files_size
: fifo_opts.max_table_files_size;
}
} // anonymous namespace
bool FIFOCompactionPicker::NeedsCompaction(
@@ -122,43 +98,10 @@ Compaction* FIFOCompactionPicker::PickTTLCompaction(
// Return a nullptr and proceed to size-based FIFO compaction if:
// 1. there are no files older than ttl OR
// 2. there are a few files older than ttl, but deleting them will not bring
// the total size to be less than the size threshold.
uint64_t effective_max =
GetEffectiveMax(mutable_cf_options.compaction_options_fifo);
// Estimate the effective remaining data after dropping TTL-expired SSTs.
// Each dropped SST also frees a proportional share of blob data.
//
// In multi-level FIFO (migration), we must use total SST across ALL levels
// as the reference, because total_blob covers all levels. Using only L0
// SST would inflate the blob estimate.
uint64_t effective_remaining = total_size;
if (mutable_cf_options.compaction_options_fifo.max_data_files_size > 0) {
uint64_t total_blob = vstorage->GetBlobStats().total_file_size;
// Compute total SST across all levels so the reference scope matches
// total_blob's scope (all levels).
uint64_t total_sst_all_levels = GetTotalFilesSize(level_files);
for (int level = 1; level < vstorage->num_levels(); ++level) {
total_sst_all_levels += GetTotalFilesSize(vstorage->LevelFiles(level));
}
// remaining_sst_all = total_sst_all - dropped_l0_sst
// total_size is the remaining L0 SST after removing expired files;
// original L0 SST minus remaining L0 SST = dropped.
uint64_t original_l0_sst = GetTotalFilesSize(level_files);
uint64_t dropped_sst = original_l0_sst - total_size;
uint64_t remaining_sst_all = total_sst_all_levels - dropped_sst;
// Proportional blob estimate: each SST byte "owns" a proportional
// share of blob bytes. Both reference sizes must come from the same
// scope (all levels) to avoid inflated estimates.
if (total_sst_all_levels > 0 && total_blob > 0) {
effective_remaining =
remaining_sst_all +
static_cast<uint64_t>(static_cast<double>(remaining_sst_all) /
total_sst_all_levels * total_blob);
} else {
effective_remaining = remaining_sst_all;
}
}
if (inputs[0].files.empty() || effective_remaining > effective_max) {
// the total size to be less than max_table_files_size threshold.
if (inputs[0].files.empty() ||
total_size >
mutable_cf_options.compaction_options_fifo.max_table_files_size) {
return nullptr;
}
@@ -208,9 +151,7 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
LogBuffer* log_buffer) {
const auto& fifo_opts = mutable_cf_options.compaction_options_fifo;
// compute the total SST size and identify the last non-empty level
// compute the total size and identify the last non-empty level
int last_level = 0;
uint64_t total_size = 0;
for (int level = 0; level < vstorage->num_levels(); ++level) {
@@ -223,13 +164,52 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
const std::vector<FileMetaData*>& last_level_files =
vstorage->LevelFiles(last_level);
// Compute effective size and limit for comparison.
uint64_t effective_size, effective_max;
GetEffectiveSizeAndLimit(fifo_opts, total_size,
vstorage->GetBlobStats().total_file_size,
&effective_size, &effective_max);
if (last_level == 0 &&
total_size <=
mutable_cf_options.compaction_options_fifo.max_table_files_size) {
// total size not exceeded, try to find intra level 0 compaction if enabled
const std::vector<FileMetaData*>& level0_files = vstorage->LevelFiles(0);
if (mutable_cf_options.compaction_options_fifo.allow_compaction &&
level0_files.size() > 0) {
CompactionInputFiles comp_inputs;
// try to prevent same files from being compacted multiple times, which
// could produce large files that may never TTL-expire. Achieve this by
// disallowing compactions with files larger than memtable (inflate its
// size by 10% to account for uncompressed L0 files that may have size
// slightly greater than memtable size limit).
size_t max_compact_bytes_per_del_file =
static_cast<size_t>(MultiplyCheckOverflow(
static_cast<uint64_t>(mutable_cf_options.write_buffer_size),
1.1));
if (FindIntraL0Compaction(
level0_files,
mutable_cf_options
.level0_file_num_compaction_trigger /* min_files_to_compact */
,
max_compact_bytes_per_del_file,
mutable_cf_options.max_compaction_bytes, &comp_inputs)) {
Compaction* c = new Compaction(
vstorage, ioptions_, mutable_cf_options, mutable_db_options,
{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,
0 /* max_subcompactions */, {},
/* earliest_snapshot */ std::nullopt,
/* snapshot_checker */ nullptr,
CompactionReason::kFIFOReduceNumFiles,
/* trim_ts */ "", vstorage->CompactionScore(0),
/* l0_files_might_overlap */ true);
return c;
}
}
if (last_level == 0 && effective_size <= effective_max) {
ROCKS_LOG_BUFFER(
log_buffer,
"[%s] FIFO compaction: nothing to do. Total size %" PRIu64
", max size %" PRIu64 "\n",
cf_name.c_str(), total_size,
mutable_cf_options.compaction_options_fifo.max_table_files_size);
return nullptr;
}
@@ -247,29 +227,11 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
inputs[0].level = last_level;
if (last_level == 0) {
// When using blob-aware sizing, use proportional estimation (same
// principle as EstimateTotalDataForSST): each SST "owns"
// effective_size / num_files of total data. This is an approximation
// — individual SSTs may reference different amounts of blob data,
// but uniform distribution is a reasonable estimate for FIFO dropping.
uint64_t remaining_size = effective_size;
const uint64_t num_files = last_level_files.size();
// Proportional estimate of data per file (SST + blob).
// Use max(1) to prevent stalling when effective_size < num_files.
const uint64_t data_per_file =
(fifo_opts.max_data_files_size > 0 && num_files > 0)
? std::max(effective_size / num_files, uint64_t{1})
: 0;
// In L0, right-most files are the oldest files.
for (auto ritr = last_level_files.rbegin(); ritr != last_level_files.rend();
++ritr) {
auto f = *ritr;
if (fifo_opts.max_data_files_size > 0) {
remaining_size -= std::min(remaining_size, data_per_file);
} else {
remaining_size -= std::min(remaining_size, f->fd.file_size);
}
total_size -= f->fd.file_size;
inputs[0].files.push_back(f);
char tmp_fsize[16];
AppendHumanBytes(f->fd.GetFileSize(), tmp_fsize, sizeof(tmp_fsize));
@@ -277,11 +239,13 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
"[%s] FIFO compaction: picking file %" PRIu64
" with size %s for deletion",
cf_name.c_str(), f->fd.GetNumber(), tmp_fsize);
if (remaining_size <= effective_max) {
if (total_size <=
mutable_cf_options.compaction_options_fifo.max_table_files_size) {
break;
}
}
} else if (effective_size > effective_max) {
} else if (total_size >
mutable_cf_options.compaction_options_fifo.max_table_files_size) {
// If the last level is non-L0, we actually don't know which file is
// logically the oldest since the file creation time only represents
// when this file was compacted to this level, which is independent
@@ -291,36 +255,34 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
// file with the smallest key will be deleted first. This design decision
// better serves a major type of FIFO use cases where smaller keys are
// associated with older data.
const uint64_t num_files = last_level_files.size();
// Proportional estimate of data per file (SST + blob), same as L0 path.
const uint64_t data_per_file =
(fifo_opts.max_data_files_size > 0 && num_files > 0)
? std::max(effective_size / num_files, uint64_t{1})
: 0;
for (const auto& f : last_level_files) {
if (f->being_compacted) {
continue;
}
if (fifo_opts.max_data_files_size > 0) {
effective_size -= std::min(effective_size, data_per_file);
} else {
effective_size -= std::min(effective_size, f->fd.file_size);
}
total_size -= f->fd.file_size;
inputs[0].files.push_back(f);
char tmp_fsize[16];
AppendHumanBytes(f->fd.GetFileSize(), tmp_fsize, sizeof(tmp_fsize));
ROCKS_LOG_BUFFER(log_buffer,
"[%s] FIFO compaction: picking file %" PRIu64
" with size %s for deletion under total size %" PRIu64
" vs max size %" PRIu64,
cf_name.c_str(), f->fd.GetNumber(), tmp_fsize,
effective_size, effective_max);
ROCKS_LOG_BUFFER(
log_buffer,
"[%s] FIFO compaction: picking file %" PRIu64
" with size %s for deletion under total size %" PRIu64
" vs max table files size %" PRIu64,
cf_name.c_str(), f->fd.GetNumber(), tmp_fsize, total_size,
mutable_cf_options.compaction_options_fifo.max_table_files_size);
if (effective_size <= effective_max) {
if (total_size <=
mutable_cf_options.compaction_options_fifo.max_table_files_size) {
break;
}
}
} else {
ROCKS_LOG_BUFFER(
log_buffer,
"[%s] FIFO compaction: nothing to do. Total size %" PRIu64
", max size %" PRIu64 "\n",
cf_name.c_str(), total_size,
mutable_cf_options.compaction_options_fifo.max_table_files_size);
return nullptr;
}
@@ -457,269 +419,6 @@ Compaction* FIFOCompactionPicker::PickTemperatureChangeCompaction(
return c;
}
Compaction* FIFOCompactionPicker::PickIntraL0Compaction(
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
LogBuffer* log_buffer) {
const auto& fifo_opts = mutable_cf_options.compaction_options_fifo;
if (!fifo_opts.allow_compaction) {
return nullptr;
}
const std::vector<FileMetaData*>& level0_files = vstorage->LevelFiles(0);
if (level0_files.empty()) {
return nullptr;
}
if (fifo_opts.use_kv_ratio_compaction) {
if (fifo_opts.max_data_files_size == 0) {
ROCKS_LOG_BUFFER(
log_buffer,
"[%s] FIFO kv-ratio compaction: skipping — "
"max_data_files_size is 0, cannot compute target file size. ",
cf_name.c_str());
} else if (fifo_opts.max_data_files_size < fifo_opts.max_table_files_size) {
ROCKS_LOG_BUFFER(log_buffer,
"[%s] FIFO kv-ratio compaction: skipping — "
"max_data_files_size (%" PRIu64
") < max_table_files_size "
"(%" PRIu64 ").",
cf_name.c_str(), fifo_opts.max_data_files_size,
fifo_opts.max_table_files_size);
} else {
return PickRatioBasedIntraL0Compaction(cf_name, mutable_cf_options,
mutable_db_options, vstorage,
log_buffer);
}
ROCKS_LOG_BUFFER(
log_buffer, "[%s] FIFO: falling back to cost-based intra-L0 compaction",
cf_name.c_str());
}
// Old intra-L0 path: merge small files using PickCostBasedIntraL0Compaction.
// Minimum files to compact follows level0_file_num_compaction_trigger.
// Try to prevent same files from being compacted multiple times, which
// could produce large files that may never TTL-expire. Achieve this by
// disallowing compactions with files larger than memtable (inflate its
// size by 10% to account for uncompressed L0 files that may have size
// slightly greater than memtable size limit).
CompactionInputFiles comp_inputs;
size_t max_compact_bytes_per_del_file =
static_cast<size_t>(MultiplyCheckOverflow(
static_cast<uint64_t>(mutable_cf_options.write_buffer_size), 1.1));
if (PickCostBasedIntraL0Compaction(
level0_files,
mutable_cf_options
.level0_file_num_compaction_trigger /* min_files_to_compact */,
max_compact_bytes_per_del_file,
mutable_cf_options.max_compaction_bytes, &comp_inputs)) {
Compaction* c = new Compaction(
vstorage, ioptions_, mutable_cf_options, mutable_db_options,
{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, 0 /* max_subcompactions */, {},
/* earliest_snapshot */ std::nullopt,
/* snapshot_checker */ nullptr, CompactionReason::kFIFOReduceNumFiles,
/* trim_ts */ "", vstorage->CompactionScore(0),
/* l0_files_might_overlap */ true);
return c;
}
return nullptr;
}
Compaction* FIFOCompactionPicker::PickRatioBasedIntraL0Compaction(
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
LogBuffer* log_buffer) {
const auto& fifo_opts = mutable_cf_options.compaction_options_fifo;
assert(fifo_opts.use_kv_ratio_compaction);
assert(fifo_opts.max_data_files_size > 0);
// During migration from level/universal compaction to FIFO, non-L0 levels
// may still contain files. The ratio-based algorithm only operates on L0,
// so skip it until PickSizeCompaction has drained all non-L0 levels.
// Once levels collapse to L0-only, this algorithm will kick in.
for (int level = 1; level < vstorage->num_levels(); ++level) {
if (!vstorage->LevelFiles(level).empty()) {
ROCKS_LOG_BUFFER(log_buffer,
"[%s] FIFO kv-ratio compaction: skipping — non-L0 "
"level %d still has %" ROCKSDB_PRIszt
" files (migration in progress)",
cf_name.c_str(), level,
vstorage->LevelFiles(level).size());
return nullptr;
}
}
if (!level0_compactions_in_progress_.empty()) {
return nullptr;
}
const std::vector<FileMetaData*>& level0_files = vstorage->LevelFiles(0);
if (mutable_cf_options.level0_file_num_compaction_trigger <= 1) {
// trigger <= 0 is invalid; trigger == 1 means compact after every flush,
// which doesn't make sense for tiered merging (the tier boundary loop
// divides by trigger, so trigger == 1 would cause an infinite loop).
return nullptr;
}
const size_t trigger = static_cast<size_t>(
mutable_cf_options.level0_file_num_compaction_trigger);
if (level0_files.size() < trigger) {
return nullptr;
}
// Determine the target compacted file size.
//
// When max_compaction_bytes > 0 (explicitly set by user), use it directly
// as the target. This allows users to override the auto-calculated value.
//
// When max_compaction_bytes == 0 (default), auto-calculate from the data
// capacity and observed SST/blob ratio:
// target = max_data_files_size * sst_ratio / trigger
//
// This is recomputed on every PickCompaction call. The computation is
// trivial (sum file sizes + arithmetic) and PickCompaction is only called
// once per flush or compaction completion, so no caching is needed.
uint64_t target = 0;
if (mutable_cf_options.max_compaction_bytes > 0) {
// User explicitly set max_compaction_bytes — use it as target
target = mutable_cf_options.max_compaction_bytes;
} else {
// Auto-calculate from capacity and observed SST/blob ratio
uint64_t total_sst = GetTotalFilesSize(level0_files);
uint64_t total_blob = vstorage->GetBlobStats().total_file_size;
uint64_t total_data = total_sst + total_blob;
if (total_data == 0 || total_sst == 0) {
return nullptr;
}
// Compute sst_ratio (inverse of EstimateTotalDataForSST's proportion):
// when no blob files exist, sst_ratio is 1.0 and the target becomes
// max_data_files_size / trigger, which is large. The algorithm will
// naturally not find small enough files to compact.
double sst_ratio =
(total_blob > 0) ? static_cast<double>(total_sst) / total_data : 1.0;
uint64_t total_sst_at_cap =
static_cast<uint64_t>(fifo_opts.max_data_files_size * sst_ratio);
target = total_sst_at_cap / trigger;
ROCKS_LOG_BUFFER(log_buffer,
"[%s] FIFO ratio-based compaction: sst_ratio=%.4f, "
"target_file_size=%" PRIu64,
cf_name.c_str(), sst_ratio, target);
}
if (target == 0) {
return nullptr;
}
// Tiered size-based file selection.
//
// Tier boundaries form a geometric sequence descending from target:
// ..., target/trigger^2, target/trigger, target
// For each boundary (smallest first), find contiguous L0 files with
// size < boundary. If their accumulated bytes >= boundary, merge them.
// The output (~boundary bytes) advances to the next tier. Files that
// reach target are "graduated" and never compacted again.
//
// Trade-off: write amplification vs L0 file count.
//
// Write amp: O(log(target/flush) / log(trigger)) per byte, instead of
// O(target / (trigger * flush)) from flat merging. Each byte is
// rewritten once per tier crossing.
//
// L0 file count: trigger + k * (trigger - 1) at steady state, where
// k = ceil(log(target/flush) / log(trigger)). This is higher than
// the original trigger target because intermediate tier files
// accumulate while waiting for the next tier merge. The trade-off
// is explicit: more L0 files in exchange for logarithmic (instead
// of linear) write amplification.
// Build tier boundaries from smallest to largest.
// Stop at 10KB minimum — SST files of most workloads are larger than
// this, so lower boundaries would only waste CPU scanning L0 files.
// Files smaller than the lowest boundary simply merge at that boundary.
static constexpr uint64_t kMinTierBoundary = 10 * 1024; // 10KB
std::vector<uint64_t> boundaries;
for (uint64_t b = target; b >= kMinTierBoundary; b /= trigger) {
boundaries.push_back(b);
}
if (boundaries.empty()) {
// target itself is below kMinTierBoundary — use target as the
// sole boundary so we can still compact at the target size.
boundaries.push_back(target);
}
std::reverse(boundaries.begin(), boundaries.end());
// For each tier boundary (smallest first), scan L0 for mergeable batches.
// L0 files are stored newest-first; oldest is at the end.
for (const uint64_t boundary : boundaries) {
for (size_t scan = level0_files.size(); scan > 0;) {
// Skip files >= boundary (they belong to higher tiers) or in-progress
if (level0_files[scan - 1]->fd.file_size >= boundary ||
level0_files[scan - 1]->being_compacted) {
--scan;
continue;
}
// Found a file < boundary — collect contiguous batch
std::vector<FileMetaData*> batch;
uint64_t accumulated = 0;
size_t pos = scan;
while (pos > 0 && level0_files[pos - 1]->fd.file_size < boundary &&
!level0_files[pos - 1]->being_compacted) {
// Don't let output exceed 2x boundary (prevent tier-skipping)
if (accumulated >= boundary &&
accumulated + level0_files[pos - 1]->fd.file_size > boundary * 2) {
break;
}
batch.push_back(level0_files[pos - 1]);
accumulated += level0_files[pos - 1]->fd.file_size;
--pos;
}
// Viable: >= 2 files and accumulated >= boundary
if (batch.size() >= 2 && accumulated >= boundary) {
CompactionInputFiles comp_inputs;
comp_inputs.level = 0;
comp_inputs.files = std::move(batch);
ROCKS_LOG_BUFFER(
log_buffer,
"[%s] FIFO kv-ratio compaction: picking %" ROCKSDB_PRIszt
" files (%" PRIu64 " bytes) at tier boundary %" PRIu64
" for intra-L0 compaction, target=%" PRIu64,
cf_name.c_str(), comp_inputs.files.size(), accumulated, boundary,
target);
Compaction* c = new Compaction(
vstorage, ioptions_, mutable_cf_options, mutable_db_options,
{comp_inputs}, 0, boundary /* 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,
0 /* max_subcompactions */, {},
/* earliest_snapshot */ std::nullopt,
/* snapshot_checker */ nullptr,
CompactionReason::kFIFOReduceNumFiles,
/* trim_ts */ "", vstorage->CompactionScore(0),
/* l0_files_might_overlap */ true);
return c;
}
// This batch wasn't enough — advance past it
scan = pos;
}
}
return nullptr;
}
// The full_history_ts_low parameter is used to control bottommost file marking
// for compaction when user-defined timestamps (UDT) are enabled.
@@ -742,22 +441,10 @@ Compaction* FIFOCompactionPicker::PickCompaction(
c = PickSizeCompaction(cf_name, mutable_cf_options, mutable_db_options,
vstorage, log_buffer);
}
// Intra-L0 compaction merges small files to reduce file count.
// It runs after size-based dropping: if PickSizeCompaction dropped files,
// it returned non-null and we skip this. Otherwise, we try to reduce
// L0 file count by merging small files together.
if (c == nullptr) {
c = PickIntraL0Compaction(cf_name, mutable_cf_options, mutable_db_options,
vstorage, log_buffer);
}
if (c == nullptr) {
c = PickTemperatureChangeCompaction(
cf_name, mutable_cf_options, mutable_db_options, vstorage, log_buffer);
}
if (c == nullptr) {
ROCKS_LOG_BUFFER(log_buffer, "[%s] FIFO compaction: no compaction picked",
cf_name.c_str());
}
RegisterCompaction(c);
return c;
}
-22
View File
@@ -55,28 +55,6 @@ class FIFOCompactionPicker : public CompactionPicker {
VersionStorageInfo* version,
LogBuffer* log_buffer);
// Intra-L0 compaction: merges small L0 files to reduce file count.
// Dispatches between two strategies based on configuration:
// - use_kv_ratio_compaction = true: PickRatioBasedIntraL0Compaction
// (BlobDB-optimized)
// - use_kv_ratio_compaction = false: PickCostBasedIntraL0Compaction
// (original)
// Only active when allow_compaction = true.
Compaction* PickIntraL0Compaction(const std::string& cf_name,
const MutableCFOptions& mutable_cf_options,
const MutableDBOptions& mutable_db_options,
VersionStorageInfo* vstorage,
LogBuffer* log_buffer);
// Capacity-derived intra-L0 compaction for BlobDB workloads.
// Uses the observed SST/blob ratio to compute a target file size,
// producing uniform files for predictable FIFO trimming.
// Called from PickIntraL0Compaction when use_kv_ratio_compaction = true.
Compaction* PickRatioBasedIntraL0Compaction(
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
LogBuffer* log_buffer);
// Will pick one file to compact at a time, starting from the oldest file.
Compaction* PickTemperatureChangeCompaction(
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
+4 -4
View File
@@ -914,10 +914,10 @@ bool LevelCompactionBuilder::PickIntraL0Compaction() {
// resort to L0->L0 compaction yet.
return false;
}
return PickCostBasedIntraL0Compaction(
level_files, kMinFilesForIntraL0Compaction,
std::numeric_limits<uint64_t>::max(),
mutable_cf_options_.max_compaction_bytes, &start_level_inputs_);
return FindIntraL0Compaction(level_files, kMinFilesForIntraL0Compaction,
std::numeric_limits<uint64_t>::max(),
mutable_cf_options_.max_compaction_bytes,
&start_level_inputs_);
}
bool LevelCompactionBuilder::PickSizeBasedIntraL0Compaction() {
File diff suppressed because it is too large. Load diff
+10 -19
View File
@@ -117,14 +117,6 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
std::string debug_str_before_wait =
compaction->input_version()->DebugString(/*hex=*/true);
// TODO: Update CompactionService API to support abort and resume
// functionality. Currently, remote compaction jobs cannot be aborted via
// AbortAllCompactions() because the CompactionService interface lacks methods
// to signal abort to remote workers and to properly resume after an abort.
// The API needs to be extended with:
// - A method to signal abort to running remote compaction jobs
// - A method to resume/re-enable scheduling after an abort is lifted
ROCKS_LOG_INFO(db_options_.info_log,
"[%s] [JOB %d] Waiting for remote compaction...",
compaction->column_family_data()->GetName().c_str(), job_id_);
@@ -320,17 +312,16 @@ CompactionServiceCompactionJob::CompactionServiceCompactionJob(
std::string output_path,
const CompactionServiceInput& compaction_service_input,
CompactionServiceResult* compaction_service_result)
: CompactionJob(
job_id, compaction, db_options, mutable_db_options, file_options,
versions, shutting_down, log_buffer, nullptr, output_directory,
nullptr, stats, db_mutex, db_error_handler, job_context,
std::move(table_cache), event_logger,
compaction->mutable_cf_options().paranoid_file_checks,
compaction->mutable_cf_options().report_bg_io_stats, dbname,
&(compaction_service_result->stats), Env::Priority::USER, io_tracer,
manual_compaction_canceled, CompactionJob::kCompactionAbortedFalse,
db_id, db_session_id,
compaction->column_family_data()->GetFullHistoryTsLow()),
: CompactionJob(job_id, compaction, db_options, mutable_db_options,
file_options, versions, shutting_down, log_buffer, nullptr,
output_directory, nullptr, stats, db_mutex,
db_error_handler, job_context, std::move(table_cache),
event_logger,
compaction->mutable_cf_options().paranoid_file_checks,
compaction->mutable_cf_options().report_bg_io_stats, dbname,
&(compaction_service_result->stats), Env::Priority::USER,
io_tracer, manual_compaction_canceled, db_id, db_session_id,
compaction->column_family_data()->GetFullHistoryTsLow()),
output_path_(std::move(output_path)),
compaction_input_(compaction_service_input),
compaction_result_(compaction_service_result) {}
+39 -465
View File
@@ -195,11 +195,9 @@ class MyTestCompactionService : public CompactionService {
std::vector<std::shared_ptr<EventListener>> listeners_;
std::vector<std::shared_ptr<TablePropertiesCollectorFactory>>
table_properties_collector_factories_;
std::atomic_bool canceled_{false};
std::atomic<CompactionServiceJobStatus> final_updated_status_{
CompactionServiceJobStatus::kUseLocal};
protected:
std::atomic_bool canceled_{false};
};
class CompactionServiceTest : public DBTestBase {
@@ -1380,9 +1378,8 @@ TEST_F(CompactionServiceTest, CancelCompactionOnPrimarySide) {
// Primary DB calls CancelAllBackgroundWork() while the compaction is running
SyncPoint::GetInstance()->SetCallBack(
"CompactionJob::Run():Inprogress", [&](void* /*arg*/) {
CancelAllBackgroundWork(db_.get(), false /*wait*/);
});
"CompactionJob::Run():Inprogress",
[&](void* /*arg*/) { CancelAllBackgroundWork(db_, false /*wait*/); });
SyncPoint::GetInstance()->EnableProcessing();
@@ -2140,12 +2137,6 @@ class ResumableCompactionService : public MyTestCompactionService {
{} /* table_properties_collector_factories */),
scenario_(scenario) {}
// Set the user key where cancellation should happen.
void SetCancelAtKey(const std::string& key, SequenceNumber seqno) {
cancel_at_key_ = key;
cancel_at_seqno_ = seqno;
}
CompactionServiceJobStatus Wait(const std::string& scheduled_job_id,
std::string* result) override {
std::string compaction_input = ExtractCompactionInput(scheduled_job_id);
@@ -2158,50 +2149,25 @@ class ResumableCompactionService : public MyTestCompactionService {
// ASSUMPTION: This makes stats.count directly proportional to keys
// processed.
SyncPoint::GetInstance()->SetCallBack(
"CompactionOutputs::ShouldStopBefore::manual_decision",
[this](void* p) {
"CompactionOutputs::ShouldStopBefore::manual_decision", [](void* p) {
auto* pair = static_cast<std::pair<bool*, const Slice>*>(p);
*(pair->first) = true; // Force file cut at every key
// If cancel_at_key_ is set, cancel when we encounter that key
if (!cancel_at_key_.empty() && !already_canceled_) {
ParsedInternalKey parsed_key;
if (ParseInternalKey(pair->second, &parsed_key, true).ok()) {
if (parsed_key.user_key.ToString() == cancel_at_key_) {
// Check sequence number if specified
if (cancel_at_seqno_ == kMaxSequenceNumber ||
parsed_key.sequence == cancel_at_seqno_) {
canceled_ = true;
already_canceled_ = true;
}
}
}
}
*(pair->first) = true;
});
// If no cancel_at_key_ is set, use the original behavior:
// Simulate cancelled compaction by overriding status at completion. So
// compaction processes all keys before this point to make stats.count
// comparison straightforward.
if (cancel_at_key_.empty()) {
SyncPoint::GetInstance()->SetCallBack(
"DBImplSecondary::CompactWithoutInstallation::End",
[&](void* status) {
auto s = static_cast<Status*>(status);
*s = Status::Incomplete(Status::SubCode::kManualCompactionPaused);
});
}
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 input keys until cancellation point
// - Processes all input keys
// - Creates output files and saves progress
// - Status overridden to "paused"
open_and_compaction_options.allow_resumption = true;
open_and_compaction_options.canceled = &canceled_;
already_canceled_ = false;
canceled_ = false;
auto phase1_stats =
RunCancelledCompaction(open_and_compaction_options, scheduled_job_id,
compaction_input, override_options);
@@ -2222,9 +2188,6 @@ class ResumableCompactionService : public MyTestCompactionService {
EXPECT_TRUE(cleanup_status.ok());
EXPECT_OK(override_options.env->CreateDir(output_dir));
already_canceled_ = false;
canceled_ = false;
phase2_stats =
RunCancelledCompaction(open_and_compaction_options, scheduled_job_id,
compaction_input, override_options);
@@ -2236,6 +2199,9 @@ class ResumableCompactionService : public MyTestCompactionService {
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
@@ -2254,12 +2220,6 @@ class ResumableCompactionService : public MyTestCompactionService {
EXPECT_OK(override_options.env->CreateDir(output_dir));
}
// Prevent triggering of cancellation
SyncPoint::GetInstance()->ClearCallBack(
"DBImplSecondary::CompactWithoutInstallation::End");
already_canceled_ = true;
canceled_ = false;
auto final_phase_stats =
RunCompaction(open_and_compaction_options, scheduled_job_id,
compaction_input, override_options, result);
@@ -2267,38 +2227,36 @@ class ResumableCompactionService : public MyTestCompactionService {
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
// Validate statistics based on scenario (only when cancelling at end)
if (cancel_at_key_.empty()) {
if (scenario_ == TestScenario::kMultipleCancelToggleResumption) {
// ASSUMPTION: Phase 1 processes all keys before cancellation
EXPECT_GT(phase1_stats.count, 0);
// 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);
// 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);
} 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);
// 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);
} 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);
}
// 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);
@@ -2368,9 +2326,6 @@ class ResumableCompactionService : public MyTestCompactionService {
}
TestScenario scenario_;
std::string cancel_at_key_;
SequenceNumber cancel_at_seqno_ = kMaxSequenceNumber;
std::atomic<bool> already_canceled_{false};
};
class ResumableCompactionServiceTest : public CompactionServiceTest {
@@ -2477,387 +2432,6 @@ TEST_F(ResumableCompactionServiceTest,
RunCompactionCancelTest(ResumableCompactionService::TestScenario::
kMultipleCancelToggleResumption);
}
class ResumableCompactionKeyTypeTest : public CompactionServiceTest {
public:
explicit ResumableCompactionKeyTypeTest() : CompactionServiceTest() {}
protected:
void SetupResumableCompactionService(
Options& options, const std::string& cancel_at_key = "",
SequenceNumber cancel_at_seqno = kMaxSequenceNumber) {
options.disable_auto_compactions = true;
statistics_ = CreateDBStatistics();
resume_cs_ = std::make_shared<ResumableCompactionService>(
dbname_, options, statistics_,
ResumableCompactionService::TestScenario::kCancelThenResume);
if (!cancel_at_key.empty()) {
resume_cs_->SetCancelAtKey(cancel_at_key, cancel_at_seqno);
}
options.compaction_service = resume_cs_;
DestroyAndReopen(options);
}
void ResetStatistics() { ASSERT_OK(statistics_->Reset()); }
void VerifyResumeBytes() {
uint64_t resumed_bytes =
statistics_->getTickerCount(REMOTE_COMPACT_RESUMED_BYTES);
ASSERT_GT(resumed_bytes, 0);
}
private:
std::shared_ptr<ResumableCompactionService> resume_cs_;
std::shared_ptr<Statistics> statistics_;
};
// Cancel compaction right before processing key "c" to test resumption at a
// deletion at the non-bottom level. When resumed, compaction will continue
// from this deletion.
TEST_F(ResumableCompactionKeyTypeTest,
CancelAndResumeWithDeleteAtNonBottomLevel) {
Options options = CurrentOptions();
SetupResumableCompactionService(options, "c");
ASSERT_OK(Put("c", "old_value"));
ASSERT_OK(Put("c_placeholder", "placeholder"));
ASSERT_OK(Flush());
MoveFilesToLevel(options.num_levels - 1);
ASSERT_OK(Put("a", "val1"));
ASSERT_OK(Put("b", "val2"));
ASSERT_OK(Put("d", "val4"));
ASSERT_OK(Flush());
ASSERT_OK(Delete("c"));
ASSERT_OK(Flush());
std::vector<std::string> input_files;
ColumnFamilyMetaData cf_meta;
db_->GetColumnFamilyMetaData(&cf_meta);
for (const auto& file : cf_meta.levels[0].files) {
input_files.push_back(file.name);
}
ASSERT_EQ(input_files.size(), 2);
ResetStatistics();
CompactionOptions compact_options;
ASSERT_OK(
db_->CompactFiles(compact_options, input_files, 1 /* output_level*/));
ASSERT_EQ(Get("a"), "val1");
ASSERT_EQ(Get("b"), "val2");
ASSERT_EQ(Get("c"), "NOT_FOUND");
ASSERT_EQ(Get("d"), "val4");
VerifyResumeBytes();
}
// Cancel compaction right before processing key "c" to test resumption at a
// deletion at the ottom level. When resumed, compaction will continue from
// the last saved progress point before the delete.
TEST_F(ResumableCompactionKeyTypeTest, CancelAndResumeWithDeleteAtBottomLevel) {
Options options = CurrentOptions();
SetupResumableCompactionService(options, "c");
ASSERT_OK(Put("c", "old_value"));
const Snapshot* snapshot = db_->GetSnapshot();
ASSERT_OK(Delete("c"));
ASSERT_OK(Flush());
MoveFilesToLevel(options.num_levels - 1);
ASSERT_OK(Put("a", "val1"));
ASSERT_OK(Put("b", "val2"));
ASSERT_OK(Put("d", "val4"));
ASSERT_OK(Flush());
ResetStatistics();
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_EQ(Get("a"), "val1");
ASSERT_EQ(Get("b"), "val2");
ASSERT_EQ(Get("c"), "NOT_FOUND");
ASSERT_EQ(Get("c", snapshot), "old_value");
ASSERT_EQ(Get("d"), "val4");
db_->ReleaseSnapshot(snapshot);
VerifyResumeBytes();
}
// Cancel compaction right before processing key "c" to test resumption at a
// merge operand. When resumed, compaction will continue from the last saved
// progress point before the merge operand.
TEST_F(ResumableCompactionKeyTypeTest, CancelAndResumeWithMerge) {
Options options = CurrentOptions();
options.merge_operator = MergeOperators::CreateStringAppendOperator();
SetupResumableCompactionService(options, "c");
ASSERT_OK(Put("c", "old_value"));
ASSERT_OK(Put("c_placeholder", "placeholder"));
ASSERT_OK(Flush());
MoveFilesToLevel(options.num_levels - 1);
ASSERT_OK(Put("a", "val1"));
ASSERT_OK(Put("b", "val2"));
ASSERT_OK(Put("d", "val4"));
ASSERT_OK(Flush());
ASSERT_OK(Merge("c", "new_value"));
ASSERT_OK(Flush());
std::vector<std::string> input_files;
ColumnFamilyMetaData cf_meta;
db_->GetColumnFamilyMetaData(&cf_meta);
for (const auto& file : cf_meta.levels[0].files) {
input_files.push_back(file.name);
}
ASSERT_EQ(input_files.size(), 2);
ResetStatistics();
CompactionOptions compact_options;
ASSERT_OK(
db_->CompactFiles(compact_options, input_files, 1 /* output_level*/));
ASSERT_EQ(Get("a"), "val1");
ASSERT_EQ(Get("b"), "val2");
ASSERT_EQ(Get("c"), "old_value,new_value");
ASSERT_EQ(Get("d"), "val4");
VerifyResumeBytes();
}
// Cancel compaction right before processing key "c" to test resumption at a
// single delete. When resumed, compaction will continue from the last saved
// progress point before the single delete.
TEST_F(ResumableCompactionKeyTypeTest, CancelAndResumeWithSingleDelete) {
Options options = CurrentOptions();
SetupResumableCompactionService(options, "c");
ASSERT_OK(Put("c", "old_value"));
ASSERT_OK(Put("c_placeholder", "placeholder"));
ASSERT_OK(Flush());
MoveFilesToLevel(options.num_levels - 1);
ASSERT_OK(Put("a", "val1"));
ASSERT_OK(Put("b", "val2"));
ASSERT_OK(Put("d", "val4"));
ASSERT_OK(Flush());
ASSERT_OK(SingleDelete("c"));
ASSERT_OK(Flush());
std::vector<std::string> input_files;
ColumnFamilyMetaData cf_meta;
db_->GetColumnFamilyMetaData(&cf_meta);
for (const auto& file : cf_meta.levels[0].files) {
input_files.push_back(file.name);
}
ASSERT_EQ(input_files.size(), 2);
ResetStatistics();
CompactionOptions compact_options;
ASSERT_OK(
db_->CompactFiles(compact_options, input_files, 1 /* output_level*/));
ASSERT_EQ(Get("a"), "val1");
ASSERT_EQ(Get("b"), "val2");
ASSERT_EQ(Get("c"), "NOT_FOUND");
ASSERT_EQ(Get("d"), "val4");
VerifyResumeBytes();
}
// Cancel compaction right before processing key "c" to test resumption at a
// range delete. When resumed, compaction will continue from the last saved
// progress point before the range delete.
TEST_F(ResumableCompactionKeyTypeTest, CancelAndResumeWithRangeDelete) {
Options options = CurrentOptions();
SetupResumableCompactionService(options, "c");
ASSERT_OK(Put("c", "old_value"));
ASSERT_OK(Put("c_placeholder", "placeholder"));
ASSERT_OK(Flush());
MoveFilesToLevel(options.num_levels - 1);
ASSERT_OK(Put("a", "val1"));
ASSERT_OK(Put("b", "val2"));
ASSERT_OK(Put("d", "val4"));
ASSERT_OK(Flush());
ASSERT_OK(
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "c", "c_"));
ASSERT_OK(Flush());
std::vector<std::string> input_files;
ColumnFamilyMetaData cf_meta;
db_->GetColumnFamilyMetaData(&cf_meta);
for (const auto& file : cf_meta.levels[0].files) {
input_files.push_back(file.name);
}
ASSERT_EQ(input_files.size(), 2);
ResetStatistics();
CompactionOptions compact_options;
ASSERT_OK(
db_->CompactFiles(compact_options, input_files, 1 /* output_level*/));
ASSERT_EQ(Get("a"), "val1");
ASSERT_EQ(Get("b"), "val2");
ASSERT_EQ(Get("c"), "NOT_FOUND");
ASSERT_EQ(Get("d"), "val4");
VerifyResumeBytes();
}
// Test resumption when a key has multiple versions spanning across file
// boundaries (i.e., the same key exists in multiple SST files).
//
// Scenario:
// File 1 largest key: key "b"
// File 2 smallest key: key "c" with seqno=4 (older version)
// File 3 largest key: key "c" with seqno=5 (newer version)
//
// Cancel compaction right before processing the older version of key "c".
// Upon resumption, compaction continues from the saved progress point "b" and
// correctly processes both versions
TEST_F(ResumableCompactionKeyTypeTest,
CancelAndResumeWithKeySpanningFileBoundaries) {
Options options = CurrentOptions();
// Set up cancellation at the older version of the key which will have
// sequence number zero-ed out
SetupResumableCompactionService(options, "c" /*cancel_at_key*/, 0 /*seqno*/);
ASSERT_OK(Put("a", "val1"));
ASSERT_OK(Put("b", "val2"));
ASSERT_OK(Put("d", "val4"));
ASSERT_OK(Flush());
ASSERT_OK(Put("c", "old_value"));
const Snapshot* snapshot = db_->GetSnapshot();
ASSERT_OK(Put("c", "new_value"));
ASSERT_OK(Flush());
ResetStatistics();
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_EQ(Get("a"), "val1");
ASSERT_EQ(Get("b"), "val2");
ASSERT_EQ(Get("c"), "new_value");
ASSERT_EQ(Get("c", snapshot), "old_value");
ASSERT_EQ(Get("d"), "val4");
db_->ReleaseSnapshot(snapshot);
VerifyResumeBytes();
}
// Cancel compaction right before processing key "c" to test resumption at a
// wide column. When resumed, compaction will continue
// from the wide column.
TEST_F(ResumableCompactionKeyTypeTest, CancelAndResumeWithWideColumn) {
Options options = CurrentOptions();
SetupResumableCompactionService(options, "c" /*cancel_at_key*/);
ASSERT_OK(Put("a", "val1"));
ASSERT_OK(Put("b", "val2"));
ASSERT_OK(Put("d", "val4"));
ASSERT_OK(Flush());
WideColumns columns{{"col1", "value1"}, {"col2", "value2"}};
ASSERT_OK(
db_->PutEntity(WriteOptions(), db_->DefaultColumnFamily(), "c", columns));
ASSERT_OK(Flush());
ResetStatistics();
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_EQ(Get("a"), "val1");
ASSERT_EQ(Get("b"), "val2");
PinnableWideColumns result;
ASSERT_OK(
db_->GetEntity(ReadOptions(), db_->DefaultColumnFamily(), "c", &result));
WideColumns expected{{"col1", "value1"}, {"col2", "value2"}};
ASSERT_EQ(result.columns(), expected);
ASSERT_EQ(Get("d"), "val4");
VerifyResumeBytes();
}
// Cancel compaction right before processing key "c" to test resumption at a
// timed put. When resumed, compaction will continue
// from the timed put.
TEST_F(ResumableCompactionKeyTypeTest, CancelAndResumeWithTimedPut) {
Options options = CurrentOptions();
options.preclude_last_level_data_seconds = 86400; // Enable TimedPut feature
options.preserve_internal_time_seconds = 86400; // Preserve write time
SetupResumableCompactionService(options, "c" /*cancel_at_key*/);
ASSERT_OK(Put("c", "old_value"));
ASSERT_OK(Put("c_placeholder", "placeholder"));
ASSERT_OK(Flush());
MoveFilesToLevel(options.num_levels - 1);
ASSERT_OK(Put("a", "val1"));
ASSERT_OK(Put("b", "val2"));
ASSERT_OK(Put("d", "val4"));
ASSERT_OK(Flush());
// Use TimedPut for key "c" with current write time
uint64_t write_time = env_->NowMicros() / 1000000;
ASSERT_OK(TimedPut("c", "val3", write_time /*write_unix_time*/));
ASSERT_OK(Put("d", "val4"));
ASSERT_OK(Flush());
std::vector<std::string> input_files;
ColumnFamilyMetaData cf_meta;
db_->GetColumnFamilyMetaData(&cf_meta);
for (const auto& file : cf_meta.levels[0].files) {
input_files.push_back(file.name);
}
ASSERT_EQ(input_files.size(), 2);
ResetStatistics();
CompactionOptions compact_options;
ASSERT_OK(
db_->CompactFiles(compact_options, input_files, 1 /* output_level*/));
ASSERT_EQ(Get("a"), "val1");
ASSERT_EQ(Get("b"), "val2");
ASSERT_EQ(Get("c"), "val3");
ASSERT_EQ(Get("d"), "val4");
VerifyResumeBytes();
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
-8
View File
@@ -95,14 +95,6 @@ class SubcompactionState {
proximal_level_outputs_.RemoveLastEmptyOutput();
}
// Cleanup output builders for abandoning in-progress files.
void CleanupOutputs() {
compaction_outputs_.Cleanup();
if (compaction->SupportsPerKeyPlacement()) {
proximal_level_outputs_.Cleanup();
}
}
void BuildSubcompactionJobInfo(
SubcompactionJobInfo& subcompaction_job_info) const {
const Compaction* c = compaction;
+4 -4
View File
@@ -1717,8 +1717,8 @@ TEST_P(PrecludeLastLevelTest, MigrationFromPreserveTimePartial) {
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
std::vector<KeyVersion> key_versions;
ASSERT_OK(GetAllKeyVersions(
db_.get(), {}, {}, std::numeric_limits<size_t>::max(), &key_versions));
ASSERT_OK(GetAllKeyVersions(db_, {}, {}, std::numeric_limits<size_t>::max(),
&key_versions));
// make sure there're more than 300 keys and first 100 keys are having seqno
// zeroed out, the last 100 key seqno not zeroed out
@@ -2319,8 +2319,8 @@ TEST_P(PrecludeLastLevelTest, LastLevelOnlyCompactionPartial) {
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
std::vector<KeyVersion> key_versions;
ASSERT_OK(GetAllKeyVersions(
db_.get(), {}, {}, std::numeric_limits<size_t>::max(), &key_versions));
ASSERT_OK(GetAllKeyVersions(db_, {}, {}, std::numeric_limits<size_t>::max(),
&key_versions));
// make sure there're more than 300 keys and first 100 keys are having seqno
// zeroed out, the last 100 key seqno not zeroed out
+9 -7
View File
@@ -258,12 +258,12 @@ class ComparatorDBTest
private:
std::string dbname_;
Env* env_;
std::unique_ptr<DB> db_;
DB* db_;
Options last_options_;
std::unique_ptr<const Comparator> comparator_guard;
public:
ComparatorDBTest() : env_(Env::Default()) {
ComparatorDBTest() : env_(Env::Default()), db_(nullptr) {
kTestComparator = BytewiseComparator();
dbname_ = test::PerThreadDBPath("comparator_db_test");
BlockBasedTableOptions toptions;
@@ -274,12 +274,12 @@ class ComparatorDBTest
}
~ComparatorDBTest() override {
db_.reset();
delete db_;
EXPECT_OK(DestroyDB(dbname_, last_options_));
kTestComparator = BytewiseComparator();
}
DB* GetDB() { return db_.get(); }
DB* GetDB() { return db_; }
void SetOwnedComparator(const Comparator* cmp, bool owner = true) {
if (owner) {
@@ -301,12 +301,14 @@ class ComparatorDBTest
}
void Destroy() {
db_.reset();
delete db_;
db_ = nullptr;
ASSERT_OK(DestroyDB(dbname_, last_options_));
}
Status TryReopen() {
db_.reset();
delete db_;
db_ = nullptr;
last_options_.create_if_missing = true;
return DB::Open(last_options_, dbname_, &db_);
@@ -316,7 +318,7 @@ class ComparatorDBTest
INSTANTIATE_TEST_CASE_P(FormatDef, ComparatorDBTest,
testing::Values(test::kDefaultFormatVersion));
INSTANTIATE_TEST_CASE_P(FormatLatest, ComparatorDBTest,
testing::Values(kLatestBbtFormatVersion));
testing::Values(kLatestFormatVersion));
TEST_P(ComparatorDBTest, Bytewise) {
for (int rand_seed = 301; rand_seed < 306; rand_seed++) {
+88 -60
View File
@@ -73,7 +73,7 @@ class CorruptionTest : public testing::Test {
std::string dbname_;
std::shared_ptr<Cache> tiny_cache_;
Options options_;
std::unique_ptr<DB> db_;
DB* db_;
CorruptionTest() {
// If LRU cache shard bit is smaller than 2 (or -1 which will automatically
@@ -105,7 +105,8 @@ class CorruptionTest : public testing::Test {
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->LoadDependency({});
SyncPoint::GetInstance()->ClearAllCallBacks();
db_.reset();
delete db_;
db_ = nullptr;
if (getenv("KEEP_DB")) {
fprintf(stdout, "db is still at %s\n", dbname_.c_str());
} else {
@@ -115,12 +116,14 @@ class CorruptionTest : public testing::Test {
}
}
void CloseDb() { db_.reset(); }
DBImpl* dbfull() { return static_cast_with_check<DBImpl>(db_.get()); }
void CloseDb() {
delete db_;
db_ = nullptr;
}
Status TryReopen(Options* options = nullptr) {
db_.reset();
delete db_;
db_ = nullptr;
Options opt = (options ? *options : options_);
if (opt.env == Options().env) {
// If env is not overridden, replace it with ErrorEnv.
@@ -138,7 +141,8 @@ class CorruptionTest : public testing::Test {
void Reopen(Options* options = nullptr) { ASSERT_OK(TryReopen(options)); }
void RepairDB() {
db_.reset();
delete db_;
db_ = nullptr;
ASSERT_OK(::ROCKSDB_NAMESPACE::RepairDB(dbname_, options_));
}
@@ -147,7 +151,8 @@ class CorruptionTest : public testing::Test {
WriteBatch batch;
for (int i = 0; i < n; i++) {
if (flush_every != 0 && i != 0 && i % flush_every == 0) {
ASSERT_OK(dbfull()->TEST_FlushMemTable());
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
ASSERT_OK(dbi->TEST_FlushMemTable());
}
// if ((i % 100) == 0) fprintf(stderr, "@ %d of %d\n", i, n);
Slice key = Key(i + start, &key_space);
@@ -431,14 +436,14 @@ TEST_F(CorruptionTest, NewFileErrorDuringWrite) {
TEST_F(CorruptionTest, TableFile) {
Build(100);
DBImpl* dbi = dbfull();
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
ASSERT_OK(dbi->TEST_FlushMemTable());
ASSERT_OK(dbi->TEST_CompactRange(0, nullptr, nullptr));
ASSERT_OK(dbi->TEST_CompactRange(1, nullptr, nullptr));
Corrupt(kTableFile, 100, 1);
Check(99, 99);
ASSERT_NOK(db_->VerifyChecksum());
ASSERT_NOK(dbi->VerifyChecksum());
}
TEST_F(CorruptionTest, VerifyChecksumReadahead) {
@@ -455,14 +460,14 @@ TEST_F(CorruptionTest, VerifyChecksumReadahead) {
Reopen(&options);
Build(10000);
DBImpl* dbi = dbfull();
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
ASSERT_OK(dbi->TEST_FlushMemTable());
ASSERT_OK(dbi->TEST_CompactRange(0, nullptr, nullptr));
ASSERT_OK(dbi->TEST_CompactRange(1, nullptr, nullptr));
senv.count_random_reads_ = true;
senv.random_read_counter_.Reset();
ASSERT_OK(db_->VerifyChecksum());
ASSERT_OK(dbi->VerifyChecksum());
// Make sure the counter is enabled.
ASSERT_GT(senv.random_read_counter_.Read(), 0);
@@ -475,7 +480,7 @@ TEST_F(CorruptionTest, VerifyChecksumReadahead) {
senv.random_read_bytes_counter_ = 0;
ReadOptions ro;
ro.readahead_size = size_t{32 * 1024};
ASSERT_OK(db_->VerifyChecksum(ro));
ASSERT_OK(dbi->VerifyChecksum(ro));
// The SST file is about 10MB. We set readahead size to 32KB.
// Give 0 to 20 reads for metadata blocks, and allow real read
// to range from 24KB to 48KB. The lower bound would be:
@@ -489,7 +494,8 @@ TEST_F(CorruptionTest, VerifyChecksumReadahead) {
// disabled).
options.allow_mmap_reads = true;
Reopen(&options);
ASSERT_OK(db_->VerifyChecksum(ro));
dbi = static_cast<DBImpl*>(db_);
ASSERT_OK(dbi->VerifyChecksum(ro));
CloseDb();
}
@@ -502,16 +508,18 @@ TEST_F(CorruptionTest, TableFileIndexData) {
Reopen(&options);
// build 2 tables, flush at 5000
Build(10000, 5000);
ASSERT_OK(dbfull()->TEST_FlushMemTable());
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
ASSERT_OK(dbi->TEST_FlushMemTable());
// corrupt an index block of an entire file
Corrupt(kTableFile, -2000, 500);
options.paranoid_checks = false;
Reopen(&options);
dbi = static_cast_with_check<DBImpl>(db_);
// one full file may be readable, since only one was corrupted
// the other file should be fully non-readable, since index was corrupted
Check(0, 5000, ReadOptions(true, true));
ASSERT_NOK(db_->VerifyChecksum());
ASSERT_NOK(dbi->VerifyChecksum());
// In paranoid mode, the db cannot be opened due to the corrupted file.
ASSERT_TRUE(TryReopen().IsCorruption());
@@ -519,7 +527,8 @@ TEST_F(CorruptionTest, TableFileIndexData) {
TEST_F(CorruptionTest, TableFileFooterMagic) {
Build(100);
ASSERT_OK(dbfull()->TEST_FlushMemTable());
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
ASSERT_OK(dbi->TEST_FlushMemTable());
Check(100, 100);
// Corrupt the whole footer
Corrupt(kTableFile, -100, 100);
@@ -534,7 +543,8 @@ TEST_F(CorruptionTest, TableFileFooterMagic) {
TEST_F(CorruptionTest, TableFileFooterNotMagic) {
Build(100);
ASSERT_OK(dbfull()->TEST_FlushMemTable());
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
ASSERT_OK(dbi->TEST_FlushMemTable());
Check(100, 100);
// Corrupt footer except magic number
Corrupt(kTableFile, -100, 92);
@@ -569,7 +579,8 @@ TEST_F(CorruptionTest, DBOpenWithWrongFileSize) {
for (auto* cfh : cfhs) {
delete cfh;
}
ASSERT_OK(dbfull()->TEST_FlushMemTable());
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
ASSERT_OK(dbi->TEST_FlushMemTable());
// ********************************************
// Corrupt the file by making the file bigger
@@ -590,8 +601,7 @@ TEST_F(CorruptionTest, DBOpenWithWrongFileSize) {
// true
options_.paranoid_checks = true;
std::vector<ColumnFamilyHandle*> cfhs;
Status s;
s = DB::Open(options_, dbname_, cf_descs, &cfhs, &db_);
auto s = DB::Open(options_, dbname_, cf_descs, &cfhs, &db_);
ASSERT_TRUE(s.IsCorruption());
ASSERT_TRUE(s.ToString().find("file size mismatch") != std::string::npos);
@@ -616,7 +626,8 @@ TEST_F(CorruptionTest, DBOpenWithWrongFileSize) {
TEST_F(CorruptionTest, TableFileWrongSize) {
Build(100);
ASSERT_OK(dbfull()->TEST_FlushMemTable());
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
ASSERT_OK(dbi->TEST_FlushMemTable());
Check(100, 100);
// ********************************************
@@ -699,11 +710,12 @@ TEST_F(CorruptionTest, SequenceNumberRecovery) {
TEST_F(CorruptionTest, CorruptedDescriptor) {
ASSERT_OK(db_->Put(WriteOptions(), "foo", "hello"));
ASSERT_OK(dbfull()->TEST_FlushMemTable());
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
ASSERT_OK(dbi->TEST_FlushMemTable());
CompactRangeOptions cro;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
ASSERT_OK(
db_->CompactRange(cro, db_->DefaultColumnFamily(), nullptr, nullptr));
dbi->CompactRange(cro, dbi->DefaultColumnFamily(), nullptr, nullptr));
Corrupt(kDescriptorFile, 0, 1000);
Status s = TryReopen();
@@ -722,7 +734,7 @@ TEST_F(CorruptionTest, CompactionInputError) {
options.env = env_.get();
Reopen(&options);
Build(10);
DBImpl* dbi = dbfull();
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
ASSERT_OK(dbi->TEST_FlushMemTable());
ASSERT_OK(dbi->TEST_CompactRange(0, nullptr, nullptr));
ASSERT_OK(dbi->TEST_CompactRange(1, nullptr, nullptr));
@@ -730,12 +742,12 @@ TEST_F(CorruptionTest, CompactionInputError) {
Corrupt(kTableFile, 100, 1);
Check(9, 9);
ASSERT_NOK(db_->VerifyChecksum());
ASSERT_NOK(dbi->VerifyChecksum());
// Force compactions by writing lots of values
Build(10000);
Check(10000, 10000);
ASSERT_NOK(db_->VerifyChecksum());
ASSERT_NOK(dbi->VerifyChecksum());
}
TEST_F(CorruptionTest, CompactionInputErrorParanoid) {
@@ -746,14 +758,14 @@ TEST_F(CorruptionTest, CompactionInputErrorParanoid) {
options.write_buffer_size = 131072;
options.max_write_buffer_number = 2;
Reopen(&options);
DBImpl* dbi = dbfull();
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
// Fill levels >= 1
for (int level = 1; level < db_->NumberLevels(); level++) {
ASSERT_OK(db_->Put(WriteOptions(), "", "begin"));
ASSERT_OK(db_->Put(WriteOptions(), "~", "end"));
for (int level = 1; level < dbi->NumberLevels(); level++) {
ASSERT_OK(dbi->Put(WriteOptions(), "", "begin"));
ASSERT_OK(dbi->Put(WriteOptions(), "~", "end"));
ASSERT_OK(dbi->TEST_FlushMemTable());
for (int comp_level = 0; comp_level < db_->NumberLevels() - level;
for (int comp_level = 0; comp_level < dbi->NumberLevels() - level;
++comp_level) {
ASSERT_OK(dbi->TEST_CompactRange(comp_level, nullptr, nullptr));
}
@@ -761,7 +773,7 @@ TEST_F(CorruptionTest, CompactionInputErrorParanoid) {
Reopen(&options);
dbi = dbfull();
dbi = static_cast_with_check<DBImpl>(db_);
Build(10);
ASSERT_OK(dbi->TEST_FlushMemTable());
ASSERT_OK(dbi->TEST_WaitForCompact());
@@ -769,7 +781,7 @@ TEST_F(CorruptionTest, CompactionInputErrorParanoid) {
CorruptTableFileAtLevel(0, 100, 1);
Check(9, 9);
ASSERT_NOK(db_->VerifyChecksum());
ASSERT_NOK(dbi->VerifyChecksum());
// Write must eventually fail because of corrupted table
Status s;
@@ -788,16 +800,17 @@ TEST_F(CorruptionTest, CompactionInputErrorParanoid) {
TEST_F(CorruptionTest, UnrelatedKeys) {
Build(10);
ASSERT_OK(dbfull()->TEST_FlushMemTable());
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
ASSERT_OK(dbi->TEST_FlushMemTable());
Corrupt(kTableFile, 100, 1);
ASSERT_NOK(db_->VerifyChecksum());
ASSERT_NOK(dbi->VerifyChecksum());
std::string tmp1, tmp2;
ASSERT_OK(db_->Put(WriteOptions(), Key(1000, &tmp1), Value(1000, &tmp2)));
std::string v;
ASSERT_OK(db_->Get(ReadOptions(), Key(1000, &tmp1), &v));
ASSERT_EQ(Value(1000, &tmp2).ToString(), v);
ASSERT_OK(dbfull()->TEST_FlushMemTable());
ASSERT_OK(dbi->TEST_FlushMemTable());
ASSERT_OK(db_->Get(ReadOptions(), Key(1000, &tmp1), &v));
ASSERT_EQ(Value(1000, &tmp2).ToString(), v);
}
@@ -844,12 +857,14 @@ TEST_F(CorruptionTest, FileSystemStateCorrupted) {
Reopen(&options);
Build(10);
ASSERT_OK(db_->Flush(FlushOptions()));
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
std::vector<LiveFileMetaData> metadata;
db_->GetLiveFilesMetaData(&metadata);
dbi->GetLiveFilesMetaData(&metadata);
ASSERT_GT(metadata.size(), 0);
std::string filename = dbname_ + metadata[0].name;
db_.reset();
delete db_;
db_ = nullptr;
if (iter == 0) { // corrupt file size
std::unique_ptr<WritableFile> file;
@@ -881,7 +896,8 @@ TEST_F(CorruptionTest, ParanoidFileChecksOnFlush) {
options.create_if_missing = true;
Status s;
for (const auto& mode : corruption_modes) {
db_.reset();
delete db_;
db_ = nullptr;
s = DestroyDB(dbname_, options);
ASSERT_OK(s);
std::shared_ptr<mock::MockTableFactory> mock =
@@ -908,7 +924,8 @@ TEST_F(CorruptionTest, ParanoidFileChecksOnCompact) {
options.create_if_missing = true;
Status s;
for (const auto& mode : corruption_modes) {
db_.reset();
delete db_;
db_ = nullptr;
s = DestroyDB(dbname_, options);
ASSERT_OK(s);
std::shared_ptr<mock::MockTableFactory> mock =
@@ -917,11 +934,12 @@ TEST_F(CorruptionTest, ParanoidFileChecksOnCompact) {
ASSERT_OK(DB::Open(options, dbname_, &db_));
assert(db_ != nullptr); // suppress false clang-analyze report
Build(100, 2);
ASSERT_OK(dbfull()->TEST_FlushMemTable());
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
ASSERT_OK(dbi->TEST_FlushMemTable());
mock->SetCorruptionMode(mode);
CompactRangeOptions cro;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
s = db_->CompactRange(cro, db_->DefaultColumnFamily(), nullptr, nullptr);
s = dbi->CompactRange(cro, dbi->DefaultColumnFamily(), nullptr, nullptr);
if (mode == mock::MockTableFactory::kCorruptNone) {
ASSERT_OK(s);
} else {
@@ -937,7 +955,8 @@ TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRangeFirst) {
options.paranoid_file_checks = true;
options.create_if_missing = true;
for (bool do_flush : {true, false}) {
db_.reset();
delete db_;
db_ = nullptr;
ASSERT_OK(DestroyDB(dbname_, options));
ASSERT_OK(DB::Open(options, dbname_, &db_));
std::string start, end;
@@ -954,11 +973,12 @@ TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRangeFirst) {
if (do_flush) {
ASSERT_OK(db_->Flush(FlushOptions()));
} else {
ASSERT_OK(dbfull()->TEST_FlushMemTable());
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
ASSERT_OK(dbi->TEST_FlushMemTable());
CompactRangeOptions cro;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
ASSERT_OK(
db_->CompactRange(cro, db_->DefaultColumnFamily(), nullptr, nullptr));
dbi->CompactRange(cro, dbi->DefaultColumnFamily(), nullptr, nullptr));
}
db_->ReleaseSnapshot(snap);
}
@@ -971,7 +991,8 @@ TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRange) {
options.paranoid_file_checks = true;
options.create_if_missing = true;
for (bool do_flush : {true, false}) {
db_.reset();
delete db_;
db_ = nullptr;
ASSERT_OK(DestroyDB(dbname_, options));
ASSERT_OK(DB::Open(options, dbname_, &db_));
assert(db_ != nullptr); // suppress false clang-analyze report
@@ -991,11 +1012,12 @@ TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRange) {
if (do_flush) {
ASSERT_OK(db_->Flush(FlushOptions()));
} else {
ASSERT_OK(dbfull()->TEST_FlushMemTable());
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
ASSERT_OK(dbi->TEST_FlushMemTable());
CompactRangeOptions cro;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
ASSERT_OK(
db_->CompactRange(cro, db_->DefaultColumnFamily(), nullptr, nullptr));
dbi->CompactRange(cro, dbi->DefaultColumnFamily(), nullptr, nullptr));
}
db_->ReleaseSnapshot(snap);
}
@@ -1008,7 +1030,8 @@ TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRangeLast) {
options.paranoid_file_checks = true;
options.create_if_missing = true;
for (bool do_flush : {true, false}) {
db_.reset();
delete db_;
db_ = nullptr;
ASSERT_OK(DestroyDB(dbname_, options));
ASSERT_OK(DB::Open(options, dbname_, &db_));
assert(db_ != nullptr); // suppress false clang-analyze report
@@ -1025,11 +1048,12 @@ TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRangeLast) {
if (do_flush) {
ASSERT_OK(db_->Flush(FlushOptions()));
} else {
ASSERT_OK(dbfull()->TEST_FlushMemTable());
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
ASSERT_OK(dbi->TEST_FlushMemTable());
CompactRangeOptions cro;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
ASSERT_OK(
db_->CompactRange(cro, db_->DefaultColumnFamily(), nullptr, nullptr));
dbi->CompactRange(cro, dbi->DefaultColumnFamily(), nullptr, nullptr));
}
db_->ReleaseSnapshot(snap);
}
@@ -1042,7 +1066,8 @@ TEST_F(CorruptionTest, LogCorruptionErrorsInCompactionIterator) {
options.create_if_missing = true;
options.allow_data_in_errors = true;
auto mode = mock::MockTableFactory::kCorruptKey;
db_.reset();
delete db_;
db_ = nullptr;
ASSERT_OK(DestroyDB(dbname_, options));
std::shared_ptr<mock::MockTableFactory> mock =
@@ -1054,11 +1079,12 @@ TEST_F(CorruptionTest, LogCorruptionErrorsInCompactionIterator) {
assert(db_ != nullptr); // suppress false clang-analyze report
Build(100, 2);
ASSERT_OK(dbfull()->TEST_FlushMemTable());
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
ASSERT_OK(dbi->TEST_FlushMemTable());
CompactRangeOptions cro;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
Status s =
db_->CompactRange(cro, db_->DefaultColumnFamily(), nullptr, nullptr);
dbi->CompactRange(cro, dbi->DefaultColumnFamily(), nullptr, nullptr);
ASSERT_NOK(s);
ASSERT_TRUE(s.IsCorruption());
}
@@ -1069,7 +1095,8 @@ TEST_F(CorruptionTest, CompactionKeyOrderCheck) {
options.env = env_.get();
options.paranoid_file_checks = false;
options.create_if_missing = true;
db_.reset();
delete db_;
db_ = nullptr;
ASSERT_OK(DestroyDB(dbname_, options));
std::shared_ptr<mock::MockTableFactory> mock =
std::make_shared<mock::MockTableFactory>();
@@ -1078,13 +1105,14 @@ TEST_F(CorruptionTest, CompactionKeyOrderCheck) {
assert(db_ != nullptr); // suppress false clang-analyze report
mock->SetCorruptionMode(mock::MockTableFactory::kCorruptReorderKey);
Build(100, 2);
ASSERT_OK(dbfull()->TEST_FlushMemTable());
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
ASSERT_OK(dbi->TEST_FlushMemTable());
mock->SetCorruptionMode(mock::MockTableFactory::kCorruptNone);
CompactRangeOptions cro;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
ASSERT_NOK(
db_->CompactRange(cro, db_->DefaultColumnFamily(), nullptr, nullptr));
dbi->CompactRange(cro, dbi->DefaultColumnFamily(), nullptr, nullptr));
}
TEST_F(CorruptionTest, FlushKeyOrderCheck) {
@@ -1111,7 +1139,7 @@ TEST_F(CorruptionTest, FlushKeyOrderCheck) {
}
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
Status s = dbfull()->TEST_FlushMemTable();
Status s = static_cast_with_check<DBImpl>(db_)->TEST_FlushMemTable();
ASSERT_NOK(s);
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
@@ -1235,7 +1263,7 @@ TEST_P(CrashDuringRecoveryWithCorruptionTest, CrashDuringRecovery) {
// while other don't.
{
ASSERT_OK(DB::Open(options, dbname_, cf_descs, &handles, &db_));
auto* dbimpl = dbfull();
auto* dbimpl = static_cast_with_check<DBImpl>(db_);
assert(dbimpl);
// Write one key to test_cf.
+12 -10
View File
@@ -21,18 +21,18 @@ class CuckooTableDBTest : public testing::Test {
private:
std::string dbname_;
Env* env_;
std::unique_ptr<DB> db_;
DB* db_;
public:
CuckooTableDBTest() : env_(Env::Default()) {
dbname_ = test::PerThreadDBPath("cuckoo_table_db_test");
EXPECT_OK(DestroyDB(dbname_, Options()));
db_.reset();
db_ = nullptr;
Reopen();
}
~CuckooTableDBTest() override {
db_.reset();
delete db_;
EXPECT_OK(DestroyDB(dbname_, Options()));
}
@@ -47,11 +47,12 @@ class CuckooTableDBTest : public testing::Test {
return options;
}
DBImpl* dbfull() { return static_cast_with_check<DBImpl>(db_.get()); }
DBImpl* dbfull() { return static_cast_with_check<DBImpl>(db_); }
// The following util methods are copied from plain_table_db_test.
void Reopen(Options* options = nullptr) {
db_.reset();
delete db_;
db_ = nullptr;
Options opts;
if (options != nullptr) {
opts = *options;
@@ -65,7 +66,8 @@ class CuckooTableDBTest : public testing::Test {
void DestroyAndReopen(Options* options) {
assert(options);
ASSERT_OK(db_->Close());
db_.reset();
delete db_;
db_ = nullptr;
ASSERT_OK(DestroyDB(dbname_, *options));
Reopen(options);
}
@@ -128,7 +130,7 @@ TEST_F(CuckooTableDBTest, Flush) {
ASSERT_OK(dbfull()->TEST_FlushMemTable());
TablePropertiesCollection ptc;
ASSERT_OK(dbfull()->GetPropertiesOfAllTables(&ptc));
ASSERT_OK(static_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc));
VerifySstUniqueIds(ptc);
ASSERT_EQ(1U, ptc.size());
ASSERT_EQ(3U, ptc.begin()->second->num_entries);
@@ -145,7 +147,7 @@ TEST_F(CuckooTableDBTest, Flush) {
ASSERT_OK(Put("key6", "v6"));
ASSERT_OK(dbfull()->TEST_FlushMemTable());
ASSERT_OK(dbfull()->GetPropertiesOfAllTables(&ptc));
ASSERT_OK(static_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc));
VerifySstUniqueIds(ptc);
ASSERT_EQ(2U, ptc.size());
auto row = ptc.begin();
@@ -163,7 +165,7 @@ TEST_F(CuckooTableDBTest, Flush) {
ASSERT_OK(Delete("key5"));
ASSERT_OK(Delete("key4"));
ASSERT_OK(dbfull()->TEST_FlushMemTable());
ASSERT_OK(dbfull()->GetPropertiesOfAllTables(&ptc));
ASSERT_OK(static_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc));
VerifySstUniqueIds(ptc);
ASSERT_EQ(3U, ptc.size());
row = ptc.begin();
@@ -188,7 +190,7 @@ TEST_F(CuckooTableDBTest, FlushWithDuplicateKeys) {
ASSERT_OK(dbfull()->TEST_FlushMemTable());
TablePropertiesCollection ptc;
ASSERT_OK(dbfull()->GetPropertiesOfAllTables(&ptc));
ASSERT_OK(static_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc));
VerifySstUniqueIds(ptc);
ASSERT_EQ(1U, ptc.size());
ASSERT_EQ(2U, ptc.begin()->second->num_entries);
+40 -33
View File
@@ -91,15 +91,17 @@ class DBBasicTest : public DBTestBase {
TEST_F(DBBasicTest, OpenWhenOpen) {
Options options = CurrentOptions();
options.env = env_;
std::unique_ptr<DB> db2;
DB* db2 = nullptr;
Status s = DB::Open(options, dbname_, &db2);
ASSERT_NOK(s) << [&db2]() {
db2.reset();
ASSERT_NOK(s) << [db2]() {
delete db2;
return "db2 open: ok";
}();
ASSERT_EQ(Status::Code::kIOError, s.code());
ASSERT_EQ(Status::SubCode::kNone, s.subcode());
ASSERT_TRUE(strstr(s.getState(), "lock ") != nullptr);
delete db2;
}
TEST_F(DBBasicTest, EnableDirectIOWithZeroBuf) {
@@ -584,14 +586,14 @@ TEST_F(DBBasicTest, GetSnapshot) {
TEST_F(DBBasicTest, CheckLock) {
do {
std::unique_ptr<DB> localdb;
DB* localdb = nullptr;
Options options = CurrentOptions();
ASSERT_OK(TryReopen(options));
// second open should fail
Status s = DB::Open(options, dbname_, &localdb);
ASSERT_NOK(s) << [&localdb]() {
localdb.reset();
ASSERT_NOK(s) << [localdb]() {
delete localdb;
return "localdb open: ok";
}();
#ifdef OS_LINUX
@@ -860,7 +862,7 @@ TEST_F(DBBasicTest, Snapshot) {
ASSERT_OK(Put(1, "foo", "1v3"));
{
ManagedSnapshot s3(db_.get());
ManagedSnapshot s3(db_);
ASSERT_EQ(3U, GetNumSnapshots());
ASSERT_EQ(time_snap1, GetTimeOldestSnapshots());
ASSERT_EQ(GetSequenceOldestSnapshots(), s1->GetSequenceNumber());
@@ -983,7 +985,7 @@ TEST_F(DBBasicTest, DBOpen_Options) {
Destroy(options);
// Does not exist, and create_if_missing == false: error
std::unique_ptr<DB> db;
DB* db = nullptr;
options.create_if_missing = false;
Status s = DB::Open(options, dbname_, &db);
ASSERT_TRUE(strstr(s.ToString().c_str(), "does not exist") != nullptr);
@@ -995,7 +997,8 @@ TEST_F(DBBasicTest, DBOpen_Options) {
ASSERT_OK(s);
ASSERT_TRUE(db != nullptr);
db.reset();
delete db;
db = nullptr;
// Does exist, and error_if_exists == true: error
options.create_if_missing = false;
@@ -1011,7 +1014,8 @@ TEST_F(DBBasicTest, DBOpen_Options) {
ASSERT_OK(s);
ASSERT_TRUE(db != nullptr);
db.reset();
delete db;
db = nullptr;
}
TEST_F(DBBasicTest, CompactOnFlush) {
@@ -1316,7 +1320,7 @@ TEST_F(DBBasicTest, DBClose) {
std::string dbname = test::PerThreadDBPath("db_close_test");
ASSERT_OK(DestroyDB(dbname, options));
std::unique_ptr<DB> db;
DB* db = nullptr;
TestEnv* env = new TestEnv(env_);
std::unique_ptr<TestEnv> local_env_guard(env);
options.create_if_missing = true;
@@ -1329,14 +1333,14 @@ TEST_F(DBBasicTest, DBClose) {
ASSERT_EQ(env->GetCloseCount(), 1);
ASSERT_EQ(s, Status::IOError());
db.reset();
delete db;
ASSERT_EQ(env->GetCloseCount(), 1);
// Do not call DB::Close() and ensure our logger Close() still gets called
s = DB::Open(options, dbname, &db);
ASSERT_OK(s);
ASSERT_TRUE(db != nullptr);
db.reset();
delete db;
ASSERT_EQ(env->GetCloseCount(), 2);
// close by WaitForCompact() with close_db option
@@ -1351,7 +1355,7 @@ TEST_F(DBBasicTest, DBClose) {
// see TestLogger::CloseHelper()
ASSERT_EQ(s, Status::IOError());
db.reset();
delete db;
ASSERT_EQ(env->GetCloseCount(), 3);
// Provide our own logger and ensure DB::Close() does not close it
@@ -1362,7 +1366,7 @@ TEST_F(DBBasicTest, DBClose) {
s = db->Close();
ASSERT_EQ(s, Status::OK());
db.reset();
delete db;
ASSERT_EQ(env->GetCloseCount(), 3);
options.info_log.reset();
ASSERT_EQ(env->GetCloseCount(), 4);
@@ -1380,7 +1384,7 @@ TEST_F(DBBasicTest, DBCloseAllDirectoryFDs) {
ASSERT_OK(DestroyDB(dbname, options));
std::unique_ptr<DB> db;
DB* db = nullptr;
std::unique_ptr<Env> env = NewCompositeEnv(
std::make_shared<CountedFileSystem>(FileSystem::Default()));
options.create_if_missing = true;
@@ -1398,7 +1402,7 @@ TEST_F(DBBasicTest, DBCloseAllDirectoryFDs) {
ASSERT_EQ(counted_fs->counters()->dir_opens,
counted_fs->counters()->dir_closes);
ASSERT_OK(s);
db.reset();
delete db;
}
TEST_F(DBBasicTest, DBCloseFlushError) {
@@ -1460,7 +1464,7 @@ TEST_P(DBMultiGetTestWithParam, MultiGetMultiCF) {
}
int get_sv_count = 0;
ROCKSDB_NAMESPACE::DBImpl* db = dbfull();
ROCKSDB_NAMESPACE::DBImpl* db = static_cast_with_check<DBImpl>(db_);
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"DBImpl::MultiCFSnapshot::AfterRefSV", [&](void* /*arg*/) {
if (++get_sv_count == 2) {
@@ -1532,9 +1536,10 @@ TEST_P(DBMultiGetTestWithParam, MultiGetMultiCF) {
ASSERT_EQ(values[2], std::get<2>(cf_kv_vec[1]) + "_2");
for (int cf = 0; cf < 8; ++cf) {
auto* cfd = static_cast_with_check<ColumnFamilyHandleImpl>(
dbfull()->GetColumnFamilyHandle(cf))
->cfd();
auto* cfd =
static_cast_with_check<ColumnFamilyHandleImpl>(
static_cast_with_check<DBImpl>(db_)->GetColumnFamilyHandle(cf))
->cfd();
ASSERT_NE(cfd->TEST_GetLocalSV()->Get(), SuperVersion::kSVInUse);
ASSERT_NE(cfd->TEST_GetLocalSV()->Get(), SuperVersion::kSVObsolete);
}
@@ -1620,9 +1625,10 @@ TEST_P(DBMultiGetTestWithParam, MultiGetMultiCFMutex) {
"cf" + std::to_string(j) + "_val" + std::to_string(retries));
}
for (int i = 0; i < 8; ++i) {
auto* cfd = static_cast_with_check<ColumnFamilyHandleImpl>(
dbfull()->GetColumnFamilyHandle(i))
->cfd();
auto* cfd =
static_cast_with_check<ColumnFamilyHandleImpl>(
static_cast_with_check<DBImpl>(db_)->GetColumnFamilyHandle(i))
->cfd();
ASSERT_NE(cfd->TEST_GetLocalSV()->Get(), SuperVersion::kSVInUse);
}
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
@@ -1646,7 +1652,7 @@ TEST_P(DBMultiGetTestWithParam, MultiGetMultiCFSnapshot) {
}
int get_sv_count = 0;
ROCKSDB_NAMESPACE::DBImpl* db = dbfull();
ROCKSDB_NAMESPACE::DBImpl* db = static_cast_with_check<DBImpl>(db_);
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"DBImpl::MultiCFSnapshot::AfterRefSV", [&](void* /*arg*/) {
if (++get_sv_count == 2) {
@@ -1687,9 +1693,10 @@ TEST_P(DBMultiGetTestWithParam, MultiGetMultiCFSnapshot) {
ASSERT_EQ(values[j], "cf" + std::to_string(j) + "_val");
}
for (int i = 0; i < 8; ++i) {
auto* cfd = static_cast_with_check<ColumnFamilyHandleImpl>(
dbfull()->GetColumnFamilyHandle(i))
->cfd();
auto* cfd =
static_cast_with_check<ColumnFamilyHandleImpl>(
static_cast_with_check<DBImpl>(db_)->GetColumnFamilyHandle(i))
->cfd();
ASSERT_NE(cfd->TEST_GetLocalSV()->Get(), SuperVersion::kSVInUse);
}
}
@@ -3294,8 +3301,8 @@ TEST_F(DBBasicTest, GetAllKeyVersions) {
ASSERT_OK(Delete(std::to_string(i)));
}
std::vector<KeyVersion> key_versions;
ASSERT_OK(GetAllKeyVersions(
db_.get(), {}, {}, std::numeric_limits<size_t>::max(), &key_versions));
ASSERT_OK(GetAllKeyVersions(db_, {}, {}, std::numeric_limits<size_t>::max(),
&key_versions));
ASSERT_EQ(kNumInserts + kNumDeletes + kNumUpdates, key_versions.size());
for (size_t i = 0; i < kNumInserts + kNumDeletes + kNumUpdates; i++) {
if (i % 3 == 0) {
@@ -3304,7 +3311,7 @@ TEST_F(DBBasicTest, GetAllKeyVersions) {
ASSERT_EQ(key_versions[i].GetTypeName(), "TypeValue");
}
}
ASSERT_OK(GetAllKeyVersions(db_.get(), handles_[0], {}, {},
ASSERT_OK(GetAllKeyVersions(db_, handles_[0], {}, {},
std::numeric_limits<size_t>::max(),
&key_versions));
ASSERT_EQ(kNumInserts + kNumDeletes + kNumUpdates, key_versions.size());
@@ -3319,14 +3326,14 @@ TEST_F(DBBasicTest, GetAllKeyVersions) {
for (size_t i = 0; i + 1 != kNumDeletes; ++i) {
ASSERT_OK(Delete(1, std::to_string(i)));
}
ASSERT_OK(GetAllKeyVersions(db_.get(), handles_[1], {}, {},
ASSERT_OK(GetAllKeyVersions(db_, handles_[1], {}, {},
std::numeric_limits<size_t>::max(),
&key_versions));
ASSERT_EQ(kNumInserts + kNumDeletes + kNumUpdates - 3, key_versions.size());
// Change from historical behavior: empty key is now interpreted literally as
// a legal key (rather than as a "not present" key)
ASSERT_OK(GetAllKeyVersions(db_.get(), handles_[1], Slice(), Slice(),
ASSERT_OK(GetAllKeyVersions(db_, handles_[1], Slice(), Slice(),
std::numeric_limits<size_t>::max(),
&key_versions));
ASSERT_EQ(key_versions.size(), 0);
+2 -2
View File
@@ -1661,7 +1661,7 @@ TEST_P(DBBlockCacheKeyTest, StableCacheKeys) {
std::string export_files_dir = dbname_ + "/exported";
ExportImportFilesMetaData* metadata_ptr_ = nullptr;
Checkpoint* checkpoint;
ASSERT_OK(Checkpoint::Create(db_.get(), &checkpoint));
ASSERT_OK(Checkpoint::Create(db_, &checkpoint));
ASSERT_OK(checkpoint->ExportColumnFamily(handles_[1], export_files_dir,
&metadata_ptr_));
ASSERT_NE(metadata_ptr_, nullptr);
@@ -1698,7 +1698,7 @@ TEST_P(DBBlockCacheKeyTest, StableCacheKeys) {
// StableCacheKeyTestFS, Checkpoint will resort to full copy not hard link.
// (Checkpoint not available in LITE mode to test this.)
auto db_copy_name = dbname_ + "-copy";
ASSERT_OK(Checkpoint::Create(db_.get(), &checkpoint));
ASSERT_OK(Checkpoint::Create(db_, &checkpoint));
ASSERT_OK(checkpoint->CreateCheckpoint(db_copy_name));
delete checkpoint;
+15 -5
View File
@@ -137,6 +137,11 @@ class SliceTransformLimitedDomainGeneric : public SliceTransform {
// prefix will be x????
return src.size() >= 5;
}
bool InRange(const Slice& dst) const override {
// prefix will be x????
return dst.size() == 5;
}
};
// KeyMayExist can lead to a few false positives, but not false negatives.
@@ -917,14 +922,14 @@ INSTANTIATE_TEST_CASE_P(
::testing::Values(
std::make_tuple(kAutoBloom,
FilterPartitioning::kCoupledPartitionedFilter,
kLatestBbtFormatVersion),
kLatestFormatVersion),
std::make_tuple(kAutoBloom,
FilterPartitioning::kDecoupledPartitionedFilter,
kLatestBbtFormatVersion),
kLatestFormatVersion),
std::make_tuple(kAutoBloom, FilterPartitioning::kUnpartitionedFilter,
kLatestBbtFormatVersion),
kLatestFormatVersion),
std::make_tuple(kAutoRibbon, FilterPartitioning::kUnpartitionedFilter,
kLatestBbtFormatVersion)));
kLatestFormatVersion)));
#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
TEST_F(DBBloomFilterTest, BloomFilterRate) {
@@ -2072,6 +2077,11 @@ class SliceTransformLimitedDomain : public SliceTransform {
// prefix will be x????
return src.size() >= 5 && src[0] == 'x';
}
bool InRange(const Slice& dst) const override {
// prefix will be x????
return dst.size() == 5 && dst[0] == 'x';
}
};
TEST_F(DBBloomFilterTest, PrefixExtractorWithFilter1) {
@@ -4135,7 +4145,7 @@ TEST_F(DBBloomFilterTest, SstQueryFilter) {
using Keys = std::vector<std::string>;
auto RangeQuery =
[factory, db = db_.get()](
[factory, db = db_](
std::string lb, std::string ub,
std::shared_ptr<SstQueryFilterConfigsManager::Factory> alt_factory =
nullptr) {
-993
View File
@@ -1,993 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#include <atomic>
#include <set>
#include <thread>
#include <unordered_map>
#include "db/compaction/compaction_job.h"
#include "db/db_impl/db_impl_secondary.h"
#include "db/db_test_util.h"
#include "options/options_helper.h"
#include "port/stack_trace.h"
#include "rocksdb/db.h"
#include "rocksdb/sst_file_writer.h"
#include "test_util/sync_point.h"
#include "test_util/testharness.h"
namespace ROCKSDB_NAMESPACE {
// Helper class to manage abort synchronization in tests.
//
// Compaction abort could happen at various stage of compaction.
// To test this, we need to trigger abort at different stage. This requires
// precise control on the timing of abort API invocation. To achieve this in a
// consistent way across various tests, we invoke AbortAllCompactions() within
// the sync point callback, that is added at various stages of compaction.
// However as the abort API is a blocking call, calling it within the sync point
// callback on the compaction thread would cause deadlock. This test helper
// class is designed to solve this challenge.
//
// 1. Abort must happen from a different thread:
// AbortAllCompactions() is typically called from the compaction thread
// via a sync point callback, so that we could precisely control the time of
// API invocation to simulate abort at different stage of compaction.
// However, we can't block the compaction thread waiting for the abort to
// complete - the compaction needs to continue executing to actually check
// the abort flag and exit. So we spawn a separate thread to call
// AbortAllCompactions().
//
// 2. We need to know when abort completes:
// After compaction returns (with aborted status), we often need to:
// - Verify state (e.g., no output files created)
// - Call ResumeAllCompactions()
// - Run compaction again to verify it succeeds
// We must wait for the abort thread to finish before proceeding, otherwise
// we might call Resume before Abort completes, causing race conditions.
//
// 3. Sync point callbacks may fire multiple times:
// With multiple subcompactions, a callback like
// "CompactionJob::ProcessKeyValueCompaction:Start" fires once per
// subcompaction. We only want to trigger abort once, so we use
// abort_triggered_ as a guard.
//
// 4. Tests may need multiple abort cycles:
// Some tests (e.g., MultipleAbortResumeSequence) do abort->resume->abort
// multiple times. The class supports this by auto-resetting when a
// previous abort has completed.
class AbortSynchronizer {
public:
AbortSynchronizer() : abort_cv_(&abort_mutex_) {}
~AbortSynchronizer() {
// Join the thread if it was started - ensures clean shutdown
if (abort_thread_.joinable()) {
abort_thread_.join();
}
}
// Non-copyable, non-movable due to thread member
AbortSynchronizer(const AbortSynchronizer&) = delete;
AbortSynchronizer& operator=(const AbortSynchronizer&) = delete;
// Trigger abort from a separate thread.
// - Safe to call multiple times; only first call in each cycle spawns thread
// - If a previous abort has completed, automatically resets state first
// - The spawned thread calls AbortAllCompactions() and signals completion
void TriggerAbort(DBImpl* db) {
// If previous abort completed, reset state to allow new abort
if (abort_triggered_.load() && abort_completed_.load()) {
Reset();
}
if (!abort_triggered_.exchange(true)) {
abort_thread_ = std::thread([this, db]() {
db->AbortAllCompactions();
SignalAbortCompleted();
});
}
}
// Wait for the abort thread to complete.
// Call this AFTER compaction returns to ensure the abort thread has finished
// before proceeding with Resume or other operations.
void WaitForAbortCompletion() {
MutexLock l(&abort_mutex_);
while (!abort_completed_.load()) {
abort_cv_.Wait();
}
}
// Reset state for reuse. Joins any previous thread first.
// Called automatically by TriggerAbort() if previous abort completed,
// but can also be called explicitly for clarity.
void Reset() {
if (abort_thread_.joinable()) {
abort_thread_.join();
}
abort_triggered_.store(false);
abort_completed_.store(false);
}
bool IsAbortTriggered() const { return abort_triggered_.load(); }
private:
void SignalAbortCompleted() {
MutexLock l(&abort_mutex_);
abort_completed_.store(true);
abort_cv_.SignalAll();
}
std::atomic<bool> abort_triggered_{false}; // Guards against multiple spawns
std::atomic<bool> abort_completed_{false}; // Signals thread completion
port::Mutex abort_mutex_;
port::CondVar abort_cv_;
std::thread abort_thread_; // The thread that calls AbortAllCompactions()
};
// Helper to clean up SyncPoint state after tests
inline void CleanupSyncPoints() {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
}
// Helper class that combines AbortSynchronizer with sync point setup for
// deterministic abort triggering. This adds sync point coordination on top
// of AbortSynchronizer:
//
// This is useful when you need deterministic timing - the callback won't
// return until AbortAllCompactions() has actually set the abort flag,
// guaranteeing the compaction will see it on the next check.
class SyncPointAbortHelper {
public:
explicit SyncPointAbortHelper(const std::string& trigger_point)
: trigger_point_(trigger_point) {}
// Set up sync points and callbacks. Call this before starting compaction.
void Setup(DBImpl* db_impl) {
db_impl_ = db_impl;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({
{"DBImpl::AbortAllCompactions:FlagSet", kWaitPointName},
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
trigger_point_, [this](void* /*arg*/) {
// Use AbortSynchronizer to handle the abort in a separate thread
abort_sync_.TriggerAbort(db_impl_);
// Wait for abort flag to be set via sync point dependency
// This ensures deterministic timing - compaction will see the flag
TEST_SYNC_POINT_CALLBACK(kWaitPointName, nullptr);
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
}
// Wait for the abort to complete. Call this after compaction returns.
void WaitForAbortCompletion() { abort_sync_.WaitForAbortCompletion(); }
// Clean up sync points and wait for abort completion in one call
void CleanupAndWait() {
CleanupSyncPoints();
WaitForAbortCompletion();
}
private:
static constexpr const char* kWaitPointName =
"SyncPointAbortHelper::WaitForAbort";
std::string trigger_point_;
DBImpl* db_impl_{nullptr};
AbortSynchronizer abort_sync_;
};
class DBCompactionAbortTest : public DBTestBase {
public:
DBCompactionAbortTest()
: DBTestBase("db_compaction_abort_test", /*env_do_fsync=*/false) {}
protected:
// Map to track the latest value of each key for verification
std::unordered_map<std::string, std::string> expected_values_;
// Statistics object for verifying compaction metrics
std::shared_ptr<Statistics> stats_;
// Get current options with statistics enabled
Options GetOptionsWithStats() {
Options options = CurrentOptions();
stats_ = CreateDBStatistics();
options.statistics = stats_;
return options;
}
// Populate database with test data.
// If overlapping=true, uses the same key range (0 to keys_per_file-1) in each
// file to ensure compaction has work to do.
// If overlapping=false, uses non-overlapping keys across files.
void PopulateData(int num_files, int keys_per_file, int value_size,
bool overlapping = true, int seed = 301) {
Random rnd(seed);
for (int i = 0; i < num_files; ++i) {
for (int j = 0; j < keys_per_file; ++j) {
int key_index = overlapping ? j : (j + i * keys_per_file);
std::string key = Key(key_index);
std::string value = rnd.RandomString(value_size);
ASSERT_OK(Put(key, value));
expected_values_[key] = value;
}
ASSERT_OK(Flush());
}
}
// Verify data integrity by reading all keys and comparing with expected
// values
void VerifyDataIntegrity(int num_keys, int start_key = 0) {
std::string val;
for (int j = start_key; j < start_key + num_keys; ++j) {
std::string key = Key(j);
ASSERT_OK(dbfull()->Get(ReadOptions(), key, &val));
auto it = expected_values_.find(key);
if (it != expected_values_.end()) {
ASSERT_EQ(it->second, val) << "Value mismatch for key: " << key;
}
}
}
// Clear expected values (useful when reopening DB or between tests)
void ClearExpectedValues() { expected_values_.clear(); }
// Run the common abort test pattern with SyncPointAbortHelper:
// 1. Set up sync point abort helper
// 2. Run compaction and verify it's aborted
// 3. Verify COMPACTION_ABORTED stat increased (if stats enabled)
// 4. Clean up, resume, and verify compaction succeeds
// 5. Verify COMPACT_WRITE_BYTES increased (if stats enabled)
void RunSyncPointAbortTest(const std::string& trigger_point,
CompactRangeOptions cro = CompactRangeOptions()) {
// Capture stats and file counts before abort
uint64_t aborted_before = 0;
uint64_t write_bytes_before = 0;
if (stats_) {
aborted_before = stats_->getTickerCount(COMPACTION_ABORTED);
write_bytes_before = stats_->getTickerCount(COMPACT_WRITE_BYTES);
}
SyncPointAbortHelper helper(trigger_point);
helper.Setup(dbfull());
Status s = dbfull()->CompactRange(cro, nullptr, nullptr);
ASSERT_TRUE(s.IsIncomplete());
ASSERT_TRUE(s.IsCompactionAborted());
// Verify abort was counted
if (stats_) {
uint64_t aborted_after = stats_->getTickerCount(COMPACTION_ABORTED);
ASSERT_GT(aborted_after, aborted_before)
<< "COMPACTION_ABORTED stat should increase after abort";
}
helper.CleanupAndWait();
dbfull()->ResumeAllCompactions();
ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr));
// Verify compaction completed and wrote bytes
if (stats_) {
uint64_t write_bytes_after = stats_->getTickerCount(COMPACT_WRITE_BYTES);
ASSERT_GT(write_bytes_after, write_bytes_before)
<< "COMPACT_WRITE_BYTES should increase after successful compaction";
}
}
};
// Parameterized test for abort with different number of max subcompactions.
// This consolidates tests that were essentially duplicates with different
// max_subcompactions values
class DBCompactionAbortSubcompactionTest
: public DBCompactionAbortTest,
public ::testing::WithParamInterface<int> {};
TEST_P(DBCompactionAbortSubcompactionTest, AbortWithVaryingSubcompactions) {
int max_subcompactions = GetParam();
Options options = GetOptionsWithStats();
options.level0_file_num_compaction_trigger = 4;
options.max_subcompactions = max_subcompactions;
options.disable_auto_compactions = true;
Reopen(options);
PopulateData(/*num_files=*/4, /*keys_per_file=*/100, /*value_size=*/100);
RunSyncPointAbortTest("CompactionJob::RunSubcompactions:BeforeStart");
VerifyDataIntegrity(/*num_keys=*/100);
}
INSTANTIATE_TEST_CASE_P(SubcompactionVariants,
DBCompactionAbortSubcompactionTest,
::testing::Values(1, 2, 4),
[](const ::testing::TestParamInfo<int>& param_info) {
return "MaxSubcompactionCount_" +
std::to_string(param_info.param);
});
// Parameterized test for abort with different compaction styles
// This consolidates tests for Level, Universal, and FIFO compaction styles
class DBCompactionAbortStyleTest
: public DBCompactionAbortTest,
public ::testing::WithParamInterface<CompactionStyle> {
protected:
// Configure options based on compaction style
void ConfigureOptionsForStyle(Options& options, CompactionStyle style) {
options.compaction_style = style;
options.level0_file_num_compaction_trigger = 4;
options.disable_auto_compactions = true;
switch (style) {
case kCompactionStyleLevel:
// Level compaction uses default settings
break;
case kCompactionStyleUniversal:
options.compaction_options_universal.size_ratio = 10;
break;
case kCompactionStyleFIFO:
// Set a large max_table_files_size to avoid deletion compaction
options.compaction_options_fifo.max_table_files_size =
100 * 1024 * 1024;
// Enable intra-L0 compaction which goes through normal compaction path
options.compaction_options_fifo.allow_compaction = true;
options.max_open_files = -1; // Required for FIFO compaction
break;
default:
break;
}
}
};
TEST_P(DBCompactionAbortStyleTest, AbortCompaction) {
CompactionStyle style = GetParam();
Options options = GetOptionsWithStats();
options.max_subcompactions = 1;
ConfigureOptionsForStyle(options, style);
Reopen(options);
PopulateData(/*num_files=*/4, /*keys_per_file=*/100, /*value_size=*/100);
RunSyncPointAbortTest("CompactionJob::RunSubcompactions:BeforeStart");
VerifyDataIntegrity(/*num_keys=*/100);
}
INSTANTIATE_TEST_CASE_P(
CompactionStyleVariants, DBCompactionAbortStyleTest,
::testing::Values(kCompactionStyleLevel, kCompactionStyleUniversal,
kCompactionStyleFIFO),
[](const ::testing::TestParamInfo<CompactionStyle>& param_info) {
return OptionsHelper::compaction_style_to_string.at(param_info.param);
});
TEST_F(DBCompactionAbortTest, AbortManualCompaction) {
Options options = GetOptionsWithStats();
options.level0_file_num_compaction_trigger = 10;
options.disable_auto_compactions = true;
Reopen(options);
PopulateData(/*num_files=*/5, /*keys_per_file=*/100, /*value_size=*/1000);
CompactRangeOptions cro;
cro.exclusive_manual_compaction = true;
RunSyncPointAbortTest("CompactionJob::ProcessKeyValueCompaction:Start", cro);
VerifyDataIntegrity(/*num_keys=*/100);
}
TEST_F(DBCompactionAbortTest, AbortAutomaticCompaction) {
Options options = CurrentOptions();
options.level0_file_num_compaction_trigger = 4;
options.max_subcompactions = 2;
options.disable_auto_compactions = false;
Reopen(options);
Random rnd(301);
AbortSynchronizer abort_sync;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"CompactionJob::ProcessKeyValueCompaction:Start",
[&](void* /*arg*/) { abort_sync.TriggerAbort(dbfull()); });
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 100; ++j) {
ASSERT_OK(Put(Key(j), rnd.RandomString(1000)));
}
ASSERT_OK(Flush());
}
ASSERT_OK(dbfull()->TEST_WaitForCompact());
CleanupSyncPoints();
abort_sync.WaitForAbortCompletion();
dbfull()->ResumeAllCompactions();
for (int j = 0; j < 100; ++j) {
ASSERT_OK(Put(Key(j), rnd.RandomString(1000)));
}
ASSERT_OK(Flush());
ASSERT_OK(dbfull()->TEST_WaitForCompact());
std::string val;
for (int j = 0; j < 100; ++j) {
ASSERT_OK(dbfull()->Get(ReadOptions(), Key(j), &val));
}
}
TEST_F(DBCompactionAbortTest, AbortAndVerifyNoOutputFiles) {
Options options = CurrentOptions();
options.level0_file_num_compaction_trigger = 4;
options.max_subcompactions = 2;
options.disable_auto_compactions = true;
Reopen(options);
PopulateData(/*num_files=*/4, /*keys_per_file=*/100, /*value_size=*/1000);
int num_l0_files_before = NumTableFilesAtLevel(0);
int num_l1_files_before = NumTableFilesAtLevel(1);
SyncPointAbortHelper helper("CompactionJob::ProcessKeyValueCompaction:Start");
helper.Setup(dbfull());
CompactRangeOptions cro;
Status s = dbfull()->CompactRange(cro, nullptr, nullptr);
ASSERT_TRUE(s.IsIncomplete());
ASSERT_TRUE(s.IsCompactionAborted());
CleanupSyncPoints();
int num_l0_files_after = NumTableFilesAtLevel(0);
int num_l1_files_after = NumTableFilesAtLevel(1);
ASSERT_EQ(num_l0_files_before, num_l0_files_after);
ASSERT_EQ(num_l1_files_before, num_l1_files_after);
helper.WaitForAbortCompletion();
dbfull()->ResumeAllCompactions();
ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr));
int num_l0_files_final = NumTableFilesAtLevel(0);
int num_l1_files_final = NumTableFilesAtLevel(1);
ASSERT_EQ(0, num_l0_files_final);
ASSERT_GT(num_l1_files_final, 0);
VerifyDataIntegrity(/*num_keys=*/100);
}
TEST_F(DBCompactionAbortTest, MultipleAbortResumeSequence) {
Options options = CurrentOptions();
options.level0_file_num_compaction_trigger = 4;
options.max_subcompactions = 2;
options.disable_auto_compactions = true;
Reopen(options);
PopulateData(/*num_files=*/4, /*keys_per_file=*/100, /*value_size=*/1000);
for (int round = 0; round < 3; ++round) {
// Use SyncPointAbortHelper for deterministic abort timing - it waits
// for the abort flag to be set via sync point dependency
SyncPointAbortHelper helper(
"CompactionJob::ProcessKeyValueCompaction:Start");
helper.Setup(dbfull());
CompactRangeOptions cro;
Status s = dbfull()->CompactRange(cro, nullptr, nullptr);
ASSERT_TRUE(s.IsIncomplete());
ASSERT_TRUE(s.IsCompactionAborted());
helper.CleanupAndWait();
dbfull()->ResumeAllCompactions();
}
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
VerifyDataIntegrity(/*num_keys=*/100);
}
TEST_F(DBCompactionAbortTest, AbortWithOutputFilesCleanup) {
Options options = CurrentOptions();
options.num_levels = 2; // Ensure compaction output goes to L1
options.level0_file_num_compaction_trigger = 4;
options.max_subcompactions = 2;
options.disable_auto_compactions = true;
options.target_file_size_base = 50 * 1024;
Reopen(options);
PopulateData(/*num_files=*/4, /*keys_per_file=*/100, /*value_size=*/100);
SyncPointAbortHelper helper("CompactionJob::RunSubcompactions:BeforeStart");
helper.Setup(dbfull());
CompactRangeOptions cro;
Status s = dbfull()->CompactRange(cro, nullptr, nullptr);
ASSERT_TRUE(s.IsIncomplete());
ASSERT_TRUE(s.IsCompactionAborted());
CleanupSyncPoints();
int num_l1_files_after_abort = NumTableFilesAtLevel(1);
ASSERT_EQ(0, num_l1_files_after_abort);
helper.WaitForAbortCompletion();
dbfull()->ResumeAllCompactions();
ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr));
// Verify L0 files are compacted and L1 has output files
int num_l0_files_final = NumTableFilesAtLevel(0);
int num_l1_files_final = NumTableFilesAtLevel(1);
ASSERT_EQ(0, num_l0_files_final)
<< "L0 should be empty after successful compaction";
ASSERT_GT(num_l1_files_final, 0)
<< "L1 should have files after successful compaction";
VerifyDataIntegrity(/*num_keys=*/100);
}
TEST_F(DBCompactionAbortTest, NestedAbortResumeCalls) {
// Test that nested AbortAllCompactions() calls work correctly with the
// counter
Options options = CurrentOptions();
options.level0_file_num_compaction_trigger = 4;
options.max_subcompactions = 2;
options.disable_auto_compactions = true;
Reopen(options);
PopulateData(/*num_files=*/4, /*keys_per_file=*/100, /*value_size=*/1000);
// First abort call
dbfull()->AbortAllCompactions();
// Nested abort call (counter should be 2)
dbfull()->AbortAllCompactions();
// Compaction should still be blocked after one resume
dbfull()->ResumeAllCompactions();
// Compaction should still return aborted because counter is still 1
CompactRangeOptions cro;
Status s = dbfull()->CompactRange(cro, nullptr, nullptr);
ASSERT_TRUE(s.IsIncomplete());
ASSERT_TRUE(s.IsCompactionAborted());
// Second resume - counter should be 0 now
dbfull()->ResumeAllCompactions();
// Compaction should succeed now
ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr));
VerifyDataIntegrity(/*num_keys=*/100);
}
TEST_F(DBCompactionAbortTest, AbortCompactFilesAPI) {
// Test that AbortAllCompactions works with CompactFiles API
Options options = CurrentOptions();
options.level0_file_num_compaction_trigger = 100; // Disable auto compaction
options.disable_auto_compactions = true;
Reopen(options);
PopulateData(/*num_files=*/4, /*keys_per_file=*/100, /*value_size=*/1000);
// Get the L0 file names
std::vector<std::string> files_to_compact;
ColumnFamilyMetaData cf_meta;
dbfull()->GetColumnFamilyMetaData(dbfull()->DefaultColumnFamily(), &cf_meta);
for (const auto& file : cf_meta.levels[0].files) {
files_to_compact.push_back(file.name);
}
ASSERT_GE(files_to_compact.size(), 2);
SyncPointAbortHelper helper("CompactionJob::ProcessKeyValueCompaction:Start");
helper.Setup(dbfull());
CompactionOptions compact_options;
Status s = dbfull()->CompactFiles(compact_options, files_to_compact, 1);
ASSERT_TRUE(s.IsIncomplete());
ASSERT_TRUE(s.IsCompactionAborted());
helper.CleanupAndWait();
dbfull()->ResumeAllCompactions();
// CompactFiles should work after resume
ASSERT_OK(dbfull()->CompactFiles(compact_options, files_to_compact, 1));
VerifyDataIntegrity(/*num_keys=*/100);
}
TEST_F(DBCompactionAbortTest, AbortDoesNotAffectFlush) {
// Test that AbortAllCompactions does not affect flush operations
Options options = CurrentOptions();
options.level0_file_num_compaction_trigger = 100;
options.disable_auto_compactions = true;
Reopen(options);
Random rnd(301);
for (int j = 0; j < 100; ++j) {
ASSERT_OK(Put(Key(j), rnd.RandomString(1000)));
}
// Abort compactions
dbfull()->AbortAllCompactions();
// Flush should still work
ASSERT_OK(Flush());
// Write more data
for (int j = 100; j < 200; ++j) {
ASSERT_OK(Put(Key(j), rnd.RandomString(1000)));
}
// Flush should still work
ASSERT_OK(Flush());
// Resume compactions
dbfull()->ResumeAllCompactions();
VerifyDataIntegrity(/*num_keys=*/200);
}
TEST_F(DBCompactionAbortTest, AbortBeforeCompactionStarts) {
// Test aborting before any compaction has started
Options options = CurrentOptions();
options.level0_file_num_compaction_trigger = 4;
options.disable_auto_compactions = true;
Reopen(options);
PopulateData(/*num_files=*/4, /*keys_per_file=*/100, /*value_size=*/1000);
// Abort before starting compaction
dbfull()->AbortAllCompactions();
// Compaction should immediately return aborted
CompactRangeOptions cro;
Status s = dbfull()->CompactRange(cro, nullptr, nullptr);
ASSERT_TRUE(s.IsIncomplete());
ASSERT_TRUE(s.IsCompactionAborted());
// Resume
dbfull()->ResumeAllCompactions();
// Now compaction should work
ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr));
// Verify L0 files are compacted
ASSERT_EQ(0, NumTableFilesAtLevel(0));
}
// Test that in-progress blob and SST files are properly cleaned up when
// compaction is aborted. This specifically tests the case where abort happens
// while files are being written (opened but not yet completed/closed).
// This catches the bug where files exist on disk but are removed from the
// outputs_ vector (e.g., by RemoveLastEmptyOutput when file_size is 0 because
// the builder was abandoned), leaving orphan files.
TEST_F(DBCompactionAbortTest, AbortWithInProgressFileCleanup) {
Options options = CurrentOptions();
options.level0_file_num_compaction_trigger = 4;
options.max_subcompactions =
1; // Single subcompaction for deterministic behavior
options.disable_auto_compactions = true;
options.target_file_size_base = 32 * 1024; // 32KB
// Enable BlobDB with garbage collection to force blob rewriting during
// compaction
options.enable_blob_files = true;
options.min_blob_size = 0; // All values go to blob files
options.blob_file_size =
1024 * 1024; // 1MB - large enough to not close during test
// Enable blob garbage collection - this forces blob data to be rewritten
// during compaction, creating new blob files
options.enable_blob_garbage_collection = true;
options.blob_garbage_collection_age_cutoff = 1.0; // Include all blob files
options.blob_garbage_collection_force_threshold = 0.0; // Always force GC
Reopen(options);
// Write enough data to trigger the periodic abort check (every 1000 records).
// 4 files * 2000 keys = 2000 unique overlapping keys processed during
// compaction. The sync point triggers at 999, 1999, etc.
PopulateData(/*num_files=*/4, /*keys_per_file=*/2000, /*value_size=*/500);
// Helper function to get blob files on disk with their names
auto GetBlobFilesOnDisk = [this]() -> std::vector<std::string> {
std::vector<std::string> blob_files;
std::vector<std::string> files;
EXPECT_OK(env_->GetChildren(dbname_, &files));
for (const auto& f : files) {
if (f.find(".blob") != std::string::npos) {
blob_files.push_back(f);
}
}
std::sort(blob_files.begin(), blob_files.end());
return blob_files;
};
// Helper function to get blob file count in metadata
auto GetBlobFilesInMetadata = [this]() -> std::vector<uint64_t> {
std::vector<uint64_t> blob_file_numbers;
ColumnFamilyMetaData cf_meta;
dbfull()->GetColumnFamilyMetaData(db_->DefaultColumnFamily(), &cf_meta);
for (const auto& blob_meta : cf_meta.blob_files) {
blob_file_numbers.push_back(blob_meta.blob_file_number);
}
std::sort(blob_file_numbers.begin(), blob_file_numbers.end());
return blob_file_numbers;
};
// Helper function to get SST files on disk
auto GetSstFilesOnDisk = [this]() -> std::vector<std::string> {
std::vector<std::string> sst_files;
std::vector<std::string> files;
EXPECT_OK(env_->GetChildren(dbname_, &files));
for (const auto& f : files) {
if (f.find(".sst") != std::string::npos) {
sst_files.push_back(f);
}
}
std::sort(sst_files.begin(), sst_files.end());
return sst_files;
};
// Helper function to get SST file numbers in metadata
auto GetSstFilesInMetadata = [this]() -> std::vector<uint64_t> {
std::vector<uint64_t> sst_file_numbers;
ColumnFamilyMetaData cf_meta;
dbfull()->GetColumnFamilyMetaData(db_->DefaultColumnFamily(), &cf_meta);
for (const auto& level : cf_meta.levels) {
for (const auto& file : level.files) {
// Extract file number from the file name (e.g., "000010.sst" -> 10)
uint64_t file_num = 0;
std::string fname = file.name;
// Remove leading path separators if present
size_t pos = fname.rfind('/');
if (pos != std::string::npos) {
fname = fname.substr(pos + 1);
}
if (sscanf(fname.c_str(), "%" PRIu64, &file_num) == 1) {
sst_file_numbers.push_back(file_num);
}
}
}
std::sort(sst_file_numbers.begin(), sst_file_numbers.end());
return sst_file_numbers;
};
std::vector<std::string> initial_blob_files = GetBlobFilesOnDisk();
std::vector<uint64_t> initial_meta_blobs = GetBlobFilesInMetadata();
std::vector<std::string> initial_sst_files = GetSstFilesOnDisk();
std::vector<uint64_t> initial_meta_ssts = GetSstFilesInMetadata();
ASSERT_GT(initial_blob_files.size(), 0u) << "Expected initial blob files";
ASSERT_EQ(initial_blob_files.size(), initial_meta_blobs.size())
<< "Initial blob files should match between disk and metadata";
ASSERT_GT(initial_sst_files.size(), 0u) << "Expected initial SST files";
ASSERT_EQ(initial_sst_files.size(), initial_meta_ssts.size())
<< "Initial SST files should match between disk and metadata";
// Tracking variables for blob file lifecycle
std::atomic<int> blob_writes{0};
std::atomic<bool> abort_triggered{false};
AbortSynchronizer abort_sync;
// Set up dependency: the wait point will block until FlagSet is hit
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({
{"DBImpl::AbortAllCompactions:FlagSet",
"DBCompactionAbortTest::InProgressBlob:WaitForAbort"},
});
// Trigger abort after some blob writes during compaction output.
// This ensures we have an in-progress blob file when abort happens.
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"BlobFileBuilder::WriteBlobToFile:AddRecord", [&](void* /*arg*/) {
int count = blob_writes.fetch_add(1) + 1;
// Trigger abort after 100 blob writes - this ensures:
// 1. A blob file has been opened (for writing)
// 2. Some data has been written to it
// 3. But it's not yet completed (blob_file_size is 1MB)
if (count == 100 && !abort_triggered.exchange(true)) {
abort_sync.TriggerAbort(dbfull());
// Wait for abort flag to be set - this sync point blocks until
// FlagSet is processed
TEST_SYNC_POINT_CALLBACK(
"DBCompactionAbortTest::InProgressBlob:WaitForAbort", nullptr);
}
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
// Run compaction - it should be aborted while blob file is in-progress
CompactRangeOptions cro;
Status s = dbfull()->CompactRange(cro, nullptr, nullptr);
ASSERT_TRUE(s.IsIncomplete())
<< "Expected compaction to be aborted, got: " << s.ToString();
CleanupSyncPoints();
abort_sync.WaitForAbortCompletion();
// Check state after abort
std::vector<std::string> post_abort_disk_blobs = GetBlobFilesOnDisk();
std::vector<uint64_t> post_abort_meta_blobs = GetBlobFilesInMetadata();
std::vector<std::string> post_abort_disk_ssts = GetSstFilesOnDisk();
std::vector<uint64_t> post_abort_meta_ssts = GetSstFilesInMetadata();
// This is the key assertion for blob files: files on disk should match
// metadata. If the in-progress blob file was NOT cleaned up, there will be an
// extra file on disk that's not in metadata (orphan).
ASSERT_EQ(post_abort_disk_blobs.size(), post_abort_meta_blobs.size())
<< "Orphan blob file detected! In-progress blob file was not cleaned up "
"after abort. Files on disk: "
<< post_abort_disk_blobs.size()
<< ", Files in metadata: " << post_abort_meta_blobs.size()
<< ". The difference indicates orphaned in-progress blob file(s).";
// This is the key assertion for SST files: files on disk should match
// metadata. If the in-progress SST file was NOT cleaned up, there will be an
// extra file on disk that's not in metadata (orphan).
ASSERT_EQ(post_abort_disk_ssts.size(), post_abort_meta_ssts.size())
<< "Orphan SST file detected! In-progress SST file was not cleaned up "
"after abort. Files on disk: "
<< post_abort_disk_ssts.size()
<< ", Files in metadata: " << post_abort_meta_ssts.size()
<< ". The difference indicates orphaned in-progress SST file(s).";
// Resume and complete compaction to verify DB is still functional
dbfull()->ResumeAllCompactions();
ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr));
// Verify data integrity - we wrote 4 files * 2000 keys with overlapping keys
VerifyDataIntegrity(/*num_keys=*/2000);
}
TEST_F(DBCompactionAbortTest, AbortBottommostLevelCompaction) {
Options options = CurrentOptions();
options.num_levels = 4;
options.level0_file_num_compaction_trigger = 2;
options.max_bytes_for_level_base = 1024 * 10; // 10KB
options.max_bytes_for_level_multiplier = 2;
options.disable_auto_compactions = true;
Reopen(options);
// Write data to fill multiple levels (non-overlapping keys)
PopulateData(/*num_files=*/6, /*keys_per_file=*/100,
/*value_size=*/500, /*overlapping=*/false);
// First compact to push data to lower levels
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
// Write more data to L0 (overlapping keys)
PopulateData(/*num_files=*/4, /*keys_per_file=*/100, /*value_size=*/500);
SyncPointAbortHelper helper("CompactionJob::ProcessKeyValueCompaction:Start");
helper.Setup(dbfull());
// Trigger bottommost level compaction
CompactRangeOptions cro;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
Status s = dbfull()->CompactRange(cro, nullptr, nullptr);
ASSERT_TRUE(s.IsIncomplete());
ASSERT_TRUE(s.IsCompactionAborted());
helper.CleanupAndWait();
dbfull()->ResumeAllCompactions();
ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr));
VerifyDataIntegrity(/*num_keys=*/600);
}
// Test that while compactions are aborted, atomic range replace
// (IngestExternalFiles with atomic_replace_range) works correctly.
// This verifies that the abort state doesn't block other write operations
// like atomic range replace.
TEST_F(DBCompactionAbortTest, AbortThenAtomicRangeReplace) {
Options options = CurrentOptions();
options.level0_file_num_compaction_trigger = 4;
options.max_subcompactions = 2;
options.disable_auto_compactions = true;
Reopen(options);
// Create a directory for SST files
std::string sst_files_dir = dbname_ + "_sst_files/";
ASSERT_OK(env_->CreateDirIfMissing(sst_files_dir));
// Populate initial data with overlapping keys
PopulateData(/*num_files=*/4, /*keys_per_file=*/100, /*value_size=*/500);
// Verify initial data
VerifyDataIntegrity(/*num_keys=*/100);
// Trigger compaction and abort it
SyncPointAbortHelper helper("CompactionJob::ProcessKeyValueCompaction:Start");
helper.Setup(dbfull());
CompactRangeOptions cro;
Status s = dbfull()->CompactRange(cro, nullptr, nullptr);
ASSERT_TRUE(s.IsIncomplete());
ASSERT_TRUE(s.IsCompactionAborted());
helper.CleanupAndWait();
// While compaction is still aborted, perform atomic range replace using
// IngestExternalFiles with atomic_replace_range. This verifies that the
// abort state doesn't block other write operations.
// Using RangeOpt() (empty range) means replace everything in the CF.
// Create an SST file with new data for keys 0-49 (replacing keys 0-99)
std::string sst_file_path = sst_files_dir + "atomic_replace_1.sst";
SstFileWriter sst_file_writer(EnvOptions(), options);
ASSERT_OK(sst_file_writer.Open(sst_file_path));
// Write new values for keys 0-49
Random rnd(42);
std::unordered_map<std::string, std::string> new_values;
for (int j = 0; j < 50; ++j) {
std::string key = Key(j);
std::string value = "replaced_" + rnd.RandomString(100);
ASSERT_OK(sst_file_writer.Put(key, value));
new_values[key] = value;
}
ASSERT_OK(sst_file_writer.Finish());
// Perform atomic range replace for the entire column family.
// Using RangeOpt() (default constructor) means replace everything in the CF.
IngestExternalFileArg arg;
arg.column_family = db_->DefaultColumnFamily();
arg.external_files = {sst_file_path};
arg.atomic_replace_range = RangeOpt();
// snapshot_consistency must be false when using atomic_replace_range
arg.options.snapshot_consistency = false;
// Atomic range replace should work even while compactions are aborted
ASSERT_OK(db_->IngestExternalFiles({arg}));
// Now resume compactions after the atomic range replace
dbfull()->ResumeAllCompactions();
// Verify that the atomic range replace worked correctly:
// 1. Keys 0-49 should have new replaced values
std::string val;
for (int j = 0; j < 50; ++j) {
std::string key = Key(j);
ASSERT_OK(db_->Get(ReadOptions(), key, &val));
auto it = new_values.find(key);
ASSERT_NE(it, new_values.end());
ASSERT_EQ(it->second, val) << "Value mismatch for replaced key: " << key;
}
// 2. Keys 50-99 should not exist (they were replaced/deleted by atomic
// replace)
for (int j = 50; j < 100; ++j) {
std::string key = Key(j);
Status get_status = db_->Get(ReadOptions(), key, &val);
ASSERT_TRUE(get_status.IsNotFound())
<< "Key " << key << " should not exist after full CF replace";
}
// Clean up SST files directory
ASSERT_OK(DestroyDir(env_, sst_files_dir));
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
+19 -24
View File
@@ -2208,8 +2208,7 @@ TEST_P(DBDeleteFileRangeTest, DeleteFileRange) {
std::string end_string = Key(2000);
Slice begin(begin_string);
Slice end(end_string);
ASSERT_OK(
DeleteFilesInRange(db_.get(), db_->DefaultColumnFamily(), &begin, &end));
ASSERT_OK(DeleteFilesInRange(db_, db_->DefaultColumnFamily(), &begin, &end));
int32_t deleted_count = 0;
for (int32_t i = 0; i < 4300; i++) {
@@ -2230,8 +2229,8 @@ TEST_P(DBDeleteFileRangeTest, DeleteFileRange) {
Slice begin1(begin_string);
Slice end1(end_string);
// Try deleting files in range which contain no keys
ASSERT_OK(DeleteFilesInRange(db_.get(), db_->DefaultColumnFamily(), &begin1,
&end1));
ASSERT_OK(
DeleteFilesInRange(db_, db_->DefaultColumnFamily(), &begin1, &end1));
// Push data from level 0 to level 1 to force all data to be deleted
// Note that we don't delete level 0 files
@@ -2240,8 +2239,8 @@ TEST_P(DBDeleteFileRangeTest, DeleteFileRange) {
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ASSERT_OK(DeleteFilesInRange(db_.get(), db_->DefaultColumnFamily(), nullptr,
nullptr));
ASSERT_OK(
DeleteFilesInRange(db_, db_->DefaultColumnFamily(), nullptr, nullptr));
int32_t deleted_count2 = 0;
for (int32_t i = 0; i < 4300; i++) {
@@ -2309,7 +2308,7 @@ TEST_P(DBDeleteFileRangeTest, DeleteFilesInRanges) {
ranges.emplace_back(begin_str1, end_str1);
ranges.emplace_back(begin_str2, end_str2);
ranges.emplace_back(begin_str3, end_str3);
ASSERT_OK(DeleteFilesInRanges(db_.get(), db_->DefaultColumnFamily(),
ASSERT_OK(DeleteFilesInRanges(db_, db_->DefaultColumnFamily(),
ranges.data(), ranges.size()));
ASSERT_EQ("0,3,7", FilesPerLevel(0));
@@ -2336,7 +2335,7 @@ TEST_P(DBDeleteFileRangeTest, DeleteFilesInRanges) {
ranges.emplace_back(&begin1, &end1);
ranges.emplace_back(&begin2, &end2);
ranges.emplace_back(&begin3, &end3);
ASSERT_OK(DeleteFilesInRanges(db_.get(), db_->DefaultColumnFamily(),
ASSERT_OK(DeleteFilesInRanges(db_, db_->DefaultColumnFamily(),
ranges.data(), ranges.size(), false));
ASSERT_EQ("0,1,4", FilesPerLevel(0));
@@ -2357,8 +2356,7 @@ TEST_P(DBDeleteFileRangeTest, DeleteFilesInRanges) {
// Delete all files.
{
RangeOpt range;
ASSERT_OK(
DeleteFilesInRanges(db_.get(), db_->DefaultColumnFamily(), &range, 1));
ASSERT_OK(DeleteFilesInRanges(db_, db_->DefaultColumnFamily(), &range, 1));
ASSERT_EQ("", FilesPerLevel(0));
for (auto i = 0; i < 1000; i++) {
@@ -2420,8 +2418,7 @@ TEST_P(DBDeleteFileRangeTest, DeleteFileRangeFileEndpointsOverlapBug) {
// "1 -> vals[0]" to reappear.
std::string begin_str = Key(0), end_str = Key(1);
Slice begin = begin_str, end = end_str;
ASSERT_OK(
DeleteFilesInRange(db_.get(), db_->DefaultColumnFamily(), &begin, &end));
ASSERT_OK(DeleteFilesInRange(db_, db_->DefaultColumnFamily(), &begin, &end));
ASSERT_EQ(vals[1], GetValue(Key(1)));
db_->ReleaseSnapshot(snapshot);
@@ -3660,7 +3657,7 @@ TEST_F(DBCompactionTest, SuggestCompactRangeNoTwoLevel0Compactions) {
GenerateNewRandomFile(&rnd, /* nowait */ true);
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
ASSERT_OK(experimental::SuggestCompactRange(db_.get(), nullptr, nullptr));
ASSERT_OK(experimental::SuggestCompactRange(db_, nullptr, nullptr));
for (int num = 0; num < options.level0_file_num_compaction_trigger + 1;
num++) {
GenerateNewRandomFile(&rnd, /* nowait */ true);
@@ -4536,8 +4533,7 @@ TEST_F(DBCompactionTest, DeleteFilesInRangeConflictWithCompaction) {
std::string end_string = Key(kMaxKey + 1);
Slice begin(begin_string);
Slice end(end_string);
ASSERT_OK(
DeleteFilesInRange(db_.get(), db_->DefaultColumnFamily(), &begin, &end));
ASSERT_OK(DeleteFilesInRange(db_, db_->DefaultColumnFamily(), &begin, &end));
SyncPoint::GetInstance()->DisableProcessing();
}
@@ -7844,7 +7840,7 @@ class DBCompactionTestL0FilesMisorderCorruption : public DBCompactionTest {
options_.level0_file_num_compaction_trigger = 3;
CompactionOptionsFIFO fifo_options;
if (compaction_path_to_test == "PickCostBasedIntraL0Compaction" ||
if (compaction_path_to_test == "FindIntraL0Compaction" ||
compaction_path_to_test == "CompactRange") {
fifo_options.allow_compaction = true;
} else if (compaction_path_to_test == "CompactFile") {
@@ -7944,7 +7940,7 @@ class DBCompactionTestL0FilesMisorderCorruption : public DBCompactionTest {
void SetupSyncPoints(const std::string& compaction_path_to_test) {
compaction_path_sync_point_called_.store(false);
if (compaction_path_to_test == "PickCostBasedIntraL0Compaction" &&
if (compaction_path_to_test == "FindIntraL0Compaction" &&
options_.compaction_style == CompactionStyle::kCompactionStyleLevel) {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"PostPickFileToCompact", [&](void* arg) {
@@ -7954,7 +7950,7 @@ class DBCompactionTestL0FilesMisorderCorruption : public DBCompactionTest {
*picked_file_to_compact = false;
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"PickCostBasedIntraL0Compaction", [&](void* /*arg*/) {
"FindIntraL0Compaction", [&](void* /*arg*/) {
compaction_path_sync_point_called_.store(true);
});
@@ -7990,12 +7986,12 @@ class DBCompactionTestL0FilesMisorderCorruption : public DBCompactionTest {
"PickDeleteTriggeredCompactionReturnNonnullptr", [&](void* /*arg*/) {
compaction_path_sync_point_called_.store(true);
});
} else if ((compaction_path_to_test == "PickCostBasedIntraL0Compaction" ||
} else if ((compaction_path_to_test == "FindIntraL0Compaction" ||
compaction_path_to_test == "CompactRange") &&
options_.compaction_style ==
CompactionStyle::kCompactionStyleFIFO) {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"PickCostBasedIntraL0Compaction", [&](void* /*arg*/) {
"FindIntraL0Compaction", [&](void* /*arg*/) {
compaction_path_sync_point_called_.store(true);
});
}
@@ -8067,7 +8063,7 @@ TEST_F(DBCompactionTest, CompactFilesSupportKeyPlacementRangeConflict) {
ASSERT_OK(Flush());
ASSERT_OK(Put("k4", "v"));
ASSERT_OK(Flush());
ASSERT_OK(experimental::PromoteL0(db_.get(), db_->DefaultColumnFamily(), 1));
ASSERT_OK(experimental::PromoteL0(db_, db_->DefaultColumnFamily(), 1));
ASSERT_EQ("0,2,1", FilesPerLevel());
ASSERT_OK(Put("k2", "v"));
@@ -8155,7 +8151,7 @@ TEST_F(DBCompactionTestL0FilesMisorderCorruption,
IngestOneKeyValue(dbfull(), Key(i), "new", options_);
}
SetupSyncPoints("PickCostBasedIntraL0Compaction");
SetupSyncPoints("FindIntraL0Compaction");
ResumeCompactionThread();
ASSERT_OK(dbfull()->TEST_WaitForCompact());
@@ -8288,8 +8284,7 @@ TEST_F(DBCompactionTestL0FilesMisorderCorruption,
TEST_F(DBCompactionTestL0FilesMisorderCorruption,
FlushAfterIntraL0FIFOCompactionWithIngestedFile) {
for (const std::string compaction_path_to_test :
{"PickCostBasedIntraL0Compaction"}) {
for (const std::string compaction_path_to_test : {"FindIntraL0Compaction"}) {
SetupOptions(CompactionStyle::kCompactionStyleFIFO,
compaction_path_to_test);
DestroyAndReopen(options_);
+6 -5
View File
@@ -709,7 +709,7 @@ class TestFlushListener : public EventListener {
// that assumption does not hold (see the test case MultiDBMultiListeners
// below).
ASSERT_TRUE(test_);
if (db == test_->db_.get()) {
if (db == test_->db_) {
std::vector<std::vector<FileMetaData>> files_by_level;
test_->dbfull()->TEST_GetFilesMetaData(db->DefaultColumnFamily(),
&files_by_level);
@@ -2533,7 +2533,7 @@ TEST_F(DBFlushTest, TombstoneVisibleInSnapshot) {
ASSERT_OK(db_->Put(WriteOptions(), "foo", "value0"));
ManagedSnapshot snapshot_guard(db_.get());
ManagedSnapshot snapshot_guard(db_);
ColumnFamilyHandle* default_cf = db_->DefaultColumnFamily();
ASSERT_OK(db_->Flush(FlushOptions(), default_cf));
@@ -2574,7 +2574,7 @@ TEST_P(DBAtomicFlushTest, ManualFlushUnder2PC) {
txn_db_opts.write_policy = TxnDBWritePolicy::WRITE_COMMITTED;
ASSERT_OK(TransactionDB::Open(options, txn_db_opts, dbname_, &txn_db));
ASSERT_NE(txn_db, nullptr);
db_.reset(txn_db);
db_ = txn_db;
// Create two more columns other than default CF.
std::vector<std::string> cfs = {"puppy", "kitty"};
@@ -2638,8 +2638,9 @@ TEST_P(DBAtomicFlushTest, ManualFlushUnder2PC) {
// it means atomic flush didn't write the min_log_number_to_keep to MANIFEST.
cfs.push_back(kDefaultColumnFamilyName);
ASSERT_OK(TryReopenWithColumnFamilies(cfs, options));
ASSERT_TRUE(dbfull()->allow_2pc());
ASSERT_NE(dbfull()->MinLogNumberToKeep(), 0);
DBImpl* db_impl = static_cast<DBImpl*>(db_);
ASSERT_TRUE(db_impl->allow_2pc());
ASSERT_NE(db_impl->MinLogNumberToKeep(), 0);
}
TEST_P(DBAtomicFlushTest, ManualAtomicFlush) {
+7 -16
View File
@@ -320,22 +320,6 @@ Status DBImpl::ResumeImpl(DBRecoverContext context) {
WaitForBackgroundWork();
TEST_SYNC_POINT("DBImpl::ResumeImpl:Start");
// With two_write_queues=true, sequence numbers are allocated via
// FetchAddLastAllocatedSequence() before writes complete, but only
// published via SetLastSequence() after success. If we're recovering from
// an error, there may be allocated-but-not-published sequence numbers.
// We must sync last_sequence_ with last_allocated_sequence_ before creating
// any new memtables/WALs, otherwise the new WAL could start with a sequence
// number lower than what was already written, causing "sequence number
// going backwards" corruption on subsequent recovery.
if (immutable_db_options_.two_write_queues) {
versions_->SyncLastSequenceWithAllocated();
}
TEST_SYNC_POINT("DBImpl::ResumeImpl:AfterSyncSeq");
Status s;
if (shutdown_initiated_) {
// Returning shutdown status to SFM during auto recovery will cause it
@@ -3925,6 +3909,10 @@ Iterator* DBImpl::NewIterator(const ReadOptions& _read_options,
read_options.io_activity = Env::IOActivity::kDBIterator;
}
if (read_options.managed) {
return NewErrorIterator(
Status::NotSupported("Managed iterator is not supported anymore."));
}
Iterator* result = nullptr;
if (read_options.read_tier == kPersistedTier) {
return NewErrorIterator(Status::NotSupported(
@@ -4124,6 +4112,9 @@ Status DBImpl::NewIterators(
if (read_options.io_activity == Env::IOActivity::kUnknown) {
read_options.io_activity = Env::IOActivity::kDBIterator;
}
if (read_options.managed) {
return Status::NotSupported("Managed iterator is not supported anymore.");
}
if (read_options.read_tier == kPersistedTier) {
return Status::NotSupported(
"ReadTier::kPersistedData is not yet supported in iterators.");
+2 -10
View File
@@ -455,8 +455,6 @@ class DBImpl : public DB {
void EnableManualCompaction() override;
void DisableManualCompaction() override;
void AbortAllCompactions() override;
void ResumeAllCompactions() override;
using DB::SetOptions;
Status SetOptions(
@@ -469,6 +467,8 @@ class DBImpl : public DB {
using DB::NumberLevels;
int NumberLevels(ColumnFamilyHandle* column_family) override;
using DB::MaxMemCompactionLevel;
int MaxMemCompactionLevel(ColumnFamilyHandle* column_family) override;
using DB::Level0StopWriteTrigger;
int Level0StopWriteTrigger(ColumnFamilyHandle* column_family) override;
const std::string& GetName() const override;
@@ -2789,14 +2789,6 @@ class DBImpl : public DB {
// compaction code paths.
std::atomic<int> manual_compaction_paused_ = false;
// If non-zero, all compaction jobs (background automatic compactions,
// manual compactions via CompactRange, and foreground CompactFiles calls)
// are being aborted. Compactions will be signaled to stop. Any new
// compaction job would fail immediately. The value indicates how many threads
// have called AbortAllCompactions(). It is accessed in read mode outside the
// DB mutex in compaction code paths.
std::atomic<int> compaction_aborted_ = 0;
// This condition variable is signaled on these conditions:
// * whenever bg_compaction_scheduled_ goes down to 0
// * if AnyManualCompaction, whenever a compaction finishes, even if it hasn't
+12 -109
View File
@@ -955,10 +955,6 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
return Status::Incomplete(Status::SubCode::kManualCompactionPaused);
}
if (compaction_aborted_.load(std::memory_order_acquire) > 0) {
return Status::Incomplete(Status::SubCode::kCompactionAborted);
}
if (options.canceled && options.canceled->load(std::memory_order_acquire)) {
return Status::Incomplete(Status::SubCode::kManualCompactionPaused);
}
@@ -1491,11 +1487,6 @@ Status DBImpl::CompactFilesImpl(
return Status::ShutdownInProgress();
}
// triggered by AbortAllCompactions
if (compaction_aborted_.load(std::memory_order_acquire) > 0) {
return Status::Incomplete(Status::SubCode::kCompactionAborted);
}
// triggered by DisableManualCompactions or by user-set canceled flag in
// CompactionOptions
if (manual_compaction_paused_.load(std::memory_order_acquire) > 0 ||
@@ -1646,9 +1637,9 @@ Status DBImpl::CompactFilesImpl(
c->mutable_cf_options().paranoid_file_checks,
c->mutable_cf_options().report_bg_io_stats, dbname_,
&compaction_job_stats, Env::Priority::USER, io_tracer_,
kManualCompactionCanceledFalse_, compaction_aborted_, db_id_,
db_session_id_, c->column_family_data()->GetFullHistoryTsLow(),
c->trim_ts(), &blob_callback_, &bg_compaction_scheduled_,
kManualCompactionCanceledFalse_, db_id_, db_session_id_,
c->column_family_data()->GetFullHistoryTsLow(), c->trim_ts(),
&blob_callback_, &bg_compaction_scheduled_,
&bg_bottom_compaction_scheduled_);
// Creating a compaction influences the compaction score because the score
@@ -1719,11 +1710,6 @@ Status DBImpl::CompactFilesImpl(
"[%s] [JOB %d] Stopping manual compaction",
c->column_family_data()->GetName().c_str(),
job_context->job_id);
} else if (status.IsCompactionAborted()) {
// Don't report aborted compaction as error
ROCKS_LOG_INFO(
immutable_db_options_.info_log, "[%s] [JOB %d] Compaction aborted",
c->column_family_data()->GetName().c_str(), job_context->job_id);
} else {
ROCKS_LOG_WARN(immutable_db_options_.info_log,
"[%s] [JOB %d] Compaction error: %s",
@@ -2025,6 +2011,10 @@ int DBImpl::NumberLevels(ColumnFamilyHandle* column_family) {
return cfh->cfd()->NumberLevels();
}
int DBImpl::MaxMemCompactionLevel(ColumnFamilyHandle* /*column_family*/) {
return 0;
}
int DBImpl::Level0StopWriteTrigger(ColumnFamilyHandle* column_family) {
auto cfh = static_cast_with_check<ColumnFamilyHandleImpl>(column_family);
InstrumentedMutexLock l(&mutex_);
@@ -2180,17 +2170,6 @@ Status DBImpl::RunManualCompaction(
return manual.status;
}
if (compaction_aborted_.load(std::memory_order_acquire) > 0) {
// All compactions are being aborted. Return immediately.
int counter = compaction_aborted_.load(std::memory_order_acquire);
ROCKS_LOG_INFO(
immutable_db_options_.info_log,
"RunManualCompaction: Aborting due to compaction_aborted_=%d", counter);
manual.status = Status::Incomplete(Status::SubCode::kCompactionAborted);
manual.done = true;
return manual.status;
}
// When a manual compaction arrives, temporarily disable scheduling of
// non-manual compactions and wait until the number of scheduled compaction
// jobs drops to zero. This used to be needed to ensure that this manual
@@ -2215,13 +2194,6 @@ Status DBImpl::RunManualCompaction(
// and `CompactRangeOptions::canceled` might not work well together.
while (bg_bottom_compaction_scheduled_ > 0 ||
bg_compaction_scheduled_ > 0) {
if (compaction_aborted_.load(std::memory_order_acquire) > 0) {
// Pretend the error came from compaction so the below cleanup/error
// handling code can process it.
manual.done = true;
manual.status = Status::Incomplete(Status::SubCode::kCompactionAborted);
break;
}
if (manual_compaction_paused_ > 0 || manual.canceled == true) {
// Pretend the error came from compaction so the below cleanup/error
// handling code can process it.
@@ -2340,12 +2312,7 @@ Status DBImpl::RunManualCompaction(
if (!scheduled) {
// There is nothing scheduled to wait on, so any cancellation can end the
// manual now.
if (compaction_aborted_.load(std::memory_order_acquire) > 0) {
// Stop waiting since it was canceled. Pretend the error came from
// compaction so the below cleanup/error handling code can process it.
manual.done = true;
manual.status = Status::Incomplete(Status::SubCode::kCompactionAborted);
} else if (manual_compaction_paused_ > 0 || manual.canceled == true) {
if (manual_compaction_paused_ > 0 || manual.canceled == true) {
// Stop waiting since it was canceled. Pretend the error came from
// compaction so the below cleanup/error handling code can process it.
manual.done = true;
@@ -2963,61 +2930,6 @@ void DBImpl::EnableManualCompaction() {
manual_compaction_paused_.fetch_sub(1, std::memory_order_release);
}
void DBImpl::AbortAllCompactions() {
InstrumentedMutexLock l(&mutex_);
// Increment the abort counter to signal all compactions to abort
compaction_aborted_.fetch_add(1, std::memory_order_release);
TEST_SYNC_POINT("DBImpl::AbortAllCompactions:FlagSet");
// Mark all manual compactions as canceled
for (const auto& manual_compaction : manual_compaction_dequeue_) {
manual_compaction->canceled = true;
}
// Wake up any waiting compaction threads to check the abort signal
bg_cv_.SignalAll();
// Wait for all running compactions (both manual and automatic) to finish
// or abort before returning.
// Note: bg_cv_.Wait() releases the mutex while waiting, so other threads
// can make progress and signal when compactions complete.
while (bg_bottom_compaction_scheduled_ > 0 || bg_compaction_scheduled_ > 0 ||
HasPendingManualCompaction()) {
bg_cv_.Wait();
}
}
void DBImpl::ResumeAllCompactions() {
InstrumentedMutexLock l(&mutex_);
int before = compaction_aborted_.load(std::memory_order_acquire);
// Guard against calling Resume without prior Abort
if (before <= 0) {
ROCKS_LOG_WARN(immutable_db_options_.info_log,
"ResumeAllCompactions called without prior "
"AbortAllCompactions (counter=%d)",
before);
return;
}
// Decrement the abort counter
compaction_aborted_.fetch_sub(1, std::memory_order_release);
// As the operation is executed under db mutex, we could just use before value
// to calculate the current value.
int current = before - 1;
ROCKS_LOG_INFO(immutable_db_options_.info_log,
"ResumeAllCompactions: counter %d -> %d", before, current);
// If this is the last resume call (abort counter back to 0), schedule
// compactions that may have been waiting
if (current == 0) {
MaybeScheduleFlushOrCompaction();
}
}
void DBImpl::MaybeScheduleFlushOrCompaction() {
mutex_.AssertHeld();
TEST_SYNC_POINT("DBImpl::MaybeScheduleFlushOrCompaction:Start");
@@ -3082,9 +2994,6 @@ void DBImpl::MaybeScheduleFlushOrCompaction() {
if (bg_compaction_paused_ > 0) {
// we paused the background compaction
return;
} else if (compaction_aborted_.load(std::memory_order_acquire) > 0) {
// we are aborting all compactions
return;
} else if (error_handler_.IsBGWorkStopped()) {
// Compaction is not part of the recovery sequence from a hard error. We
// might get here because recovery might do a flush and install a new
@@ -3622,8 +3531,7 @@ void DBImpl::BackgroundCallCompaction(PrepickedCompaction* prepicked_compaction,
10000); // prevent hot loop
mutex_.Lock();
} else if (!s.ok() && !s.IsShutdownInProgress() &&
!s.IsManualCompactionPaused() && !s.IsColumnFamilyDropped() &&
!s.IsCompactionAborted()) {
!s.IsManualCompactionPaused() && !s.IsColumnFamilyDropped()) {
// Wait a little bit before retrying background compaction in
// case this is an environmental problem and we do not want to
// chew up resources for failed compactions for the duration of
@@ -3655,7 +3563,6 @@ void DBImpl::BackgroundCallCompaction(PrepickedCompaction* prepicked_compaction,
// case of a failure). Thus, we force full scan in FindObsoleteFiles()
FindObsoleteFiles(&job_context, !s.ok() && !s.IsShutdownInProgress() &&
!s.IsManualCompactionPaused() &&
!s.IsCompactionAborted() &&
!s.IsColumnFamilyDropped() &&
!s.IsBusy());
TEST_SYNC_POINT("DBImpl::BackgroundCallCompaction:FoundObsoleteFiles");
@@ -3760,8 +3667,6 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
if (!error_handler_.IsBGWorkStopped()) {
if (shutting_down_.load(std::memory_order_acquire)) {
status = Status::ShutdownInProgress();
} else if (compaction_aborted_.load(std::memory_order_acquire) > 0) {
status = Status::Incomplete(Status::SubCode::kCompactionAborted);
} else if (is_manual &&
manual_compaction->canceled.load(std::memory_order_acquire)) {
status = Status::Incomplete(Status::SubCode::kManualCompactionPaused);
@@ -4378,9 +4283,8 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
&compaction_job_stats, thread_pri, io_tracer_,
is_manual ? manual_compaction->canceled
: kManualCompactionCanceledFalse_,
compaction_aborted_, db_id_, db_session_id_,
c->column_family_data()->GetFullHistoryTsLow(), c->trim_ts(),
&blob_callback_, &bg_compaction_scheduled_,
db_id_, db_session_id_, c->column_family_data()->GetFullHistoryTsLow(),
c->trim_ts(), &blob_callback_, &bg_compaction_scheduled_,
&bg_bottom_compaction_scheduled_);
compaction_job.Prepare(std::nullopt /*subcompact to be computed*/);
@@ -4463,7 +4367,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
}
if (status.ok() || status.IsCompactionTooLarge() ||
status.IsManualCompactionPaused() || status.IsCompactionAborted()) {
status.IsManualCompactionPaused()) {
// Done
} else if (status.IsColumnFamilyDropped() || status.IsShutdownInProgress()) {
// Ignore compaction errors found during shutting down
@@ -4726,7 +4630,6 @@ void DBImpl::BuildCompactionJobInfo(
compaction_job_info->cf_id = cfd->GetID();
compaction_job_info->cf_name = cfd->GetName();
compaction_job_info->status = st;
compaction_job_info->aborted = st.IsCompactionAborted();
compaction_job_info->thread_id = env_->GetThreadID();
compaction_job_info->job_id = job_id;
compaction_job_info->base_input_level = c->start_level();
+4 -4
View File
@@ -2264,7 +2264,7 @@ Status DB::OpenAndTrimHistory(
return s;
}
std::unique_ptr<DB> db;
DB* db = nullptr;
s = DB::Open(db_options, dbname, column_families, handles, &db);
if (!s.ok()) {
return s;
@@ -2273,7 +2273,7 @@ Status DB::OpenAndTrimHistory(
CompactRangeOptions options;
options.bottommost_level_compaction =
BottommostLevelCompaction::kForceOptimized;
auto db_impl = static_cast_with_check<DBImpl>(db.get());
auto db_impl = static_cast_with_check<DBImpl>(db);
for (auto handle : *handles) {
assert(handle != nullptr);
auto cfh = static_cast_with_check<ColumnFamilyHandleImpl>(handle);
@@ -2295,14 +2295,14 @@ Status DB::OpenAndTrimHistory(
assert(temp_s.ok());
}
handles->clear();
db.reset();
delete db;
};
if (!s.ok()) {
clean_op();
return s;
}
*dbptr = std::move(db);
dbptr->reset(db);
return s;
}
+16 -11
View File
@@ -509,6 +509,10 @@ Iterator* DBImplSecondary::NewIterator(const ReadOptions& _read_options,
if (read_options.io_activity == Env::IOActivity::kUnknown) {
read_options.io_activity = Env::IOActivity::kDBIterator;
}
if (read_options.managed) {
return NewErrorIterator(
Status::NotSupported("Managed iterator is not supported anymore."));
}
if (read_options.read_tier == kPersistedTier) {
return NewErrorIterator(Status::NotSupported(
"ReadTier::kPersistedData is not yet supported in iterators."));
@@ -584,6 +588,9 @@ Status DBImplSecondary::NewIterators(
if (read_options.io_activity == Env::IOActivity::kUnknown) {
read_options.io_activity = Env::IOActivity::kDBIterator;
}
if (read_options.managed) {
return Status::NotSupported("Managed iterator is not supported anymore.");
}
if (read_options.read_tier == kPersistedTier) {
return Status::NotSupported(
"ReadTier::kPersistedData is not yet supported in iterators.");
@@ -980,8 +987,11 @@ Status DBImplSecondary::ParseCompactionProgressFile(
Slice slice;
std::string record;
while (compaction_progress_reader.ReadRecord(&slice, &record) &&
reader_status.ok()) {
while (compaction_progress_reader.ReadRecord(&slice, &record)) {
if (!reader_status.ok()) {
return reader_status;
}
VersionEdit edit;
s = edit.DecodeFrom(slice);
if (!s.ok()) {
@@ -994,10 +1004,6 @@ Status DBImplSecondary::ParseCompactionProgressFile(
}
}
if (!reader_status.ok()) {
return reader_status;
}
if (!s.ok()) {
return s;
}
@@ -1539,7 +1545,7 @@ Status DB::OpenAndCompact(
}
// 5. Open db As Secondary
std::unique_ptr<DB> db;
DB* db;
std::vector<ColumnFamilyHandle*> handles;
s = DB::OpenAsSecondary(db_options, name, output_directory, column_families,
&handles, &db);
@@ -1549,7 +1555,7 @@ Status DB::OpenAndCompact(
assert(db);
TEST_SYNC_POINT_CALLBACK(
"DBImplSecondary::OpenAndCompact::AfterOpenAsSecondary:0", db.get());
"DBImplSecondary::OpenAndCompact::AfterOpenAsSecondary:0", db);
// 6. Find the handle of the Column Family that this will compact
ColumnFamilyHandle* cfh = nullptr;
@@ -1564,8 +1570,7 @@ Status DB::OpenAndCompact(
// 7. Run the compaction without installation.
// Output will be stored in the directory specified by output_directory
CompactionServiceResult compaction_result;
DBImplSecondary* db_secondary =
static_cast_with_check<DBImplSecondary>(db.get());
DBImplSecondary* db_secondary = static_cast_with_check<DBImplSecondary>(db);
s = db_secondary->CompactWithoutInstallation(options, cfh, compaction_input,
&compaction_result);
@@ -1576,7 +1581,7 @@ Status DB::OpenAndCompact(
for (auto& handle : handles) {
delete handle;
}
db.reset();
delete db;
if (s.ok()) {
return serialization_status;
} else {
-9
View File
@@ -23,7 +23,6 @@
#include "memory/arena.h"
#include "monitoring/perf_context_imp.h"
#include "rocksdb/env.h"
#include "rocksdb/io_dispatcher.h"
#include "rocksdb/iterator.h"
#include "rocksdb/merge_operator.h"
#include "rocksdb/options.h"
@@ -1622,14 +1621,6 @@ void DBIter::Prepare(const MultiScanArgs& scan_opts) {
new_scan_opts.emplace(scan_opts);
scan_opts_.swap(new_scan_opts);
scan_index_ = 0;
// Create a shared IODispatcher if not provided. This allows all
// BlockBasedTableIterators in this scan to share a single dispatcher,
// enabling better IO coordination and future rate limiting.
if (!scan_opts_.value().io_dispatcher) {
scan_opts_->io_dispatcher.reset(NewIODispatcher());
}
if (!scan_opts.empty()) {
iter_.Prepare(&scan_opts_.value());
} else {
+100 -319
View File
@@ -16,7 +16,6 @@
#include "db/db_test_util.h"
#include "port/port.h"
#include "port/stack_trace.h"
#include "rocksdb/io_dispatcher.h"
#include "rocksdb/iostats_context.h"
#include "rocksdb/perf_context.h"
#include "table/block_based/flush_block_policy_impl.h"
@@ -1842,6 +1841,11 @@ class SliceTransformLimitedDomainGeneric : public SliceTransform {
// prefix will be x????
return src.size() >= 1;
}
bool InRange(const Slice& dst) const override {
// prefix will be x????
return dst.size() == 1;
}
};
TEST_P(DBIteratorTest, IterSeekForPrevCrossingFiles) {
@@ -2569,7 +2573,7 @@ TEST_P(DBIteratorTest, AutoRefreshIterator) {
ReadOptions read_options;
std::unique_ptr<ManagedSnapshot> snapshot = nullptr;
if (explicit_snapshot) {
snapshot = std::make_unique<ManagedSnapshot>(db_.get());
snapshot = std::make_unique<ManagedSnapshot>(db_);
}
read_options.snapshot =
explicit_snapshot ? snapshot->snapshot() : nullptr;
@@ -5024,373 +5028,150 @@ TEST_P(DBMultiScanIteratorTest, AsyncPrefetchWithExternalFileIngestion) {
iter.reset();
}
TEST_P(DBMultiScanIteratorTest, IODispatcherStatsVerification) {
// Test that verifies all IOs go through the IODispatcher by checking stats
TEST_P(DBMultiScanIteratorTest, StatisticsTest) {
// Test that multi scan statistics are properly recorded
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();
// Use small block size to ensure multiple blocks
BlockBasedTableOptions table_options;
table_options.block_size = 256;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
DestroyAndReopen(options);
Random rnd(307);
// Create data - enough to create multiple data blocks
for (int i = 0; i < 500; ++i) {
// Create data across multiple blocks
for (int i = 0; i < 100; ++i) {
std::stringstream ss;
ss << "k" << std::setw(5) << std::setfill('0') << i;
ASSERT_OK(Put(ss.str(), rnd.RandomString(1 << 10))); // 1KiB values
ss << std::setw(3) << std::setfill('0') << i;
// Use larger values to ensure multiple blocks
ASSERT_OK(Put("k" + ss.str(), std::string(100, 'v')));
}
ASSERT_OK(Flush());
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
// Set up scan ranges
std::vector<std::string> key_ranges({"k00000", "k00200", "k00300", "k00400"});
// Reset stats before multi scan
ASSERT_OK(options.statistics->Reset());
// Set up two scan ranges
std::vector<std::string> key_ranges({"k010", "k030", "k060", "k080"});
ReadOptions ro;
ro.fill_cache = GetParam();
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
// Create a tracking IODispatcher to verify IO statistics
auto tracking_dispatcher = std::make_shared<TrackingIODispatcher>();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.use_async_io = false; // Use sync IO for predictable stats
scan_options.io_dispatcher = tracking_dispatcher;
scan_options.insert(key_ranges[0], key_ranges[1]);
scan_options.insert(key_ranges[2], key_ranges[3]);
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
std::unique_ptr<MultiScan> iter =
dbfull()->NewMultiScan(ro, cfh, scan_options);
ASSERT_NE(iter, nullptr);
// Scan through all data
int total_keys = 0;
// Iterate through all ranges
int count = 0;
try {
for (auto range : *iter) {
for (auto it : range) {
it.first.ToString();
total_keys++;
(void)it;
count++;
}
}
} 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();
}
// We scanned ~200 keys in range 1 and ~100 keys in range 2
ASSERT_EQ(total_keys, 300);
// Verify that IO operations went through the IODispatcher
// The total IO operations should be > 0 (either sync reads, async reads, or
// cache hits)
uint64_t total_ops = tracking_dispatcher->GetTotalIOOperations();
ASSERT_GT(total_ops, 0) << "Expected some IO operations through IODispatcher";
// Verify that we have at least one ReadSet created
ASSERT_GT(tracking_dispatcher->GetReadSets().size(), 0)
<< "Expected at least one ReadSet to be created";
// Since we used sync IO, we should have sync reads (or cache hits if cached)
uint64_t sync_reads = tracking_dispatcher->GetTotalSyncReads();
uint64_t cache_hits = tracking_dispatcher->GetTotalCacheHits();
ASSERT_GT(sync_reads + cache_hits, 0)
<< "Expected sync reads or cache hits for sync IO mode";
ASSERT_EQ(count, 40); // 20 keys per range
iter.reset();
// Check statistics
// MULTISCAN_PREPARE_CALLS should be at least 1
ASSERT_GE(TestGetTickerCount(options, MULTISCAN_PREPARE_CALLS), 1);
// MULTISCAN_PREPARE_ERRORS should be 0
ASSERT_EQ(TestGetTickerCount(options, MULTISCAN_PREPARE_ERRORS), 0);
// MULTISCAN_SEEK_ERRORS should be 0
ASSERT_EQ(TestGetTickerCount(options, MULTISCAN_SEEK_ERRORS), 0);
// Blocks should be prefetched or from cache
uint64_t blocks_prefetched =
TestGetTickerCount(options, MULTISCAN_BLOCKS_PREFETCHED);
uint64_t blocks_from_cache =
TestGetTickerCount(options, MULTISCAN_BLOCKS_FROM_CACHE);
ASSERT_GT(blocks_prefetched + blocks_from_cache, 0);
// If blocks were prefetched, prefetch bytes and IO requests should be > 0
if (blocks_prefetched > 0) {
ASSERT_GT(TestGetTickerCount(options, MULTISCAN_PREFETCH_BYTES), 0);
uint64_t io_requests = TestGetTickerCount(options, MULTISCAN_IO_REQUESTS);
ASSERT_GT(io_requests, 0);
ASSERT_LE(io_requests, blocks_prefetched);
}
// Wasted blocks should be 0 since we iterated through everything
ASSERT_EQ(TestGetTickerCount(options, MULTISCAN_PREFETCH_BLOCKS_WASTED), 0);
}
TEST_P(DBMultiScanIteratorTest, IODispatcherPrefetchKnownBlocks) {
// Test that verifies we prefetch a known/expected number of blocks.
// Uses FlushBlockEveryKeyPolicyFactory to create exactly one block per key,
// making the block count predictable and verifiable.
TEST_P(DBMultiScanIteratorTest, StatisticsWastedBlocksTest) {
// Test that wasted blocks are tracked when iteration is abandoned early
auto options = CurrentOptions();
options.compression = kNoCompression;
options.disable_auto_compactions = true;
// Configure to create exactly one block per key
BlockBasedTableOptions table_options;
table_options.flush_block_policy_factory =
std::make_shared<FlushBlockEveryKeyPolicyFactory>();
// Use a block cache (required by IODispatcher), but use a fresh one
// that won't have any cached data
table_options.block_cache = NewLRUCache(10 * 1024 * 1024); // 10MB cache
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
DestroyAndReopen(options);
// Create exactly 100 keys, each in its own block
const int kNumKeys = 100;
const int kValueSize = 100; // Fixed value size for predictability
std::string value(kValueSize, 'v');
for (int i = 0; i < kNumKeys; ++i) {
std::stringstream ss;
ss << "k" << std::setw(3) << std::setfill('0') << i;
ASSERT_OK(Put(ss.str(), value));
}
ASSERT_OK(Flush());
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
// Create a tracking IODispatcher to verify IO statistics
auto tracking_dispatcher = std::make_shared<TrackingIODispatcher>();
// Define scan ranges with known block counts:
// Range 1: k000 to k020 (20 keys = 20 blocks)
// Range 2: k050 to k060 (10 keys = 10 blocks)
// Total expected blocks to read: 30
MultiScanArgs scan_options(BytewiseComparator());
scan_options.use_async_io = false; // Use sync IO for predictable stats
scan_options.io_dispatcher = tracking_dispatcher;
scan_options.insert("k000", "k020");
scan_options.insert("k050", "k060");
ReadOptions ro;
ro.fill_cache = false; // Don't fill cache, ensure fresh reads
std::unique_ptr<MultiScan> iter =
dbfull()->NewMultiScan(ro, cfh, scan_options);
ASSERT_NE(iter, nullptr);
// Scan through all data and count keys
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();
}
// Verify we scanned the expected number of keys
// Range 1: k000-k019 = 20 keys, Range 2: k050-k059 = 10 keys
ASSERT_EQ(total_keys, 30) << "Expected 30 keys from two ranges";
// Verify IODispatcher statistics
uint64_t total_ops = tracking_dispatcher->GetTotalIOOperations();
uint64_t sync_reads = tracking_dispatcher->GetTotalSyncReads();
// We should have at least as many IO operations as blocks we need to read
// (could be more due to index/filter blocks)
ASSERT_GE(total_ops, 30)
<< "Expected at least 30 IO operations for 30 data blocks";
// Since cache is fresh and fill_cache=false, all should be sync reads
ASSERT_GE(sync_reads, 30)
<< "Expected at least 30 sync reads for 30 data blocks";
// Verify we created ReadSets (one per range)
size_t num_readsets = tracking_dispatcher->GetReadSets().size();
ASSERT_GE(num_readsets, 1) << "Expected at least one ReadSet";
// Log the stats for debugging
std::cout << "IODispatcher Stats: total_ops=" << total_ops
<< ", sync_reads=" << sync_reads
<< ", async_reads=" << tracking_dispatcher->GetTotalAsyncReads()
<< ", cache_hits=" << tracking_dispatcher->GetTotalCacheHits()
<< ", readsets=" << num_readsets << std::endl;
iter.reset();
}
TEST_P(DBMultiScanIteratorTest, IODispatcherCacheHitVerification) {
// Test that verifies cache hits are properly tracked through IODispatcher.
// First scan populates cache, second scan should show cache hits.
auto options = CurrentOptions();
options.compression = kNoCompression;
options.disable_auto_compactions = true;
BlockBasedTableOptions table_options;
table_options.flush_block_policy_factory =
std::make_shared<FlushBlockEveryKeyPolicyFactory>();
// Enable block cache with enough space for all blocks
table_options.block_cache = NewLRUCache(10 * 1024 * 1024); // 10MB cache
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
DestroyAndReopen(options);
// Create 50 keys, each in its own block
const int kNumKeys = 50;
std::string value(100, 'v');
for (int i = 0; i < kNumKeys; ++i) {
std::stringstream ss;
ss << "k" << std::setw(3) << std::setfill('0') << i;
ASSERT_OK(Put(ss.str(), value));
}
ASSERT_OK(Flush());
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
// First scan: populate the cache
{
auto dispatcher1 = std::make_shared<TrackingIODispatcher>();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.use_async_io = false;
scan_options.io_dispatcher = dispatcher1;
scan_options.insert("k000", "k025"); // 25 keys
ReadOptions ro;
ro.fill_cache = true; // Fill cache on first scan
std::unique_ptr<MultiScan> iter =
dbfull()->NewMultiScan(ro, cfh, scan_options);
ASSERT_NE(iter, nullptr);
int count = 0;
try {
for (auto range : *iter) {
for (auto it : range) {
it.first.ToString();
count++;
}
}
} catch (MultiScanException& ex) {
FAIL() << "First scan failed: " << ex.what();
}
ASSERT_EQ(count, 25);
// First scan should have sync reads (cache was empty)
uint64_t first_sync = dispatcher1->GetTotalSyncReads();
ASSERT_GE(first_sync, 25) << "First scan should have sync reads";
std::cout << "First scan stats: sync_reads=" << first_sync
<< ", cache_hits=" << dispatcher1->GetTotalCacheHits()
<< std::endl;
}
// Second scan: should get cache hits
{
auto dispatcher2 = std::make_shared<TrackingIODispatcher>();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.use_async_io = false;
scan_options.io_dispatcher = dispatcher2;
scan_options.insert("k000", "k025"); // Same range as before
ReadOptions ro;
ro.fill_cache = true;
std::unique_ptr<MultiScan> iter =
dbfull()->NewMultiScan(ro, cfh, scan_options);
ASSERT_NE(iter, nullptr);
int count = 0;
try {
for (auto range : *iter) {
for (auto it : range) {
it.first.ToString();
count++;
}
}
} catch (MultiScanException& ex) {
FAIL() << "Second scan failed: " << ex.what();
}
ASSERT_EQ(count, 25);
// Second scan should have cache hits (blocks were cached in first scan)
uint64_t second_cache_hits = dispatcher2->GetTotalCacheHits();
uint64_t second_sync = dispatcher2->GetTotalSyncReads();
std::cout << "Second scan stats: sync_reads=" << second_sync
<< ", cache_hits=" << second_cache_hits << std::endl;
// We expect cache hits on the second scan for data blocks
// Note: Some blocks might still need sync reads (e.g., if cache was
// evicted)
ASSERT_GE(second_cache_hits, 20)
<< "Second scan should have cache hits for most blocks";
}
}
TEST_P(DBMultiScanIteratorTest, WastedBlocksTracking) {
// Test that verifies wasted prefetch blocks are properly tracked.
// When blocks are prefetched but skipped (e.g., due to seek), they should
// be counted as wasted and recorded to MULTISCAN_PREFETCH_BLOCKS_WASTED.
auto options = CurrentOptions();
options.compression = kNoCompression;
options.disable_auto_compactions = true;
options.statistics = CreateDBStatistics();
// Use small block size to ensure multiple blocks
BlockBasedTableOptions table_options;
table_options.flush_block_policy_factory =
std::make_shared<FlushBlockEveryKeyPolicyFactory>();
table_options.block_cache = NewLRUCache(10 * 1024 * 1024); // 10MB cache
table_options.block_size = 256;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
DestroyAndReopen(options);
// Create 100 keys, each in its own block
const int kNumKeys = 100;
std::string value(100, 'v');
for (int i = 0; i < kNumKeys; ++i) {
// Create data across multiple blocks
for (int i = 0; i < 100; ++i) {
std::stringstream ss;
ss << "k" << std::setw(3) << std::setfill('0') << i;
ASSERT_OK(Put(ss.str(), value));
ss << std::setw(3) << std::setfill('0') << i;
ASSERT_OK(Put("k" + ss.str(), std::string(100, 'v')));
}
ASSERT_OK(Flush());
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
// Reset the wasted blocks counter before test
options.statistics->setTickerCount(MULTISCAN_PREFETCH_BLOCKS_WASTED, 0);
// Set up MultiScan with two non-contiguous ranges:
// Range 1: k000-k020 (20 keys/blocks)
// Range 2: k050-k070 (20 keys/blocks)
// The blocks between k020-k050 (30 blocks) should be wasted if prefetched
MultiScanArgs scan_options(BytewiseComparator());
scan_options.use_async_io = false;
scan_options.insert("k000", "k020");
scan_options.insert("k050", "k070");
// Reset stats before multi scan
ASSERT_OK(options.statistics->Reset());
// Set up a large scan range
ReadOptions ro;
ro.fill_cache = GetParam();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.insert("k000", "k099");
{
std::unique_ptr<MultiScan> iter =
dbfull()->NewMultiScan(ro, cfh, scan_options);
ASSERT_NE(iter, nullptr);
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
std::unique_ptr<MultiScan> iter =
dbfull()->NewMultiScan(ro, cfh, scan_options);
int count = 0;
try {
for (auto range : *iter) {
for (auto it : range) {
it.first.ToString();
count++;
// Only iterate through a few keys, then abandon
int count = 0;
try {
for (auto range : *iter) {
for (auto it : range) {
(void)it;
count++;
if (count >= 5) {
break; // Abandon iteration early
}
}
} catch (MultiScanException& ex) {
FAIL() << "Scan failed: " << ex.what();
if (count >= 5) {
break;
}
}
} catch (MultiScanException& ex) {
ASSERT_NOK(ex.status());
std::cerr << "Iterator returned status " << ex.what();
abort();
}
ASSERT_EQ(count, 5);
// We should have scanned 40 keys total (20 + 20)
ASSERT_EQ(count, 40);
} // Iterator destroyed here, wasted blocks recorded
// Destroy iterator to trigger wasted blocks counting
iter.reset();
// Check that wasted blocks were recorded
// The exact count depends on how many blocks were prefetched between ranges
uint64_t wasted =
options.statistics->getTickerCount(MULTISCAN_PREFETCH_BLOCKS_WASTED);
uint64_t blocks_prefetched =
TestGetTickerCount(options, MULTISCAN_BLOCKS_PREFETCHED);
// We expect some wasted blocks due to the gap between ranges
// The exact number depends on prefetch behavior, but should be > 0
// if blocks between k020-k050 were prefetched
std::cout << "Wasted blocks: " << wasted << std::endl;
// Note: The test verifies the tracking mechanism works.
// The actual count depends on prefetch heuristics which may vary.
// If blocks were prefetched, some should be wasted since we abandoned early
if (blocks_prefetched > 1) {
// We only read a few keys, so there should be wasted blocks
ASSERT_GT(TestGetTickerCount(options, MULTISCAN_PREFETCH_BLOCKS_WASTED), 0);
}
}
} // namespace ROCKSDB_NAMESPACE
+5 -7
View File
@@ -180,15 +180,13 @@ TEST_F(DBTestXactLogIterator, TransactionLogIteratorCheckWhenArchive) {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
ASSERT_OK(dbfull()->Flush(FlushOptions(), cf));
// Try lots of things to ensure callback is triggered
ASSERT_OK(dbfull()->TEST_SwitchWAL());
ASSERT_OK(dbfull()->TEST_WaitForBackgroundWork());
ASSERT_OK(dbfull()->TEST_WaitForPurge());
delete cf;
ASSERT_TRUE(callback_hit.LoadRelaxed());
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
Close();
// Normally hit several times; WART: perhaps more in parallel after flush
// FIXME: this test is flaky
// ASSERT_TRUE(callback_hit.LoadRelaxed());
} while (ChangeCompactOptions());
Close();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
}
#endif
+23 -22
View File
@@ -67,7 +67,7 @@ TEST_F(DBLogicalBlockSizeCacheTest, OpenClose) {
options.db_paths = {{data_path_0_, 2048}, {data_path_1_, 2048}};
for (int i = 0; i < 2; i++) {
std::unique_ptr<DB> db;
DB* db;
if (!i) {
printf("Open\n");
ASSERT_OK(DB::Open(options, dbname_, &db));
@@ -82,6 +82,7 @@ TEST_F(DBLogicalBlockSizeCacheTest, OpenClose) {
ASSERT_EQ(1, cache_->GetRefCount(data_path_1_));
ASSERT_OK(db->Close());
ASSERT_EQ(0, cache_->Size());
delete db;
}
ASSERT_OK(DestroyDB(dbname_, options, {}));
}
@@ -94,7 +95,7 @@ TEST_F(DBLogicalBlockSizeCacheTest, OpenDelete) {
options.env = env_.get();
for (int i = 0; i < 2; i++) {
std::unique_ptr<DB> db;
DB* db;
if (!i) {
printf("Open\n");
ASSERT_OK(DB::Open(options, dbname_, &db));
@@ -105,7 +106,7 @@ TEST_F(DBLogicalBlockSizeCacheTest, OpenDelete) {
ASSERT_EQ(1, cache_->Size());
ASSERT_TRUE(cache_->Contains(dbname_));
ASSERT_EQ(1, cache_->GetRefCount(dbname_));
db.reset();
delete db;
ASSERT_EQ(0, cache_->Size());
}
ASSERT_OK(DestroyDB(dbname_, options, {}));
@@ -121,7 +122,7 @@ TEST_F(DBLogicalBlockSizeCacheTest, CreateColumnFamily) {
ColumnFamilyOptions cf_options;
cf_options.cf_paths = {{cf_path_0_, 1024}, {cf_path_1_, 2048}};
std::unique_ptr<DB> db;
DB* db;
ASSERT_OK(DB::Open(options, dbname_, &db));
ASSERT_EQ(1, cache_->Size());
ASSERT_TRUE(cache_->Contains(dbname_));
@@ -152,7 +153,7 @@ TEST_F(DBLogicalBlockSizeCacheTest, CreateColumnFamily) {
ASSERT_TRUE(cache_->Contains(dbname_));
ASSERT_EQ(1, cache_->GetRefCount(dbname_));
db.reset();
delete db;
ASSERT_EQ(0, cache_->Size());
ASSERT_OK(DestroyDB(dbname_, options, {{"cf", cf_options}}));
}
@@ -172,7 +173,7 @@ TEST_F(DBLogicalBlockSizeCacheTest, CreateColumnFamilies) {
ColumnFamilyOptions cf_options;
cf_options.cf_paths = {{cf_path_0_, 1024}};
std::unique_ptr<DB> db;
DB* db;
ASSERT_OK(DB::Open(options, dbname_, &db));
ASSERT_EQ(1, cache_->Size());
ASSERT_TRUE(cache_->Contains(dbname_));
@@ -210,7 +211,7 @@ TEST_F(DBLogicalBlockSizeCacheTest, CreateColumnFamilies) {
ASSERT_OK(db->DestroyColumnFamilyHandle(cfs[1]));
ASSERT_TRUE(cache_->Contains(dbname_));
ASSERT_EQ(1, cache_->GetRefCount(dbname_));
db.reset();
delete db;
// Now cf_path_0_ in cache_ has been properly decreased and cf_path_0_'s entry
// is dropped from cache
@@ -232,15 +233,15 @@ TEST_F(DBLogicalBlockSizeCacheTest, OpenWithColumnFamilies) {
cf_options.cf_paths = {{cf_path_0_, 1024}};
for (int i = 0; i < 2; i++) {
std::unique_ptr<DB> db;
ASSERT_OK(DB::Open(options, dbname_, &db));
DB* db;
ColumnFamilyHandle* cf1 = nullptr;
ColumnFamilyHandle* cf2 = nullptr;
ASSERT_OK(DB::Open(options, dbname_, &db));
ASSERT_OK(db->CreateColumnFamily(cf_options, "cf1", &cf1));
ASSERT_OK(db->CreateColumnFamily(cf_options, "cf2", &cf2));
ASSERT_OK(db->DestroyColumnFamilyHandle(cf1));
ASSERT_OK(db->DestroyColumnFamilyHandle(cf2));
db.reset();
delete db;
ASSERT_EQ(0, cache_->Size());
std::vector<ColumnFamilyHandle*> cfs;
@@ -297,7 +298,7 @@ TEST_F(DBLogicalBlockSizeCacheTest, OpenWithColumnFamilies) {
ASSERT_TRUE(cache_->Contains(dbname_));
ASSERT_EQ(1, cache_->GetRefCount(dbname_));
db.reset();
delete db;
ASSERT_EQ(0, cache_->Size());
}
ASSERT_OK(
@@ -314,7 +315,7 @@ TEST_F(DBLogicalBlockSizeCacheTest, DestroyColumnFamilyHandle) {
ColumnFamilyOptions cf_options;
cf_options.cf_paths = {{cf_path_0_, 1024}};
std::unique_ptr<DB> db;
DB* db;
ASSERT_OK(DB::Open(options, dbname_, &db));
ASSERT_EQ(1, cache_->Size());
ASSERT_TRUE(cache_->Contains(dbname_));
@@ -335,7 +336,7 @@ TEST_F(DBLogicalBlockSizeCacheTest, DestroyColumnFamilyHandle) {
ASSERT_TRUE(cache_->Contains(cf_path_0_));
ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_));
db.reset();
delete db;
ASSERT_EQ(0, cache_->Size());
// Open with column families.
@@ -368,7 +369,7 @@ TEST_F(DBLogicalBlockSizeCacheTest, DestroyColumnFamilyHandle) {
ASSERT_TRUE(cache_->Contains(cf_path_0_));
ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_));
db.reset();
delete db;
ASSERT_EQ(0, cache_->Size());
}
ASSERT_OK(DestroyDB(dbname_, options, {{"cf", cf_options}}));
@@ -383,7 +384,7 @@ TEST_F(DBLogicalBlockSizeCacheTest, MultiDBWithDifferentPaths) {
ASSERT_OK(env_->CreateDirIfMissing(dbname_));
std::unique_ptr<DB> db0;
DB* db0;
ASSERT_OK(DB::Open(options, data_path_0_, &db0));
ASSERT_EQ(1, cache_->Size());
ASSERT_TRUE(cache_->Contains(data_path_0_));
@@ -398,7 +399,7 @@ TEST_F(DBLogicalBlockSizeCacheTest, MultiDBWithDifferentPaths) {
ASSERT_TRUE(cache_->Contains(cf_path_0_));
ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_));
std::unique_ptr<DB> db1;
DB* db1;
ASSERT_OK(DB::Open(options, data_path_1_, &db1));
ASSERT_EQ(3, cache_->Size());
ASSERT_TRUE(cache_->Contains(data_path_0_));
@@ -423,7 +424,7 @@ TEST_F(DBLogicalBlockSizeCacheTest, MultiDBWithDifferentPaths) {
ASSERT_EQ(1, cache_->GetRefCount(cf_path_1_));
ASSERT_OK(db0->DestroyColumnFamilyHandle(cf0));
db0.reset();
delete db0;
ASSERT_EQ(2, cache_->Size());
ASSERT_TRUE(cache_->Contains(data_path_1_));
ASSERT_EQ(1, cache_->GetRefCount(data_path_1_));
@@ -432,7 +433,7 @@ TEST_F(DBLogicalBlockSizeCacheTest, MultiDBWithDifferentPaths) {
ASSERT_OK(DestroyDB(data_path_0_, options, {{"cf", cf_options0}}));
ASSERT_OK(db1->DestroyColumnFamilyHandle(cf1));
db1.reset();
delete db1;
ASSERT_EQ(0, cache_->Size());
ASSERT_OK(DestroyDB(data_path_1_, options, {{"cf", cf_options1}}));
}
@@ -449,7 +450,7 @@ TEST_F(DBLogicalBlockSizeCacheTest, MultiDBWithSamePaths) {
ASSERT_OK(env_->CreateDirIfMissing(dbname_));
std::unique_ptr<DB> db0;
DB* db0;
ASSERT_OK(DB::Open(options, dbname_ + "/db0", &db0));
ASSERT_EQ(1, cache_->Size());
ASSERT_TRUE(cache_->Contains(data_path_0_));
@@ -463,7 +464,7 @@ TEST_F(DBLogicalBlockSizeCacheTest, MultiDBWithSamePaths) {
ASSERT_TRUE(cache_->Contains(cf_path_0_));
ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_));
std::unique_ptr<DB> db1;
DB* db1;
ASSERT_OK(DB::Open(options, dbname_ + "/db1", &db1));
ASSERT_EQ(2, cache_->Size());
ASSERT_TRUE(cache_->Contains(data_path_0_));
@@ -480,7 +481,7 @@ TEST_F(DBLogicalBlockSizeCacheTest, MultiDBWithSamePaths) {
ASSERT_EQ(2, cache_->GetRefCount(cf_path_0_));
ASSERT_OK(db0->DestroyColumnFamilyHandle(cf0));
db0.reset();
delete db0;
ASSERT_EQ(2, cache_->Size());
ASSERT_TRUE(cache_->Contains(data_path_0_));
ASSERT_EQ(1, cache_->GetRefCount(data_path_0_));
@@ -489,7 +490,7 @@ TEST_F(DBLogicalBlockSizeCacheTest, MultiDBWithSamePaths) {
ASSERT_OK(DestroyDB(dbname_ + "/db0", options, {{"cf", cf_options}}));
ASSERT_OK(db1->DestroyColumnFamilyHandle(cf1));
db1.reset();
delete db1;
ASSERT_EQ(0, cache_->Size());
ASSERT_OK(DestroyDB(dbname_ + "/db1", options, {{"cf", cf_options}}));
}
+2
View File
@@ -117,6 +117,8 @@ class TestPrefixExtractor : public SliceTransform {
return separator(key) != nullptr;
}
bool InRange(const Slice& /*key*/) const override { return false; }
private:
const char* separator(const Slice& key) const {
return static_cast<const char*>(memchr(key.data(), '_', key.size()));
+4 -4
View File
@@ -386,7 +386,7 @@ TEST_F(DBMergeOperatorTest, MergeOperandThresholdExceeded) {
snapshots.reserve(3);
for (size_t i = 0; i < keys.size(); ++i) {
snapshots.emplace_back(db_.get());
snapshots.emplace_back(db_);
const std::string suffix = std::to_string(i + 1);
@@ -985,7 +985,7 @@ TEST_F(DBMergeOperatorTest, MaxSuccessiveMergesBaseValues) {
// max_successive_merges.
constexpr size_t max_key_versions = 8;
std::vector<KeyVersion> key_versions;
ASSERT_OK(GetAllKeyVersions(db_.get(), db_->DefaultColumnFamily(), key, key,
ASSERT_OK(GetAllKeyVersions(db_, db_->DefaultColumnFamily(), key, key,
max_key_versions, &key_versions));
ASSERT_EQ(key_versions.size(), 2);
ASSERT_EQ(key_versions[0].type, kTypeValue);
@@ -1009,7 +1009,7 @@ TEST_F(DBMergeOperatorTest, MaxSuccessiveMergesBaseValues) {
// max_successive_merges.
constexpr size_t max_key_versions = 8;
std::vector<KeyVersion> key_versions;
ASSERT_OK(GetAllKeyVersions(db_.get(), db_->DefaultColumnFamily(), key, key,
ASSERT_OK(GetAllKeyVersions(db_, db_->DefaultColumnFamily(), key, key,
max_key_versions, &key_versions));
ASSERT_EQ(key_versions.size(), 3);
ASSERT_EQ(key_versions[0].type, kTypeValue);
@@ -1038,7 +1038,7 @@ TEST_F(DBMergeOperatorTest, MaxSuccessiveMergesBaseValues) {
// max_successive_merges.
constexpr size_t max_key_versions = 8;
std::vector<KeyVersion> key_versions;
ASSERT_OK(GetAllKeyVersions(db_.get(), db_->DefaultColumnFamily(), key, key,
ASSERT_OK(GetAllKeyVersions(db_, db_->DefaultColumnFamily(), key, key,
max_key_versions, &key_versions));
ASSERT_EQ(key_versions.size(), 3);
ASSERT_EQ(key_versions[0].type, kTypeWideColumnEntity);
+3 -3
View File
@@ -384,7 +384,7 @@ TEST_F(DBPropertiesTest, AggregatedTableProperties) {
// Hold open a snapshot to prevent range tombstones from being compacted
// away.
ManagedSnapshot snapshot(db_.get());
ManagedSnapshot snapshot(db_);
Random rnd(5632);
for (int table = 1; table <= kTableCount; ++table) {
@@ -582,7 +582,7 @@ TEST_F(DBPropertiesTest, AggregatedTablePropertiesAtLevel) {
DestroyAndReopen(options);
// Hold open a snapshot to prevent range tombstones from being compacted away.
ManagedSnapshot snapshot(db_.get());
ManagedSnapshot snapshot(db_);
std::string level_tp_strings[kMaxLevel];
std::string tp_string;
@@ -1864,7 +1864,7 @@ TEST_F(DBPropertiesTest, MinObsoleteSstNumberToKeep) {
options.listeners.push_back(listener);
options.level0_file_num_compaction_trigger = kNumL0Files;
DestroyAndReopen(options);
listener->SetDB(db_.get());
listener->SetDB(db_);
for (int i = 0; i < kNumL0Files; ++i) {
// Make sure they overlap in keyspace to prevent trivial move
+1 -1
View File
@@ -2047,7 +2047,7 @@ TEST_F(DBRangeDelTest, IteratorReseek) {
// Immutable memtable
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(1),
Key(2)));
ASSERT_OK(dbfull()->TEST_SwitchMemtable());
ASSERT_OK(static_cast_with_check<DBImpl>(db_)->TEST_SwitchMemtable());
std::string value;
ASSERT_TRUE(dbfull()->GetProperty(db_->DefaultColumnFamily(),
"rocksdb.num-immutable-mem-table", &value));
+4 -4
View File
@@ -237,7 +237,7 @@ TEST_F(DBReadOnlyTestWithTimestamp, IteratorAndGet) {
it->Next(), ++count, ++key) {
CheckIterUserEntry(it.get(), Key1(key), kTypeValue,
"value" + std::to_string(i), write_timestamps[i]);
get_value_and_check(db_.get(), read_opts, it->key(), it->value(),
get_value_and_check(db_, read_opts, it->key(), it->value(),
write_timestamps[i]);
}
ASSERT_OK(it->status());
@@ -250,7 +250,7 @@ TEST_F(DBReadOnlyTestWithTimestamp, IteratorAndGet) {
it->Prev(), ++count, --key) {
CheckIterUserEntry(it.get(), Key1(key), kTypeValue,
"value" + std::to_string(i), write_timestamps[i]);
get_value_and_check(db_.get(), read_opts, it->key(), it->value(),
get_value_and_check(db_, read_opts, it->key(), it->value(),
write_timestamps[i]);
}
ASSERT_OK(it->status());
@@ -272,7 +272,7 @@ TEST_F(DBReadOnlyTestWithTimestamp, IteratorAndGet) {
it->Valid(); it->Next(), ++key, ++count) {
CheckIterUserEntry(it.get(), Key1(key), kTypeValue,
"value" + std::to_string(i), write_timestamps[i]);
get_value_and_check(db_.get(), read_opts, it->key(), it->value(),
get_value_and_check(db_, read_opts, it->key(), it->value(),
write_timestamps[i]);
}
ASSERT_OK(it->status());
@@ -282,7 +282,7 @@ TEST_F(DBReadOnlyTestWithTimestamp, IteratorAndGet) {
it->Valid(); it->Prev(), --key, ++count) {
CheckIterUserEntry(it.get(), Key1(key - 1), kTypeValue,
"value" + std::to_string(i), write_timestamps[i]);
get_value_and_check(db_.get(), read_opts, it->key(), it->value(),
get_value_and_check(db_, read_opts, it->key(), it->value(),
write_timestamps[i]);
}
ASSERT_OK(it->status());
+15 -14
View File
@@ -56,11 +56,12 @@ class DBSecondaryTestBase : public DBBasicTestWithTimestampBase {
ASSERT_OK(db_secondary_->DestroyColumnFamilyHandle(h));
}
handles_secondary_.clear();
db_secondary_.reset();
delete db_secondary_;
db_secondary_ = nullptr;
}
DBImplSecondary* db_secondary_full() {
return static_cast<DBImplSecondary*>(db_secondary_.get());
return static_cast<DBImplSecondary*>(db_secondary_);
}
void CheckFileTypeCounts(const std::string& dir, int expected_log,
@@ -68,7 +69,7 @@ class DBSecondaryTestBase : public DBBasicTestWithTimestampBase {
std::string secondary_path_;
std::vector<ColumnFamilyHandle*> handles_secondary_;
std::unique_ptr<DB> db_secondary_;
DB* db_secondary_;
};
void DBSecondaryTestBase::OpenSecondary(const Options& options) {
@@ -151,8 +152,8 @@ TEST_F(DBSecondaryTest, NonExistingDb) {
options.env = env_;
options.max_open_files = -1;
const std::string dbname = "/doesnt/exist";
std::unique_ptr<DB> dbptr;
Status s = DB::OpenAsSecondary(options, dbname, secondary_path_, &dbptr);
Status s =
DB::OpenAsSecondary(options, dbname, secondary_path_, &db_secondary_);
ASSERT_TRUE(s.IsIOError());
}
@@ -181,7 +182,7 @@ TEST_F(DBSecondaryTest, ReopenAsSecondary) {
ReadOptions ropts;
ropts.verify_checksums = true;
auto db1 = static_cast<DBImplSecondary*>(db_.get());
auto db1 = static_cast<DBImplSecondary*>(db_);
ASSERT_NE(nullptr, db1);
Iterator* iter = db1->NewIterator(ropts);
ASSERT_NE(nullptr, iter);
@@ -833,7 +834,7 @@ TEST_F(DBSecondaryTest, OpenWithSubsetOfColumnFamilies) {
options1.max_open_files = -1;
OpenSecondary(options1);
ASSERT_EQ(0, handles_secondary_.size());
ASSERT_NE(nullptr, db_secondary_.get());
ASSERT_NE(nullptr, db_secondary_);
ASSERT_OK(Put(0 /*cf*/, "foo", "foo_value"));
ASSERT_OK(Put(1 /*cf*/, "foo", "foo_value"));
@@ -1151,7 +1152,7 @@ TEST_F(DBSecondaryTest, DISABLED_SwitchWAL) {
for (int k = 0; k != 16; ++k) {
ASSERT_OK(Put("key" + std::to_string(k), "value" + std::to_string(k)));
ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
verify_db(dbfull(), db_secondary_.get());
verify_db(dbfull(), db_secondary_);
}
}
@@ -1220,7 +1221,7 @@ TEST_F(DBSecondaryTest, DISABLED_SwitchWALMultiColumnFamilies) {
TEST_SYNC_POINT(
"DBSecondaryTest::SwitchWALMultipleColumnFamilies:BeforeCatchUp");
ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
verify_db(dbfull(), handles_, db_secondary_.get(), handles_secondary_);
verify_db(dbfull(), handles_, db_secondary_, handles_secondary_);
SyncPoint::GetInstance()->ClearTrace();
}
}
@@ -1356,7 +1357,7 @@ TEST_F(DBSecondaryTest, OpenWithTransactionDB) {
TransactionDBOptions txn_db_opts;
ASSERT_OK(TransactionDB::Open(options, txn_db_opts, dbname_, &txn_db));
ASSERT_NE(txn_db, nullptr);
db_.reset(txn_db);
db_ = txn_db;
std::vector<std::string> cfs = {"new_CF"};
CreateColumnFamilies(cfs, options);
@@ -1560,7 +1561,7 @@ TEST_F(DBSecondaryTestWithTimestamp, IteratorAndGet) {
it->Next(), ++count, ++key) {
CheckIterUserEntry(it.get(), Key1(key), kTypeValue,
"value" + std::to_string(i), write_timestamps[i]);
get_value_and_check(db_.get(), read_opts, it->key(), it->value(),
get_value_and_check(db_, read_opts, it->key(), it->value(),
write_timestamps[i]);
}
ASSERT_OK(it->status());
@@ -1573,7 +1574,7 @@ TEST_F(DBSecondaryTestWithTimestamp, IteratorAndGet) {
it->Prev(), ++count, --key) {
CheckIterUserEntry(it.get(), Key1(key), kTypeValue,
"value" + std::to_string(i), write_timestamps[i]);
get_value_and_check(db_.get(), read_opts, it->key(), it->value(),
get_value_and_check(db_, read_opts, it->key(), it->value(),
write_timestamps[i]);
}
ASSERT_OK(it->status());
@@ -1595,7 +1596,7 @@ TEST_F(DBSecondaryTestWithTimestamp, IteratorAndGet) {
it->Valid(); it->Next(), ++key, ++count) {
CheckIterUserEntry(it.get(), Key1(key), kTypeValue,
"value" + std::to_string(i), write_timestamps[i]);
get_value_and_check(db_.get(), read_opts, it->key(), it->value(),
get_value_and_check(db_, read_opts, it->key(), it->value(),
write_timestamps[i]);
}
ASSERT_OK(it->status());
@@ -1605,7 +1606,7 @@ TEST_F(DBSecondaryTestWithTimestamp, IteratorAndGet) {
it->Valid(); it->Prev(), --key, ++count) {
CheckIterUserEntry(it.get(), Key1(key - 1), kTypeValue,
"value" + std::to_string(i), write_timestamps[i]);
get_value_and_check(db_.get(), read_opts, it->key(), it->value(),
get_value_and_check(db_, read_opts, it->key(), it->value(),
write_timestamps[i]);
}
ASSERT_OK(it->status());
+3 -2
View File
@@ -321,7 +321,7 @@ TEST_F(DBStatisticsTest, BytesWrittenStats) {
options.enable_pipelined_write = enable_pipelined_write;
ASSERT_OK(TransactionDB::Open(options, txn_db_opts, dbname_, &txn_db));
ASSERT_NE(txn_db, nullptr);
db_.reset(txn_db);
db_ = txn_db->GetBaseDB();
WriteOptions wopts;
TransactionOptions txn_opts;
@@ -351,7 +351,8 @@ TEST_F(DBStatisticsTest, BytesWrittenStats) {
WriteBatchInternal::kHeader);
// Cleanup
db_.reset();
db_ = nullptr;
delete txn_db;
}
}
+21 -6
View File
@@ -69,6 +69,14 @@ TEST_F(DBTablePropertiesTest, GetPropertiesOfAllTablesTest) {
// Create 4 tables
for (int table = 0; table < 4; ++table) {
// Use old meta name for table properties for one file
if (table == 3) {
SyncPoint::GetInstance()->SetCallBack(
"BlockBasedTableBuilder::WritePropertiesBlock:Meta", [&](void* meta) {
*static_cast<const std::string**>(meta) = &kPropertiesBlockOldName;
});
SyncPoint::GetInstance()->EnableProcessing();
}
// Build file
for (int i = 0; i < 10 + table; ++i) {
ASSERT_OK(
@@ -76,6 +84,7 @@ TEST_F(DBTablePropertiesTest, GetPropertiesOfAllTablesTest) {
}
ASSERT_OK(db_->Flush(FlushOptions()));
}
SyncPoint::GetInstance()->DisableProcessing();
std::string original_session_id;
ASSERT_OK(db_->GetDbSessionId(original_session_id));
@@ -90,7 +99,7 @@ TEST_F(DBTablePropertiesTest, GetPropertiesOfAllTablesTest) {
// Clear out auto-opened files
dbfull()->TEST_table_cache()->EraseUnRefEntries();
ASSERT_EQ(dbfull()->TEST_table_cache()->GetUsage(), 0U);
VerifyTableProperties(db_.get(), 10 + 11 + 12 + 13);
VerifyTableProperties(db_, 10 + 11 + 12 + 13);
// 2. Put two tables to table cache and
Reopen(options);
@@ -103,7 +112,7 @@ TEST_F(DBTablePropertiesTest, GetPropertiesOfAllTablesTest) {
Get(std::to_string(i * 100 + 0));
}
VerifyTableProperties(db_.get(), 10 + 11 + 12 + 13);
VerifyTableProperties(db_, 10 + 11 + 12 + 13);
// 3. Put all tables to table cache
Reopen(options);
@@ -111,7 +120,7 @@ TEST_F(DBTablePropertiesTest, GetPropertiesOfAllTablesTest) {
for (int i = 0; i < 4; ++i) {
Get(std::to_string(i * 100 + 0));
}
VerifyTableProperties(db_.get(), 10 + 11 + 12 + 13);
VerifyTableProperties(db_, 10 + 11 + 12 + 13);
// 4. Try to read CORRUPT properties (a) directly from file, and (b)
// through reader on Get
@@ -160,7 +169,10 @@ TEST_F(DBTablePropertiesTest, GetPropertiesOfAllTablesTest) {
SyncPoint::GetInstance()->DisableProcessing();
}
TEST_F(DBTablePropertiesTest, InvalidReportedAsCorruption) {
TEST_F(DBTablePropertiesTest, InvalidIgnored) {
// RocksDB versions 2.5 - 2.7 generate some properties that Block considers
// invalid in some way. This approximates that.
// Inject properties block data that Block considers invalid
SyncPoint::GetInstance()->SetCallBack(
"BlockBasedTableBuilder::WritePropertiesBlock:BlockData",
@@ -177,10 +189,13 @@ TEST_F(DBTablePropertiesTest, InvalidReportedAsCorruption) {
for (int i = 0; i < 10; ++i) {
ASSERT_OK(db_->Put(WriteOptions(), std::to_string(i), "val"));
}
// Corrupted properties block should be detected and reported as corruption
ASSERT_TRUE(db_->Flush(FlushOptions()).IsCorruption());
ASSERT_OK(db_->Flush(FlushOptions()));
SyncPoint::GetInstance()->DisableProcessing();
// Not crashing is good enough
TablePropertiesCollection props;
ASSERT_OK(db_->GetPropertiesOfAllTables(&props));
}
TEST_F(DBTablePropertiesTest, CreateOnDeletionCollectorFactory) {
+128 -86
View File
@@ -104,7 +104,7 @@ TEST_F(DBTest, MockEnvTest) {
Options options;
options.create_if_missing = true;
options.env = env.get();
std::unique_ptr<DB> db;
DB* db;
const Slice keys[] = {Slice("aaa"), Slice("bbb"), Slice("ccc")};
const Slice vals[] = {Slice("foo"), Slice("bar"), Slice("baz")};
@@ -132,7 +132,7 @@ TEST_F(DBTest, MockEnvTest) {
ASSERT_OK(iterator->status());
delete iterator;
DBImpl* dbi = static_cast_with_check<DBImpl>(db.get());
DBImpl* dbi = static_cast_with_check<DBImpl>(db);
ASSERT_OK(dbi->TEST_FlushMemTable());
for (size_t i = 0; i < 3; ++i) {
@@ -141,7 +141,7 @@ TEST_F(DBTest, MockEnvTest) {
ASSERT_TRUE(res == vals[i]);
}
db.reset();
delete db;
}
TEST_F(DBTest, RequestIdPlumbingTest) {
@@ -264,7 +264,7 @@ TEST_F(DBTest, MemEnvTest) {
Options options;
options.create_if_missing = true;
options.env = env.get();
std::unique_ptr<DB> db;
DB* db;
const Slice keys[] = {Slice("aaa"), Slice("bbb"), Slice("ccc")};
const Slice vals[] = {Slice("foo"), Slice("bar"), Slice("baz")};
@@ -292,7 +292,7 @@ TEST_F(DBTest, MemEnvTest) {
ASSERT_OK(iterator->status());
delete iterator;
DBImpl* dbi = static_cast_with_check<DBImpl>(db.get());
DBImpl* dbi = static_cast_with_check<DBImpl>(db);
ASSERT_OK(dbi->TEST_FlushMemTable());
for (size_t i = 0; i < 3; ++i) {
@@ -301,7 +301,7 @@ TEST_F(DBTest, MemEnvTest) {
ASSERT_TRUE(res == vals[i]);
}
db.reset();
delete db;
options.create_if_missing = false;
ASSERT_OK(DB::Open(options, "/dir/db", &db));
@@ -310,7 +310,7 @@ TEST_F(DBTest, MemEnvTest) {
ASSERT_OK(db->Get(ReadOptions(), keys[i], &res));
ASSERT_TRUE(res == vals[i]);
}
db.reset();
delete db;
}
TEST_F(DBTest, WriteEmptyBatch) {
@@ -1078,9 +1078,7 @@ TEST_F(DBTest, WrongLevel0Config) {
options.level0_stop_writes_trigger = 1;
options.level0_slowdown_writes_trigger = 2;
options.level0_file_num_compaction_trigger = 3;
{
ASSERT_OK(DB::Open(options, dbname_, &db_));
}
ASSERT_OK(DB::Open(options, dbname_, &db_));
}
TEST_F(DBTest, GetOrderedByLevels) {
@@ -1209,10 +1207,8 @@ TEST_F(DBTest, FlushSchedule) {
t.join();
}
auto default_tables =
GetNumberOfSstFilesForColumnFamily(db_.get(), "default");
auto pikachu_tables =
GetNumberOfSstFilesForColumnFamily(db_.get(), "pikachu");
auto default_tables = GetNumberOfSstFilesForColumnFamily(db_, "default");
auto pikachu_tables = GetNumberOfSstFilesForColumnFamily(db_, "pikachu");
ASSERT_LE(default_tables, static_cast<uint64_t>(10));
ASSERT_GT(default_tables, static_cast<uint64_t>(0));
ASSERT_LE(pikachu_tables, static_cast<uint64_t>(10));
@@ -2372,7 +2368,7 @@ TEST_F(DBTest, Snapshot) {
ASSERT_OK(Put(1, "foo", "1v3"));
{
ManagedSnapshot s3(db_.get());
ManagedSnapshot s3(db_);
ASSERT_EQ(3U, GetNumSnapshots());
ASSERT_EQ(time_snap1, GetTimeOldestSnapshots());
ASSERT_EQ(GetSequenceOldestSnapshots(), s1->GetSequenceNumber());
@@ -2729,43 +2725,37 @@ TEST_F(DBTest, DBOpen_Options) {
ASSERT_OK(DestroyDB(dbname, options));
// Does not exist, and create_if_missing == false: error
std::unique_ptr<DB> db;
DB* db = nullptr;
options.create_if_missing = false;
{
Status s = DB::Open(options, dbname, &db);
ASSERT_TRUE(strstr(s.ToString().c_str(), "does not exist") != nullptr);
}
Status s = DB::Open(options, dbname, &db);
ASSERT_TRUE(strstr(s.ToString().c_str(), "does not exist") != nullptr);
ASSERT_TRUE(db == nullptr);
// Does not exist, and create_if_missing == true: OK
options.create_if_missing = true;
{
Status s = DB::Open(options, dbname, &db);
ASSERT_OK(s);
}
s = DB::Open(options, dbname, &db);
ASSERT_OK(s);
ASSERT_TRUE(db != nullptr);
db.reset();
delete db;
db = nullptr;
// Does exist, and error_if_exists == true: error
options.create_if_missing = false;
options.error_if_exists = true;
{
Status s = DB::Open(options, dbname, &db);
ASSERT_TRUE(strstr(s.ToString().c_str(), "exists") != nullptr);
}
s = DB::Open(options, dbname, &db);
ASSERT_TRUE(strstr(s.ToString().c_str(), "exists") != nullptr);
ASSERT_TRUE(db == nullptr);
// Does exist, and error_if_exists == false: OK
options.create_if_missing = true;
options.error_if_exists = false;
{
Status s = DB::Open(options, dbname, &db);
ASSERT_OK(s);
}
s = DB::Open(options, dbname, &db);
ASSERT_OK(s);
ASSERT_TRUE(db != nullptr);
db.reset();
delete db;
db = nullptr;
}
TEST_F(DBTest, DBOpen_Change_NumLevels) {
@@ -2803,36 +2793,25 @@ TEST_F(DBTest, DestroyDBMetaDatabase) {
ASSERT_OK(DestroyDB(dbname, options));
// Setup databases
{
std::unique_ptr<DB> db;
ASSERT_OK(DB::Open(options, dbname, &db));
}
{
std::unique_ptr<DB> db;
ASSERT_OK(DB::Open(options, metadbname, &db));
}
{
std::unique_ptr<DB> db;
ASSERT_OK(DB::Open(options, metametadbname, &db));
}
DB* db = nullptr;
ASSERT_OK(DB::Open(options, dbname, &db));
delete db;
db = nullptr;
ASSERT_OK(DB::Open(options, metadbname, &db));
delete db;
db = nullptr;
ASSERT_OK(DB::Open(options, metametadbname, &db));
delete db;
db = nullptr;
// Delete databases
ASSERT_OK(DestroyDB(dbname, options));
// Check if deletion worked.
options.create_if_missing = false;
{
std::unique_ptr<DB> dbptr;
ASSERT_TRUE(!(DB::Open(options, dbname, &dbptr)).ok());
}
{
std::unique_ptr<DB> dbptr;
ASSERT_TRUE(!(DB::Open(options, metadbname, &dbptr)).ok());
}
{
std::unique_ptr<DB> dbptr;
ASSERT_TRUE(!(DB::Open(options, metametadbname, &dbptr)).ok());
}
ASSERT_TRUE(!(DB::Open(options, dbname, &db)).ok());
ASSERT_TRUE(!(DB::Open(options, metadbname, &db)).ok());
ASSERT_TRUE(!(DB::Open(options, metametadbname, &db)).ok());
}
TEST_F(DBTest, SnapshotFiles) {
@@ -2911,11 +2890,13 @@ TEST_F(DBTest, SnapshotFiles) {
column_families.emplace_back("default", ColumnFamilyOptions());
column_families.emplace_back("pikachu", ColumnFamilyOptions());
std::vector<ColumnFamilyHandle*> cf_handles;
std::unique_ptr<DB> snapdb;
DB* snapdb;
DBOptions opts;
opts.env = env_;
opts.create_if_missing = false;
ASSERT_OK(DB::Open(opts, snapdir, column_families, &cf_handles, &snapdb));
Status stat =
DB::Open(opts, snapdir, column_families, &cf_handles, &snapdb);
ASSERT_OK(stat);
ReadOptions roptions;
std::string val;
@@ -2926,7 +2907,7 @@ TEST_F(DBTest, SnapshotFiles) {
for (auto cfh : cf_handles) {
delete cfh;
}
snapdb.reset();
delete snapdb;
// look at the new live files after we added an 'extra' key
// and after we took the first snapshot.
@@ -3128,7 +3109,7 @@ struct MTThread {
static void MTThreadBody(void* arg) {
MTThread* t = static_cast<MTThread*>(arg);
int id = t->id;
DB* db = t->state->test->db_.get();
DB* db = t->state->test->db_;
int counter = 0;
std::shared_ptr<SystemClock> clock = SystemClock::Default();
auto end_micros = clock->NowMicros() + kTestSeconds * 1000000U;
@@ -3343,7 +3324,7 @@ TEST_F(DBTest, GroupCommitTest) {
GCThread thread[kGCNumThreads];
for (int id = 0; id < kGCNumThreads; id++) {
thread[id].id = id;
thread[id].db = db_.get();
thread[id].db = db_;
thread[id].done = false;
env_->StartThread(GCThreadBody, &thread[id]);
}
@@ -3710,8 +3691,6 @@ class ModelDB : public DB {
void EnableManualCompaction() override {}
void DisableManualCompaction() override {}
void AbortAllCompactions() override {}
void ResumeAllCompactions() override {}
Status WaitForCompact(
const WaitForCompactOptions& /* wait_for_compact_options */) override {
@@ -4015,10 +3994,8 @@ TEST_P(DBTestRandomized, Randomized) {
// than return a key that is close to it.
if (option_config_ != kBlockBasedTableWithWholeKeyHashIndex &&
option_config_ != kBlockBasedTableWithPrefixHashIndex) {
ASSERT_TRUE(
CompareIterators(step, &model, db_.get(), nullptr, nullptr));
ASSERT_TRUE(
CompareIterators(step, &model, db_.get(), model_snap, db_snap));
ASSERT_TRUE(CompareIterators(step, &model, db_, nullptr, nullptr));
ASSERT_TRUE(CompareIterators(step, &model, db_, model_snap, db_snap));
}
// Save a snapshot from each DB this time that we'll use next
@@ -4032,7 +4009,7 @@ TEST_P(DBTestRandomized, Randomized) {
}
Reopen(options);
ASSERT_TRUE(CompareIterators(step, &model, db_.get(), nullptr, nullptr));
ASSERT_TRUE(CompareIterators(step, &model, db_, nullptr, nullptr));
model_snap = model.GetSnapshot();
db_snap = db_->GetSnapshot();
@@ -5458,7 +5435,7 @@ TEST_P(DBTestWithParam, PreShutdownManualCompaction) {
// Compact all
MakeTables(1, "a", "z", 1);
ASSERT_EQ("1,0,2", FilesPerLevel(1));
CancelAllBackgroundWork(db_.get());
CancelAllBackgroundWork(db_);
ASSERT_TRUE(
db_->CompactRange(CompactRangeOptions(), handles_[1], nullptr, nullptr)
.IsShutdownInProgress());
@@ -5478,7 +5455,7 @@ TEST_F(DBTest, PreShutdownFlush) {
Options options = CurrentOptions();
CreateAndReopenWithCF({"pikachu"}, options);
ASSERT_OK(Put(1, "key", "value"));
CancelAllBackgroundWork(db_.get());
CancelAllBackgroundWork(db_);
Status s =
db_->CompactRange(CompactRangeOptions(), handles_[1], nullptr, nullptr);
ASSERT_TRUE(s.IsShutdownInProgress());
@@ -5559,7 +5536,7 @@ TEST_P(DBTestWithParam, PreShutdownMultipleCompaction) {
TEST_SYNC_POINT("DBTest::PreShutdownMultipleCompaction:Preshutdown");
ASSERT_GE(operation_count[ThreadStatus::OP_COMPACTION], 1);
CancelAllBackgroundWork(db_.get());
CancelAllBackgroundWork(db_);
TEST_SYNC_POINT("DBTest::PreShutdownMultipleCompaction:VerifyPreshutdown");
ASSERT_OK(dbfull()->TEST_WaitForBackgroundWork());
// Record the number of compactions at a time.
@@ -5645,7 +5622,7 @@ TEST_P(DBTestWithParam, PreShutdownCompactionMiddle) {
}
ASSERT_GE(operation_count[ThreadStatus::OP_COMPACTION], 1);
CancelAllBackgroundWork(db_.get());
CancelAllBackgroundWork(db_);
TEST_SYNC_POINT("DBTest::PreShutdownCompactionMiddle:Preshutdown");
TEST_SYNC_POINT("DBTest::PreShutdownCompactionMiddle:VerifyPreshutdown");
ASSERT_OK(dbfull()->TEST_WaitForBackgroundWork());
@@ -5666,7 +5643,7 @@ TEST_F(DBTest, FlushOnDestroy) {
WriteOptions wo;
wo.disableWAL = true;
ASSERT_OK(Put("foo", "v1", wo));
CancelAllBackgroundWork(db_.get());
CancelAllBackgroundWork(db_);
}
TEST_F(DBTest, DynamicCompactionOptions) {
@@ -6205,6 +6182,72 @@ TEST_F(DBTest, L0L1L2AndUpHitCounter) {
TestGetTickerCount(options, GET_HIT_L2_AND_UP));
}
TEST_F(DBTest, EncodeDecompressedBlockSizeTest) {
// Allow testing format_version=1
bool& allow_unsupported_fv = TEST_AllowUnsupportedFormatVersion();
SaveAndRestore guard(&allow_unsupported_fv);
ASSERT_FALSE(allow_unsupported_fv);
// iter 0 -- zlib
// iter 1 -- bzip2
// iter 2 -- lz4
// iter 3 -- lz4HC
// iter 4 -- xpress
CompressionType compressions[] = {kZlibCompression, kBZip2Compression,
kLZ4Compression, kLZ4HCCompression,
kXpressCompression};
for (auto comp : compressions) {
if (!CompressionTypeSupported(comp)) {
continue;
}
// first_table_version 1 -- generate with table_version == 1, read with
// table_version == 2
// first_table_version 2 -- generate with table_version == 2, read with
// table_version == 1
for (int first_table_version = 1; first_table_version <= 2;
++first_table_version) {
BlockBasedTableOptions table_options;
table_options.format_version = first_table_version;
table_options.filter_policy.reset(NewBloomFilterPolicy(10));
Options options = CurrentOptions();
// Hack to generate old files (checked in factory construction)
allow_unsupported_fv = true;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
ASSERT_EQ(options.table_factory->GetOptions<BlockBasedTableOptions>()
->format_version,
first_table_version);
// Able to read old files without the hack
allow_unsupported_fv = false;
options.create_if_missing = true;
options.compression = comp;
DestroyAndReopen(options);
int kNumKeysWritten = 1000;
Random rnd(301);
for (int i = 0; i < kNumKeysWritten; ++i) {
// compressible string
ASSERT_OK(Put(Key(i), rnd.RandomString(128) + std::string(128, 'a')));
}
ASSERT_OK(Flush());
table_options.format_version = first_table_version == 1 ? 2 : 1;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
// format_version (for writing) is sanitized to minimum supported
ASSERT_EQ(options.table_factory->GetOptions<BlockBasedTableOptions>()
->format_version,
BlockBasedTableFactory::kMinSupportedFormatVersion);
Reopen(options);
for (int i = 0; i < kNumKeysWritten; ++i) {
auto r = Get(Key(i));
ASSERT_EQ(r.substr(128), std::string(128, 'a'));
}
}
}
}
TEST_F(DBTest, CloseSpeedup) {
Options options = CurrentOptions();
options.compaction_style = kCompactionStyleLevel;
@@ -6441,8 +6484,7 @@ TEST_P(DBTestWithParam, CompactionTotalTimeTest) {
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
// Hard-coded number in CompactionJob::ProcessKeyValueCompaction().
// Uses 1024 (power of 2) for efficient bitwise check.
const int kRecordStatsEvery = 1024;
const int kRecordStatsEvery = 1000;
// The stat COMPACTION_CPU_TOTAL_TIME should be recorded
// during compaction and once more after compaction.
ASSERT_EQ(n / kRecordStatsEvery + 1, record_count);
@@ -6534,7 +6576,7 @@ TEST_F(DBTest, SuggestCompactRangeTest) {
// compact it three times
for (int i = 0; i < 3; ++i) {
ASSERT_OK(experimental::SuggestCompactRange(db_.get(), nullptr, nullptr));
ASSERT_OK(experimental::SuggestCompactRange(db_, nullptr, nullptr));
ASSERT_OK(dbfull()->TEST_WaitForCompact());
}
@@ -6547,7 +6589,7 @@ TEST_F(DBTest, SuggestCompactRangeTest) {
// nonoverlapping with the file on level 0
Slice start("a"), end("b");
ASSERT_OK(experimental::SuggestCompactRange(db_.get(), &start, &end));
ASSERT_OK(experimental::SuggestCompactRange(db_, &start, &end));
ASSERT_OK(dbfull()->TEST_WaitForCompact());
// should not compact the level 0 file
@@ -6555,7 +6597,7 @@ TEST_F(DBTest, SuggestCompactRangeTest) {
start = Slice("j");
end = Slice("m");
ASSERT_OK(experimental::SuggestCompactRange(db_.get(), &start, &end));
ASSERT_OK(experimental::SuggestCompactRange(db_, &start, &end));
ASSERT_OK(dbfull()->TEST_WaitForCompact());
// SuggestCompactRange() is not going to be reported as manual compaction
ASSERT_TRUE(!CompactionFilterFactoryGetContext::IsManual(
@@ -6606,7 +6648,7 @@ TEST_F(DBTest, SuggestCompactRangeUniversal) {
// nonoverlapping with the file on level 0
Slice start("a"), end("b");
ASSERT_OK(experimental::SuggestCompactRange(db_.get(), &start, &end));
ASSERT_OK(experimental::SuggestCompactRange(db_, &start, &end));
ASSERT_OK(dbfull()->TEST_WaitForCompact());
// should not compact the level 0 file
@@ -6614,7 +6656,7 @@ TEST_F(DBTest, SuggestCompactRangeUniversal) {
start = Slice("j");
end = Slice("m");
ASSERT_OK(experimental::SuggestCompactRange(db_.get(), &start, &end));
ASSERT_OK(experimental::SuggestCompactRange(db_, &start, &end));
ASSERT_OK(dbfull()->TEST_WaitForCompact());
// now it should compact the level 0 file to the last level
@@ -6651,7 +6693,7 @@ TEST_F(DBTest, PromoteL0) {
ASSERT_EQ(NumTableFilesAtLevel(1, 0), 0); // No files in L1
// Promote L0 level to L2.
ASSERT_OK(experimental::PromoteL0(db_.get(), db_->DefaultColumnFamily(), 2));
ASSERT_OK(experimental::PromoteL0(db_, db_->DefaultColumnFamily(), 2));
// We expect that all the files were trivially moved from L0 to L2
ASSERT_EQ(NumTableFilesAtLevel(0, 0), 0);
ASSERT_EQ(NumTableFilesAtLevel(2, 0), level0_files);
@@ -6676,7 +6718,7 @@ TEST_F(DBTest, PromoteL0Failure) {
Status status;
// Fails because L0 has overlapping files.
status = experimental::PromoteL0(db_.get(), db_->DefaultColumnFamily());
status = experimental::PromoteL0(db_, db_->DefaultColumnFamily());
ASSERT_TRUE(status.IsInvalidArgument());
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
@@ -6686,7 +6728,7 @@ TEST_F(DBTest, PromoteL0Failure) {
ASSERT_OK(Put(Key(5), ""));
ASSERT_OK(Flush());
// Fails because L1 is non-empty.
status = experimental::PromoteL0(db_.get(), db_->DefaultColumnFamily());
status = experimental::PromoteL0(db_, db_->DefaultColumnFamily());
ASSERT_TRUE(status.IsInvalidArgument());
}
@@ -7757,7 +7799,7 @@ TEST_F(DBTest, ShuttingDownNotBlockStalledWrites) {
});
TEST_SYNC_POINT("DBTest::ShuttingDownNotBlockStalledWrites");
CancelAllBackgroundWork(db_.get(), true);
CancelAllBackgroundWork(db_, true);
thd.join();
}
+81 -79
View File
@@ -39,7 +39,7 @@ class DBTest2 : public DBTestBase {
};
TEST_F(DBTest2, OpenForReadOnly) {
std::unique_ptr<DB> db_ptr;
DB* db_ptr = nullptr;
std::string dbname = test::PerThreadDBPath("db_readonly");
Options options = CurrentOptions();
options.create_if_missing = true;
@@ -63,7 +63,7 @@ TEST_F(DBTest2, OpenForReadOnly) {
}
TEST_F(DBTest2, OpenForReadOnlyWithColumnFamilies) {
std::unique_ptr<DB> db_ptr;
DB* db_ptr = nullptr;
std::string dbname = test::PerThreadDBPath("db_readonly");
Options options = CurrentOptions();
options.create_if_missing = true;
@@ -349,9 +349,9 @@ TEST_P(DBTestSharedWriteBufferAcrossCFs, SharedWriteBufferAcrossCFs) {
ASSERT_OK(Put(3, Key(1), DummyString(1), wo));
ASSERT_OK(Put(0, Key(1), DummyString(1), wo));
ASSERT_OK(Flush(0));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "default"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
static_cast<uint64_t>(1));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "nikitich"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
static_cast<uint64_t>(1));
flush_listener->expected_flush_reason = FlushReason::kWriteBufferManager;
@@ -371,13 +371,13 @@ TEST_P(DBTestSharedWriteBufferAcrossCFs, SharedWriteBufferAcrossCFs) {
// No flush should trigger
wait_flush();
{
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "default"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
static_cast<uint64_t>(1));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "pikachu"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
static_cast<uint64_t>(0));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "dobrynia"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
static_cast<uint64_t>(0));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "nikitich"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
static_cast<uint64_t>(1));
}
@@ -387,13 +387,13 @@ TEST_P(DBTestSharedWriteBufferAcrossCFs, SharedWriteBufferAcrossCFs) {
ASSERT_OK(Put(0, Key(1), DummyString(1), wo));
wait_flush();
{
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "default"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
static_cast<uint64_t>(1));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "pikachu"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
static_cast<uint64_t>(0));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "dobrynia"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
static_cast<uint64_t>(0));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "nikitich"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
static_cast<uint64_t>(2));
}
@@ -405,13 +405,13 @@ TEST_P(DBTestSharedWriteBufferAcrossCFs, SharedWriteBufferAcrossCFs) {
ASSERT_OK(Put(2, Key(1), DummyString(1), wo));
wait_flush();
{
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "default"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
static_cast<uint64_t>(1));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "pikachu"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
static_cast<uint64_t>(0));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "dobrynia"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
static_cast<uint64_t>(0));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "nikitich"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
static_cast<uint64_t>(2));
}
@@ -428,13 +428,13 @@ TEST_P(DBTestSharedWriteBufferAcrossCFs, SharedWriteBufferAcrossCFs) {
ASSERT_OK(Put(0, Key(1), DummyString(1), wo));
wait_flush();
{
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "default"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
static_cast<uint64_t>(2));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "pikachu"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
static_cast<uint64_t>(0));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "dobrynia"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
static_cast<uint64_t>(0));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "nikitich"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
static_cast<uint64_t>(2));
}
@@ -450,13 +450,13 @@ TEST_P(DBTestSharedWriteBufferAcrossCFs, SharedWriteBufferAcrossCFs) {
wait_flush();
{
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "default"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
static_cast<uint64_t>(2));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "pikachu"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
static_cast<uint64_t>(0));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "dobrynia"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
static_cast<uint64_t>(1));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "nikitich"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
static_cast<uint64_t>(2));
}
if (cost_cache_) {
@@ -506,7 +506,7 @@ TEST_F(DBTest2, SharedWriteBufferLimitAcrossDB) {
CreateAndReopenWithCF({"cf1", "cf2"}, options);
ASSERT_OK(DestroyDB(dbname2, options));
std::unique_ptr<DB> db2;
DB* db2 = nullptr;
ASSERT_OK(DB::Open(options, dbname2, &db2));
WriteOptions wo;
@@ -516,12 +516,12 @@ TEST_F(DBTest2, SharedWriteBufferLimitAcrossDB) {
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[0]));
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[1]));
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[2]));
ASSERT_OK(static_cast<DBImpl*>(db2.get())->TEST_WaitForFlushMemTable());
ASSERT_OK(static_cast<DBImpl*>(db2)->TEST_WaitForFlushMemTable());
// Ensure background work is fully finished including listener callbacks
// before accessing listener state.
ASSERT_OK(dbfull()->TEST_WaitForBackgroundWork());
ASSERT_OK(static_cast_with_check<DBImpl>(db2.get())
->TEST_WaitForBackgroundWork());
ASSERT_OK(
static_cast_with_check<DBImpl>(db2)->TEST_WaitForBackgroundWork());
};
// Trigger a flush on cf2
@@ -537,13 +537,13 @@ TEST_F(DBTest2, SharedWriteBufferLimitAcrossDB) {
ASSERT_OK(Put(2, Key(1), DummyString(1), wo));
wait_flush();
ASSERT_OK(static_cast<DBImpl*>(db2.get())->TEST_WaitForFlushMemTable());
ASSERT_OK(static_cast<DBImpl*>(db2)->TEST_WaitForFlushMemTable());
{
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "default") +
GetNumberOfSstFilesForColumnFamily(db_.get(), "cf1") +
GetNumberOfSstFilesForColumnFamily(db_.get(), "cf2"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default") +
GetNumberOfSstFilesForColumnFamily(db_, "cf1") +
GetNumberOfSstFilesForColumnFamily(db_, "cf2"),
static_cast<uint64_t>(1));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db2.get(), "default"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db2, "default"),
static_cast<uint64_t>(0));
}
@@ -553,13 +553,13 @@ TEST_F(DBTest2, SharedWriteBufferLimitAcrossDB) {
ASSERT_OK(Put(2, Key(1), DummyString(1), wo));
wait_flush();
{
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "default"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
static_cast<uint64_t>(1));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "cf1"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "cf1"),
static_cast<uint64_t>(0));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "cf2"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "cf2"),
static_cast<uint64_t>(1));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db2.get(), "default"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db2, "default"),
static_cast<uint64_t>(0));
}
@@ -568,19 +568,19 @@ TEST_F(DBTest2, SharedWriteBufferLimitAcrossDB) {
wait_flush();
ASSERT_OK(db2->Put(wo, Key(1), DummyString(1)));
wait_flush();
ASSERT_OK(static_cast<DBImpl*>(db2.get())->TEST_WaitForFlushMemTable());
ASSERT_OK(static_cast<DBImpl*>(db2)->TEST_WaitForFlushMemTable());
{
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "default"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
static_cast<uint64_t>(1));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "cf1"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "cf1"),
static_cast<uint64_t>(0));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "cf2"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "cf2"),
static_cast<uint64_t>(1));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db2.get(), "default"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db2, "default"),
static_cast<uint64_t>(1));
}
db2.reset();
delete db2;
ASSERT_OK(DestroyDB(dbname2, options));
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
@@ -785,7 +785,7 @@ TEST_F(DBTest2, WalFilterTest) {
while (true) {
// Ensure that expected keys exists
// and not expected keys don't exist after recovery
ValidateKeyExistence(db_.get(), keys_must_exist, keys_must_not_exist);
ValidateKeyExistence(db_, keys_must_exist, keys_must_not_exist);
if (checked_after_reopen) {
break;
@@ -922,7 +922,7 @@ TEST_F(DBTest2, WalFilterTestWithChangeBatch) {
while (true) {
// Ensure that expected keys exists
// and not expected keys don't exist after recovery
ValidateKeyExistence(db_.get(), keys_must_exist, keys_must_not_exist);
ValidateKeyExistence(db_, keys_must_exist, keys_must_not_exist);
if (checked_after_reopen) {
break;
@@ -1004,7 +1004,7 @@ TEST_F(DBTest2, WalFilterTestWithChangeBatchExtraKeys) {
}
}
ValidateKeyExistence(db_.get(), keys_must_exist, keys_must_not_exist);
ValidateKeyExistence(db_, keys_must_exist, keys_must_not_exist);
}
TEST_F(DBTest2, WalFilterTestWithColumnFamilies) {
@@ -1292,7 +1292,7 @@ TEST_F(DBTest2, DuplicateSnapshot) {
Options options;
options = CurrentOptions(options);
std::vector<const Snapshot*> snapshots;
DBImpl* dbi = dbfull();
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
SequenceNumber oldest_ww_snap, first_ww_snap;
ASSERT_OK(Put("k", "v")); // inc seq
@@ -3694,16 +3694,16 @@ TEST_F(DBTest2, TraceAndReplay) {
// Using a different name than db2, to pacify infer's use-after-lifetime
// warnings (http://fbinfer.com).
std::unique_ptr<DB> db2_init;
DB* db2_init = nullptr;
options.create_if_missing = true;
ASSERT_OK(DB::Open(options, dbname2, &db2_init));
ColumnFamilyHandle* cf;
ASSERT_OK(
db2_init->CreateColumnFamily(ColumnFamilyOptions(), "pikachu", &cf));
delete cf;
db2_init.reset();
delete db2_init;
std::unique_ptr<DB> db2;
DB* db2 = nullptr;
std::vector<ColumnFamilyDescriptor> column_families;
ColumnFamilyOptions cf_options;
cf_options.merge_operator = MergeOperators::CreatePutOperator();
@@ -3790,7 +3790,7 @@ TEST_F(DBTest2, TraceAndReplay) {
for (auto handle : handles) {
delete handle;
}
db2.reset();
delete db2;
ASSERT_OK(DestroyDB(dbname2, options));
}
@@ -3885,16 +3885,16 @@ TEST_F(DBTest2, TraceAndManualReplay) {
// Using a different name than db2, to pacify infer's use-after-lifetime
// warnings (http://fbinfer.com).
std::unique_ptr<DB> db2_init;
DB* db2_init = nullptr;
options.create_if_missing = true;
ASSERT_OK(DB::Open(options, dbname2, &db2_init));
ColumnFamilyHandle* cf;
ASSERT_OK(
db2_init->CreateColumnFamily(ColumnFamilyOptions(), "pikachu", &cf));
delete cf;
db2_init.reset();
delete db2_init;
std::unique_ptr<DB> db2;
DB* db2 = nullptr;
std::vector<ColumnFamilyDescriptor> column_families;
ColumnFamilyOptions cf_options;
cf_options.merge_operator = MergeOperators::CreatePutOperator();
@@ -4130,7 +4130,7 @@ TEST_F(DBTest2, TraceAndManualReplay) {
for (auto handle : handles) {
delete handle;
}
db2.reset();
delete db2;
ASSERT_OK(DestroyDB(dbname2, options));
}
@@ -4161,16 +4161,16 @@ TEST_F(DBTest2, TraceWithLimit) {
// Using a different name than db2, to pacify infer's use-after-lifetime
// warnings (http://fbinfer.com).
std::unique_ptr<DB> db2_init;
DB* db2_init = nullptr;
options.create_if_missing = true;
ASSERT_OK(DB::Open(options, dbname2, &db2_init));
ColumnFamilyHandle* cf;
ASSERT_OK(
db2_init->CreateColumnFamily(ColumnFamilyOptions(), "pikachu", &cf));
delete cf;
db2_init.reset();
delete db2_init;
std::unique_ptr<DB> db2;
DB* db2 = nullptr;
std::vector<ColumnFamilyDescriptor> column_families;
ColumnFamilyOptions cf_options;
cf_options.merge_operator = MergeOperators::CreatePutOperator();
@@ -4203,7 +4203,7 @@ TEST_F(DBTest2, TraceWithLimit) {
for (auto handle : handles) {
delete handle;
}
db2.reset();
delete db2;
ASSERT_OK(DestroyDB(dbname2, options));
}
@@ -4235,16 +4235,16 @@ TEST_F(DBTest2, TraceWithSampling) {
// Using a different name than db2, to pacify infer's use-after-lifetime
// warnings (http://fbinfer.com).
std::unique_ptr<DB> db2_init;
DB* db2_init = nullptr;
options.create_if_missing = true;
ASSERT_OK(DB::Open(options, dbname2, &db2_init));
ColumnFamilyHandle* cf;
ASSERT_OK(
db2_init->CreateColumnFamily(ColumnFamilyOptions(), "pikachu", &cf));
delete cf;
db2_init.reset();
delete db2_init;
std::unique_ptr<DB> db2;
DB* db2 = nullptr;
std::vector<ColumnFamilyDescriptor> column_families;
ColumnFamilyOptions cf_options;
column_families.emplace_back("default", cf_options);
@@ -4279,7 +4279,7 @@ TEST_F(DBTest2, TraceWithSampling) {
for (auto handle : handles) {
delete handle;
}
db2.reset();
delete db2;
ASSERT_OK(DestroyDB(dbname2, options));
}
@@ -4339,16 +4339,16 @@ TEST_F(DBTest2, TraceWithFilter) {
// Using a different name than db2, to pacify infer's use-after-lifetime
// warnings (http://fbinfer.com).
std::unique_ptr<DB> db2_init;
DB* db2_init = nullptr;
options.create_if_missing = true;
ASSERT_OK(DB::Open(options, dbname2, &db2_init));
ColumnFamilyHandle* cf;
ASSERT_OK(
db2_init->CreateColumnFamily(ColumnFamilyOptions(), "pikachu", &cf));
delete cf;
db2_init.reset();
delete db2_init;
std::unique_ptr<DB> db2;
DB* db2 = nullptr;
std::vector<ColumnFamilyDescriptor> column_families;
ColumnFamilyOptions cf_options;
cf_options.merge_operator = MergeOperators::CreatePutOperator();
@@ -4384,28 +4384,28 @@ TEST_F(DBTest2, TraceWithFilter) {
for (auto handle : handles) {
delete handle;
}
db2.reset();
delete db2;
ASSERT_OK(DestroyDB(dbname2, options));
// Set up a new db.
std::string dbname3 = test::PerThreadDBPath(env_, "db_not_trace_read");
ASSERT_OK(DestroyDB(dbname3, options));
std::unique_ptr<DB> db3_init;
DB* db3_init = nullptr;
options.create_if_missing = true;
ColumnFamilyHandle* cf3;
ASSERT_OK(DB::Open(options, dbname3, &db3_init));
ASSERT_OK(
db3_init->CreateColumnFamily(ColumnFamilyOptions(), "pikachu", &cf3));
delete cf3;
db3_init.reset();
delete db3_init;
column_families.clear();
column_families.emplace_back("default", cf_options);
column_families.emplace_back("pikachu", ColumnFamilyOptions());
handles.clear();
std::unique_ptr<DB> db3;
DB* db3 = nullptr;
ASSERT_OK(DB::Open(db_opts, dbname3, column_families, &handles, &db3));
env_->SleepForMicroseconds(100);
@@ -4435,7 +4435,7 @@ TEST_F(DBTest2, TraceWithFilter) {
for (auto handle : handles) {
delete handle;
}
db3.reset();
delete db3;
ASSERT_OK(DestroyDB(dbname3, options));
std::unique_ptr<TraceReader> trace_reader3;
@@ -4626,7 +4626,7 @@ TEST_F(DBTest2, TestGetColumnFamilyHandleUnlocked) {
CreateColumnFamilies({"test1", "test2"}, Options());
ASSERT_EQ(handles_.size(), 2);
DBImpl* dbi = dbfull();
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
port::Thread user_thread1([&]() {
auto cfh = dbi->GetColumnFamilyHandleUnlocked(handles_[0]->GetID());
ASSERT_EQ(cfh->GetID(), handles_[0]->GetID());
@@ -4830,7 +4830,7 @@ TEST_F(DBTest2, MultiDBParallelOpenTest) {
// Verify empty DBs can be created in parallel
std::vector<std::thread> open_threads;
std::vector<std::unique_ptr<DB>> dbs(kNumDbs);
std::vector<DB*> dbs{static_cast<unsigned int>(kNumDbs), nullptr};
options.create_if_missing = true;
for (int i = 0; i < kNumDbs; ++i) {
open_threads.emplace_back(
@@ -4845,7 +4845,7 @@ TEST_F(DBTest2, MultiDBParallelOpenTest) {
for (int i = 0; i < kNumDbs; ++i) {
open_threads[i].join();
ASSERT_OK(dbs[i]->Put(WriteOptions(), "xi", "gua"));
dbs[i].reset();
delete dbs[i];
}
// Verify non-empty DBs can be recovered in parallel
@@ -4861,7 +4861,7 @@ TEST_F(DBTest2, MultiDBParallelOpenTest) {
// Wait and cleanup
for (int i = 0; i < kNumDbs; ++i) {
open_threads[i].join();
dbs[i].reset();
delete dbs[i];
ASSERT_OK(DestroyDB(dbnames[i], options));
}
}
@@ -4922,7 +4922,8 @@ TEST_F(DBTest2, CloseWithUnreleasedSnapshot) {
ASSERT_NOK(db_->Close());
db_->ReleaseSnapshot(ss);
ASSERT_OK(db_->Close());
db_.reset();
delete db_;
db_ = nullptr;
}
TEST_F(DBTest2, PrefixBloomReseek) {
@@ -6773,7 +6774,7 @@ TEST_F(DBTest2, CheckpointFileTemperature) {
test_fs->PopRequestedSstFileTemperatures();
Checkpoint* checkpoint;
ASSERT_OK(Checkpoint::Create(db_.get(), &checkpoint));
ASSERT_OK(Checkpoint::Create(db_, &checkpoint));
ASSERT_OK(
checkpoint->CreateCheckpoint(dbname_ + kFilePathSeparator + "tempcp"));
@@ -7535,7 +7536,7 @@ TEST_F(DBTest2, GetFileChecksumsFromCurrentManifest_CRC32) {
opts.level0_file_num_compaction_trigger = 10;
// Bootstrap the test database.
std::unique_ptr<DB> db;
DB* db = nullptr;
std::string dbname = test::PerThreadDBPath("file_chksum");
ASSERT_OK(DB::Open(opts, dbname, &db));
@@ -7569,7 +7570,8 @@ TEST_F(DBTest2, GetFileChecksumsFromCurrentManifest_CRC32) {
db->GetLiveFilesMetaData(&live_files);
ASSERT_OK(db->Close());
db.reset();
delete db;
db = nullptr;
// Process current MANIFEST file and build internal file checksum mappings.
std::unique_ptr<FileChecksumList> checksum_list(NewFileChecksumList());
+10 -13
View File
@@ -97,7 +97,7 @@ DBTestBase::DBTestBase(const std::string path, bool env_do_fsync)
EXPECT_OK(DestroyDB(dbname_, delete_options));
// Destroy it for not alternative WAL dir is used.
EXPECT_OK(DestroyDB(dbname_, options));
db_.reset();
db_ = nullptr;
Reopen(options);
Random::GetTLSInstance()->Reset(0xdeadbeef);
}
@@ -520,7 +520,7 @@ Options DBTestBase::GetOptions(
}
case kBlockBasedTableWithLatestFormat: {
// In case different from default
table_options.format_version = kLatestBbtFormatVersion;
table_options.format_version = kLatestFormatVersion;
break;
}
case kOptimizeFiltersForHits: {
@@ -664,8 +664,7 @@ Status DBTestBase::TryReopenWithColumnFamilies(
DBOptions db_opts = DBOptions(options[0]);
last_options_ = options[0];
MaybeInstallTimeElapseOnlySleep(db_opts);
Status s = DB::Open(db_opts, dbname_, column_families, &handles_, &db_);
return s;
return DB::Open(db_opts, dbname_, column_families, &handles_, &db_);
}
Status DBTestBase::TryReopenWithColumnFamilies(
@@ -684,7 +683,8 @@ void DBTestBase::Close() {
EXPECT_OK(db_->DestroyColumnFamilyHandle(h));
}
handles_.clear();
db_.reset();
delete db_;
db_ = nullptr;
}
void DBTestBase::DestroyAndReopen(const Options& options) {
@@ -709,8 +709,7 @@ void DBTestBase::Destroy(const Options& options, bool delete_cf_paths) {
Status DBTestBase::ReadOnlyReopen(const Options& options) {
Close();
MaybeInstallTimeElapseOnlySleep(options);
Status s = DB::OpenForReadOnly(options, dbname_, &db_);
return s;
return DB::OpenForReadOnly(options, dbname_, &db_);
}
Status DBTestBase::EnforcedReadOnlyReopen(const Options& options) {
@@ -721,8 +720,7 @@ Status DBTestBase::EnforcedReadOnlyReopen(const Options& options) {
std::make_shared<ReadOnlyFileSystem>(env_->GetFileSystem());
env_read_only_ = std::make_shared<CompositeEnvWrapper>(env_, fs_read_only);
options_copy.env = env_read_only_.get();
Status s = DB::OpenForReadOnly(options_copy, dbname_, &db_);
return s;
return DB::OpenForReadOnly(options_copy, dbname_, &db_);
}
Status DBTestBase::TryReopen(const Options& options) {
@@ -737,8 +735,7 @@ Status DBTestBase::TryReopen(const Options& options) {
// clears the block cache.
last_options_ = options;
MaybeInstallTimeElapseOnlySleep(options);
Status s = DB::Open(options, dbname_, &db_);
return s;
return DB::Open(options, dbname_, &db_);
}
bool DBTestBase::IsDirectIOSupported() {
@@ -1165,7 +1162,7 @@ int DBTestBase::NumTableFilesAtLevel(int level, int cf) {
int DBTestBase::NumTableFilesAtLevel(int level, ColumnFamilyHandle* cfh,
DB* db) {
if (!db) {
db = db_.get();
db = db_;
}
std::string property;
EXPECT_TRUE(db->GetProperty(
@@ -1211,7 +1208,7 @@ std::string DBTestBase::FilesPerLevel(int cf) {
std::string DBTestBase::FilesPerLevel(ColumnFamilyHandle* cfh, DB* db) {
if (!db) {
db = db_.get();
db = db_;
}
int num_levels = db->NumberLevels(cfh);
std::string result;
+2 -2
View File
@@ -1072,7 +1072,7 @@ class DBTestBase : public testing::Test {
SpecialEnv* env_;
std::shared_ptr<Env> env_read_only_;
std::shared_ptr<Env> env_guard_;
std::unique_ptr<DB> db_;
DB* db_;
std::vector<ColumnFamilyHandle*> handles_;
int option_config_;
@@ -1157,7 +1157,7 @@ class DBTestBase : public testing::Test {
const anon::OptionsOverride& options_override =
anon::OptionsOverride()) const;
DBImpl* dbfull() { return static_cast_with_check<DBImpl>(db_.get()); }
DBImpl* dbfull() { return static_cast_with_check<DBImpl>(db_); }
void CreateColumnFamilies(const std::vector<std::string>& cfs,
const Options& options);
+29 -33
View File
@@ -395,13 +395,13 @@ TEST_P(DBWALTestWithTimestamp, RecoverAndNoFlush) {
read_opts.timestamp = &ts_slice;
ASSERT_OK(CreateAndReopenWithTs({"pikachu"}, ts_options, persist_udt,
avoid_flush_during_recovery));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "pikachu"), 0U);
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"), 0U);
ASSERT_OK(Put(1, "foo", ts1, "v1"));
ASSERT_OK(Put(1, "baz", ts1, "v5"));
ASSERT_OK(ReopenColumnFamiliesWithTs({"pikachu"}, ts_options, persist_udt,
avoid_flush_during_recovery));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "pikachu"), 0U);
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"), 0U);
// Do a timestamped read with ts1 after second reopen.
CheckGet(read_opts, 1, "foo", "v1", ts1);
CheckGet(read_opts, 1, "baz", "v5", ts1);
@@ -415,7 +415,7 @@ TEST_P(DBWALTestWithTimestamp, RecoverAndNoFlush) {
ASSERT_OK(ReopenColumnFamiliesWithTs({"pikachu"}, ts_options, persist_udt,
avoid_flush_during_recovery));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "pikachu"), 0U);
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"), 0U);
std::string ts3;
PutFixed64(&ts3, 3);
ASSERT_OK(Put(1, "foo", ts3, "v4"));
@@ -466,14 +466,14 @@ TEST_P(DBWALTestWithTimestamp, RecoverAndFlush) {
ASSERT_OK(CreateAndReopenWithTs({"pikachu"}, ts_options, persist_udt));
// No flush, no sst files, because of no data.
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "pikachu"), 0U);
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"), 0U);
ASSERT_OK(Put(1, largest_ukey_without_ts, write_ts, "v1"));
ASSERT_OK(Put(1, smallest_ukey_without_ts, write_ts, "v5"));
ASSERT_OK(ReopenColumnFamiliesWithTs({"pikachu"}, ts_options, persist_udt));
// Memtable recovered from WAL flushed because `avoid_flush_during_recovery`
// defaults to false, created one L0 file.
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "pikachu"), 1U);
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"), 1U);
std::vector<std::vector<FileMetaData>> level_to_files;
dbfull()->TEST_GetFilesMetaData(handles_[1], &level_to_files);
@@ -1347,7 +1347,7 @@ TEST_F(DBWALTest, RecoverCheckFileAmountWithSmallWriteBuffer) {
auto tables = ListTableFiles(env_, dbname_);
ASSERT_EQ(tables.size(), static_cast<size_t>(1));
// Make sure 'dobrynia' was flushed: check sst files amount
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "dobrynia"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
static_cast<uint64_t>(1));
}
// New WAL file
@@ -1363,16 +1363,16 @@ TEST_F(DBWALTest, RecoverCheckFileAmountWithSmallWriteBuffer) {
options);
{
// No inserts => default is empty
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "default"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
static_cast<uint64_t>(0));
// First 4 keys goes to separate SSTs + 1 more SST for 2 smaller keys
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "pikachu"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
static_cast<uint64_t>(5));
// 1 SST for big key + 1 SST for small one
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "dobrynia"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
static_cast<uint64_t>(2));
// 1 SST for all keys
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "nikitich"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
static_cast<uint64_t>(1));
}
}
@@ -1401,7 +1401,7 @@ TEST_F(DBWALTest, RecoverCheckFileAmount) {
{
auto tables = ListTableFiles(env_, dbname_);
ASSERT_EQ(tables.size(), static_cast<size_t>(1));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "nikitich"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
static_cast<uint64_t>(1));
}
// Memtable for 'nikitich' has flushed, new WAL file has opened
@@ -1425,7 +1425,7 @@ TEST_F(DBWALTest, RecoverCheckFileAmount) {
{
auto tables = ListTableFiles(env_, dbname_);
ASSERT_EQ(tables.size(), static_cast<size_t>(2));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "nikitich"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
static_cast<uint64_t>(2));
}
@@ -1437,13 +1437,13 @@ TEST_F(DBWALTest, RecoverCheckFileAmount) {
// first, second and third WALs went to the same SST.
// So, there is 6 SSTs: three for 'nikitich', one for 'default', one for
// 'dobrynia', one for 'pikachu'
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "default"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
static_cast<uint64_t>(1));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "nikitich"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
static_cast<uint64_t>(3));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "dobrynia"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
static_cast<uint64_t>(1));
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "pikachu"),
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
static_cast<uint64_t>(1));
}
}
@@ -1521,9 +1521,9 @@ TEST_F(DBWALTest, DISABLED_RecycleMultipleWalsCrash) {
// from an old incarnation of the WAL on recovery
ASSERT_OK(db_->PauseBackgroundWork());
ASSERT_OK(Put("ignore1", Random::GetTLSInstance()->RandomString(500)));
ASSERT_OK(dbfull()->TEST_SwitchMemtable());
ASSERT_OK(static_cast_with_check<DBImpl>(db_)->TEST_SwitchMemtable());
ASSERT_OK(Put("ignore2", Random::GetTLSInstance()->RandomString(500)));
ASSERT_OK(dbfull()->TEST_SwitchMemtable());
ASSERT_OK(static_cast_with_check<DBImpl>(db_)->TEST_SwitchMemtable());
ASSERT_OK(db_->ContinueBackgroundWork());
ASSERT_OK(Flush());
ASSERT_OK(Put("ignore3", Random::GetTLSInstance()->RandomString(500)));
@@ -1545,13 +1545,13 @@ TEST_F(DBWALTest, DISABLED_RecycleMultipleWalsCrash) {
// gap in sequence numbers to interfere with recovery
ASSERT_OK(db_->PauseBackgroundWork());
ASSERT_OK(Put("key1", "val1"));
ASSERT_OK(dbfull()->TEST_SwitchMemtable());
ASSERT_OK(static_cast_with_check<DBImpl>(db_)->TEST_SwitchMemtable());
ASSERT_OK(Put("key2", "val2"));
ASSERT_OK(dbfull()->TEST_SwitchMemtable());
ASSERT_OK(static_cast_with_check<DBImpl>(db_)->TEST_SwitchMemtable());
// Need a gap in sequence numbers, so e.g. ingest external file
// with an open snapshot
{
ManagedSnapshot snapshot(db_.get());
ManagedSnapshot snapshot(db_);
ASSERT_OK(
db_->IngestExternalFile({external_file1}, IngestExternalFileOptions()));
}
@@ -1560,7 +1560,7 @@ TEST_F(DBWALTest, DISABLED_RecycleMultipleWalsCrash) {
// Need an SST file that is logically after that WAL, so that dropping WAL
// data is not a valid point in time.
{
ManagedSnapshot snapshot(db_.get());
ManagedSnapshot snapshot(db_);
ASSERT_OK(
db_->IngestExternalFile({external_file2}, IngestExternalFileOptions()));
}
@@ -1655,10 +1655,10 @@ TEST_F(DBWALTest, SyncWalPartialFailure) {
// with a single thread, to exercise as much logic as we reasonably can.
ASSERT_OK(db_->PauseBackgroundWork());
ASSERT_OK(Put("key1", "val1"));
ASSERT_OK(dbfull()->TEST_SwitchMemtable());
ASSERT_OK(static_cast_with_check<DBImpl>(db_)->TEST_SwitchMemtable());
ASSERT_OK(db_->SyncWAL());
ASSERT_OK(Put("key2", "val2"));
ASSERT_OK(dbfull()->TEST_SwitchMemtable());
ASSERT_OK(static_cast_with_check<DBImpl>(db_)->TEST_SwitchMemtable());
ASSERT_OK(Put("key3", "val3"));
// Allow 1 of the WALs to sync, but another won't
@@ -1879,11 +1879,9 @@ TEST_F(DBWALTest, TrackAndVerifyWALsRecycleWAL) {
// Drop `Put("key1", "old_value")` in the first WAL
ASSERT_OK(test::TruncateFile(options.env, log_name, 0 /* new_length */));
{
Status s = DB::Open(options, dbname_, &db_);
Status s = DB::Open(options, dbname_, &db_);
ASSERT_OK(s);
}
ASSERT_OK(s);
ASSERT_EQ("wal_to_recycle", Get("key_ignore2"));
ASSERT_EQ("NOT_FOUND", Get("key1"));
@@ -1981,10 +1979,7 @@ TEST_P(DBWALTrackAndVerifyWALsWithParamsTest, Basic) {
ASSERT_OK(options.env->DeleteFile(second_log_name));
}
Status s;
{
s = DB::Open(options, dbname_, &db_);
}
Status s = DB::Open(options, dbname_, &db_);
if (i == 0) {
ASSERT_OK(s);
@@ -2271,10 +2266,11 @@ TEST_F(DBWALTest, RaceInstallFlushResultsWithWalObsoletion) {
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
std::unique_ptr<DB> db1;
DB* db1 = nullptr;
Status s = DB::OpenForReadOnly(options, dbname_, &db1);
ASSERT_OK(s);
assert(db1);
delete db1;
}
TEST_F(DBWALTest, FixSyncWalOnObseletedWalWithNewManifestCausingMissingWAL) {
+7 -11
View File
@@ -662,7 +662,7 @@ TEST_F(DBBasicTestWithTimestamp, TrimHistoryTest) {
ASSERT_OK(db_->Put(WriteOptions(), "k1", Timestamp(4, 0), "v2"));
ASSERT_OK(db_->Delete(WriteOptions(), "k1", Timestamp(5, 0)));
ASSERT_OK(db_->Put(WriteOptions(), "k1", Timestamp(6, 0), "v3"));
check_value_by_ts(db_.get(), "k1", Timestamp(7, 0), Status::OK(), "v3",
check_value_by_ts(db_, "k1", Timestamp(7, 0), Status::OK(), "v3",
Timestamp(6, 0));
ASSERT_OK(Flush());
Close();
@@ -675,27 +675,27 @@ TEST_F(DBBasicTestWithTimestamp, TrimHistoryTest) {
// Trim data whose version > Timestamp(5, 0), read(k1, ts(7)) <- NOT_FOUND.
ASSERT_OK(DB::OpenAndTrimHistory(db_options, dbname_, column_families,
&handles_, &db_, Timestamp(5, 0)));
check_value_by_ts(db_.get(), "k1", Timestamp(7, 0), Status::NotFound(), "",
check_value_by_ts(db_, "k1", Timestamp(7, 0), Status::NotFound(), "",
Timestamp(5, 0));
Close();
// Trim data whose timestamp > Timestamp(4, 0), read(k1, ts(7)) <- v2
ASSERT_OK(DB::OpenAndTrimHistory(db_options, dbname_, column_families,
&handles_, &db_, Timestamp(4, 0)));
check_value_by_ts(db_.get(), "k1", Timestamp(7, 0), Status::OK(), "v2",
check_value_by_ts(db_, "k1", Timestamp(7, 0), Status::OK(), "v2",
Timestamp(4, 0));
Close();
Reopen(options);
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "k1",
"k3", Timestamp(7, 0)));
check_value_by_ts(db_.get(), "k1", Timestamp(8, 0), Status::NotFound(), "",
check_value_by_ts(db_, "k1", Timestamp(8, 0), Status::NotFound(), "",
Timestamp(7, 0));
Close();
// Trim data whose timestamp > Timestamp(6, 0), read(k1, ts(8)) <- v2
ASSERT_OK(DB::OpenAndTrimHistory(db_options, dbname_, column_families,
&handles_, &db_, Timestamp(6, 0)));
check_value_by_ts(db_.get(), "k1", Timestamp(8, 0), Status::OK(), "v2",
check_value_by_ts(db_, "k1", Timestamp(8, 0), Status::OK(), "v2",
Timestamp(4, 0));
Close();
}
@@ -1427,12 +1427,8 @@ TEST_F(DBBasicTestWithTimestamp, ReseekToNextUserKey) {
{
std::string ts_str = Timestamp(static_cast<uint64_t>(kNumKeys + 1), 0);
WriteBatch batch(0, 0, 0, kTimestampSize);
{
ASSERT_OK(batch.Put("a", "new_value"));
}
{
ASSERT_OK(batch.Put("b", "new_value"));
}
{ ASSERT_OK(batch.Put("a", "new_value")); }
{ ASSERT_OK(batch.Put("b", "new_value")); }
s = batch.UpdateTimestamps(
ts_str, [kTimestampSize](uint32_t) { return kTimestampSize; });
ASSERT_OK(s);
+20 -20
View File
@@ -183,11 +183,11 @@ TEST_P(DBWriteBufferManagerTest, SharedWriteBufferAcrossCFs2) {
// is waiting to be finished but DBs tries to write meanwhile.
TEST_P(DBWriteBufferManagerTest, SharedWriteBufferLimitAcrossDB) {
std::vector<std::string> dbnames;
std::vector<std::unique_ptr<DB>> dbs;
std::vector<DB*> dbs;
int num_dbs = 3;
for (int i = 0; i < num_dbs; i++) {
dbs.emplace_back();
dbs.push_back(nullptr);
dbnames.push_back(
test::PerThreadDBPath("db_shared_wb_db" + std::to_string(i)));
}
@@ -266,7 +266,7 @@ TEST_P(DBWriteBufferManagerTest, SharedWriteBufferLimitAcrossDB) {
// Last writer will write and when its blocked it will signal Flush to
// continue to clear the stall.
threads.emplace_back(write_db, db_.get());
threads.emplace_back(write_db, db_);
// Wait untill first DB is blocked and then create the multiple writers for
// different DBs which will be blocked from getting added to the queue because
// stall is in effect.
@@ -277,7 +277,7 @@ TEST_P(DBWriteBufferManagerTest, SharedWriteBufferLimitAcrossDB) {
}
}
for (int i = 0; i < num_dbs; i++) {
threads.emplace_back(write_db, dbs[i].get());
threads.emplace_back(write_db, dbs[i]);
}
for (auto& t : threads) {
t.join();
@@ -289,7 +289,7 @@ TEST_P(DBWriteBufferManagerTest, SharedWriteBufferLimitAcrossDB) {
for (int i = 0; i < num_dbs; i++) {
ASSERT_OK(dbs[i]->Close());
ASSERT_OK(DestroyDB(dbnames[i], options));
dbs[i].reset();
delete dbs[i];
}
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
@@ -300,11 +300,11 @@ TEST_P(DBWriteBufferManagerTest, SharedWriteBufferLimitAcrossDB) {
// blocked when stall by WriteBufferManager is in effect.
TEST_P(DBWriteBufferManagerTest, SharedWriteBufferLimitAcrossDB1) {
std::vector<std::string> dbnames;
std::vector<std::unique_ptr<DB>> dbs;
std::vector<DB*> dbs;
int num_dbs = 3;
for (int i = 0; i < num_dbs; i++) {
dbs.emplace_back();
dbs.push_back(nullptr);
dbnames.push_back(
test::PerThreadDBPath("db_shared_wb_db" + std::to_string(i)));
}
@@ -407,7 +407,7 @@ TEST_P(DBWriteBufferManagerTest, SharedWriteBufferLimitAcrossDB1) {
// |
// Last writer thread will write and when its blocked it will signal Flush to
// continue to clear the stall.
threads.emplace_back(write_db, db_.get());
threads.emplace_back(write_db, db_);
// Wait untill first thread is blocked and then create the multiple writer
// threads.
{
@@ -421,7 +421,7 @@ TEST_P(DBWriteBufferManagerTest, SharedWriteBufferLimitAcrossDB1) {
// Write to multiple columns of db_.
writer_threads.emplace_back(write_cf, i % 3);
// Write to different dbs.
threads.emplace_back(write_db, dbs[i].get());
threads.emplace_back(write_db, dbs[i]);
}
for (auto& t : threads) {
t.join();
@@ -441,7 +441,7 @@ TEST_P(DBWriteBufferManagerTest, SharedWriteBufferLimitAcrossDB1) {
for (int i = 0; i < num_dbs; i++) {
ASSERT_OK(dbs[i]->Close());
ASSERT_OK(DestroyDB(dbnames[i], options));
dbs[i].reset();
delete dbs[i];
}
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
@@ -604,11 +604,11 @@ TEST_P(DBWriteBufferManagerTest, MixedSlowDownOptionsSingleDB) {
// dbs by passing different values to WriteOption.no_slown_down.
TEST_P(DBWriteBufferManagerTest, MixedSlowDownOptionsMultipleDB) {
std::vector<std::string> dbnames;
std::vector<std::unique_ptr<DB>> dbs;
std::vector<DB*> dbs;
int num_dbs = 4;
for (int i = 0; i < num_dbs; i++) {
dbs.emplace_back();
dbs.push_back(nullptr);
dbnames.push_back(
test::PerThreadDBPath("db_shared_wb_db" + std::to_string(i)));
}
@@ -732,7 +732,7 @@ TEST_P(DBWriteBufferManagerTest, MixedSlowDownOptionsMultipleDB) {
// |
// Last writer thread will write and when its blocked/return it will signal
// Flush to continue to clear the stall.
threads.emplace_back(write_slow_down, db_.get());
threads.emplace_back(write_slow_down, db_);
// Wait untill first thread writing to DB is blocked and then
// create the multiple writers.
{
@@ -744,11 +744,11 @@ TEST_P(DBWriteBufferManagerTest, MixedSlowDownOptionsMultipleDB) {
for (int i = 0; i < num_dbs; i += 2) {
// Write to multiple columns of db_.
writer_threads.emplace_back(write_slow_down, db_.get());
writer_threads.emplace_back(write_no_slow_down, db_.get());
writer_threads.emplace_back(write_slow_down, db_);
writer_threads.emplace_back(write_no_slow_down, db_);
// Write to different DBs.
threads.emplace_back(write_slow_down, dbs[i].get());
threads.emplace_back(write_no_slow_down, dbs[i + 1].get());
threads.emplace_back(write_slow_down, dbs[i]);
threads.emplace_back(write_no_slow_down, dbs[i + 1]);
}
for (auto& t : threads) {
@@ -773,7 +773,7 @@ TEST_P(DBWriteBufferManagerTest, MixedSlowDownOptionsMultipleDB) {
for (int i = 0; i < num_dbs; i++) {
ASSERT_OK(dbs[i]->Close());
ASSERT_OK(DestroyDB(dbnames[i], options));
dbs[i].reset();
delete dbs[i];
}
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
@@ -809,7 +809,7 @@ TEST_P(DBWriteBufferManagerTest, StopSwitchingMemTablesOnceFlushing) {
Reopen(options);
std::string dbname = test::PerThreadDBPath("db_shared_wbm_db");
std::unique_ptr<DB> shared_wbm_db;
DB* shared_wbm_db = nullptr;
ASSERT_OK(DestroyDB(dbname, options));
ASSERT_OK(DB::Open(options, dbname, &shared_wbm_db));
@@ -842,7 +842,7 @@ TEST_P(DBWriteBufferManagerTest, StopSwitchingMemTablesOnceFlushing) {
sleeping_task_high.WaitUntilDone();
ASSERT_OK(shared_wbm_db->Close());
ASSERT_OK(DestroyDB(dbname, options));
shared_wbm_db.reset();
delete shared_wbm_db;
}
TEST_F(DBWriteBufferManagerTest, RuntimeChangeableAllowStall) {
+5
View File
@@ -980,6 +980,11 @@ class InternalKeySliceTransform : public SliceTransform {
return transform_->InDomain(user_key);
}
bool InRange(const Slice& dst) const override {
auto user_key = ExtractUserKey(dst);
return transform_->InRange(user_key);
}
const SliceTransform* user_prefix_extractor() const { return transform_; }
private:
+14 -12
View File
@@ -1550,7 +1550,7 @@ TEST_F(DBErrorHandlingFSTest, MultiDBCompactionError) {
std::vector<FaultInjectionTestFS*> fault_fs;
std::vector<Options> options;
std::vector<std::shared_ptr<ErrorHandlerFSListener>> listener;
std::vector<std::unique_ptr<DB>> db;
std::vector<DB*> db;
std::shared_ptr<SstFileManager> sfm(NewSstFileManager(def_env));
int kNumDbInstances = 3;
Random rnd(301);
@@ -1567,6 +1567,7 @@ TEST_F(DBErrorHandlingFSTest, MultiDBCompactionError) {
options[i].writable_file_max_buffer_size = 32768;
options[i].listeners.emplace_back(listener[i]);
options[i].sst_file_manager = sfm;
DB* dbptr;
char buf[16];
listener[i]->EnableAutoRecovery();
@@ -1575,8 +1576,8 @@ TEST_F(DBErrorHandlingFSTest, MultiDBCompactionError) {
IOStatus::NoSpace("Out of space"));
snprintf(buf, sizeof(buf), "_%d", i);
ASSERT_OK(DestroyDB(dbname_ + std::string(buf), options[i]));
ASSERT_OK(
DB::Open(options[i], dbname_ + std::string(buf), &db.emplace_back()));
ASSERT_OK(DB::Open(options[i], dbname_ + std::string(buf), &dbptr));
db.emplace_back(dbptr);
}
for (auto i = 0; i < kNumDbInstances; ++i) {
@@ -1608,7 +1609,7 @@ TEST_F(DBErrorHandlingFSTest, MultiDBCompactionError) {
}
for (auto i = 0; i < kNumDbInstances; ++i) {
Status s = static_cast<DBImpl*>(db[i].get())->TEST_WaitForCompact();
Status s = static_cast<DBImpl*>(db[i])->TEST_WaitForCompact();
ASSERT_EQ(s.severity(), Status::Severity::kSoftError);
fault_fs[i]->SetFilesystemActive(true);
}
@@ -1617,7 +1618,7 @@ TEST_F(DBErrorHandlingFSTest, MultiDBCompactionError) {
for (auto i = 0; i < kNumDbInstances; ++i) {
std::string prop;
ASSERT_EQ(listener[i]->WaitForRecovery(5000000), true);
ASSERT_OK(static_cast<DBImpl*>(db[i].get())->TEST_WaitForCompact());
ASSERT_OK(static_cast<DBImpl*>(db[i])->TEST_WaitForCompact());
EXPECT_TRUE(db[i]->GetProperty(
"rocksdb.num-files-at-level" + std::to_string(0), &prop));
EXPECT_EQ(atoi(prop.c_str()), 0);
@@ -1633,7 +1634,7 @@ TEST_F(DBErrorHandlingFSTest, MultiDBCompactionError) {
for (auto i = 0; i < kNumDbInstances; ++i) {
char buf[16];
snprintf(buf, sizeof(buf), "_%d", i);
db[i].reset();
delete db[i];
fault_fs[i]->SetFilesystemActive(true);
if (getenv("KEEP_DB")) {
printf("DB is still at %s%s\n", dbname_.c_str(), buf);
@@ -1656,7 +1657,7 @@ TEST_F(DBErrorHandlingFSTest, MultiDBVariousErrors) {
std::vector<FaultInjectionTestFS*> fault_fs;
std::vector<Options> options;
std::vector<std::shared_ptr<ErrorHandlerFSListener>> listener;
std::vector<std::unique_ptr<DB>> db;
std::vector<DB*> db;
std::shared_ptr<SstFileManager> sfm(NewSstFileManager(def_env));
int kNumDbInstances = 3;
Random rnd(301);
@@ -1673,6 +1674,7 @@ TEST_F(DBErrorHandlingFSTest, MultiDBVariousErrors) {
options[i].writable_file_max_buffer_size = 32768;
options[i].listeners.emplace_back(listener[i]);
options[i].sst_file_manager = sfm;
DB* dbptr;
char buf[16];
listener[i]->EnableAutoRecovery();
@@ -1693,8 +1695,8 @@ TEST_F(DBErrorHandlingFSTest, MultiDBVariousErrors) {
}
snprintf(buf, sizeof(buf), "_%d", i);
ASSERT_OK(DestroyDB(dbname_ + std::string(buf), options[i]));
ASSERT_OK(
DB::Open(options[i], dbname_ + std::string(buf), &db.emplace_back()));
ASSERT_OK(DB::Open(options[i], dbname_ + std::string(buf), &dbptr));
db.emplace_back(dbptr);
}
for (auto i = 0; i < kNumDbInstances; ++i) {
@@ -1730,7 +1732,7 @@ TEST_F(DBErrorHandlingFSTest, MultiDBVariousErrors) {
}
for (auto i = 0; i < kNumDbInstances; ++i) {
Status s = static_cast<DBImpl*>(db[i].get())->TEST_WaitForCompact();
Status s = static_cast<DBImpl*>(db[i])->TEST_WaitForCompact();
switch (i) {
case 0:
ASSERT_EQ(s.severity(), Status::Severity::kSoftError);
@@ -1752,7 +1754,7 @@ TEST_F(DBErrorHandlingFSTest, MultiDBVariousErrors) {
ASSERT_EQ(listener[i]->WaitForRecovery(5000000), true);
}
if (i == 1) {
ASSERT_OK(static_cast<DBImpl*>(db[i].get())->TEST_WaitForCompact());
ASSERT_OK(static_cast<DBImpl*>(db[i])->TEST_WaitForCompact());
}
EXPECT_TRUE(db[i]->GetProperty(
"rocksdb.num-files-at-level" + std::to_string(0), &prop));
@@ -1770,7 +1772,7 @@ TEST_F(DBErrorHandlingFSTest, MultiDBVariousErrors) {
char buf[16];
snprintf(buf, sizeof(buf), "_%d", i);
fault_fs[i]->SetFilesystemActive(true);
db[i].reset();
delete db[i];
if (getenv("KEEP_DB")) {
printf("DB is still at %s%s\n", dbname_.c_str(), buf);
} else {
+1 -2
View File
@@ -1188,8 +1188,7 @@ class SstQueryFilterConfigsManagerImpl : public SstQueryFilterConfigsManager {
break;
default:
// TODO? Report problem
{
}
{}
// Unknown filter type
}
if (!may_match) {
+1 -1
View File
@@ -2816,7 +2816,7 @@ TEST_F(ExternalSSTFileBasicTest, FailIfNotBottommostLevelAndDisallowMemtable) {
{
const Snapshot* snapshot = db_->GetSnapshot();
ManagedSnapshot snapshot_guard(db_.get(), snapshot);
ManagedSnapshot snapshot_guard(db_, snapshot);
IngestExternalFileOptions ifo;
ifo.fail_if_not_bottommost_level = true;
ifo.snapshot_consistency = true;
+15 -21
View File
@@ -80,7 +80,8 @@ class ExternSSTFileLinkFailFallbackTest
}
void TearDown() override {
db_.reset();
delete db_;
db_ = nullptr;
ASSERT_OK(DestroyDB(dbname_, options_));
}
@@ -2569,19 +2570,14 @@ TEST_F(ExternalSSTFileTest, SkipBloomFilter) {
options.statistics->getTickerCount(Tickers::BLOCK_CACHE_FILTER_ADD), 1);
}
// Create external SST file but skip bloom filters by using options
// with no filter policy
// Create external SST file but skip bloom filters
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
DestroyAndReopen(options);
{
std::string file_path = sst_files_dir_ + "sst_with_no_bloom.sst";
// Use options with no filter policy to skip bloom filters
Options no_filter_options = options;
BlockBasedTableOptions no_filter_table_options = table_options;
no_filter_table_options.filter_policy.reset();
no_filter_options.table_factory.reset(
NewBlockBasedTableFactory(no_filter_table_options));
SstFileWriter sst_file_writer(EnvOptions(), no_filter_options);
SstFileWriter sst_file_writer(EnvOptions(), options, nullptr, true,
Env::IOPriority::IO_TOTAL,
true /* skip_filters */);
ASSERT_OK(sst_file_writer.Open(file_path));
ASSERT_OK(sst_file_writer.Put("Key1", "Value1"));
ASSERT_OK(sst_file_writer.Finish());
@@ -4044,7 +4040,7 @@ TEST_P(IngestDBGeneratedFileTest2, NotOverlapWithDB) {
std::string db2_path = test::PerThreadDBPath("DB2");
Options db2_options;
db2_options.create_if_missing = true;
std::unique_ptr<DB> db2;
DB* db2 = nullptr;
ASSERT_OK(DB::Open(db2_options, db2_path, &db2));
// Write some base data.
expected_value.emplace_back(rnd.RandomString(100));
@@ -4073,10 +4069,10 @@ TEST_P(IngestDBGeneratedFileTest2, NotOverlapWithDB) {
ASSERT_OK(db_->DropColumnFamily(temp_cfh));
ASSERT_OK(db_->DestroyColumnFamilyHandle(temp_cfh));
ASSERT_OK(db2->Close());
db2.reset();
delete db2;
ASSERT_OK(DB::Open(db2_options, db2_path, &db2));
ASSERT_OK(db2->Close());
db2.reset();
delete db2;
ASSERT_OK(DestroyDB(db2_path, db2_options));
} else {
ASSERT_OK(db_->DropColumnFamily(temp_cfh));
@@ -4139,7 +4135,6 @@ TEST_P(IngestDBGeneratedFileTest2, NonZeroSeqno) {
// Create temp CF/DB
Options temp_cf_opts;
ColumnFamilyHandle* temp_cfh = nullptr;
std::unique_ptr<DB> temp_db_holder;
DB* from_db = nullptr;
std::string temp_db_name;
// Using a separate DB also validates that latest sequence number
@@ -4160,11 +4155,10 @@ TEST_P(IngestDBGeneratedFileTest2, NonZeroSeqno) {
if (use_temp_db) {
temp_cf_opts.create_if_missing = true;
temp_db_name = dbname_ + "/temp_db_" + std::to_string(rnd->Next());
ASSERT_OK(DB::Open(temp_cf_opts, temp_db_name, &temp_db_holder));
from_db = temp_db_holder.get();
ASSERT_OK(DB::Open(temp_cf_opts, temp_db_name, &from_db));
temp_cfh = from_db->DefaultColumnFamily();
} else {
from_db = db_.get();
from_db = db_;
ASSERT_OK(
from_db->CreateColumnFamily(temp_cf_opts, "temp_cf", &temp_cfh));
}
@@ -4299,7 +4293,7 @@ TEST_P(IngestDBGeneratedFileTest2, NonZeroSeqno) {
ASSERT_OK(db_->WaitForCompact({}));
if (use_temp_db) {
ASSERT_OK(from_db->Close());
temp_db_holder.reset();
delete from_db;
ASSERT_OK(DestroyDB(temp_db_name, temp_cf_opts));
} else {
ASSERT_OK(db_->DropColumnFamily(temp_cfh));
@@ -4387,7 +4381,7 @@ TEST_P(IngestDBGeneratedFileTest2, ZeroAndNonZeroSeqno) {
std::string temp_db_name =
dbname_ + "/temp_db_" + std::to_string(rnd->Next());
std::unique_ptr<DB> temp_db;
DB* temp_db = nullptr;
ASSERT_OK(DB::Open(temp_db_opts, temp_db_name, &temp_db));
const Snapshot* snapshot = db_->GetSnapshot();
@@ -4450,7 +4444,7 @@ TEST_P(IngestDBGeneratedFileTest2, ZeroAndNonZeroSeqno) {
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
ASSERT_OK(temp_db->CompactRange(cro, nullptr, nullptr));
SCOPED_TRACE("Temp DB LSM: " +
FilesPerLevel(temp_db->DefaultColumnFamily(), temp_db.get()));
FilesPerLevel(temp_db->DefaultColumnFamily(), temp_db));
// Base data from snapshot
std::vector<std::string> sst_file_paths_zero_seqno;
@@ -4545,7 +4539,7 @@ TEST_P(IngestDBGeneratedFileTest2, ZeroAndNonZeroSeqno) {
ASSERT_OK(db_->DestroyColumnFamilyHandle(live_write_cfh));
ASSERT_OK(temp_db->Close());
temp_db.reset();
delete temp_db;
ASSERT_OK(DestroyDB(temp_db_name, temp_db_opts));
} while (ChangeOptions(kSkipPlainTable | kSkipFIFOCompaction));
}
+6 -4
View File
@@ -76,7 +76,7 @@ class FaultInjectionTest
std::string dbname_;
std::shared_ptr<Cache> tiny_cache_;
Options options_;
std::unique_ptr<DB> db_;
DB* db_;
FaultInjectionTest()
: option_config_(std::get<1>(GetParam())),
@@ -260,7 +260,10 @@ class FaultInjectionTest
return Slice(*storage);
}
void CloseDB() { db_.reset(); }
void CloseDB() {
delete db_;
db_ = nullptr;
}
Status OpenDB() {
CloseDB();
@@ -345,8 +348,7 @@ class FaultInjectionTest
}
void WaitCompactionFinish() {
ASSERT_OK(static_cast_with_check<DBImpl>(db_->GetRootDB())
->TEST_WaitForCompact());
ASSERT_OK(static_cast<DBImpl*>(db_->GetRootDB())->TEST_WaitForCompact());
ASSERT_OK(db_->Put(WriteOptions(), "", ""));
}
+6 -5
View File
@@ -344,18 +344,19 @@ int main(int argc, char** argv) {
status = ROCKSDB_NAMESPACE::DestroyDB(path, options);
assert(status.ok());
std::unique_ptr<ROCKSDB_NAMESPACE::DB> db;
status = ROCKSDB_NAMESPACE::DB::Open(options, path, &db);
ROCKSDB_NAMESPACE::DB* db_raw;
status = ROCKSDB_NAMESPACE::DB::Open(options, path, &db_raw);
assert(status.ok());
std::unique_ptr<ROCKSDB_NAMESPACE::DB> db(db_raw);
std::vector<ShardState> shard_states(FLAGS_shards + 1);
std::deque<Reader> readers;
while (static_cast<int>(readers.size()) < FLAGS_readers) {
readers.emplace_back(&shard_states, db.get());
readers.emplace_back(&shard_states, db_raw);
}
std::deque<Writer> writers;
while (static_cast<int>(writers.size()) < FLAGS_writers) {
writers.emplace_back(&shard_states, db.get());
writers.emplace_back(&shard_states, db_raw);
}
// Each shard gets a random reader and random writer assigned to it
@@ -366,7 +367,7 @@ int main(int argc, char** argv) {
shard_states[i].writer = &writers[writer_dist(rng)];
}
StatsThread stats_thread(db.get());
StatsThread stats_thread(db_raw);
for (Writer& w : writers) {
w.start();
}
+17 -17
View File
@@ -371,7 +371,7 @@ TEST_F(ImportColumnFamilyTest, ImportExportedSSTFromAnotherCF) {
ASSERT_OK(Flush(1));
Checkpoint* checkpoint;
ASSERT_OK(Checkpoint::Create(db_.get(), &checkpoint));
ASSERT_OK(Checkpoint::Create(db_, &checkpoint));
ASSERT_OK(checkpoint->ExportColumnFamily(handles_[1], export_files_dir_,
&metadata_ptr_));
ASSERT_NE(metadata_ptr_, nullptr);
@@ -481,14 +481,14 @@ TEST_F(ImportColumnFamilyTest, ImportExportedSSTFromAnotherDB) {
ASSERT_OK(Flush(1));
Checkpoint* checkpoint;
ASSERT_OK(Checkpoint::Create(db_.get(), &checkpoint));
ASSERT_OK(Checkpoint::Create(db_, &checkpoint));
ASSERT_OK(checkpoint->ExportColumnFamily(handles_[1], export_files_dir_,
&metadata_ptr_));
ASSERT_NE(metadata_ptr_, nullptr);
delete checkpoint;
// Create a new db and import the files.
std::unique_ptr<DB> db_copy;
DB* db_copy;
ASSERT_OK(DestroyDir(env_, dbname_ + "/db_copy"));
ASSERT_OK(DB::Open(options, dbname_ + "/db_copy", &db_copy));
ColumnFamilyHandle* cfh = nullptr;
@@ -504,7 +504,7 @@ TEST_F(ImportColumnFamilyTest, ImportExportedSSTFromAnotherDB) {
}
ASSERT_OK(db_copy->DropColumnFamily(cfh));
ASSERT_OK(db_copy->DestroyColumnFamilyHandle(cfh));
db_copy.reset();
delete db_copy;
ASSERT_OK(DestroyDir(env_, dbname_ + "/db_copy"));
}
@@ -529,7 +529,7 @@ TEST_F(ImportColumnFamilyTest,
ASSERT_OK(db_->DeleteRange(WriteOptions(), handles_[1], Key(0), Key(2)));
Checkpoint* checkpoint;
ASSERT_OK(Checkpoint::Create(db_.get(), &checkpoint));
ASSERT_OK(Checkpoint::Create(db_, &checkpoint));
ASSERT_OK(checkpoint->ExportColumnFamily(handles_[1], export_files_dir_,
&metadata_ptr_));
ASSERT_NE(metadata_ptr_, nullptr);
@@ -605,14 +605,14 @@ TEST_F(ImportColumnFamilyTest, LevelFilesOverlappingAtEndpoints) {
ASSERT_GT(NumTableFilesAtLevel(1, 1), 1);
Checkpoint* checkpoint;
ASSERT_OK(Checkpoint::Create(db_.get(), &checkpoint));
ASSERT_OK(Checkpoint::Create(db_, &checkpoint));
ASSERT_OK(checkpoint->ExportColumnFamily(handles_[1], export_files_dir_,
&metadata_ptr_));
ASSERT_NE(metadata_ptr_, nullptr);
delete checkpoint;
// Create a new db and import the files.
std::unique_ptr<DB> db_copy;
DB* db_copy;
ASSERT_OK(DestroyDir(env_, dbname_ + "/db_copy"));
ASSERT_OK(DB::Open(options, dbname_ + "/db_copy", &db_copy));
ColumnFamilyHandle* cfh = nullptr;
@@ -627,7 +627,7 @@ TEST_F(ImportColumnFamilyTest, LevelFilesOverlappingAtEndpoints) {
}
ASSERT_OK(db_copy->DropColumnFamily(cfh));
ASSERT_OK(db_copy->DestroyColumnFamilyHandle(cfh));
db_copy.reset();
delete db_copy;
ASSERT_OK(DestroyDir(env_, dbname_ + "/db_copy"));
for (const Snapshot* snapshot : snapshots) {
db_->ReleaseSnapshot(snapshot);
@@ -771,12 +771,12 @@ TEST_F(ImportColumnFamilyTest, ImportMultiColumnFamilyTest) {
Checkpoint* checkpoint1;
Checkpoint* checkpoint2;
ASSERT_OK(Checkpoint::Create(db_.get(), &checkpoint1));
ASSERT_OK(Checkpoint::Create(db_, &checkpoint1));
ASSERT_OK(checkpoint1->ExportColumnFamily(handles_[1], export_files_dir_,
&metadata_ptr_));
// Create a new db and import the files.
std::unique_ptr<DB> db_copy;
DB* db_copy;
ASSERT_OK(DestroyDir(env_, dbname_ + "/db_copy"));
ASSERT_OK(DB::Open(options, dbname_ + "/db_copy", &db_copy));
ColumnFamilyHandle* copy_cfh = nullptr;
@@ -796,7 +796,7 @@ TEST_F(ImportColumnFamilyTest, ImportMultiColumnFamilyTest) {
ASSERT_OK(db_copy->Flush(FlushOptions()));
// Flush again to create another L0 file. It should have higher sequencer.
ASSERT_OK(Checkpoint::Create(db_copy.get(), &checkpoint2));
ASSERT_OK(Checkpoint::Create(db_copy, &checkpoint2));
ASSERT_OK(checkpoint2->ExportColumnFamily(copy_cfh, export_files_dir2_,
&metadata_ptr2_));
@@ -826,7 +826,7 @@ TEST_F(ImportColumnFamilyTest, ImportMultiColumnFamilyTest) {
ASSERT_OK(db_copy->DropColumnFamily(copy_cfh));
ASSERT_OK(db_copy->DestroyColumnFamilyHandle(copy_cfh));
db_copy.reset();
delete db_copy;
ASSERT_OK(DestroyDir(env_, dbname_ + "/db_copy"));
}
@@ -840,12 +840,12 @@ TEST_F(ImportColumnFamilyTest, ImportMultiColumnFamilyWithOverlap) {
Checkpoint* checkpoint1;
Checkpoint* checkpoint2;
ASSERT_OK(Checkpoint::Create(db_.get(), &checkpoint1));
ASSERT_OK(Checkpoint::Create(db_, &checkpoint1));
ASSERT_OK(checkpoint1->ExportColumnFamily(handles_[1], export_files_dir_,
&metadata_ptr_));
// Create a new db and import the files.
std::unique_ptr<DB> db_copy;
DB* db_copy;
ASSERT_OK(DestroyDir(env_, dbname_ + "/db_copy"));
ASSERT_OK(DB::Open(options, dbname_ + "/db_copy", &db_copy));
ColumnFamilyHandle* copy_cfh = nullptr;
@@ -857,7 +857,7 @@ TEST_F(ImportColumnFamilyTest, ImportMultiColumnFamilyWithOverlap) {
ASSERT_OK(db_copy->Flush(FlushOptions()));
// Flush again to create another L0 file. It should have higher sequencer.
ASSERT_OK(Checkpoint::Create(db_copy.get(), &checkpoint2));
ASSERT_OK(Checkpoint::Create(db_copy, &checkpoint2));
ASSERT_OK(checkpoint2->ExportColumnFamily(copy_cfh, export_files_dir2_,
&metadata_ptr2_));
@@ -877,7 +877,7 @@ TEST_F(ImportColumnFamilyTest, ImportMultiColumnFamilyWithOverlap) {
ASSERT_OK(db_copy->DropColumnFamily(copy_cfh));
ASSERT_OK(db_copy->DestroyColumnFamilyHandle(copy_cfh));
db_copy.reset();
delete db_copy;
ASSERT_OK(DestroyDir(env_, dbname_ + "/db_copy"));
}
@@ -1017,7 +1017,7 @@ TEST_F(ImportColumnFamilyTest, AssignEpochNumberToMultipleCF) {
// corruption where two L0 files can have the same epoch number but
// with overlapping key range.
Checkpoint* checkpoint1;
ASSERT_OK(Checkpoint::Create(db_.get(), &checkpoint1));
ASSERT_OK(Checkpoint::Create(db_, &checkpoint1));
ASSERT_OK(checkpoint1->ExportColumnFamily(handles_[1], export_files_dir_,
&metadata_ptr_));
ASSERT_OK(checkpoint1->ExportColumnFamily(handles_[2], export_files_dir2_,
-13
View File
@@ -310,7 +310,6 @@ static const std::string aggregated_table_properties_at_level =
static const std::string num_running_compactions = "num-running-compactions";
static const std::string num_running_compaction_sorted_runs =
"num-running-compaction-sorted-runs";
static const std::string compaction_abort_count = "compaction-abort-count";
static const std::string num_running_flushes = "num-running-flushes";
static const std::string actual_delayed_write_rate =
"actual-delayed-write-rate";
@@ -363,8 +362,6 @@ const std::string DB::Properties::kNumRunningCompactions =
rocksdb_prefix + num_running_compactions;
const std::string DB::Properties::kNumRunningCompactionSortedRuns =
rocksdb_prefix + num_running_compaction_sorted_runs;
const std::string DB::Properties::kCompactionAbortCount =
rocksdb_prefix + compaction_abort_count;
const std::string DB::Properties::kNumRunningFlushes =
rocksdb_prefix + num_running_flushes;
const std::string DB::Properties::kBackgroundErrors =
@@ -597,9 +594,6 @@ const UnorderedMap<std::string, DBPropertyInfo>
{DB::Properties::kNumRunningCompactionSortedRuns,
{false, nullptr, &InternalStats::HandleNumRunningCompactionSortedRuns,
nullptr, nullptr}},
{DB::Properties::kCompactionAbortCount,
{false, nullptr, &InternalStats::HandleCompactionAbortCount, nullptr,
nullptr}},
{DB::Properties::kActualDelayedWriteRate,
{false, nullptr, &InternalStats::HandleActualDelayedWriteRate, nullptr,
nullptr}},
@@ -1298,13 +1292,6 @@ bool InternalStats::HandleNumRunningCompactionSortedRuns(uint64_t* value,
return true;
}
bool InternalStats::HandleCompactionAbortCount(uint64_t* value, DBImpl* db,
Version* /*version*/) {
*value = static_cast<uint64_t>(
db->compaction_aborted_.load(std::memory_order_acquire));
return true;
}
bool InternalStats::HandleBackgroundErrors(uint64_t* value, DBImpl* /*db*/,
Version* /*version*/) {
// Accumulated number of errors in background flushes or compactions.
-2
View File
@@ -852,8 +852,6 @@ class InternalStats {
Version* version);
bool HandleNumRunningCompactionSortedRuns(uint64_t* value, DBImpl* db,
Version* version);
bool HandleCompactionAbortCount(uint64_t* value, DBImpl* db,
Version* version);
bool HandleBackgroundErrors(uint64_t* value, DBImpl* db, Version* version);
bool HandleCurSizeActiveMemTable(uint64_t* value, DBImpl* db,
Version* version);
+27 -31
View File
@@ -105,7 +105,7 @@ class TestCompactionListener : public EventListener {
ASSERT_EQ(ci.output_files.size(), ci.output_file_infos.size());
ASSERT_TRUE(test_);
ASSERT_EQ(test_->db_.get(), db);
ASSERT_EQ(test_->db_, db);
std::vector<std::vector<FileMetaData>> files_by_level;
test_->dbfull()->TEST_GetFilesMetaData(test_->handles_[ci.cf_id],
@@ -197,7 +197,7 @@ TEST_F(EventListenerTest, OnSingleDBCompactionTest) {
ASSERT_EQ(listener->compacted_dbs_.size(), cf_names.size());
for (size_t i = 0; i < cf_names.size(); ++i) {
ASSERT_EQ(listener->compacted_dbs_[i], db_.get());
ASSERT_EQ(listener->compacted_dbs_[i], db_);
}
}
@@ -268,7 +268,7 @@ class TestFlushListener : public EventListener {
// that assumption does not hold (see the test case MultiDBMultiListeners
// below).
ASSERT_TRUE(test_);
if (db == test_->db_.get()) {
if (db == test_->db_) {
std::vector<std::vector<FileMetaData>> files_by_level;
ASSERT_LT(info.cf_id, test_->handles_.size());
ASSERT_GE(info.cf_id, 0u);
@@ -343,7 +343,7 @@ TEST_F(EventListenerTest, OnSingleDBFlushTest) {
// make sure callback functions are called in the right order
for (size_t i = 0; i < cf_names.size(); ++i) {
ASSERT_EQ(listener->flushed_dbs_[i], db_.get());
ASSERT_EQ(listener->flushed_dbs_[i], db_);
ASSERT_EQ(listener->flushed_column_family_names_[i], cf_names[i]);
}
}
@@ -387,7 +387,7 @@ TEST_F(EventListenerTest, MultiCF) {
// make sure callback functions are called in the right order
if (i == 7) {
for (size_t j = 0; j < cf_names.size(); j++) {
ASSERT_EQ(listener->flushed_dbs_[j], db_.get());
ASSERT_EQ(listener->flushed_dbs_[j], db_);
ASSERT_EQ(listener->flushed_column_family_names_[j], cf_names[j]);
}
}
@@ -422,21 +422,22 @@ TEST_F(EventListenerTest, MultiDBMultiListeners) {
DBOptions db_opts(options);
ColumnFamilyOptions cf_opts(options);
std::vector<std::unique_ptr<DB>> dbs;
std::vector<DB*> dbs;
std::vector<std::vector<ColumnFamilyHandle*>> vec_handles;
for (int d = 0; d < kNumDBs; ++d) {
ASSERT_OK(DestroyDB(dbname_ + std::to_string(d), options));
ASSERT_OK(
DB::Open(options, dbname_ + std::to_string(d), &dbs.emplace_back()));
DB* db;
std::vector<ColumnFamilyHandle*> handles;
ASSERT_OK(DB::Open(options, dbname_ + std::to_string(d), &db));
for (size_t c = 0; c < cf_names.size(); ++c) {
ColumnFamilyHandle* handle;
ASSERT_OK(dbs.back()->CreateColumnFamily(cf_opts, cf_names[c], &handle));
ASSERT_OK(db->CreateColumnFamily(cf_opts, cf_names[c], &handle));
handles.push_back(handle);
}
vec_handles.push_back(std::move(handles));
dbs.push_back(db);
}
for (int d = 0; d < kNumDBs; ++d) {
@@ -449,23 +450,23 @@ TEST_F(EventListenerTest, MultiDBMultiListeners) {
for (size_t c = 0; c < cf_names.size(); ++c) {
for (int d = 0; d < kNumDBs; ++d) {
ASSERT_OK(dbs[d]->Flush(FlushOptions(), vec_handles[d][c]));
ASSERT_OK(static_cast_with_check<DBImpl>(dbs[d].get())
->TEST_WaitForFlushMemTable());
ASSERT_OK(
static_cast_with_check<DBImpl>(dbs[d])->TEST_WaitForFlushMemTable());
}
}
for (int d = 0; d < kNumDBs; ++d) {
// Ensure background work is fully finished including listener callbacks
// before accessing listener state.
ASSERT_OK(static_cast_with_check<DBImpl>(dbs[d].get())
->TEST_WaitForBackgroundWork());
ASSERT_OK(
static_cast_with_check<DBImpl>(dbs[d])->TEST_WaitForBackgroundWork());
}
for (auto* listener : listeners) {
int pos = 0;
for (size_t c = 0; c < cf_names.size(); ++c) {
for (int d = 0; d < kNumDBs; ++d) {
ASSERT_EQ(listener->flushed_dbs_[pos], dbs[d].get());
ASSERT_EQ(listener->flushed_dbs_[pos], dbs[d]);
ASSERT_EQ(listener->flushed_column_family_names_[pos], cf_names[c]);
pos++;
}
@@ -480,8 +481,8 @@ TEST_F(EventListenerTest, MultiDBMultiListeners) {
}
vec_handles.clear();
for (auto& db : dbs) {
db.reset();
for (auto db : dbs) {
delete db;
}
}
@@ -1308,21 +1309,16 @@ class BlobDBJobLevelEventListenerTest : public EventListener {
explicit BlobDBJobLevelEventListenerTest(EventListenerTest* test)
: test_(test), call_count_(0) {}
// NOTE: it's not safe to rely on test_->db_ for these functions because
// the DB may be in the process of closing when these are called, and the
// unique_ptr is set to nullptr before invoking ~DB()
const VersionStorageInfo* GetVersionStorageInfo(DB* db) const {
DBImpl* db_impl = static_cast_with_check<DBImpl>(db);
VersionSet* const versions = db_impl->GetVersionSet();
const VersionStorageInfo* GetVersionStorageInfo() const {
VersionSet* const versions = test_->dbfull()->GetVersionSet();
assert(versions);
ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault();
EXPECT_NE(cfd, nullptr);
db_impl->TEST_LockMutex();
test_->dbfull()->TEST_LockMutex();
Version* const current = cfd->current();
db_impl->TEST_UnlockMutex();
test_->dbfull()->TEST_UnlockMutex();
EXPECT_NE(current, nullptr);
const VersionStorageInfo* const storage_info = current->storage_info();
@@ -1332,9 +1328,8 @@ class BlobDBJobLevelEventListenerTest : public EventListener {
}
void CheckBlobFileAdditions(
DB* db,
const std::vector<BlobFileAdditionInfo>& blob_file_addition_infos) const {
const auto* vstorage = GetVersionStorageInfo(db);
const auto* vstorage = GetVersionStorageInfo();
EXPECT_FALSE(blob_file_addition_infos.empty());
@@ -1362,7 +1357,7 @@ class BlobDBJobLevelEventListenerTest : public EventListener {
return result;
}
void OnFlushCompleted(DB* db, const FlushJobInfo& info) override {
void OnFlushCompleted(DB* /*db*/, const FlushJobInfo& info) override {
{
std::lock_guard<std::mutex> lock(mutex_);
IncreaseCallCount(/*mutex_locked*/ true);
@@ -1371,15 +1366,16 @@ class BlobDBJobLevelEventListenerTest : public EventListener {
EXPECT_EQ(info.blob_compression_type, kNoCompression);
CheckBlobFileAdditions(db, info.blob_file_addition_infos);
CheckBlobFileAdditions(info.blob_file_addition_infos);
}
void OnCompactionCompleted(DB* db, const CompactionJobInfo& info) override {
void OnCompactionCompleted(DB* /*db*/,
const CompactionJobInfo& info) override {
IncreaseCallCount(/*mutex_locked*/ false);
EXPECT_EQ(info.blob_compression_type, kNoCompression);
CheckBlobFileAdditions(db, info.blob_file_addition_infos);
CheckBlobFileAdditions(info.blob_file_addition_infos);
EXPECT_FALSE(info.blob_file_garbage_infos.empty());
+6 -6
View File
@@ -98,7 +98,7 @@ class LogCompactionFilter : public CompactionFilter {
TEST_F(ManualCompactionTest, CompactTouchesAllKeys) {
for (int iter = 0; iter < 2; ++iter) {
std::unique_ptr<DB> db;
DB* db;
Options options;
if (iter == 0) { // level compaction
options.num_levels = 3;
@@ -128,7 +128,7 @@ TEST_F(ManualCompactionTest, CompactTouchesAllKeys) {
delete itr;
delete options.compaction_filter;
db.reset();
delete db;
ASSERT_OK(DestroyDB(dbname_, options));
}
}
@@ -137,7 +137,7 @@ TEST_F(ManualCompactionTest, Test) {
// Open database. Disable compression since it affects the creation
// of layers and the code below is trying to test against a very
// specific scenario.
std::unique_ptr<DB> db;
DB* db;
Options db_options;
db_options.write_buffer_size = 1024;
db_options.create_if_missing = true;
@@ -185,12 +185,12 @@ TEST_F(ManualCompactionTest, Test) {
ASSERT_EQ(kNumKeys, num_keys) << "Bad number of keys";
// close database
db.reset();
delete db;
ASSERT_OK(DestroyDB(dbname_, Options()));
}
TEST_F(ManualCompactionTest, SkipLevel) {
std::unique_ptr<DB> db;
DB* db;
Options options;
options.level_compaction_dynamic_level_bytes = false;
options.num_levels = 3;
@@ -298,7 +298,7 @@ TEST_F(ManualCompactionTest, SkipLevel) {
}
delete filter;
db.reset();
delete db;
ASSERT_OK(DestroyDB(dbname_, options));
}
+4 -3
View File
@@ -33,12 +33,12 @@ std::string ValueWithWriteTime(std::string value, uint64_t write_time) {
class MemTableListTest : public testing::Test {
public:
std::string dbname;
std::unique_ptr<DB> db;
DB* db;
Options options;
std::vector<ColumnFamilyHandle*> handles;
std::atomic<uint64_t> file_number;
MemTableListTest() : file_number(1) {
MemTableListTest() : db(nullptr), file_number(1) {
dbname = test::PerThreadDBPath("memtable_list_test");
options.create_if_missing = true;
EXPECT_OK(DestroyDB(dbname, options));
@@ -88,7 +88,8 @@ class MemTableListTest : public testing::Test {
}
}
handles.clear();
db.reset();
delete db;
db = nullptr;
EXPECT_OK(DestroyDB(dbname, options, cf_descs));
}
}
+35 -37
View File
@@ -19,7 +19,6 @@
#include "rocksdb/utilities/db_ttl.h"
#include "rocksdb/wide_columns.h"
#include "test_util/testharness.h"
#include "util/cast_util.h"
#include "util/coding.h"
#include "utilities/merge_operators.h"
@@ -97,9 +96,9 @@ class EnvMergeTest : public EnvWrapper {
uint64_t EnvMergeTest::now_nanos_count_{0};
std::unique_ptr<EnvMergeTest> EnvMergeTest::singleton_;
std::unique_ptr<DB> OpenDb(const std::string& dbname, const bool ttl = false,
std::shared_ptr<DB> OpenDb(const std::string& dbname, const bool ttl = false,
const size_t max_successive_merges = 0) {
std::unique_ptr<DB> db;
DB* db;
Options options;
options.create_if_missing = true;
options.merge_operator = std::make_shared<CountMergeOperator>();
@@ -110,7 +109,7 @@ std::unique_ptr<DB> OpenDb(const std::string& dbname, const bool ttl = false,
if (ttl) {
DBWithTTL* db_with_ttl;
s = DBWithTTL::Open(options, dbname, &db_with_ttl);
db.reset(db_with_ttl);
db = db_with_ttl;
} else {
s = DB::Open(options, dbname, &db);
}
@@ -119,7 +118,7 @@ std::unique_ptr<DB> OpenDb(const std::string& dbname, const bool ttl = false,
// Allowed to call NowNanos during DB creation (in GenerateRawUniqueId() for
// session ID)
EnvMergeTest::now_nanos_count_ = 0;
return db;
return std::shared_ptr<DB>(db);
}
// Imagine we are maintaining a set of uint64 counters.
@@ -129,7 +128,7 @@ std::unique_ptr<DB> OpenDb(const std::string& dbname, const bool ttl = false,
// This is a quick implementation without a Merge operation.
class Counters {
protected:
UnownedPtr<DB> db_;
std::shared_ptr<DB> db_;
WriteOptions put_option_;
ReadOptions get_option_;
@@ -138,7 +137,7 @@ class Counters {
uint64_t default_;
public:
explicit Counters(UnownedPtr<DB> db, uint64_t defaultCount = 0)
explicit Counters(std::shared_ptr<DB> db, uint64_t defaultCount = 0)
: db_(db),
put_option_(),
get_option_(),
@@ -243,7 +242,7 @@ class MergeBasedCounters : public Counters {
WriteOptions merge_option_; // for merge
public:
explicit MergeBasedCounters(UnownedPtr<DB> db, uint64_t defaultCount = 0)
explicit MergeBasedCounters(std::shared_ptr<DB> db, uint64_t defaultCount = 0)
: Counters(db, defaultCount), merge_option_() {}
// mapped to a rocksdb Merge operation
@@ -262,7 +261,7 @@ class MergeBasedCounters : public Counters {
}
};
void dumpDb(const std::unique_ptr<DB>& db) {
void dumpDb(DB* db) {
auto it = std::unique_ptr<Iterator>(db->NewIterator(ReadOptions()));
for (it->SeekToFirst(); it->Valid(); it->Next()) {
// uint64_t value = DecodeFixed64(it->value().data());
@@ -271,8 +270,7 @@ void dumpDb(const std::unique_ptr<DB>& db) {
assert(it->status().ok()); // Check for any errors found during the scan
}
void testCounters(Counters& counters, const std::unique_ptr<DB>& db,
bool test_compaction) {
void testCounters(Counters& counters, DB* db, bool test_compaction) {
FlushOptions o;
o.wait = true;
@@ -322,8 +320,7 @@ void testCounters(Counters& counters, const std::unique_ptr<DB>& db,
}
}
void testCountersWithFlushAndCompaction(Counters& counters,
const std::unique_ptr<DB>& db) {
void testCountersWithFlushAndCompaction(Counters& counters, DB* db) {
ASSERT_OK(db->Put({}, "1", "1"));
ASSERT_OK(db->Flush(FlushOptions()));
@@ -391,12 +388,12 @@ void testCountersWithFlushAndCompaction(Counters& counters,
SyncPoint::GetInstance()->EnableProcessing();
port::Thread set_options_thread([&]() {
ASSERT_OK(static_cast_with_check<DBImpl>(db.get())->SetOptions(
ASSERT_OK(static_cast<DBImpl*>(db)->SetOptions(
{{"disable_auto_compactions", "false"}}));
});
TEST_SYNC_POINT("testCountersWithCompactionAndFlush:BeforeCompact");
port::Thread compact_thread([&]() {
ASSERT_OK(static_cast_with_check<DBImpl>(db.get())->CompactRange(
ASSERT_OK(static_cast<DBImpl*>(db)->CompactRange(
CompactRangeOptions(), db->DefaultColumnFamily(), nullptr, nullptr));
});
@@ -443,8 +440,8 @@ void testSuccessiveMerge(Counters& counters, size_t max_num_merges,
}
}
void testPartialMerge(Counters* counters, const std::unique_ptr<DB>& db,
size_t max_merge, size_t min_merge, size_t count) {
void testPartialMerge(Counters* counters, DB* db, size_t max_merge,
size_t min_merge, size_t count) {
FlushOptions o;
o.wait = true;
@@ -484,8 +481,8 @@ void testPartialMerge(Counters* counters, const std::unique_ptr<DB>& db,
ASSERT_EQ(EnvMergeTest::now_nanos_count_, 0U);
}
void testSingleBatchSuccessiveMerge(const std::unique_ptr<DB>& db,
size_t max_num_merges, size_t num_merges) {
void testSingleBatchSuccessiveMerge(DB* db, size_t max_num_merges,
size_t num_merges) {
ASSERT_GT(num_merges, max_num_merges);
Slice key("BatchSuccessiveMerge");
@@ -523,13 +520,13 @@ void runTest(const std::string& dbname, const bool use_ttl = false) {
auto db = OpenDb(dbname, use_ttl);
{
Counters counters(db.get(), 0);
testCounters(counters, db, true);
Counters counters(db, 0);
testCounters(counters, db.get(), true);
}
{
MergeBasedCounters counters(db.get(), 0);
testCounters(counters, db, use_compression);
MergeBasedCounters counters(db, 0);
testCounters(counters, db.get(), use_compression);
}
}
@@ -538,10 +535,10 @@ void runTest(const std::string& dbname, const bool use_ttl = false) {
{
size_t max_merge = 5;
auto db = OpenDb(dbname, use_ttl, max_merge);
MergeBasedCounters counters(db.get(), 0);
testCounters(counters, db, use_compression);
MergeBasedCounters counters(db, 0);
testCounters(counters, db.get(), use_compression);
testSuccessiveMerge(counters, max_merge, max_merge * 2);
testSingleBatchSuccessiveMerge(db, 5, 7);
testSingleBatchSuccessiveMerge(db.get(), 5, 7);
ASSERT_OK(db->Close());
ASSERT_OK(DestroyDB(dbname, Options()));
}
@@ -552,15 +549,16 @@ void runTest(const std::string& dbname, const bool use_ttl = false) {
uint32_t min_merge = 2;
for (uint32_t count = min_merge - 1; count <= min_merge + 1; count++) {
auto db = OpenDb(dbname, use_ttl, max_merge);
MergeBasedCounters counters(db.get(), 0);
testPartialMerge(&counters, db, max_merge, min_merge, count);
MergeBasedCounters counters(db, 0);
testPartialMerge(&counters, db.get(), max_merge, min_merge, count);
ASSERT_OK(db->Close());
ASSERT_OK(DestroyDB(dbname, Options()));
}
{
auto db = OpenDb(dbname, use_ttl, max_merge);
MergeBasedCounters counters(db.get(), 0);
testPartialMerge(&counters, db, max_merge, min_merge, min_merge * 10);
MergeBasedCounters counters(db, 0);
testPartialMerge(&counters, db.get(), max_merge, min_merge,
min_merge * 10);
ASSERT_OK(db->Close());
ASSERT_OK(DestroyDB(dbname, Options()));
}
@@ -569,18 +567,18 @@ void runTest(const std::string& dbname, const bool use_ttl = false) {
{
{
auto db = OpenDb(dbname);
MergeBasedCounters counters(db.get(), 0);
MergeBasedCounters counters(db, 0);
counters.add("test-key", 1);
counters.add("test-key", 1);
counters.add("test-key", 1);
ASSERT_OK(db->CompactRange(CompactRangeOptions(), nullptr, nullptr));
}
std::unique_ptr<DB> reopen_db;
DB* reopen_db;
ASSERT_OK(DB::Open(Options(), dbname, &reopen_db));
std::string value;
ASSERT_NOK(reopen_db->Get(ReadOptions(), "test-key", &value));
reopen_db.reset();
delete reopen_db;
ASSERT_OK(DestroyDB(dbname, Options()));
}
@@ -589,13 +587,13 @@ void runTest(const std::string& dbname, const bool use_ttl = false) {
std::cout << "Test merge-operator not set after reopen (recovery case)\n";
{
auto db = OpenDb(dbname);
MergeBasedCounters counters(db.get(), 0);
MergeBasedCounters counters(db, 0);
counters.add("test-key", 1);
counters.add("test-key", 1);
counters.add("test-key", 1);
}
std::unique_ptr<DB> reopen_db;
DB* reopen_db;
ASSERT_TRUE(DB::Open(Options(), dbname, &reopen_db).IsInvalidArgument());
}
*/
@@ -616,8 +614,8 @@ TEST_F(MergeTest, MergeWithCompactionAndFlush) {
{
auto db = OpenDb(dbname);
{
MergeBasedCounters counters(db.get(), 0);
testCountersWithFlushAndCompaction(counters, db);
MergeBasedCounters counters(db, 0);
testCountersWithFlushAndCompaction(counters, db.get());
}
}
ASSERT_OK(DestroyDB(dbname, Options()));
+1 -2
View File
@@ -309,8 +309,7 @@ TEST_F(ObsoleteFilesTest, GetSortedWalFilesHangsAfterNoopPurge) {
// 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_.get(); // Only using `db` makes it clear we only use DB-level APIs.
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()));
+4 -4
View File
@@ -66,16 +66,16 @@ TEST_F(OptionsFileTest, NumberOfOptionsFiles) {
opt.create_if_missing = true;
ASSERT_OK(DestroyDB(dbname_, opt));
std::unordered_set<std::string> filename_history;
std::unique_ptr<DB> db;
DB* db;
for (int i = 0; i < kReopenCount; ++i) {
ASSERT_OK(DB::Open(opt, dbname_, &db));
int num_options_files = 0;
UpdateOptionsFiles(db.get(), &filename_history, &num_options_files);
UpdateOptionsFiles(db, &filename_history, &num_options_files);
ASSERT_GT(num_options_files, 0);
ASSERT_LE(num_options_files, 2);
// Make sure we always keep the latest option files.
VerifyOptionsFileName(db.get(), filename_history);
db.reset();
VerifyOptionsFileName(db, filename_history);
delete db;
}
}
+11 -9
View File
@@ -38,8 +38,8 @@ const std::string kDbName =
namespace ROCKSDB_NAMESPACE {
std::unique_ptr<DB> OpenDb(bool read_only = false) {
std::unique_ptr<DB> db;
std::shared_ptr<DB> OpenDb(bool read_only = false) {
DB* db;
Options options;
options.create_if_missing = true;
options.max_open_files = -1;
@@ -61,7 +61,7 @@ std::unique_ptr<DB> OpenDb(bool read_only = false) {
s = DB::OpenForReadOnly(options, kDbName, &db);
}
EXPECT_OK(s);
return db;
return std::shared_ptr<DB>(db);
}
class PerfContextTest : public testing::Test {};
@@ -659,11 +659,12 @@ TEST_F(PerfContextTest, ToString) {
TEST_F(PerfContextTest, MergeOperatorTime) {
ASSERT_OK(DestroyDB(kDbName, Options()));
std::unique_ptr<DB> db;
DB* db;
Options options;
options.create_if_missing = true;
options.merge_operator = MergeOperators::CreateStringAppendOperator();
EXPECT_OK(DB::Open(options, kDbName, &db));
Status s = DB::Open(options, kDbName, &db);
EXPECT_OK(s);
std::string val;
ASSERT_OK(db->Merge(WriteOptions(), "k1", "val1"));
@@ -703,7 +704,7 @@ TEST_F(PerfContextTest, MergeOperatorTime) {
#endif
EXPECT_GT(get_perf_context()->merge_operator_time_nanos, 0);
db.reset();
delete db;
}
TEST_F(PerfContextTest, CopyAndMove) {
@@ -971,12 +972,13 @@ TEST_F(PerfContextTest, CPUTimer) {
TEST_F(PerfContextTest, MergeOperandCount) {
ASSERT_OK(DestroyDB(kDbName, Options()));
DB* db = nullptr;
Options options;
options.create_if_missing = true;
options.merge_operator = MergeOperators::CreateStringAppendOperator();
std::unique_ptr<DB> db;
ASSERT_OK(DB::Open(options, kDbName, &db));
std::unique_ptr<DB> db_guard(db);
constexpr size_t num_keys = 3;
const std::string key_prefix("key");
@@ -1005,7 +1007,7 @@ TEST_F(PerfContextTest, MergeOperandCount) {
for (size_t j = 0; j <= i; ++j) {
// Take a snapshot before each Merge so they are preserved and not
// collapsed during flush.
snapshots.emplace_back(db.get());
snapshots.emplace_back(db);
ASSERT_OK(db->Merge(WriteOptions(), keys[i], value + std::to_string(j)));
}
@@ -1122,7 +1124,7 @@ TEST_F(PerfContextTest, MergeOperandCount) {
TEST_F(PerfContextTest, WriteMemtableTimePerfLevel) {
// Write and check time
ASSERT_OK(DestroyDB(kDbName, Options()));
auto db = OpenDb();
std::shared_ptr<DB> db = OpenDb();
SetPerfLevel(PerfLevel::kEnableWait);
PerfContext* perf_ctx = get_perf_context();
+6 -6
View File
@@ -157,13 +157,13 @@ TEST_F(PeriodicTaskSchedulerTest, MultiInstances) {
[&](void*) { pst_st_counter++; });
SyncPoint::GetInstance()->EnableProcessing();
auto dbs = std::vector<std::unique_ptr<DB>>(kInstanceNum);
auto dbs = std::vector<DB*>(kInstanceNum);
for (int i = 0; i < kInstanceNum; i++) {
ASSERT_OK(
DB::Open(options, test::PerThreadDBPath(std::to_string(i)), &(dbs[i])));
}
auto dbi = static_cast_with_check<DBImpl>(dbs[kInstanceNum - 1].get());
auto dbi = static_cast_with_check<DBImpl>(dbs[kInstanceNum - 1]);
const PeriodicTaskScheduler& scheduler = dbi->TEST_GetPeriodicTaskScheduler();
// kRecordSeqnoTime is not registered since the feature is not enabled
@@ -190,7 +190,7 @@ TEST_F(PeriodicTaskSchedulerTest, MultiInstances) {
int half = kInstanceNum / 2;
for (int i = 0; i < half; i++) {
dbs[i].reset();
delete dbs[i];
}
expected_run += (kInstanceNum - half) * 2;
@@ -204,7 +204,7 @@ TEST_F(PeriodicTaskSchedulerTest, MultiInstances) {
for (int i = half; i < kInstanceNum; i++) {
ASSERT_OK(dbs[i]->Close());
dbs[i].reset();
delete dbs[i];
}
}
@@ -229,11 +229,11 @@ TEST_F(PeriodicTaskSchedulerTest, MultiEnv) {
options1.env = mock_env2.get();
std::string dbname = test::PerThreadDBPath("multi_env_test");
std::unique_ptr<DB> db;
DB* db;
ASSERT_OK(DB::Open(options2, dbname, &db));
ASSERT_OK(db->Close());
db.reset();
delete db;
Close();
}
+17 -10
View File
@@ -98,7 +98,7 @@ class PlainTableDBTest : public testing::Test,
private:
std::string dbname_;
Env* env_;
std::unique_ptr<DB> db_;
DB* db_;
bool mmap_mode_;
Options last_options_;
@@ -107,7 +107,7 @@ class PlainTableDBTest : public testing::Test,
PlainTableDBTest() : env_(Env::Default()) {}
~PlainTableDBTest() override {
db_.reset();
delete db_;
EXPECT_OK(DestroyDB(dbname_, Options()));
}
@@ -115,7 +115,7 @@ class PlainTableDBTest : public testing::Test,
mmap_mode_ = GetParam();
dbname_ = test::PerThreadDBPath("plain_table_db_test");
EXPECT_OK(DestroyDB(dbname_, Options()));
db_.reset();
db_ = nullptr;
Reopen();
}
@@ -144,11 +144,14 @@ class PlainTableDBTest : public testing::Test,
return options;
}
DBImpl* dbfull() { return static_cast_with_check<DBImpl>(db_.get()); }
DBImpl* dbfull() { return static_cast_with_check<DBImpl>(db_); }
void Reopen(Options* options = nullptr) { ASSERT_OK(TryReopen(options)); }
void Close() { db_.reset(); }
void Close() {
delete db_;
db_ = nullptr;
}
bool mmap_mode() const { return mmap_mode_; }
@@ -159,21 +162,24 @@ class PlainTableDBTest : public testing::Test,
}
void Destroy(Options* options) {
db_.reset();
delete db_;
db_ = nullptr;
ASSERT_OK(DestroyDB(dbname_, *options));
}
Status PureReopen(Options* options, std::unique_ptr<DB>* db) {
Status PureReopen(Options* options, DB** db) {
return DB::Open(*options, dbname_, db);
}
Status ReopenForReadOnly(Options* options) {
db_.reset();
delete db_;
db_ = nullptr;
return DB::OpenForReadOnly(*options, dbname_, &db_);
}
Status TryReopen(Options* options = nullptr) {
db_.reset();
delete db_;
db_ = nullptr;
Options opts;
if (options != nullptr) {
opts = *options;
@@ -489,7 +495,8 @@ TEST_P(PlainTableDBTest, Flush) {
ASSERT_GT(int_num, 0U);
TablePropertiesCollection ptc;
ASSERT_OK(dbfull()->GetPropertiesOfAllTables(&ptc));
ASSERT_OK(
static_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc));
ASSERT_EQ(1U, ptc.size());
auto row = ptc.begin();
auto tp = row->second;
+8 -6
View File
@@ -220,6 +220,8 @@ class SamePrefixTransform : public SliceTransform {
return false;
}
bool InRange(const Slice& dst) const override { return dst == prefix_; }
bool FullLengthEnabled(size_t* /*len*/) const override { return false; }
};
@@ -227,8 +229,8 @@ class SamePrefixTransform : public SliceTransform {
class PrefixTest : public testing::Test {
public:
std::unique_ptr<DB> OpenDb() {
std::unique_ptr<DB> db;
std::shared_ptr<DB> OpenDb() {
DB* db;
options.create_if_missing = true;
options.write_buffer_size = FLAGS_write_buffer_size;
@@ -249,7 +251,7 @@ class PrefixTest : public testing::Test {
Status s = DB::Open(options, kDbName, &db);
EXPECT_OK(s);
return db;
return std::shared_ptr<DB>(db);
}
void FirstOption() { option_config_ = kBegin; }
@@ -302,7 +304,7 @@ class PrefixTest : public testing::Test {
};
TEST(SamePrefixTest, InDomainTest) {
std::unique_ptr<DB> db;
DB* db;
Options options;
options.create_if_missing = true;
options.prefix_extractor.reset(new SamePrefixTransform("HHKB"));
@@ -329,7 +331,7 @@ TEST(SamePrefixTest, InDomainTest) {
ASSERT_EQ(db_iter->value(), "idk");
delete db_iter;
db.reset();
delete db;
ASSERT_OK(DestroyDB(kDbName, Options()));
}
@@ -346,7 +348,7 @@ TEST(SamePrefixTest, InDomainTest) {
ASSERT_TRUE(db_iter->Valid());
ASSERT_OK(db_iter->status());
delete db_iter;
db.reset();
delete db;
ASSERT_OK(DestroyDB(kDbName, Options()));
}
}
+5 -5
View File
@@ -661,14 +661,14 @@ TEST_P(SeqnoTimeTablePropTest, MultiInstancesBasic) {
options.stats_dump_period_sec = 0;
options.stats_persist_period_sec = 0;
auto dbs = std::vector<std::unique_ptr<DB>>(kInstanceNum);
auto dbs = std::vector<DB*>(kInstanceNum);
for (int i = 0; i < kInstanceNum; i++) {
ASSERT_OK(
DB::Open(options, test::PerThreadDBPath(std::to_string(i)), &(dbs[i])));
}
// Make sure the second instance has the worker enabled
auto dbi = static_cast_with_check<DBImpl>(dbs[1].get());
auto dbi = static_cast_with_check<DBImpl>(dbs[1]);
WriteOptions wo;
for (int i = 0; i < 200; i++) {
ASSERT_OK(dbi->Put(wo, Key(i), "value"));
@@ -680,7 +680,7 @@ TEST_P(SeqnoTimeTablePropTest, MultiInstancesBasic) {
for (int i = 0; i < kInstanceNum; i++) {
ASSERT_OK(dbs[i]->Close());
dbs[i].reset();
delete dbs[i];
}
}
@@ -792,8 +792,8 @@ TEST_P(SeqnoTimeTablePropTest, SeqnoToTimeMappingUniversal) {
}
ASSERT_GT(num_seqno_zeroing, 0);
std::vector<KeyVersion> key_versions;
ASSERT_OK(GetAllKeyVersions(
db_.get(), {}, {}, std::numeric_limits<size_t>::max(), &key_versions));
ASSERT_OK(GetAllKeyVersions(db_, {}, {}, std::numeric_limits<size_t>::max(),
&key_versions));
// make sure there're more than 300 keys and first 100 keys are having seqno
// zeroed out, the last 100 key seqno not zeroed out
ASSERT_GT(key_versions.size(), 300);
+2 -2
View File
@@ -83,7 +83,7 @@ bool VersionEdit::EncodeTo(std::string* dst,
PutVarint32Varint64(dst, kNextFileNumber, next_file_number_);
}
if (has_max_column_family_) {
PutVarint32(dst, kMaxColumnFamily, max_column_family_);
PutVarint32Varint32(dst, kMaxColumnFamily, max_column_family_);
}
if (has_min_log_number_to_keep_) {
PutVarint32Varint64(dst, kMinLogNumberToKeep, min_log_number_to_keep_);
@@ -143,7 +143,7 @@ bool VersionEdit::EncodeTo(std::string* dst,
// 0 is default and does not need to be explicitly written
if (column_family_ != 0) {
PutVarint32(dst, kColumnFamily, column_family_);
PutVarint32Varint32(dst, kColumnFamily, column_family_);
}
if (is_column_family_add_) {
-1
View File
@@ -25,7 +25,6 @@
#include "rocksdb/advanced_options.h"
#include "table/table_reader.h"
#include "table/unique_id_impl.h"
#include "test_util/sync_point.h"
#include "util/autovector.h"
namespace ROCKSDB_NAMESPACE {
Loaded 100 of 338 files, more files were not shown because too many files have changed in this diff. Show more