Compare commits

..
8 Commits
Author SHA1 Message Date
anand76 4a3d362471 Revert "Fix Windows build issue in trie_index_factory.h"
This reverts commit b7b8326c89.
2026-10-15 10:40:31 -07:00
Anand Ananthabhotla 987e62e576 Make InjectedErrorLog::Entry 256 bytes on 32-bit targets (#15388)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15388

`InjectedErrorLog::Entry` in `utilities/fault_injection_fs.h` is written to the injected error log in its native layout, and a `static_assert` requires `sizeof(Entry) == 256` so the record size stays stable. That assert fails on i386 builds.

Root cause: the fields add up to only 252 bytes. On targets where `alignof(uint64_t)` is 8 (x86_64, aarch64, armv7), the compiler adds 4 bytes of implicit tail padding, so the size is 256. On i386, `uint64_t` members are 4-byte aligned inside a struct, so there is no tail padding and the size is 252.

Fix: add an explicit trailing `uint8_t padding[4]` field so the size no longer depends on `uint64_t` alignment.

Compatibility: on 64-bit targets the new field takes the place of the old implicit tail padding, so every field offset and the 256-byte record layout are unchanged. `Record()` value-initializes the entry (`Entry entry{}`), so the padding bytes are always written as zero. No decoder in the repo depends on the padding bytes.

___

landed-with-radar-review

Differential Revision: D124873369

fbshipit-source-id: f5c6f43ca85013b0e769853ad13b8d4f3f89b958
2026-10-13 10:43:29 -07:00
xingbowang 77bd1f3815 Fix big-endian builds for the fault-injection filesystem (#15297)
Summary:
The persistent injected-error log writes `InjectedErrorLog::Entry` objects using their native in-memory layout, while the log format's consumer expects little-endian multi-byte fields. The existing `static_assert(port::kLittleEndian)` protected that format by rejecting every big-endian build that included the fault-injection filesystem, even though the log is an optional diagnostic.

This change keeps RocksDB and the fault-injection filesystem available on supported big-endian platforms while disabling only the incompatible persistent log. Log-file creation now returns an inactive descriptor when `port::kLittleEndian` is false, which makes `Record()` and `Flush()` remain safe no-ops. The fixed 256-byte entry-size assertion remains in place, and format-dependent tests are bypassed on big-endian targets.

The change also adds a release note and documents big-endian support plus the preferred capability-check pattern for optional architecture-specific features.

## Compatibility and limitations

- Little-endian behavior and the on-disk log layout are unchanged.
- On big-endian platforms, configuring an injected-error log path does not create or populate a log file; the rest of fault injection remains available.
- Windows already leaves this POSIX-backed diagnostic disabled.
- This does not introduce a portable log encoding. Supporting persistent injected-error logs on big-endian platforms would require a format change.

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

Test Plan:
- `build_tools/rockstest.sh fault_injection_fs_test '--gtest_filter=InjectedErrorLogTest.*'` (little-endian macOS): all 5 tests passed.
- `make check-sources`
- `git diff --check upstream/main...2026_10_01_endian_fix`

Native big-endian build and test execution were not available locally; that path relies on the compile-time `port::kLittleEndian` capability and should be covered by big-endian CI.

Reviewed By: jaykorean

Differential Revision: D122880760

Pulled By: xingbowang

fbshipit-source-id: 59dae9661a47608aa12f41078ad1b3ef0fa7231e
2026-10-13 10:43:14 -07:00
anand76 b7b8326c89 Fix Windows build issue in trie_index_factory.h 2026-10-13 10:43:03 -07:00
Xingbo Wang ed73bb989e Update version and HISTORY.md for 11.9.1 release 2026-08-19 05:06:41 -07:00
Xingbo Wang 951647e596 Add bootstrap configuration for RocksDB external tables
Summary:
Add an opaque external_table_config option to the external table adapter. RocksDB persists the string in OPTIONS files and passes it to ExternalTableFactory::Configure during bootstrap, allowing implementations such as Nimble to parse implementation-specific settings without adding RocksDB-specific fields. The option is immutable after DB open, and attempted runtime updates fail safely for non-clonable table factories. A reopen test verifies that callers can supply a different valid configuration between DB opens. Initialize the adapter option metadata lazily as const to avoid namespace-scope dynamic initialization and mutable-global lint issues.

PR https://github.com/facebook/rocksdb/pull/15119

Reviewed By: anand1976

Differential Revision: D116362444

fbshipit-source-id: 225a4aedc455f320b5c90ac2f0862917da477ccb
(cherry picked from commit db1adf47bb)
2026-08-18 16:34:10 -07:00
Anand Ananthabhotla 8941ff459d Verify compressed block bytes after checksum computation (#15098)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15098

Compute the base block checksum in `CompressAndVerifyBlock()` before `verify_compression` decompresses the compressed output. Parallel compression stores that base checksum on the block rep so worker threads can keep the CPU-heavy checksum and verification work off the ordered write path; the writer only applies the offset context modifier before writing the trailer.

Update the regression test to cover both serial and parallel compression paths.

Reviewed By: pdillinger

Differential Revision: D115950785

fbshipit-source-id: b5e47f7d7c41680e7e7907443fc805bc0d043d6e
(cherry picked from commit 92a6308300)
2026-08-18 16:34:10 -07:00
rocksdb-release-bot 1aaec657a5 Update HISTORY.md for 11.9.0 release 2026-08-17 09:53:29 -07:00
909 changed files with 15390 additions and 104957 deletions

No files matched your search

+38
View File
@@ -0,0 +1,38 @@
name: post-steps
description: Steps that are taken after a RocksDB job
inputs:
artifact-prefix:
description: Prefix to append to the name of artifacts that are uploaded
required: true
default: "${{ github.job }}"
runs:
using: composite
steps:
- name: Upload Test Results artifact
uses: actions/upload-artifact@v4.0.0
with:
name: "${{ inputs.artifact-prefix }}-test-results"
path: "${{ runner.temp }}/test-results/**"
- name: Upload DB LOG file artifact
uses: actions/upload-artifact@v4.0.0
with:
name: "${{ inputs.artifact-prefix }}-db-log-file"
path: LOG
- name: Copy Test Logs (on Failure)
if: ${{ failure() }}
run: |
mkdir -p ${{ runner.temp }}/failure-test-logs
cp -r t/* ${{ runner.temp }}/failure-test-logs
shell: bash
- name: Upload Test Logs (on Failure) artifact
uses: actions/upload-artifact@v4.0.0
with:
name: "${{ inputs.artifact-prefix }}-failure-test-logs"
path: ${{ runner.temp }}/failure-test-logs/**
if-no-files-found: ignore
- name: Upload Core Dumps artifact
uses: actions/upload-artifact@v4.0.0
with:
name: "${{ inputs.artifact-prefix }}-core-dumps"
path: "core.*"
if-no-files-found: ignore
+8 -16
View File
@@ -2,21 +2,12 @@ name: pre-steps
runs:
using: composite
steps:
- name: Dump system info for diagnostics
- name: Dump ulimits for diagnostics
run: |
echo "=== ulimits (soft) ==="
ulimit -a -S || true
echo "=== ulimits (hard) ==="
ulimit -a -H || true
echo "=== OS ==="
uname -a || true
echo "=== CPU ==="
if [ "$RUNNER_OS" = "macOS" ]; then
sysctl machdep.cpu.brand_string hw.ncpu || true
else
lscpu || true
grep -m1 '^microcode' /proc/cpuinfo || true
fi
shell: bash
- name: Install lld linker for faster builds
run: apt-get update -y && apt-get install -y lld 2>/dev/null || true
@@ -24,19 +15,20 @@ runs:
- name: Setup Environment Variables
run: |-
echo "GTEST_THROW_ON_FAILURE=0" >> "$GITHUB_ENV"
echo "GTEST_OUTPUT=\"xml:${{ runner.temp }}/test-results/\"" >> "$GITHUB_ENV"
echo "SKIP_FORMAT_BUCK_CHECKS=1" >> "$GITHUB_ENV"
echo "GTEST_COLOR=1" >> "$GITHUB_ENV"
echo "CTEST_OUTPUT_ON_FAILURE=1" >> "$GITHUB_ENV"
echo "CTEST_TEST_TIMEOUT=300" >> "$GITHUB_ENV"
# These third-party sources are mirrored on the "third-party-deps" GitHub
# release. To add one, upload the asset here (intentionally hidden):
# https://github.com/meta/rocksdb/releases/edit/third-party-deps
# https://github.com/facebook/rocksdb/releases/edit/third-party-deps
# Note how file names are generated below (vs. from the original source)
# before uploading. Removing or modifying files will break builds for
# older releases/revisions (not found or checksum mismatch).
echo "ZLIB_DOWNLOAD_PREFIX=https://github.com/meta/rocksdb/releases/download/third-party-deps/zlib-" >> "$GITHUB_ENV"
echo "BZIP2_DOWNLOAD_PREFIX=https://github.com/meta/rocksdb/releases/download/third-party-deps/bzip2-" >> "$GITHUB_ENV"
echo "SNAPPY_DOWNLOAD_PREFIX=https://github.com/meta/rocksdb/releases/download/third-party-deps/snappy-" >> "$GITHUB_ENV"
echo "LZ4_DOWNLOAD_PREFIX=https://github.com/meta/rocksdb/releases/download/third-party-deps/lz4-" >> "$GITHUB_ENV"
echo "ZSTD_DOWNLOAD_PREFIX=https://github.com/meta/rocksdb/releases/download/third-party-deps/zstd-" >> "$GITHUB_ENV"
echo "ZLIB_DOWNLOAD_PREFIX=https://github.com/facebook/rocksdb/releases/download/third-party-deps/zlib-" >> "$GITHUB_ENV"
echo "BZIP2_DOWNLOAD_PREFIX=https://github.com/facebook/rocksdb/releases/download/third-party-deps/bzip2-" >> "$GITHUB_ENV"
echo "SNAPPY_DOWNLOAD_PREFIX=https://github.com/facebook/rocksdb/releases/download/third-party-deps/snappy-" >> "$GITHUB_ENV"
echo "LZ4_DOWNLOAD_PREFIX=https://github.com/facebook/rocksdb/releases/download/third-party-deps/lz4-" >> "$GITHUB_ENV"
echo "ZSTD_DOWNLOAD_PREFIX=https://github.com/facebook/rocksdb/releases/download/third-party-deps/zstd-" >> "$GITHUB_ENV"
shell: bash
+1 -1
View File
@@ -8,7 +8,7 @@ runs:
run: chown `whoami` . || true
shell: bash
- name: Set upstream
run: git remote add upstream https://github.com/meta/rocksdb.git
run: git remote add upstream https://github.com/facebook/rocksdb.git
shell: bash
- name: Fetch upstream
run: git fetch upstream
+98
View File
@@ -0,0 +1,98 @@
// Parse Codex review artifacts and produce a markdown review comment.
//
// Usage from actions/github-script:
// const parse = require('./.github/scripts/parse-codex-review.js');
// const markdown = parse({ responseFile, recoveryFile, findingsFile, logFile,
// exitCode, meta });
//
// Parameters:
// responseFile - path to Codex final response output
// recoveryFile - path to recovery output formatted from review-findings.md
// findingsFile - path to incremental findings file written during review
// logFile - path to Codex stdout/stderr log
// exitCode - Codex process exit code
// meta - { trigger, autoMode, headSha, reviewer, isQuery, isPartial }
const fs = require('fs');
const buildComment = require('./build-ai-review-comment.js');
module.exports = function parseCodex(
{responseFile, recoveryFile, findingsFile, logFile, exitCode, meta}) {
function getTriggerLine() {
if (meta.trigger !== 'auto') {
return `*Requested by @${meta.reviewer}*`;
}
const shortSha = meta.headSha ? meta.headSha.substring(0, 7) : 'unknown';
if (meta.autoMode === 'early') {
return `*Auto-triggered after CI reached the early-review threshold — reviewing commit ${
shortSha}*`;
}
return `*Auto-triggered after CI passed — reviewing commit ${shortSha}*`;
}
function readIfPresent(path) {
if (!path || !fs.existsSync(path)) {
return '';
}
const text = fs.readFileSync(path, 'utf8').trim();
return text;
}
function tailFile(path, maxChars = 12000) {
const text = readIfPresent(path);
if (!text) {
return '';
}
if (text.length <= maxChars) {
return text;
}
return text.slice(text.length - maxChars);
}
let responseBody = '';
const recovered = readIfPresent(recoveryFile);
const direct = readIfPresent(responseFile);
const findings = readIfPresent(findingsFile);
if (recovered) {
responseBody = recovered;
} else if (direct) {
responseBody = direct;
} else if (findings) {
responseBody =
'⚠️ **Review incomplete — Codex did not produce a final response.**\n\n' +
'Below are the incremental findings recovered from `review-findings.md`.\n\n---\n\n' +
findings;
} else {
const logTail = tailFile(logFile);
responseBody = logTail ?
`❌ **Codex review failed before producing findings.**\n\n\`\`\`\n${
logTail}\n\`\`\`` :
'❌ Codex review failed before producing any output.';
}
const isPartial = !!meta.isPartial;
const code = Number.parseInt(exitCode || '1', 10);
const icon = isPartial ? '🟡' : (code === 0 ? '✅' : '⚠️');
const triggerLine = getTriggerLine();
return buildComment({
icon,
headerTitle: meta.isQuery ? 'Codex Response' : 'Codex Code Review',
triggerLine,
responseBody,
footerLines: [
'Generated by Codex CLI.',
'Review methodology: `claude_md/ci_review_prompt.md`',
'',
'**Limitations:**',
'- Codex may miss context from files not in the diff',
'- Large PRs may be truncated',
'- Always apply human judgment to AI suggestions',
'',
'**Commands:**',
'- `/codex-review [context]` — Request a code review',
'- `/codex-query <question>` — Ask about the PR or codebase',
],
});
};
+1 -1
View File
@@ -117,7 +117,7 @@ function assertObsoleteComment(comment, originalBody) {
const context = {
repo: {
owner: 'meta',
owner: 'facebook',
repo: 'rocksdb',
},
};
+418 -5
View File
@@ -1,8 +1,18 @@
# Shared analysis workflow for AI PR reviews.
#
# This workflow is invoked by provider-specific wrappers (such as
# claude-review.yml) so the event triggers and workflow names stay
# user-friendly while the implementation lives in one place.
# This workflow is invoked by provider-specific wrappers (Claude/Codex) so the
# event triggers and workflow names stay user-friendly while the implementation
# lives in one place.
#
# Security note for Codex:
# Codex runs in non-interactive CI, so the Codex CLI steps below pass
# `--dangerously-bypass-approvals-and-sandbox`. That disables Codex's
# approval prompts and sandbox protections, so treat those steps as
# arbitrary command execution on the runner. This file therefore keeps Codex
# in the read-only analysis workflow, restricts the pull_request_target path
# to same-repo PRs, and leaves write-scoped PR commenting in a separate
# workflow. Re-evaluate that trust boundary before expanding Codex auto
# review to broader trigger sources.
name: AI Review Analysis
@@ -10,7 +20,7 @@ on:
workflow_call:
inputs:
provider:
description: Provider key, for example "claude"
description: Provider key, for example "claude" or "codex"
required: true
type: string
display_name:
@@ -109,7 +119,7 @@ jobs:
const FAIL_CONCLUSIONS = new Set([
'failure', 'cancelled', 'timed_out', 'action_required'
]);
const AI_CHECK_TOKENS = ['claude'];
const AI_CHECK_TOKENS = ['claude', 'codex'];
const MIN_CHECKS = 10;
const THRESHOLD = 0.5;
const POLL_INTERVAL_MS = 90 * 1000;
@@ -409,6 +419,24 @@ jobs:
fetch-depth: 0
persist-credentials: false
- name: Setup Node.js
if: >-
steps.pr_info.outputs.skip != 'true' &&
steps.check_existing.outputs.skip != 'true' &&
inputs.provider == 'codex'
uses: actions/setup-node@v4
with:
node-version: 22
- name: Install Codex CLI
if: >-
steps.pr_info.outputs.skip != 'true' &&
steps.check_existing.outputs.skip != 'true' &&
inputs.provider == 'codex'
run: |
npm install -g @openai/codex
codex --version
- name: Generate diff and prompt
if: steps.pr_info.outputs.skip != 'true' && steps.check_existing.outputs.skip != 'true'
env:
@@ -513,6 +541,72 @@ jobs:
core.setOutput('label', label);
core.setOutput('tokens', budget);
- name: Classify PR complexity (Codex)
if: >-
steps.pr_info.outputs.skip != 'true' &&
steps.check_existing.outputs.skip != 'true' &&
inputs.provider == 'codex'
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
CODEX_HOME: /tmp/codex-home
CODEX_MODEL: ${{ inputs.default_model }}
run: |
if [ -z "${OPENAI_API_KEY}" ]; then
echo "OPENAI_API_KEY not configured, skipping Codex review"
printf 'complex\n' > codex-complexity-output.txt
exit 0
fi
mkdir -p "${CODEX_HOME}"
set +e
codex exec \
-m "${CODEX_MODEL}" \
-c model_reasoning_effort="medium" \
--dangerously-bypass-approvals-and-sandbox \
--output-last-message codex-complexity-output.txt \
- < /tmp/complexity_prompt.txt > codex-complexity.log 2>&1
exit_code=$?
set -e
if [ "${exit_code}" -ne 0 ]; then
echo "Codex complexity classification failed with exit code ${exit_code}"
echo "Defaulting to complex review budget"
if [ -s codex-complexity.log ]; then
echo "Last 100 lines of codex-complexity.log:"
tail -100 codex-complexity.log
fi
printf 'complex\n' > codex-complexity-output.txt
fi
- name: Pick thinking budget (Codex)
if: >-
steps.pr_info.outputs.skip != 'true' &&
steps.check_existing.outputs.skip != 'true' &&
inputs.provider == 'codex'
id: budget_codex
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
let label = 'complex';
try {
const text = fs.readFileSync(
'codex-complexity-output.txt',
'utf8'
).trim().toLowerCase();
if (/\bsimple\b/.test(text) && !/\bcomplex\b/.test(text)) {
label = 'simple';
}
} catch (error) {
core.warning(
`Could not parse classifier output: ${error.message}`);
}
const effort = label === 'simple' ? 'high' : 'xhigh';
core.info(
`Complexity classified as: ${label} ` +
`-> model_reasoning_effort=${effort}`
);
core.setOutput('label', label);
core.setOutput('effort', effort);
- name: Run Claude
if: >-
steps.pr_info.outputs.skip != 'true' &&
@@ -609,6 +703,99 @@ jobs:
});
fs.writeFileSync(process.env.COMMENT_FILE, body);
- name: Run Codex
if: >-
steps.pr_info.outputs.skip != 'true' &&
steps.check_existing.outputs.skip != 'true' &&
inputs.provider == 'codex'
id: codex
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
CODEX_HOME: /tmp/codex-home
CODEX_MODEL: ${{ inputs.default_model }}
CODEX_REASONING_EFFORT: ${{ steps.budget_codex.outputs.effort }}
run: |
if [ -z "${OPENAI_API_KEY}" ]; then
echo "OPENAI_API_KEY not configured, skipping Codex review"
exit 0
fi
mkdir -p "${CODEX_HOME}"
set +e
codex exec \
-m "${CODEX_MODEL}" \
-c model_reasoning_effort="${CODEX_REASONING_EFFORT}" \
--dangerously-bypass-approvals-and-sandbox \
--output-last-message codex-review-output.md \
- < /tmp/prompt.txt > codex-review-execution.log 2>&1
exit_code=$?
set -e
echo "exit_code=${exit_code}" >> "${GITHUB_OUTPUT}"
echo "${exit_code}" > codex-review-exit-code.txt
- name: Recover partial review (Codex)
if: >-
steps.pr_info.outputs.skip != 'true' &&
steps.check_existing.outputs.skip != 'true' &&
inputs.provider == 'codex' &&
steps.codex.outputs.exit_code != '0' &&
hashFiles('review-findings.md') != ''
id: recover_codex
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
CODEX_HOME: /tmp/codex-home
CODEX_MODEL: ${{ inputs.default_model }}
CODEX_REASONING_EFFORT: ${{ steps.budget_codex.outputs.effort }}
run: |
set +e
codex exec \
-m "${CODEX_MODEL}" \
-c model_reasoning_effort="${CODEX_REASONING_EFFORT}" \
--dangerously-bypass-approvals-and-sandbox \
--output-last-message codex-review-recovery.md \
- < claude_md/ci_recovery_prompt.md > codex-review-recovery.log 2>&1
exit_code=$?
set -e
echo "exit_code=${exit_code}" >> "${GITHUB_OUTPUT}"
- name: Build review comment (Codex)
if: >-
steps.pr_info.outputs.skip != 'true' &&
steps.check_existing.outputs.skip != 'true' &&
inputs.provider == 'codex'
env:
RESPONSE_FILE: codex-review-output.md
RECOVERY_FILE: codex-review-recovery.md
FINDINGS_FILE: review-findings.md
LOG_FILE: codex-review-execution.log
EXIT_CODE: ${{ steps.codex.outputs.exit_code }}
HEAD_SHA: ${{ steps.pr_info.outputs.head_sha }}
AUTO_MODE: ${{ steps.pr_info.outputs.auto_mode }}
USED_RECOVERY: ${{ steps.recover_codex.outputs.exit_code == '0' && 'true' || 'false' }}
COMMENT_FILE: ${{ inputs.comment_file }}
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const parse = require('./.github/scripts/parse-codex-review.js');
const body = parse({
responseFile: process.env.RESPONSE_FILE,
recoveryFile: process.env.RECOVERY_FILE,
findingsFile: process.env.FINDINGS_FILE,
logFile: process.env.LOG_FILE,
exitCode: process.env.EXIT_CODE,
meta: {
trigger: 'auto',
autoMode: process.env.AUTO_MODE,
headSha: process.env.HEAD_SHA,
reviewer: '',
isQuery: false,
isPartial: process.env.USED_RECOVERY === 'true' ||
(!fs.existsSync(process.env.RESPONSE_FILE) &&
fs.existsSync(process.env.FINDINGS_FILE)),
}
});
fs.writeFileSync(process.env.COMMENT_FILE, body);
- name: Save PR number
if: steps.pr_info.outputs.skip != 'true' && steps.check_existing.outputs.skip != 'true'
run: |
@@ -659,6 +846,27 @@ jobs:
retention-days: 7
if-no-files-found: ignore
- name: Upload review logs (Codex)
if: >-
always() &&
steps.pr_info.outputs.skip != 'true' &&
steps.check_existing.outputs.skip != 'true' &&
inputs.provider == 'codex'
uses: actions/upload-artifact@v4
with:
name: codex-review-logs
path: |
codex-review-execution.log
codex-complexity.log
codex-complexity-output.txt
codex-review-recovery.log
codex-review-output.md
codex-review-recovery.md
review-findings.md
codex-review-exit-code.txt
retention-days: 7
if-no-files-found: ignore
manual-review:
name: Manual ${{ inputs.display_name }} Review
runs-on: ubuntu-latest
@@ -782,6 +990,18 @@ jobs:
fetch-depth: 0
persist-credentials: false
- name: Setup Node.js
if: steps.request.outputs.skip != 'true' && inputs.provider == 'codex'
uses: actions/setup-node@v4
with:
node-version: 22
- name: Install Codex CLI
if: steps.request.outputs.skip != 'true' && inputs.provider == 'codex'
run: |
npm install -g @openai/codex
codex --version
- name: Generate diff and prompt
if: steps.request.outputs.skip != 'true'
env:
@@ -933,6 +1153,89 @@ jobs:
core.setOutput('label', label);
core.setOutput('tokens', budget);
- name: Classify PR complexity (Codex)
if: >-
steps.request.outputs.skip != 'true' &&
steps.request.outputs.type == 'review' &&
inputs.provider == 'codex'
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
CODEX_HOME: /tmp/codex-home
CODEX_MODEL: ${{ inputs.selected_model != '' && inputs.selected_model || inputs.default_model }}
run: |
if [ -z "${OPENAI_API_KEY}" ]; then
echo "OPENAI_API_KEY not configured, skipping Codex review"
printf 'complex\n' > codex-complexity-output.txt
exit 0
fi
mkdir -p "${CODEX_HOME}"
set +e
codex exec \
-m "${CODEX_MODEL}" \
-c model_reasoning_effort="medium" \
--dangerously-bypass-approvals-and-sandbox \
--output-last-message codex-complexity-output.txt \
- < /tmp/complexity_prompt.txt > codex-complexity.log 2>&1
exit_code=$?
set -e
if [ "${exit_code}" -ne 0 ]; then
echo "Codex complexity classification failed with exit code ${exit_code}"
echo "Defaulting to complex review budget"
if [ -s codex-complexity.log ]; then
echo "Last 100 lines of codex-complexity.log:"
tail -100 codex-complexity.log
fi
printf 'complex\n' > codex-complexity-output.txt
fi
- name: Pick thinking budget (Codex)
if: steps.request.outputs.skip != 'true' && inputs.provider == 'codex'
id: manual_budget_codex
env:
OVERRIDE: ${{ inputs.thinking_budget }}
COMMAND_TYPE: ${{ steps.request.outputs.type }}
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
if (process.env.COMMAND_TYPE !== 'review') {
core.setOutput('label', 'query');
core.setOutput('effort', 'medium');
return;
}
const override = (process.env.OVERRIDE || '').trim();
if (override && /^\d+$/.test(override)) {
const budget = parseInt(override, 10);
const effort = budget >= 32000 ? 'xhigh' : 'high';
core.info(
`Using thinking_budget override: ${override} ` +
`-> model_reasoning_effort=${effort}`
);
core.setOutput('label', 'override');
core.setOutput('effort', effort);
return;
}
let label = 'complex';
try {
const text = fs.readFileSync(
'codex-complexity-output.txt',
'utf8'
).trim().toLowerCase();
if (/\bsimple\b/.test(text) && !/\bcomplex\b/.test(text)) {
label = 'simple';
}
} catch (error) {
core.warning(
`Could not parse classifier output: ${error.message}`);
}
const effort = label === 'simple' ? 'high' : 'xhigh';
core.info(
`Complexity classified as: ${label} ` +
`-> model_reasoning_effort=${effort}`
);
core.setOutput('label', label);
core.setOutput('effort', effort);
- name: Run Claude
if: steps.request.outputs.skip != 'true' && inputs.provider == 'claude'
id: manual_claude
@@ -1025,6 +1328,99 @@ jobs:
});
fs.writeFileSync(process.env.COMMENT_FILE, body);
- name: Run Codex
if: steps.request.outputs.skip != 'true' && inputs.provider == 'codex'
id: manual_codex
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
CODEX_HOME: /tmp/codex-home
CODEX_MODEL: ${{ inputs.selected_model != '' && inputs.selected_model || inputs.default_model }}
CODEX_REASONING_EFFORT: ${{ steps.manual_budget_codex.outputs.effort }}
COMMAND_TYPE: ${{ steps.request.outputs.type }}
run: |
mkdir -p "${CODEX_HOME}"
set +e
if [ "${COMMAND_TYPE}" = "query" ]; then
codex exec \
-m "${CODEX_MODEL}" \
-c model_reasoning_effort="${CODEX_REASONING_EFFORT}" \
--dangerously-bypass-approvals-and-sandbox \
--output-last-message codex-review-output.md \
- < /tmp/prompt.txt > codex-review-execution.log 2>&1
else
codex exec \
-m "${CODEX_MODEL}" \
-c model_reasoning_effort="${CODEX_REASONING_EFFORT}" \
--dangerously-bypass-approvals-and-sandbox \
--output-last-message codex-review-output.md \
- < /tmp/prompt.txt > codex-review-execution.log 2>&1
fi
exit_code=$?
set -e
echo "exit_code=${exit_code}" >> "${GITHUB_OUTPUT}"
echo "${exit_code}" > codex-review-exit-code.txt
- name: Recover partial review (Codex)
if: >-
steps.request.outputs.skip != 'true' &&
inputs.provider == 'codex' &&
steps.request.outputs.type == 'review' &&
steps.manual_codex.outputs.exit_code != '0' &&
hashFiles('review-findings.md') != ''
id: manual_recover_codex
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
CODEX_HOME: /tmp/codex-home
CODEX_MODEL: ${{ inputs.selected_model != '' && inputs.selected_model || inputs.default_model }}
CODEX_REASONING_EFFORT: ${{ steps.manual_budget_codex.outputs.effort }}
run: |
set +e
codex exec \
-m "${CODEX_MODEL}" \
-c model_reasoning_effort="${CODEX_REASONING_EFFORT}" \
--dangerously-bypass-approvals-and-sandbox \
--output-last-message codex-review-recovery.md \
- < claude_md/ci_recovery_prompt.md > codex-review-recovery.log 2>&1
exit_code=$?
set -e
echo "exit_code=${exit_code}" >> "${GITHUB_OUTPUT}"
- name: Build review comment (Codex)
if: steps.request.outputs.skip != 'true' && inputs.provider == 'codex'
env:
RESPONSE_FILE: codex-review-output.md
RECOVERY_FILE: codex-review-recovery.md
FINDINGS_FILE: review-findings.md
LOG_FILE: codex-review-execution.log
EXIT_CODE: ${{ steps.manual_codex.outputs.exit_code }}
HEAD_SHA: ${{ steps.pr_info.outputs.head_sha }}
REVIEWER: ${{ steps.request.outputs.reviewer }}
COMMAND_TYPE: ${{ steps.request.outputs.type }}
USED_RECOVERY: ${{ steps.manual_recover_codex.outputs.exit_code == '0' && 'true' || 'false' }}
COMMENT_FILE: ${{ inputs.comment_file }}
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const parse = require('./.github/scripts/parse-codex-review.js');
const body = parse({
responseFile: process.env.RESPONSE_FILE,
recoveryFile: process.env.RECOVERY_FILE,
findingsFile: process.env.FINDINGS_FILE,
logFile: process.env.LOG_FILE,
exitCode: process.env.EXIT_CODE,
meta: {
trigger: 'manual',
headSha: process.env.HEAD_SHA,
reviewer: process.env.REVIEWER,
isQuery: process.env.COMMAND_TYPE !== 'review',
isPartial: process.env.USED_RECOVERY === 'true' ||
(!fs.existsSync(process.env.RESPONSE_FILE) &&
fs.existsSync(process.env.FINDINGS_FILE)),
}
});
fs.writeFileSync(process.env.COMMENT_FILE, body);
- name: Save PR number
if: steps.request.outputs.skip != 'true'
run: |
@@ -1078,3 +1474,20 @@ jobs:
path: ${{ steps.manual_format_recovery_claude.outputs.execution_file }}
retention-days: 7
if-no-files-found: ignore
- name: Upload review logs (Codex)
if: always() && steps.request.outputs.skip != 'true' && inputs.provider == 'codex'
uses: actions/upload-artifact@v4
with:
name: codex-review-logs
path: |
codex-review-execution.log
codex-complexity.log
codex-complexity-output.txt
codex-review-recovery.log
codex-review-output.md
codex-review-recovery.md
review-findings.md
codex-review-exit-code.txt
retention-days: 7
if-no-files-found: ignore
+2 -3
View File
@@ -6,7 +6,7 @@ on:
workflow_call:
inputs:
provider:
description: Provider key, for example "claude"
description: Provider key, for example "claude" or "codex"
required: true
type: string
result_artifact_name:
@@ -71,8 +71,7 @@ jobs:
const post = require('./.github/scripts/post-pr-comment.js');
const provider = process.env.PROVIDER;
const providerTitle =
provider.charAt(0).toUpperCase() + provider.slice(1);
const providerTitle = provider === 'codex' ? 'Codex' : 'Claude';
const body = fs.readFileSync(commentFile, 'utf8');
const prNumber = parseInt(
fs.readFileSync(prNumberFile, 'utf8').trim(),
+2 -2
View File
@@ -1,4 +1,4 @@
name: meta/rocksdb/benchmark-linux
name: facebook/rocksdb/benchmark-linux
on: workflow_dispatch
permissions: {}
# FIXME: Disabled temporarily
@@ -6,7 +6,7 @@ permissions: {}
# - cron: 7 */2 * * * # At minute 7 past every 2nd hour
jobs:
benchmark-linux:
if: ${{ github.repository_owner == 'meta' }}
if: ${{ github.repository_owner == 'facebook' }}
runs-on: ubuntu-latest # FIXME: change this back to self-hosted when ready
steps:
- uses: actions/checkout@v4.1.0
+2 -3
View File
@@ -7,12 +7,11 @@ permissions: {}
jobs:
clang-tidy:
if: github.repository_owner == 'meta'
if: github.repository_owner == 'facebook'
runs-on:
labels: 4-core-ubuntu
container:
image: ghcr.io/meta/rocksdb_ubuntu:24.1
options: --shm-size=8gb
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
steps:
- uses: actions/checkout@v4.1.0
with:
+1 -1
View File
@@ -8,7 +8,7 @@ name: Claude Code Review
on:
workflow_run:
workflows: ["meta/rocksdb/pr-jobs"]
workflows: ["facebook/rocksdb/pr-jobs"]
types: [completed]
# The early pull_request_target path is limited to same-repo PRs by the job
@@ -0,0 +1,24 @@
# Codex Code Review — Comment Posting Workflow
#
# Thin wrapper around .github/workflows/ai-review-comment.yml so the provider
# specific workflow name stays stable while the shared implementation lives in
# one reusable workflow.
name: Post Codex Review Comment
on:
workflow_run:
workflows: ["Codex Code Review"]
types: [completed]
permissions:
pull-requests: write
issues: write
jobs:
review-comment:
uses: ./.github/workflows/ai-review-comment.yml
with:
provider: codex
result_artifact_name: codex-review-result
comment_file: codex-review-comment.md
+63
View File
@@ -0,0 +1,63 @@
# Codex Code Review — Analysis Workflow
#
# Thin wrapper around .github/workflows/ai-review-analysis.yml so provider
# specific triggers and workflow_dispatch inputs stay stable while the shared
# implementation lives in one reusable workflow.
name: Codex Code Review
on:
# Automatic PR reviews are temporarily disabled while Codex authentication is
# repaired. Manual review requests remain available through issue comments
# and workflow_dispatch.
issue_comment:
types: [created]
workflow_dispatch:
inputs:
pr_number:
description: PR number to review
required: true
type: number
model:
description: Codex model to use
required: false
type: choice
options:
- gpt-5.5
- gpt-5.3-codex
- gpt-5.2-codex
default: gpt-5.5
thinking_budget:
description: Override MAX_THINKING_TOKENS (blank = auto-classify)
required: false
type: string
default: ""
permissions:
contents: read
# The shared analysis workflow polls check state, inspects prior runs, and
# reads PR metadata. Keep those permissions read-only in the caller.
actions: read
checks: read
pull-requests: read
jobs:
review:
if: >-
github.event_name != 'pull_request_target' ||
github.event.pull_request.head.repo.full_name == github.repository
uses: ./.github/workflows/ai-review-analysis.yml
with:
provider: codex
display_name: Codex
review_command: /codex-review
query_command: /codex-query
result_artifact_name: codex-review-result
comment_file: codex-review-comment.md
default_model: gpt-5.5
selected_model: ${{ github.event_name == 'workflow_dispatch' && inputs.model || '' }}
thinking_budget: ${{ github.event_name == 'workflow_dispatch' && inputs.thinking_budget || '' }}
dispatch_pr_number: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || '' }}
secrets: inherit
+4 -3
View File
@@ -1,18 +1,19 @@
name: meta/rocksdb/nightly-candidate
name: facebook/rocksdb/nightly
on: workflow_dispatch
permissions: {}
jobs:
# These jobs would be in nightly but are failing or otherwise broken for
# some reason.
build-linux-arm-test-full:
if: ${{ github.repository_owner == 'meta' }}
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: arm64large
container:
image: ubuntu-2004:202111-02
options: --shm-size=8gb
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- uses: "./.github/actions/install-gflags"
- run: make V=1 J=4 -j4 check
- uses: "./.github/actions/post-steps"
+38 -81
View File
@@ -1,4 +1,4 @@
name: meta/rocksdb/nightly
name: facebook/rocksdb/nightly
on:
schedule:
- cron: 0 9 * * *
@@ -6,12 +6,12 @@ on:
permissions: {}
jobs:
build-format-compatible:
if: ${{ github.repository_owner == 'meta' }}
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
image: ghcr.io/meta/rocksdb_ubuntu:22.1
options: --shm-size=32gb
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
with:
@@ -26,44 +26,28 @@ jobs:
mkdir /dev/shm/rocksdb
git config --global --add safe.directory /__w/rocksdb/rocksdb
tools/check_format_compatible.sh
- name: Check that old facebook/rocksdb URLs still redirect
# Alerts the RocksDB team if GitHub stops redirecting the pre-transfer
# repo URL to its current location (or the old name gets taken by
# another repo). Existing releases (e.g. dependency download URLs in
# java/Makefile), existing clones, and external links still use the old
# URL, so they would all break.
if: ${{ !cancelled() }}
run: |-
if ! result=$(curl -sSL --retry 3 --retry-all-errors -o /dev/null \
-w '%{http_code} %{url_effective}' https://github.com/facebook/rocksdb); then
echo "::error::Failed to fetch https://github.com/facebook/rocksdb (network problem?)"
exit 1
fi
echo "Old URL resolved to: $result"
if [ "$result" != "200 https://github.com/meta/rocksdb" ]; then
echo "::error::https://github.com/facebook/rocksdb no longer redirects to https://github.com/meta/rocksdb. Builds of existing releases, existing clones, and external links that use the old URL are likely broken."
exit 1
fi
- uses: "./.github/actions/post-steps"
build-linux-non-shm:
if: ${{ github.repository_owner == 'meta' }}
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
image: ghcr.io/meta/rocksdb_ubuntu:22.1
options: --shm-size=32gb
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
env:
TEST_TMPDIR: "/tmp/rocksdb_test_tmp"
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- run: make V=1 -j32 check
- uses: "./.github/actions/post-steps"
build-linux-clang-21-asan-ubsan-with-folly:
if: ${{ github.repository_owner == 'meta' }}
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
image: ghcr.io/meta/rocksdb_ubuntu:24.1
options: --shm-size=32gb
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
options: --shm-size=16gb
env:
CC: clang-21
CXX: clang++-21
@@ -93,13 +77,14 @@ jobs:
make build_folly
shell: bash
- run: LIB_MODE=static USE_CLANG=1 USE_FOLLY=1 COMPILE_WITH_UBSAN=1 COMPILE_WITH_ASAN=1 make -j32 check
- uses: "./.github/actions/post-steps"
build-linux-cmake-with-folly:
if: ${{ github.repository_owner == 'meta' }}
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
image: ghcr.io/meta/rocksdb_ubuntu:22.1
options: --shm-size=32gb
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -111,13 +96,14 @@ jobs:
with:
cache-hit: ${{ steps.cache-folly.outputs.cache-hit }}
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 .. && make VERBOSE=1 -j20 && ctest -j20)"
- uses: "./.github/actions/post-steps"
build-linux-release-with-folly:
if: ${{ github.repository_owner == 'meta' }}
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
image: ghcr.io/meta/rocksdb_ubuntu:22.1
options: --shm-size=32gb
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -126,51 +112,17 @@ jobs:
- run: "DEBUG_LEVEL=0 make -j20 build_folly"
- run: "USE_FOLLY=1 LIB_MODE=static DEBUG_LEVEL=0 V=1 make -j20 release"
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 -DCMAKE_BUILD_TYPE=Release .. && make VERBOSE=1 -j20 && ctest -j20)"
- uses: "./.github/actions/post-steps"
build-windows-msvc-avx2:
if: ${{ github.repository_owner == 'meta' }}
if: ${{ github.repository_owner == 'facebook' }}
runs-on: windows-8-core
env:
CMAKE_PORTABLE: AVX2
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/windows-build-steps"
build-macos-cmake:
if: ${{ github.repository_owner == 'meta' }}
runs-on: macos-15-xlarge
strategy:
matrix:
run_sharded_tests: [0, 1, 2, 3]
steps:
- uses: actions/checkout@v4.1.0
- uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: 16.4.0
- uses: "./.github/actions/setup-ccache"
with:
cache-key-prefix: macos-cmake
- uses: "./.github/actions/increase-max-open-files-on-macos"
- uses: "./.github/actions/install-gflags-on-macos"
- uses: "./.github/actions/pre-steps-macos"
- name: cmake generate project file
run: ulimit -S -n `ulimit -H -n` && mkdir build && cd build && cmake -DWITH_GFLAGS=1 ..
- name: Build tests
run: cd build && make VERBOSE=1 -j8
- name: Run shard 0 out of 4 test shards
run: ulimit -S -n `ulimit -H -n` && cd build && ctest -j8 -I 0,,4
if: ${{ matrix.run_sharded_tests == 0 }}
- name: Run shard 1 out of 4 test shards
run: ulimit -S -n `ulimit -H -n` && cd build && ctest -j8 -I 1,,4
if: ${{ matrix.run_sharded_tests == 1 }}
- name: Run shard 2 out of 4 test shards
run: ulimit -S -n `ulimit -H -n` && cd build && ctest -j8 -I 2,,4
if: ${{ matrix.run_sharded_tests == 2 }}
- name: Run shard 3 out of 4 test shards
run: ulimit -S -n `ulimit -H -n` && cd build && ctest -j8 -I 3,,4
if: ${{ matrix.run_sharded_tests == 3 }}
- uses: "./.github/actions/teardown-ccache"
if: always()
build-linux-arm-test-full:
if: ${{ github.repository_owner == 'meta' }}
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 4-core-ubuntu-arm
steps:
@@ -178,8 +130,9 @@ jobs:
- uses: "./.github/actions/pre-steps"
- run: sudo apt-get update && sudo apt-get install -y build-essential libgflags-dev
- run: make V=1 J=4 -j4 check
- uses: "./.github/actions/post-steps"
build-linux-arm-crashtest:
if: ${{ github.repository_owner == 'meta' }}
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 4-core-ubuntu-arm
steps:
@@ -201,25 +154,27 @@ jobs:
- run: ulimit -S -n `ulimit -H -n` && make V=1 -j8 CRASH_TEST_EXT_ARGS='--duration=1800 --max_key=2500000' blackbox_crash_test_with_atomic_flush
- run: rm -rf /dev/shm/rocksdb.*
- run: ulimit -S -n `ulimit -H -n` && make V=1 -j8 CRASH_TEST_EXT_ARGS='--duration=1800 --max_key=2500000' blackbox_crash_test_with_multiops_wc_txn
- uses: "./.github/actions/post-steps"
build-examples:
if: ${{ github.repository_owner == 'meta' }}
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 4-core-ubuntu
container:
image: ghcr.io/meta/rocksdb_ubuntu:22.1
options: --shm-size=8gb
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- name: Build examples
run: make V=1 -j4 static_lib && cd examples && make V=1 -j4
- uses: "./.github/actions/post-steps"
build-fuzzers:
if: ${{ github.repository_owner == 'meta' }}
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 4-core-ubuntu
container:
image: ghcr.io/meta/rocksdb_ubuntu:24.1
options: --shm-size=8gb
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -227,16 +182,18 @@ jobs:
run: CC=clang-21 CXX=clang++-21 USE_CLANG=1 make -j4 static_lib
- name: Build fuzzers
run: cd fuzz && make sst_file_writer_fuzzer db_fuzzer db_map_fuzzer
- uses: "./.github/actions/post-steps"
build-linux-cmake-with-folly-lite:
if: ${{ github.repository_owner == 'meta' }}
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
image: ghcr.io/meta/rocksdb_ubuntu:22.1
options: --shm-size=32gb
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- uses: "./.github/actions/cache-getdeps-downloads"
- uses: "./.github/actions/setup-folly"
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY_LITE=1 -DWITH_GFLAGS=1 -DCMAKE_CXX_FLAGS=-DGLOG_USE_GLOG_EXPORT .. && make VERBOSE=1 -j20 && ctest -j20)"
- uses: "./.github/actions/post-steps"
+5 -3
View File
@@ -1,4 +1,4 @@
name: meta/rocksdb/pr-jobs-candidate
name: facebook/rocksdb/pr-jobs-candidate
on: workflow_dispatch
permissions: {}
jobs:
@@ -6,7 +6,7 @@ jobs:
# some reason.
# =========================== ARM Jobs ============================ #
build-linux-arm:
if: ${{ github.repository_owner == 'meta' }}
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: arm64large # GitHub hosted ARM runners do not yet exist
steps:
@@ -14,8 +14,9 @@ jobs:
- uses: "./.github/actions/pre-steps"
- uses: "./.github/actions/install-gflags"
- run: ROCKSDBTESTS_PLATFORM_DEPENDENT=only make V=1 J=4 -j4 all_but_some_tests check_some
- uses: "./.github/actions/post-steps"
build-linux-arm-cmake-no_test_run:
if: ${{ github.repository_owner == 'meta' }}
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: arm64large # GitHub hosted ARM runners do not yet exist
env:
@@ -43,3 +44,4 @@ jobs:
cd build
cmake -DJNI=1 -DCMAKE_BUILD_TYPE=Release -DWITH_GFLAGS=1 ..
make -j4 rocksdb rocksdbjni
- uses: "./.github/actions/post-steps"
+133 -67
View File
@@ -1,9 +1,9 @@
name: meta/rocksdb/pr-jobs
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 meta/rocksdb repo only).
# normal behavior (all jobs on facebook repo only).
ONLY_JOB: ''
jobs:
config:
@@ -19,7 +19,7 @@ jobs:
# workflow minimizes the problem because it will be suspicious if there are
# no GHA results.
#
# The if: ${{ github.repository_owner == 'meta' }} lines prevent the
# The if: ${{ github.repository_owner == 'facebook' }} lines prevent the
# jobs from attempting to run on repo forks, because of a few problems:
# * runs-on labels are repository (owner) specific, so the job might wait
# for days waiting for a runner that simply isn't available.
@@ -45,7 +45,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 == 'meta')
if: needs.config.outputs.only_job == 'check-format-and-targets' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
runs-on: ubuntu-24.04
steps:
@@ -66,8 +66,8 @@ jobs:
clang-format --version
- name: Download clang-format-diff.py
# Mirrored on the "third-party-deps" GitHub release; add/update via
# https://github.com/meta/rocksdb/releases/edit/third-party-deps
run: wget --tries=3 --retry-on-host-error https://github.com/meta/rocksdb/releases/download/third-party-deps/clang-format-diff.py
# https://github.com/facebook/rocksdb/releases/edit/third-party-deps
run: wget --tries=3 --retry-on-host-error https://github.com/facebook/rocksdb/releases/download/third-party-deps/clang-format-diff.py
- name: Check format
run: VERBOSE_CHECK=1 make check-format
- name: Compare buckify output
@@ -86,13 +86,13 @@ 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 == 'meta')
if: needs.config.outputs.only_job == 'build-linux' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
runs-on:
labels: 16-core-ubuntu
container:
image: ghcr.io/meta/rocksdb_ubuntu:22.1
options: --shm-size=32gb
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -102,14 +102,15 @@ jobs:
- run: make V=1 J=32 -j32 check
- uses: "./.github/actions/teardown-ccache"
if: always()
- 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 == 'meta')
if: needs.config.outputs.only_job == 'build-linux-cmake-mingw' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
runs-on:
labels: 4-core-ubuntu
container:
image: ghcr.io/meta/rocksdb_ubuntu:24.1
options: --shm-size=8gb
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -126,14 +127,15 @@ 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/teardown-ccache"
if: always()
- 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 == 'meta')
if: needs.config.outputs.only_job == 'build-linux-make-with-folly' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
runs-on:
labels: 32-core-ubuntu
container:
image: ghcr.io/meta/rocksdb_ubuntu:22.1
options: --shm-size=64gb
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -150,14 +152,15 @@ jobs:
- run: USE_FOLLY=1 LIB_MODE=static V=1 make -j64 check
- uses: "./.github/actions/teardown-ccache"
if: always()
- 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 == 'meta')
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
runs-on:
labels: 16-core-ubuntu
container:
image: ghcr.io/meta/rocksdb_ubuntu:22.1
options: --shm-size=32gb
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -169,14 +172,15 @@ jobs:
- run: USE_FOLLY_LITE=1 EXTRA_CXXFLAGS=-DGLOG_USE_GLOG_EXPORT V=1 make -j32 all
- uses: "./.github/actions/teardown-ccache"
if: always()
- 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 == 'meta')
if: needs.config.outputs.only_job == 'build-linux-cmake-with-folly-coroutines' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
runs-on:
labels: 32-core-ubuntu
container:
image: ghcr.io/meta/rocksdb_ubuntu:22.1
options: --shm-size=64gb
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -193,8 +197,9 @@ jobs:
- run: "(mkdir build && cd build && cmake -DUSE_COROUTINES=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 -DPORTABLE=ON .. && make VERBOSE=1 -j64 && ctest -j64)"
- uses: "./.github/actions/teardown-ccache"
if: always()
- 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 == 'meta')
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
runs-on:
labels: 16-core-ubuntu
@@ -203,8 +208,8 @@ jobs:
matrix:
shard: [0, 1, 2, 3]
container:
image: ghcr.io/meta/rocksdb_ubuntu:22.1
options: --shm-size=32gb
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -217,14 +222,17 @@ jobs:
run: cd build && ctest -j20 -I ${{ matrix.shard }},,4
- uses: "./.github/actions/teardown-ccache"
if: always()
- uses: "./.github/actions/post-steps"
with:
artifact-prefix: "${{ github.job }}-shard${{ matrix.shard }}"
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 == 'meta')
if: needs.config.outputs.only_job == 'build-linux-encrypted_env-no_compression' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
runs-on:
labels: 16-core-ubuntu
container:
image: ghcr.io/meta/rocksdb_ubuntu:22.1
options: --shm-size=32gb
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -235,15 +243,16 @@ jobs:
- run: "./sst_dump --help | grep -E -q 'Supported built-in compression types: kNoCompression$' # Verify no compiled in compression\n"
- uses: "./.github/actions/teardown-ccache"
if: always()
- 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 == 'meta')
if: needs.config.outputs.only_job == 'build-linux-release' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
runs-on:
labels: 16-core-ubuntu
container:
image: ghcr.io/meta/rocksdb_ubuntu:22.1
options: --shm-size=32gb
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/setup-ccache"
@@ -267,13 +276,14 @@ jobs:
- run: if ./trace_analyzer --version; then false; else true; fi
- uses: "./.github/actions/teardown-ccache"
if: always()
- 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 == 'meta')
if: needs.config.outputs.only_job == 'build-linux-clang-13-no_test_run' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
runs-on:
labels: 8-core-ubuntu
container:
image: ghcr.io/meta/rocksdb_ubuntu:22.1
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
@@ -288,14 +298,15 @@ 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/teardown-ccache"
if: always()
- uses: "./.github/actions/post-steps"
build-linux-clang-21-no_test_run:
if: needs.config.outputs.only_job == 'build-linux-clang-21-no_test_run' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
if: needs.config.outputs.only_job == 'build-linux-clang-21-no_test_run' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
runs-on:
labels: 16-core-ubuntu
container:
image: ghcr.io/meta/rocksdb_ubuntu:24.1
options: --shm-size=32gb
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -354,14 +365,15 @@ jobs:
CLANG_FORMAT_BINARY=clang-format-21
- uses: "./.github/actions/teardown-ccache"
if: always()
- 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 == 'meta')
if: needs.config.outputs.only_job == 'build-linux-gcc-14-no_test_run' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
runs-on:
labels: 16-core-ubuntu
container:
image: ghcr.io/meta/rocksdb_ubuntu:24.1
options: --shm-size=32gb
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -371,17 +383,18 @@ jobs:
- run: CC=gcc-14 CXX=g++-14 V=1 make -j32 all microbench
- uses: "./.github/actions/teardown-ccache"
if: always()
- uses: "./.github/actions/post-steps"
# ======================== Linux Other Checks ======================= #
build-linux-unity-and-headers:
if: needs.config.outputs.only_job == 'build-linux-unity-and-headers' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
if: needs.config.outputs.only_job == 'build-linux-unity-and-headers' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
runs-on:
labels: 4-core-ubuntu
container:
image: gcc:latest
options: --shm-size=8gb
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- run: apt-get update -y && apt-get install -y libgflags-dev
@@ -393,8 +406,9 @@ jobs:
- run: make V=1 -j8 -k check-headers
- uses: "./.github/actions/teardown-ccache"
if: always()
- 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 == 'meta')
if: needs.config.outputs.only_job == 'build-linux-mini-crashtest' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
runs-on:
labels: 4-core-ubuntu
@@ -407,8 +421,8 @@ jobs:
- crash_test_target: blackbox_crash_test
crash_duration: 240
container:
image: ghcr.io/meta/rocksdb_ubuntu:22.1
options: --shm-size=8gb --ulimit nofile=1048576:1048576
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb --ulimit nofile=1048576:1048576
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -428,9 +442,12 @@ jobs:
USE_COROUTINES=1 USE_FOLLY=1 LIB_MODE=static make V=1 -j8 CRASH_TEST_EXT_ARGS='--duration=${{ matrix.crash_duration }} --max_key=2500000' ${{ matrix.crash_test_target }}
- uses: "./.github/actions/teardown-ccache"
if: always()
- uses: "./.github/actions/post-steps"
with:
artifact-prefix: "${{ github.job }}-${{ matrix.crash_test_target }}"
# ======================= Linux with Sanitizers ===================== #
build-linux-clang21-asan-ubsan:
if: needs.config.outputs.only_job == 'build-linux-clang21-asan-ubsan' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
if: needs.config.outputs.only_job == 'build-linux-clang21-asan-ubsan' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
runs-on:
labels: 32-core-ubuntu
@@ -439,8 +456,8 @@ jobs:
matrix:
shard: [0, 1, 2]
container:
image: ghcr.io/meta/rocksdb_ubuntu:24.1
options: --shm-size=64gb
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -453,8 +470,11 @@ jobs:
CI_TOTAL_SHARDS: 3
- uses: "./.github/actions/teardown-ccache"
if: always()
- uses: "./.github/actions/post-steps"
with:
artifact-prefix: "${{ github.job }}-shard${{ matrix.shard }}"
build-linux-clang21-mini-tsan:
if: needs.config.outputs.only_job == 'build-linux-clang21-mini-tsan' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
if: needs.config.outputs.only_job == 'build-linux-clang21-mini-tsan' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
runs-on:
labels: 32-core-ubuntu
@@ -463,8 +483,8 @@ jobs:
matrix:
shard: [0, 1, 2]
container:
image: ghcr.io/meta/rocksdb_ubuntu:24.1
options: --shm-size=64gb
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -477,14 +497,17 @@ jobs:
CI_TOTAL_SHARDS: 3
- uses: "./.github/actions/teardown-ccache"
if: always()
- uses: "./.github/actions/post-steps"
with:
artifact-prefix: "${{ github.job }}-shard${{ matrix.shard }}"
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 == 'meta')
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
runs-on:
labels: 16-core-ubuntu
container:
image: ghcr.io/meta/rocksdb_ubuntu:22.1
options: --shm-size=32gb
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -494,10 +517,10 @@ jobs:
- run: ASSERT_STATUS_CHECKED=1 TEST_UINT128_COMPAT=1 ROCKSDB_MODIFY_NPHASH=1 LIB_MODE=static OPT="-DROCKSDB_USE_STD_SEMAPHORES -DROCKSDB_NAMESPACE=alternative_rocksdb_ns" make V=1 -j24 check
- uses: "./.github/actions/teardown-ccache"
if: always()
- uses: "./.github/actions/post-steps"
# ========================= MacOS build only ======================== #
# NOTE: build-macos-cmake (with tests) is in "nightly" to save resources
build-macos:
if: needs.config.outputs.only_job == 'build-macos' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
if: needs.config.outputs.only_job == 'build-macos' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
runs-on: macos-15-xlarge
env:
@@ -517,10 +540,49 @@ jobs:
run: ulimit -S -n `ulimit -H -n` && make V=1 J=16 -j8 all
- uses: "./.github/actions/teardown-ccache"
if: always()
- 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
runs-on: macos-15-xlarge
strategy:
matrix:
run_sharded_tests: [0, 1, 2, 3]
steps:
- uses: actions/checkout@v4.1.0
- uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: 16.4.0
- uses: "./.github/actions/setup-ccache"
with:
cache-key-prefix: macos-cmake
- uses: "./.github/actions/increase-max-open-files-on-macos"
- uses: "./.github/actions/install-gflags-on-macos"
- uses: "./.github/actions/pre-steps-macos"
- name: cmake generate project file
run: ulimit -S -n `ulimit -H -n` && mkdir build && cd build && cmake -DWITH_GFLAGS=1 ..
- name: Build tests
run: cd build && make VERBOSE=1 -j8
- name: Run shard 0 out of 4 test shards
run: ulimit -S -n `ulimit -H -n` && cd build && ctest -j8 -I 0,,4
if: ${{ matrix.run_sharded_tests == 0 }}
- name: Run shard 1 out of 4 test shards
run: ulimit -S -n `ulimit -H -n` && cd build && ctest -j8 -I 1,,4
if: ${{ matrix.run_sharded_tests == 1 }}
- name: Run shard 2 out of 4 test shards
run: ulimit -S -n `ulimit -H -n` && cd build && ctest -j8 -I 2,,4
if: ${{ matrix.run_sharded_tests == 2 }}
- name: Run shard 3 out of 4 test shards
run: ulimit -S -n `ulimit -H -n` && cd build && ctest -j8 -I 3,,4
if: ${{ matrix.run_sharded_tests == 3 }}
- uses: "./.github/actions/teardown-ccache"
if: always()
- uses: "./.github/actions/post-steps"
# ======================== Windows with Tests ======================= #
# NOTE: some windows jobs are in "nightly" to save resources
build-windows-msvc:
if: needs.config.outputs.only_job == 'build-windows-msvc' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
if: needs.config.outputs.only_job == 'build-windows-msvc' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
runs-on: windows-8-core
strategy:
@@ -546,13 +608,13 @@ jobs:
run-java: ${{ matrix.run_java }}
# ============================ Java Jobs ============================ #
build-linux-java:
if: needs.config.outputs.only_job == 'build-linux-java' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
if: needs.config.outputs.only_job == 'build-linux-java' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
runs-on:
labels: 4-core-ubuntu
container:
image: ghcr.io/meta/rocksdb_ubuntu:22.1
options: --shm-size=8gb
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -569,13 +631,13 @@ jobs:
- uses: "./.github/actions/teardown-ccache"
if: always()
build-linux-java-static:
if: needs.config.outputs.only_job == 'build-linux-java-static' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
if: needs.config.outputs.only_job == 'build-linux-java-static' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
runs-on:
labels: 4-core-ubuntu
container:
image: ghcr.io/meta/rocksdb_ubuntu:22.1
options: --shm-size=8gb
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -592,7 +654,7 @@ jobs:
- uses: "./.github/actions/teardown-ccache"
if: always()
build-macos-java:
if: needs.config.outputs.only_job == 'build-macos-java' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
if: needs.config.outputs.only_job == 'build-macos-java' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
runs-on: macos-15-xlarge
env:
@@ -618,8 +680,9 @@ jobs:
run: make V=1 J=16 -j16 jtest
- uses: "./.github/actions/teardown-ccache"
if: always()
- 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 == 'meta')
if: needs.config.outputs.only_job == 'build-macos-java-static' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
runs-on: macos-15-xlarge
steps:
@@ -643,8 +706,9 @@ jobs:
run: make V=1 J=16 -j16 rocksdbjavastaticosx
- uses: "./.github/actions/teardown-ccache"
if: always()
- 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 == 'meta')
if: needs.config.outputs.only_job == 'build-macos-java-static-universal' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
runs-on: macos-15-xlarge
steps:
@@ -668,14 +732,15 @@ jobs:
run: make V=1 J=16 -j16 rocksdbjavastaticosx_ub
- uses: "./.github/actions/teardown-ccache"
if: always()
- 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 == 'meta')
if: needs.config.outputs.only_job == 'build-linux-java-pmd' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
runs-on:
labels: 4-core-ubuntu
container:
image: evolvedbinary/rocksjava:alpine3_x64-be
options: --shm-size=8gb
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/install-maven"
@@ -696,7 +761,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 == 'meta')
if: needs.config.outputs.only_job == 'build-linux-arm' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
runs-on:
labels: 4-core-ubuntu-arm
@@ -712,3 +777,4 @@ jobs:
run: ccache -s
if: always()
shell: bash
- uses: "./.github/actions/post-steps"
+5 -4
View File
@@ -1,4 +1,4 @@
name: meta/rocksdb/weekly
name: facebook/rocksdb/weekly
on:
schedule:
- cron: 0 9 * * 0
@@ -6,14 +6,15 @@ on:
permissions: {}
jobs:
build-linux-valgrind:
if: ${{ github.repository_owner == 'meta' }}
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
timeout-minutes: 840
container:
image: ghcr.io/meta/rocksdb_ubuntu:22.1
options: --shm-size=32gb
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- run: make V=1 -j20 valgrind_test
- uses: "./.github/actions/post-steps"
+20 -103
View File
@@ -96,7 +96,6 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
"db/merge_operator.cc",
"db/multi_scan.cc",
"db/output_validator.cc",
"db/periodic_compaction_phaser.cc",
"db/periodic_task_scheduler.cc",
"db/range_del_aggregator.cc",
"db/range_tombstone_fragmenter.cc",
@@ -206,7 +205,6 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
"table/block_based/block_cache.cc",
"table/block_based/block_prefetcher.cc",
"table/block_based/block_prefix_index.cc",
"table/block_based/builtin_index_factory.cc",
"table/block_based/data_block_footer.cc",
"table/block_based/data_block_hash_index.cc",
"table/block_based/filter_block_reader_common.cc",
@@ -241,7 +239,6 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
"table/plain/plain_table_index.cc",
"table/plain/plain_table_key_coding.cc",
"table/plain/plain_table_reader.cc",
"table/range_del_block.cc",
"table/sst_file_dumper.cc",
"table/sst_file_reader.cc",
"table/sst_file_writer.cc",
@@ -259,7 +256,6 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
"tools/sst_dump_tool.cc",
"trace_replay/block_cache_tracer.cc",
"trace_replay/io_tracer.cc",
"trace_replay/trace_analyzer.cc",
"trace_replay/trace_record.cc",
"trace_replay/trace_record_handler.cc",
"trace_replay/trace_record_result.cc",
@@ -282,7 +278,6 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
"util/file_checksum_helper.cc",
"util/hash.cc",
"util/io_dispatcher_imp.cc",
"util/io_prefetch_scheduler.cc",
"util/murmurhash.cc",
"util/random.cc",
"util/rate_limiter.cc",
@@ -316,7 +311,6 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
"utilities/convenience/info_log_finder.cc",
"utilities/copy_engine/copy_engine.cc",
"utilities/counted_fs.cc",
"utilities/db_split_merge.cc",
"utilities/debug.cc",
"utilities/env_mirror.cc",
"utilities/env_timed.cc",
@@ -324,8 +318,6 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
"utilities/fault_injection_fs.cc",
"utilities/fault_injection_secondary_cache.cc",
"utilities/leveldb_options/leveldb_options.cc",
"utilities/live_tune/live_tune_options_helper.cc",
"utilities/live_tune/live_tuner.cc",
"utilities/memory/memory_util.cc",
"utilities/merge_operators.cc",
"utilities/merge_operators/bytesxor.cc",
@@ -390,22 +382,22 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
"utilities/write_batch_with_index/write_batch_with_index.cc",
"utilities/write_batch_with_index/write_batch_with_index_internal.cc",
], deps=[
"fbsource//xplat/folly/container:f14_hash",
"fbsource//xplat/folly/coro:baton",
"fbsource//xplat/folly/coro:blocking_wait",
"fbsource//xplat/folly/coro:collect",
"fbsource//xplat/folly/coro:coroutine",
"fbsource//xplat/folly/coro:nothrow",
"fbsource//xplat/folly/coro:task",
"fbsource//xplat/folly/executors/thread_factory:named_thread_factory",
"fbsource//xplat/folly/executors:io_executor",
"fbsource//xplat/folly/executors:io_thread_pool_executor",
"fbsource//xplat/folly/io/async:async_base",
"fbsource//xplat/folly/io/async:event_base_manager",
"fbsource//xplat/folly/io/async:io_uring_backend",
"fbsource//xplat/folly/io/async:request_context",
"fbsource//xplat/folly/synchronization:distributed_mutex",
"fbsource//xplat/folly:executor",
"//folly/container:f14_hash",
"//folly/coro:baton",
"//folly/coro:blocking_wait",
"//folly/coro:collect",
"//folly/coro:coroutine",
"//folly/coro:nothrow",
"//folly/coro:task",
"//folly/executors/thread_factory:named_thread_factory",
"//folly/executors:io_executor",
"//folly/executors:io_thread_pool_executor",
"//folly/io/async:async_base",
"//folly/io/async:event_base_manager",
"//folly/io/async:io_uring_backend",
"//folly/io/async:request_context",
"//folly/synchronization:distributed_mutex",
"//folly:executor",
], headers=glob(["**/*.h"]), link_whole=False, extra_test_libs=False)
cpp_library_wrapper(name="rocksdb_whole_archive_lib", srcs=[], deps=[":rocksdb_lib"], headers=[], link_whole=True, extra_test_libs=False)
@@ -421,27 +413,13 @@ cpp_library_wrapper(name="rocksdb_test_lib", srcs=[
"table/mock_table.cc",
"test_util/mock_time_env.cc",
"test_util/secondary_cache_test_util.cc",
"test_util/simple_external_table_factory.cc",
"test_util/testharness.cc",
"test_util/testutil.cc",
"tools/block_cache_analyzer/block_cache_trace_analyzer.cc",
"tools/trace_analyzer_tool.cc",
"utilities/agg_merge/test_agg_merge.cc",
"utilities/cassandra/test_utils.cc",
], deps=[":rocksdb_lib"], headers=[
"db/db_test_util.h",
"db/db_with_timestamp_test_util.h",
"table/mock_table.h",
"test_util/mock_time_env.h",
"test_util/secondary_cache_test_util.h",
"test_util/simple_external_table_factory.h",
"test_util/testharness.h",
"test_util/testutil.h",
"tools/block_cache_analyzer/block_cache_trace_analyzer.h",
"tools/trace_analyzer_tool.h",
"utilities/agg_merge/test_agg_merge.h",
"utilities/cassandra/test_utils.h",
], link_whole=False, extra_test_libs=True)
], deps=[":rocksdb_lib"], headers=[], link_whole=False, extra_test_libs=True)
cpp_library_wrapper(name="rocksdb_with_faiss_test_lib", srcs=[
"db/db_test_util.cc",
@@ -449,27 +427,13 @@ cpp_library_wrapper(name="rocksdb_with_faiss_test_lib", srcs=[
"table/mock_table.cc",
"test_util/mock_time_env.cc",
"test_util/secondary_cache_test_util.cc",
"test_util/simple_external_table_factory.cc",
"test_util/testharness.cc",
"test_util/testutil.cc",
"tools/block_cache_analyzer/block_cache_trace_analyzer.cc",
"tools/trace_analyzer_tool.cc",
"utilities/agg_merge/test_agg_merge.cc",
"utilities/cassandra/test_utils.cc",
], deps=[":rocksdb_with_faiss_lib"], headers=[
"db/db_test_util.h",
"db/db_with_timestamp_test_util.h",
"table/mock_table.h",
"test_util/mock_time_env.h",
"test_util/secondary_cache_test_util.h",
"test_util/simple_external_table_factory.h",
"test_util/testharness.h",
"test_util/testutil.h",
"tools/block_cache_analyzer/block_cache_trace_analyzer.h",
"tools/trace_analyzer_tool.h",
"utilities/agg_merge/test_agg_merge.h",
"utilities/cassandra/test_utils.h",
], link_whole=False, extra_test_libs=True)
], deps=[":rocksdb_with_faiss_lib"], headers=[], link_whole=False, extra_test_libs=True)
cpp_library_wrapper(name="rocksdb_tools_lib", srcs=[
"test_util/testutil.cc",
@@ -478,12 +442,7 @@ cpp_library_wrapper(name="rocksdb_tools_lib", srcs=[
"tools/simulated_hybrid_file_system.cc",
"tools/tool_hooks.cc",
"tools/trace_analyzer_tool.cc",
], deps=[":rocksdb_lib"], headers=[
"test_util/testutil.h",
"tools/block_cache_analyzer/block_cache_trace_analyzer.h",
"tools/simulated_hybrid_file_system.h",
"tools/trace_analyzer_tool.h",
], link_whole=False, extra_test_libs=False)
], deps=[":rocksdb_lib"], headers=[], link_whole=False, extra_test_libs=False)
cpp_library_wrapper(name="rocksdb_cache_bench_tools_lib", srcs=["cache/cache_bench_tool.cc"], deps=[":rocksdb_lib"], headers=[], link_whole=False, extra_test_libs=False)
@@ -507,34 +466,14 @@ rocks_cpp_library_wrapper(name="rocksdb_stress_lib", srcs=[
"db_stress_tool/expected_value.cc",
"db_stress_tool/multi_ops_txns_stress.cc",
"db_stress_tool/no_batched_ops_stress.cc",
"test_util/simple_external_table_factory.cc",
"test_util/testutil.cc",
"tools/block_cache_analyzer/block_cache_trace_analyzer.cc",
"tools/trace_analyzer_tool.cc",
], headers=[
"db_stress_tool/db_stress_common.h",
"db_stress_tool/db_stress_compaction_service.h",
"db_stress_tool/db_stress_compression_manager.h",
"db_stress_tool/db_stress_driver.h",
"db_stress_tool/db_stress_filters.h",
"db_stress_tool/db_stress_listener.h",
"db_stress_tool/db_stress_shared_state.h",
"db_stress_tool/db_stress_test_base.h",
"db_stress_tool/db_stress_wide_merge_operator.h",
"db_stress_tool/expected_state.h",
"db_stress_tool/expected_value.h",
"db_stress_tool/multi_ops_txns_stress.h",
"test_util/simple_external_table_factory.h",
"test_util/testutil.h",
"tools/block_cache_analyzer/block_cache_trace_analyzer.h",
"tools/trace_analyzer_tool.h",
])
], headers=[])
cpp_binary_wrapper(name="ldb", srcs=["tools/ldb.cc"], deps=[":rocksdb_tools_lib"], extra_preprocessor_flags=[], extra_bench_libs=False)
cpp_binary_wrapper(name="db_split_merge", srcs=["tools/db_split_merge.cc"], deps=[":rocksdb_lib"], extra_preprocessor_flags=[], extra_bench_libs=False)
cpp_binary_wrapper(name="db_stress", srcs=["db_stress_tool/db_stress.cc"], deps=[":rocksdb_stress_lib"], extra_preprocessor_flags=[], extra_bench_libs=False)
cpp_binary_wrapper(name="db_bench", srcs=["tools/db_bench.cc"], deps=[":rocksdb_tools_lib"], extra_preprocessor_flags=[], extra_bench_libs=False)
@@ -4674,12 +4613,6 @@ cpp_unittest_wrapper(name="bloom_test",
extra_compiler_flags=[])
cpp_unittest_wrapper(name="builtin_index_factory_test",
srcs=["table/block_based/builtin_index_factory_test.cc"],
deps=[":rocksdb_test_lib"],
extra_compiler_flags=[])
cpp_unittest_wrapper(name="cache_reservation_manager_test",
srcs=["cache/cache_reservation_manager_test.cc"],
deps=[":rocksdb_test_lib"],
@@ -4820,7 +4753,6 @@ cpp_unittest_wrapper(name="compression_test",
cpp_unittest_wrapper(name="configurable_test",
srcs=["options/configurable_test.cc"],
source_headers=["options/configurable_test.h"],
deps=[":rocksdb_test_lib"],
extra_compiler_flags=[])
@@ -5095,12 +5027,6 @@ cpp_unittest_wrapper(name="db_secondary_test",
extra_compiler_flags=[])
cpp_unittest_wrapper(name="db_split_merge_test",
srcs=["utilities/db_split_merge_test.cc"],
deps=[":rocksdb_test_lib"],
extra_compiler_flags=[])
cpp_unittest_wrapper(name="db_sst_test",
srcs=["db/db_sst_test.cc"],
deps=[":rocksdb_test_lib"],
@@ -5397,12 +5323,6 @@ cpp_unittest_wrapper(name="listener_test",
extra_compiler_flags=[])
cpp_unittest_wrapper(name="live_tuner_test",
srcs=["utilities/live_tune/live_tuner_test.cc"],
deps=[":rocksdb_test_lib"],
extra_compiler_flags=[])
cpp_unittest_wrapper(name="log_test",
srcs=["db/log_test.cc"],
deps=[":rocksdb_test_lib"],
@@ -5537,7 +5457,6 @@ cpp_unittest_wrapper(name="periodic_task_scheduler_test",
cpp_unittest_wrapper(name="persistent_cache_test",
srcs=["utilities/persistent_cache/persistent_cache_test.cc"],
source_headers=["utilities/persistent_cache/persistent_cache_test.h"],
deps=[":rocksdb_test_lib"],
extra_compiler_flags=[])
@@ -5556,7 +5475,6 @@ cpp_unittest_wrapper(name="point_lock_manager_stress_test",
cpp_unittest_wrapper(name="point_lock_manager_test",
srcs=["utilities/transactions/lock/point/point_lock_manager_test.cc"],
source_headers=["utilities/transactions/lock/point/point_lock_manager_test.h"],
deps=[":rocksdb_test_lib"],
extra_compiler_flags=[])
@@ -5767,7 +5685,6 @@ cpp_unittest_wrapper(name="trace_analyzer_test",
cpp_unittest_wrapper(name="transaction_test",
srcs=["utilities/transactions/transaction_test.cc"],
source_headers=["utilities/transactions/transaction_test.h"],
deps=[":rocksdb_test_lib"],
extra_compiler_flags=[])
+7 -27
View File
@@ -8,24 +8,17 @@ This document provides guidance for generating and reviewing code in the RocksDB
### 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.
**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. Avoid static_cast, reinterpret_cast, and C-style casts; static_cast_with_check, up_cast, and lossless_cast from cast_util.h are preferred.
**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.
**Refactoring traps:** At least in production code, avoid constructs that could allow existing code to unexpectedly, quietly change meaning.
Specifically:
* Avoid new defaulted parameters. This is the #1 trap on refactoring! (Permitted exception: optional outputs that are generally safe to ignore.)
* Avoid static_cast, reinterpret_cast, and C-style casts; static_cast_with_check, up_cast, and lossless_cast from cast_util.h are preferred.
* Avoid declared type `auto` (`auto&`, `auto*` OK).
### 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. For non-OK Statuses, prefer comparing code() rather than using ASSERT_TRUE
which is bad for debugging.
**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.
@@ -39,7 +32,7 @@ which is bad for debugging.
**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 (CACHE_LINE_SIZE, typically 64 bytes) and avoid false sharing in concurrent code (see CacheAlignedWrapper). Especially when adding new data members, be mindful of ordering within structs and classes to reduce unnecessary padding (special care with bool members) and avoid larger-than-necessary types (e.g. prefer OptSlice to std::optional<Slice>).
**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.
@@ -54,14 +47,12 @@ which is bad for debugging.
- **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:** Performance is known critical in places like inner loops of compaction, read path and write path. Elsewhere, try to find a "holy trinity" solution of *correct, simple, and reasonably efficient*. More complex solutions are only appropriate when profiling/benchmarking data shows a measurable payoff.
**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).
**Behavioral Compatibility:** Behavior changes are human judgment calls that must be intentional, well-documented (including release notes), and well researched for potential downstream production impact. Agents and reviewers must call out potential gaps, ambiguity, or adverse impacts in intentionality, documentation, and production impact. For an existing accepted input—including an undocumented, discouraged, or historically ignored value—the analysis should compare old and new observable behavior, including performance and resource impact, identify known direct callers and stored configurations, and add or update regression coverage as appropriate.
**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, without unnecessary embelishment nor dwelling on implementation details nor project planning. When non-obvious, include usage examples, parameter descriptions, known bugs or limitations, notes on thread safety, performance characteristics, and compatibility considerations. Re-read comments for ambiguous terminology and phrasing, such as ambiguously re-purposed programming jargon.
@@ -82,7 +73,7 @@ The database core handles write-ahead logging (WAL), memtables, compaction, and
**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 / "Public API" (`include/rocksdb/`)
### Public Headers (`include`)
Public headers define RocksDB's API surface. Changes here have the highest compatibility impact.
@@ -245,11 +236,6 @@ from an implementation detail instead of an explicit option.
## Important tips
### Pull request and diff summaries
* Before writing or updating an external RocksDB pull request summary or its
corresponding internal Phabricator diff summary, read and follow
`claude_md/pull_request_summary_guideline.md`.
### Build system
* There are 3 build system. Make for git clones, BUCK (meta internal) for hg
clones, and CMake for some special cases.
@@ -387,9 +373,6 @@ architectures.
### Adding new public API
Refer to claude_md/add_public_api.md
### Changing existing public API behavior
Follow the Behavioral Compatibility requirements above.
### Adding new option
Refer to claude_md/add_option.md
@@ -419,7 +402,7 @@ architectures.
* Release note should be kept short at high level for external user consumption.
Release notes identify what users might care about most in a release. They
are not exhaustive and are not a guide. PLEASE learn from past agents who
tried to build elaborate release notes with implementation details and
ried to build elaborate release notes with implementation details and
elsewhere-documented nuance. That wastes time. Fight the bias that
"my change" is important so must be worthy of release note mention.
* If more than single markdown line, consider how their formatting will be
@@ -435,10 +418,7 @@ architectures.
multiple minutes.
* Run `AUTO_CLEAN=1 ASSERT_STATUS_CHECKED=1 make check` to verify all Status
objects are properly checked. This catches missing error handling that can
lead to silent data corruption. Merely building with ASSERT_STATUS_CHECKED=1
accomplishes nothing; it enables a runtime check. New unit tests missing
Status checks is a common failure point, even from agents. Avoid new uses
of PermitUncheckedError(). That's the lazy solution.
lead to silent data corruption.
### Monitoring make check progress
* Use `make check-progress` to get machine-parseable JSON progress while
+2 -21
View File
@@ -522,6 +522,8 @@ elseif(CMAKE_SYSTEM_NAME MATCHES "Linux")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fuse-ld=lld")
endif()
endif()
elseif(CMAKE_SYSTEM_NAME MATCHES "SunOS")
add_definitions(-DOS_SOLARIS)
elseif(CMAKE_SYSTEM_NAME MATCHES "kFreeBSD")
add_definitions(-DOS_GNU_KFREEBSD)
elseif(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
@@ -648,9 +650,6 @@ if(USE_FOLLY)
exec_program(ls ARGS -d ${FOLLY_INST_PATH}/../glog* OUTPUT_VARIABLE
GLOG_INST_PATH)
set(Boost_DIR ${BOOST_INST_PATH}/lib/cmake/Boost-1.83.0)
# Boost's per-library CMake configs find their dependencies (e.g.
# boost_assert) only through the package search path.
set(CMAKE_PREFIX_PATH ${BOOST_INST_PATH} ${CMAKE_PREFIX_PATH})
if(EXISTS ${FMT_INST_PATH}/lib64)
set(fmt_DIR ${FMT_INST_PATH}/lib64/cmake/fmt)
else()
@@ -697,13 +696,7 @@ if(USE_FOLLY)
exec_program(sed ARGS -i 's/gflags_shared//g'
${FOLLY_INST_PATH}/lib/cmake/folly/folly-targets.cmake)
# folly-config.cmake only checks for gflags under a target name that this
# install doesn't define, then falls back to our Findgflags.cmake, which
# mistakes the gflags_static target name for a library path. gflags is
# resolved below instead.
set(CMAKE_DISABLE_FIND_PACKAGE_gflags TRUE)
include(${FOLLY_INST_PATH}/lib/cmake/folly/folly-config.cmake)
unset(CMAKE_DISABLE_FIND_PACKAGE_gflags)
# Fix gflags library name for debug builds. The getdeps gflags shared
# library is versioned (e.g. libgflags_debug.so.2.3), so resolve the
@@ -845,7 +838,6 @@ set(SOURCES
db/merge_operator.cc
db/multi_scan.cc
db/output_validator.cc
db/periodic_compaction_phaser.cc
db/periodic_task_scheduler.cc
db/range_del_aggregator.cc
db/range_tombstone_fragmenter.cc
@@ -945,7 +937,6 @@ set(SOURCES
table/block_based/block_cache.cc
table/block_based/block_prefetcher.cc
table/block_based/block_prefix_index.cc
table/block_based/builtin_index_factory.cc
table/block_based/data_block_hash_index.cc
table/block_based/data_block_footer.cc
table/block_based/filter_block_reader_common.cc
@@ -979,7 +970,6 @@ set(SOURCES
table/plain/plain_table_index.cc
table/plain/plain_table_key_coding.cc
table/plain/plain_table_reader.cc
table/range_del_block.cc
table/sst_file_dumper.cc
table/sst_file_reader.cc
table/sst_file_writer.cc
@@ -1000,7 +990,6 @@ set(SOURCES
tools/trace_analyzer_tool.cc
trace_replay/block_cache_tracer.cc
trace_replay/io_tracer.cc
trace_replay/trace_analyzer.cc
trace_replay/trace_record_handler.cc
trace_replay/trace_record_result.cc
trace_replay/trace_record.cc
@@ -1021,7 +1010,6 @@ set(SOURCES
util/dynamic_bloom.cc
util/hash.cc
util/io_dispatcher_imp.cc
util/io_prefetch_scheduler.cc
util/murmurhash.cc
util/random.cc
util/rate_limiter.cc
@@ -1051,10 +1039,7 @@ set(SOURCES
utilities/cassandra/format.cc
utilities/cassandra/merge_operator.cc
utilities/checkpoint/checkpoint_impl.cc
utilities/live_tune/live_tune_options_helper.cc
utilities/live_tune/live_tuner.cc
utilities/compaction_filters.cc
utilities/db_split_merge.cc
utilities/sorted_run_builder/sorted_run_builder.cc
utilities/compaction_filters/remove_emptyvalue_compactionfilter.cc
utilities/counted_fs.cc
@@ -1511,7 +1496,6 @@ if(WITH_TESTS)
db/db_compaction_test.cc
db/db_compaction_abort_test.cc
db/db_clip_test.cc
utilities/db_split_merge_test.cc
db/db_dynamic_level_test.cc
db/db_encryption_test.cc
db/db_etc2_test.cc
@@ -1610,7 +1594,6 @@ if(WITH_TESTS)
options/options_test.cc
table/block_based/block_based_table_reader_test.cc
table/block_based/block_test.cc
table/block_based/builtin_index_factory_test.cc
table/block_based/data_block_hash_index_test.cc
table/block_based/full_filter_block_test.cc
table/block_based/partitioned_filter_block_test.cc
@@ -1662,7 +1645,6 @@ if(WITH_TESTS)
utilities/cassandra/cassandra_row_merge_test.cc
utilities/cassandra/cassandra_serialize_test.cc
utilities/checkpoint/checkpoint_test.cc
utilities/live_tune/live_tuner_test.cc
utilities/copy_engine/copy_engine_test.cc
utilities/sorted_run_builder/sorted_run_builder_test.cc
utilities/env_timed_test.cc
@@ -1703,7 +1685,6 @@ if(WITH_TESTS)
db/db_with_timestamp_test_util.cc
monitoring/thread_status_updater_debug.cc
table/mock_table.cc
test_util/simple_external_table_factory.cc
utilities/agg_merge/test_agg_merge.cc
utilities/cassandra/test_utils.cc
)
+18 -214
View File
@@ -1,206 +1,10 @@
# Rocksdb Change Log
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
## 11.14.0 (11/05/2026)
### New Features
* Added experimental `SetAsyncReadCPUTimeEnabled()`: the next async or coroutine read started on the thread records its CPU time in `PerfContext::get_cpu_nanos` at any perf level except `kDisable`, without turning on the other timers of `kEnableTimeAndCPUTimeExceptForMutex`.
* Added `SplitDB()`, `DeleteSplitRanges()`, and `MergeDB()` in `rocksdb/utilities/db_split_merge.h`, plus a `db_split_merge` command-line tool, for moving selected column-family key ranges into a new DB and copying ranges into existing column families.
* Added opt-in sampled logging of long-held DB-wide mutexes under `StatsLevel::kAll`, with a runtime-changeable `mutex_hold_log_threshold_us` DB option defaulting to 0 (disabled).
* Add experimental live tuning of a DB's options (`StartLiveTuneCycle()`, `GetLiveTuneStatus()`, `StopLiveTuneCycle()` in `include/rocksdb/live_tune.h`): one call runs a cycle that measures the DB, asks an application-provided `LiveTuneAdvisor` for option changes, applies them, and reverts them when they break the application's constraints or do not prove better.
* Added `CompactionFilter::GetMergeOrder()` and `CompactionFilterFactory::GetMergeOrder()` to optionally run the merge operator before the compaction filter during compaction, so that the filter sees merge results. The default behavior is unchanged.
* Parallel compression (`CompressionOptions::parallel_threads > 1`) now works with a `user_defined_index_factory` whose builders support it, including the built-in trie index. Previously a custom index always forced single-threaded compression.
* Added option `reject_too_old_file` to `IngestExternalFileOptions` and `ImportColumnFamilyOptions` to reject files whose data is already older than the column family's `periodic_compaction_seconds` (or `ttl` for FIFO compaction).
* Added BlobDB support for remote compaction. Blob garbage collection uses the
cutoff computed by the primary; workers receiving legacy input without a cutoff
safely disable blob garbage collection for that job. When the primary rejects a
remote blob result because its metadata, files, or capabilities fail validation,
it reruns the compaction locally, so `CompactRange` and `CompactFiles` succeed.
`CompactionService` `kFailure` semantics are unchanged; ZippyDB production also
maps service failures to local fallback through `HandleFailure(FALLBACK_LOCAL)`.
* `CompactRangeOptions::skip_missing_blob_files` enables offline repair that deletes values referencing integrated external blob files absent from Version metadata, preserves available wide entity columns, and rewrites selected SSTs locally, bypassing `DBOptions::compaction_service`. It defaults to false; FIFO compaction and legacy BlobDB are unsupported.
* Added `db_ownership_start_time` and `data_file_provenance` (decoded by `DataFileProvenance`) to `SstFileMetaData`, recording when the DB began holding each SST file and how it got the file (e.g. flush, compaction, ingestion, or import).
* Added `TableFactory::IsTableBuilderSupported()` and `ExternalTableFactory::IsTableBuilderSupported()` for table factories that only read ingested or imported files. A column family whose factory returns false must set `disallow_memtable_writes`, is never scheduled for automatic compaction, does not stall writes for L0 files or pending compaction bytes, and returns `NotSupported` from `CompactRange()` and `CompactFiles()` instead of failing a background compaction.
* trace_analyzer: new `--analyze_put_entity` flag analyzes PutEntity queries (previously their statistics were neither enabled nor disabled explicitly).
* Added `TraceOptions::include_read_write_options`: query traces can record the main `ReadOptions` and `WriteOptions` of each traced call (e.g. `fill_cache`, `disableWAL`), which replay then uses instead of default options.
## 11.9.1 (08/18/2026)
### Public API Changes
* Added `GetEncodedEntrySize()` and an `EncodeEntry()` overload that writes internal keys into caller-owned buffers without allocating on success.
* Added `Env::Priority::READ` and `ThreadStatus::READ_PRIORITY` for a dedicated Env-managed read-work thread pool. `Env::Priority::USER` remains reserved for internal statistics and is not backed by a thread pool.
* Added experimental `MultiScanArgs::use_parallel_async_io_block_decompression` for materializing asynchronously prefetched data blocks, including decompression, in parallel on the `Env::Priority::READ` thread pool. A zero-sized pool preserves inline materialization.
* Added the experimental `IndexFactory` interface (`include/rocksdb/index_factory.h`) for pluggable SST indexes; the `UserDefinedIndex*` names remain as aliases. `BlockBasedTableOptions::index_mode` chooses which indexes an SST is built with and which one reads use by default, superseding the now-deprecated `use_udi_as_primary_index` and `fail_if_no_udi_on_open`. `ReadOptions::read_index` overrides that choice per read, and the `standard_index_is_stub` table property marks SSTs built with `kCustomOnly`. Both options are also available in the C API.
* Added experimental `DB::GetEntityWithMetadata()` and `DB::MultiGetEntityWithMetadata()`, the wide-column entity counterparts of `DB::GetWithMetadata()` and `DB::MultiGetWithMetadata()` with opt-in explicit-snapshot newer-version detection. Requesting key timestamps returns `NotSupported`.
* Removed support for building on Solaris, which had not compiled since 5.18. Removed RocksJava `Environment.isSolaris()`.
### Behavior Changes
* With `ReadOptions::async_io`, `MultiGet` now reads data blocks of direct-IO files with asynchronous reads (`FSRandomAccessFile::ReadAsync`) instead of a synchronous `MultiRead`. Each block read is aligned separately rather than merged with adjacent reads. The bytes read are still charged to `DBOptions::rate_limiter` at `ReadOptions::rate_limiter_priority`.
* Asynchronous file reads (`FSRandomAccessFile::ReadAsync`, used with `ReadOptions::async_io` by `MultiGet` and iterator prefetching) are now charged to `DBOptions::rate_limiter` at `ReadOptions::rate_limiter_priority`, like synchronous reads. A read is charged once the file system accepts it.
* `CreateColumnFamily()` and `CreateColumnFamilies()` now run the table factory option validation that `DB::Open()` runs, so they return `InvalidArgument` instead of creating a column family that the next `DB::Open()` would reject. External table factories wrapped by `NewExternalTableFactory()` now receive `ValidateOptions()` calls.
* With `CompactionOptionsFIFO::allow_trivial_copy_when_change_temperature`, each file copied for a temperature change is now reported to `EventListener::OnTableFileCreationStarted()` and `OnTableFileCreated()` (with the original file's table properties) and included in `CompactionJobInfo::table_properties`.
* `DB::GetMergeOperands()` now resets only the `merge_operands` entries it fills, instead of all `expected_max_number_of_operands` entries. Entries past `*number_of_operands`, and the whole array on a non-OK result, are left as the caller passed them.
* The JSON written to the info LOG by `EventLogger` (`EVENT_LOG_v1` lines) and by `VersionEdit::DebugJSON()` now escapes special characters in keys and string values, and writes non-finite floating-point values as `null`, so the output is always valid JSON.
* Improved how `periodic_compaction_phase_recovery_percent` spreads catch-up periodic compactions after it is enabled or changed (or the DB is reopened). This reduces a transient burst in periodic compactions when many DBs make such a change at once.
* The new DB option `use_separate_remote_compaction_pool` defaults to false and can be changed through `SetDBOptions()` for newly scheduled jobs. When enabled with CompactionService, remote jobs use a dedicated pool and each local subcompaction uses LOW/BOTTOM and counts against the DB compaction limit. Existing jobs finish using their original scheduling model.
* `DB::SetOptions()` now returns OK without writing a new OPTIONS file or installing a new SuperVersion when the call changes no option value, as `DB::SetDBOptions()` already does. Calls that set an object option, such as `table_factory` or one of its fields, are still applied in full. After a failed OPTIONS file write, the next `SetOptions()` or `SetDBOptions()` call writes the file even if no option value changes.
* Custom index factories are rejected with user-defined timestamps in every index mode except `kStandardOnly`, which ignores the factory. Custom-index block-cache hits, misses, and adds now contribute to `BLOCK_CACHE_INDEX_*` instead of `BLOCK_CACHE_DATA_*`.
Trie index construction returns `NotSupported` when a data-block offset or size exceeds `UINT32_MAX`, including when the trie is optional in `kStandardDefault`. Flush, compaction, and `SstFileWriter::Finish()` can therefore fail for such output; an SST larger than 4 GiB is not itself the rejection condition. File-size targets do not split L0 outputs or `SstFileWriter` outputs. Bound those outputs or disable trie construction with `kStandardOnly`.
### Bug Fixes
* Fixed a race where `IngestExternalFiles()` with `atomic_replace_range` could delete an existing SST file that a concurrently picked compaction (e.g. a universal compaction outputting to L0) used as input, causing the compaction to fail with "Corruption: VersionBuilder: Cannot delete table file ... since it is not in the LSM tree" and putting the DB into read-only mode. Atomic range replacement now returns `NotSupported` with FIFO compaction.
* Fixed blob direct write when sealing a blob file fails during a flush or a write: the flush left its memtables marked as being flushed, so no later flush picked them up and error recovery waited for them forever, and the blob file was dropped from tracking, so a retried flush could install table files whose blob file was never added to the MANIFEST.
* Fixed blob direct write with more than one concurrent flush of a column family: a later flush sealed and published the blob files of the oldest pending memtable instead of its own, so flushes failed with `Corruption: VersionBuilder: Blob file #N already added` and the database stopped.
* Fixed blob direct write when the `SstFileManager` space limit is reached while a blob file is sealed: the sealed blob file was deleted while memtables still referenced it, so reads of its values failed and the values were lost, and reporting the error could deadlock with a concurrent memtable switch. The seal and the write or flush now succeed and keep the file, and writes then stop with a `SpaceLimit` background error, so for blob direct write the limit is soft: usage can exceed it by up to about `blob_file_size` × `blob_direct_write_partitions` for each memtable not flushed yet. Also fixed a write batch that failed after writing some of its values to blob files: those blobs are now counted as garbage, so their blob file can still be deleted.
* Values that retain external blob references now keep their original timestamps during history compaction, preventing blob key verification failures.
* Fixed `DB::ClipColumnFamily()` leaving an empty-string key outside the kept range when it is the only key in an L0 SST file.
* Fixed `format_version=8` with `ReverseBytewiseComparator`: a block's stored common key prefix could extend past a user key (one following longer user keys that extend it), so reads and iteration could miss that key (or its deletion, exposing an older value), and compactions could fail with `Corruption`. Files written by affected versions (11.10 through 11.13) are now read correctly, and new files no longer have this problem.
* Fixed SST file times that could defer periodic compaction and TTL. Files written by `SstFileWriter`, flushed during WAL recovery, or recovered by `RepairDB` now record a file creation time. Files added by `IngestExternalFile()` or `CreateColumnFamilyWithImport()` now keep the creation and oldest ancestor times from the file or export metadata (capped at the current time) instead of taking the time they were added, so ingesting or importing old data can make it due for periodic compaction or TTL soon afterward. FIFO TTL compaction now also deletes ingested `SstFileWriter` files, which previously blocked TTL deletion of themselves and all newer files. Files copied by FIFO compaction for a temperature change now keep the original file's creation time.
* Fixed a debug-build assertion failure and incorrect cache charge and block-cache byte statistics in release builds when a full, strict-capacity LRU cache with metadata charging creates an uncharged standalone handle, including during secondary-cache promotion.
* Fixed atomic flushes of all column families when another column family is dropped while they wait: `CompactRange()` no longer fails with a "Column family dropped" error, and `WaitForCompact()` and DB close no longer return OK without flushing the remaining column families.
* Fixed a bug where MemPurge returned with the DB mutex unlocked when the compaction filter created for flush did not ignore snapshots.
* Fixed a crash in MultiScan `Iterator::Prepare()` with a single scan range without a limit when the DB has L0 files, and an assertion failure in debug builds when `Prepare()` is called on an already-positioned iterator. `Prepare()` now leaves the iterator invalid until it is positioned again (e.g. by `Seek()`).
* Fixed `MultiGet` returning NotFound for existing keys that are outside the prefix extractor's domain, with partitioned filters, `whole_key_filtering=false`, and filter partitions cut together with index partitions (`decouple_partitioned_filters=false`, also the layout of files written by older versions). A filter partition holding only such keys is empty, and `MultiGet` treated every key mapped to it as absent. `Get` was not affected.
* Fixed prepared external file ingestions retaining unrelated obsolete SSTs until commit or abort. Prepared ingestions now protect only their staged files from deletion.
* Fixed data loss with `min_tombstones_for_range_conversion` and external file ingestion. An iterator whose view predated the ingestion could convert the deleted keys around the ingested keys into a range tombstone that deleted them. This happened when the file got no new sequence number (sequence number zero, `ingest_behind`, or `allow_db_generated_files`), and when an iterator was created during the ingestion.
* Fixed a data loss bug where an iterator with `ReadOptions::read_tier = kMemtableTier` could, with `min_tombstones_for_range_conversion` enabled, insert a range tombstone that deleted live keys stored only in SST files. Read-path range tombstone conversion now runs only for iterators with `read_tier = kReadAllTier`.
* Fixed a race with `recycle_log_file_num > 0` where an obsolete WAL could be reused for a new WAL while its old writer was still open. Closing the old writer then truncated the reused file to the old WAL's size, so writes in the new WAL past that point, including acknowledged `sync` writes, were lost on the next reopen.
* Fixed remote compaction ignoring `preclude_last_level_data_seconds` and `preserve_internal_time_seconds`, which caused recent data to be written to the last level and recent sequence numbers to be zeroed. Takes effect once both the primary and the compaction worker are upgraded.
* Fixed `DB::OpenAndCompact()` with `OpenAndCompactOptions::allow_resumption` not calling `EventListener::OnSubcompactionCompleted()` when the compaction fails to resume from its saved progress.
* Fixed MANIFEST catch-up on secondary instances (`TryCatchUpWithPrimary()`) and follower instances installing a column family with no files and still returning OK. This happened when catch-up switched to a new MANIFEST and the primary deleted a file that MANIFEST lists before catch-up checked it, recording the deletion in a newer MANIFEST. Catch-up now keeps the column family's previous view until a complete state from the new MANIFEST is available.
* Custom index blocks now reuse cached contents on SST reopen, and deprecated UDI boolean options can be cleared without overriding an explicitly configured index mode.
Legacy UDI options copied from a factory can now be cleared before constructing a new factory without retaining the derived mode. The C API preserves both that rollback behavior and explicitly selected modes. OPTIONS files serialize equivalent legacy booleans for `kStandardDefault`, `kStandardRequired`, and `kCustomDefault` so older readers retain routing.
Empty custom-index output retains the standard-index fallback in modes that write both indexes; custom-only output must remain nonempty. Default primary reads retain the table factory's precedence over a legacy per-read factory pointer, while explicit custom selectors still check its name.
* Preserve legacy UDI routing when loading old OPTIONS files, and reject invalid index modes in standalone SST readers and writers. Table readers now charge the custom index blocks they own; a custom reader whose `ApproximateMemoryUsage()` already includes that block should override `IndexFactoryReader::MemoryUsageIncludesIndexBlock()` to return true so the block is not charged twice.
Keep cached custom-index Slice views intact when a reader consumes its input. Register custom-index cache helpers without requiring a later change. Trie index size estimates now account for shared prefixes, preventing premature compaction file cuts, and reject data-block offsets or sizes exceeding the 32-bit on-disk limit instead of truncating them.
### Performance Improvements
* With `ReadOptions::pin_data`, iterators over read-only DBs opened with `max_open_files = -1` and `allow_mmap_reads = true` no longer accumulate memory as they move across SST files.
* On a read-write primary, `DB::GetSnapshot()` and `DB::ReleaseSnapshot()` no longer acquire the DB mutex in the common case. Secondary and follower snapshot creation retains the DB mutex needed to synchronize with view installation, while release remains sharded. The snapshot list is now split into 16 per-CPU shards, each with its own lock, so primary threads creating and releasing snapshots no longer serialize on one lock or wait behind flush, compaction, or other work holding the DB mutex. Snapshot-release maintenance is deferred to existing flush, compaction, and periodic maintenance paths. Primary snapshot creation serializes with the DB-mutex-held phases of external file ingestion but can proceed while the MANIFEST write temporarily releases that mutex.
## 11.13.0 (10/21/2026)
### New Features
* Added `FileSystem::IsPreallocationSupported()`. A `FileSystem` that never preallocates file space can return false so that DB open skips reopening and truncating the last WAL file.
### Public API Changes
* Added the experimental `DBOptions::track_options_file_number_in_manifest`
rollout option (with C and Java bindings). The option is persisted in each
OPTIONS file. When enabled, RocksDB durably creates an OPTIONS file and then
commits its file number in MANIFEST. Column-family create/drop commits the
configuration snapshot and membership in one MANIFEST record. A newer tracked
OPTIONS file without a commit is ignored after a crash, while a newer OPTIONS
file written with tracking disabled retains the legacy highest-numbered-file
behavior. Batched column-family changes publish one snapshot; after partial
success it can be a safe superset that persisted-options APIs filter against
live MANIFEST membership. Ordinary `DB::Open` continues to use caller-supplied
options and does not depend on the operational snapshot.
* Added `EncodeEntry()`, `PackSequenceAndType()`, and `UnPackSequenceAndType()` in `rocksdb/utilities/types_util.h` to construct internal keys and pack or unpack their sequence numbers and entry types. `kInternalKeyFooterSize` exposes the footer size in bytes.
* Added `ldb update_manifest --rebind_options_file --options_file=OPTIONS-<number>` for offline repair of a committed MANIFEST OPTIONS file number. Operators must select a trusted snapshot that has MANIFEST tracking enabled and provide any external DB/CF allocation roots that are not serialized in OPTIONS.
### Behavior Changes
* With `max_bgerror_resume_count <= 0` (auto resume disabled), a retryable or file-scoped IO error during compaction or flush is now a hard background error that stops writes until `DB::Resume()` is called. Previously, compaction errors were silently retried, and flush errors with a healthy WAL were soft errors that let writes continue while flushes were stopped.
### Bug Fixes
* Fixed two more `SstFileManager` trash accounting leaks: retrying a failed trash deletion immediately (for example with rate limiting disabled) after the file had disappeared, and a trash file that was deleted but whose directory sync then failed. In both cases the file's size stayed in the total trash size and tracked size until restart.
* Fixed `SstFileManager` trash accounting leaking the size of a queued trash file that disappeared before deletion (for example because its directory was renamed). The leaked bytes stayed in the total trash size until restart, which could push every later deletion past `max_trash_db_ratio` and make it immediate instead of rate limited.
* Fixed `MultiGet` with `ReadOptions::async_io` returning `NotSupported` when io_uring initialization or an asynchronous read submission fails. It now checks async I/O support on the reading thread and falls back to synchronous reads when needed.
* Fixed subset checkpoints so their newest OPTIONS file lists only the column
families retained by the checkpoint. This applies with both legacy
highest-numbered OPTIONS selection and MANIFEST-tracked OPTIONS selection, and
prevents `LoadLatestOptions` from recreating column families that the subset
checkpoint intentionally dropped from its copied MANIFEST.
### Performance Improvements
* Perf context counters and timers no longer call the thread-local init function of `perf_context` on every update. With `kEnableCount`, a counter update costs about 60% less.
## 11.12.0 (10/07/2026)
### New Features
* Added `FullExternalTableFactory` and related full-mode external table APIs that preserve RocksDB internal keys, enabling external tables to support nonzero sequence numbers, point and single deletions, merge operands, blob indexes, wide-column entities, snapshots, live DB writes, and multi-level compactions.
### Public API Changes
* Added experimental coroutine capability interfaces for basic and full `ExternalTableReader` implementations. Coroutine-capable external readers can initiate file reads through `FSRandomAccessFile::SubmitReadAsync()`.
* `ExternalTableFactory::NewTableReader()` now receives an already-opened `FSRandomAccessFile` and its file size instead of requiring implementations to reopen `file_path`. The supplied file preserves RocksDB's direct-I/O alignment handling, rate limiting, read statistics and listener notifications, prefetching, and synchronous, asynchronous, and batched reads.
* Added `PutRangeDeletionBlock()`, `GetRangeDeletionBlock()`, and `IsDeleteRangeSupported()` to the external table interfaces so formats can opt in to range deletions using RocksDB's raw range-deletion metadata block.
* Opaque `PreparedFileInfo` metadata can now be serialized to a checksummed representation with `SerializePreparedFileInfo()` and restored with `DeserializePreparedFileInfo()` for reuse across processes.
* Removed the deprecated `IngestExternalFileOptions::write_global_seqno` option and its C, Java, and `ldb` APIs, `TableProperties::external_sst_file_global_seqno_offset`, and the `file_offset` output from `ExternalTableReader::GetPropertiesBlock()`. Existing external SST files whose global sequence number was rewritten without updating the properties-block checksum remain readable.
* `TablePropertiesNames` has no apparent use in the public API so has been deprecated there.
* `TableProperties::creation_time` is renamed to `oldest_ancestor_time`, which better describes its meaning, along with `TablePropertiesNames::kCreationTime` to `kOldestAncestorTime`. The misspelled `SstFileMetaData::oldest_ancester_time` is renamed to `oldest_ancestor_time`. The old names remain available as deprecated aliases and will be removed in a future release.
* Added `WriteBufferFlushPolicy` and `WriteBufferManager` policy selection for flushing the oldest or largest column family, including configurable batched flushing of the largest column families across DBs sharing a manager, with C and Java bindings.
### Behavior Changes
* When `ExternalTableBuilderBase::PutPropertiesBlock()` returns `NotSupported`, RocksDB now overrides `num_entries`, `raw_key_size`, and `raw_value_size` from `ExternalTableBuilderBase::GetTableProperties()` with the logical totals tracked by the external-table adapter. Format-specific properties such as `data_size` remain unchanged.
* The `FSWritableFile` passed to `ExternalTableFactory::NewTableBuilder()` now reports logical file size including RocksDB-buffered data and exposes the underlying file's direct-I/O mode and required buffer alignment.
* `Iterator::Refresh()` now returns `NotSupported` on iterators from a secondary DB's `DB::NewIterators()` only, because those iterators are pinned to one cross-column-family read view. Iterators from `DB::NewIterator()` are unaffected; to advance a batch, call `DB::NewIterators()` again after `TryCatchUpWithPrimary()`.
* `SstFileReader` now reports standard SST read statistics and file I/O listener notifications, and honors the configured read rate limiter.
### Bug Fixes
* Fixed full recovery from retryable background errors to atomically advance WAL replay boundaries for all column families, preventing partial recovery of multi-column-family write batches after a failed recovery flush.
* Fixed RocksDB builds on big-endian architectures such as s390x by disabling the optional persistent fault-injection log, whose binary format is little-endian-only.
* Fixed `DB::ClipColumnFamily()` deleting a non-L0 SST file whose largest key equals `begin_key`, which lost keys at the start of the kept range.
* Fixed swapped `oldest_key_time` and `newest_key_time` values in the `table_file_creation` event log entry.
* Fixed `ExternalTableReader::VerifyChecksum()` results not being honored by the external table adapter.
* Fixed a bug in manifest handling that could lead to a hang or memory corruption in the case of LSM sanity checks being fatally violated (exceptional; see `force_consistency_checks`).
* Fixed `MultiGet()` with `ReadOptions::read_tier == kBlockCacheTier` reading data blocks from disk when `allow_mmap_reads` is false. Keys whose data block is not in the block cache now return `Status::Incomplete()`, the same as `Get()`.
* Fixed trace replay (`DB::NewDefaultReplayer()`, db_bench `replay`) failing every read record (Get, MultiGet, iterator seek) on the default column family with `Corruption: Invalid Column Family ID` when the default column family handle was not passed, which is always the case in db_bench with `--num_column_families=1`.
* Fixed multi-threaded `Replayer::Replay()` ignoring errors from executing trace records and returning OK. It now stops and returns the error, including `Status::NotSupported()` from executing a record, as single-threaded replay does.
* Fixed secondary DB `NewIterators()` returning iterators from different database states or blocking for `TryCatchUpWithPrimary()` by serving the last completed catch-up view.
### Performance Improvements
* Single Get coroutine reads using non-mmap, non-direct I/O on a `kFSBuffer`-capable `FileSystem` now use `FSRandomAccessFile::SubmitReadAsync()` instead of falling back to synchronous `Read()`. When rate-limited, these reads acquire their full token budget before submission to preserve a single filesystem-owned buffer, which can make physical I/O burstier than reads using caller-provided buffers.
## 11.11.0 (09/21/2026)
### New Features
* External tables now support DB-assigned global sequence numbers when `IngestExternalFileOptions::allow_global_seqno` is enabled, allowing overlapping external tables to be ingested across multiple levels when compactions are disabled. This includes the deprecated `write_global_seqno` path when the reader supplies the properties block's file offset.
* Added checksum handoff support for SST file writes when `BlockBasedTableOptions::checksum` is `kCRC32c`, allowing block and trailer CRC32C checksums to be passed from the table builder to the file writer for buffered-data validation and filesystem in-flight write protection.
* Enhanced `sst_dump --command=recompress` to be more powerful for benchmarking, including through automation with a new `--json` option for machine-readable output.
### Public API Changes
* Changed the experimental async read statistics API by removing `DB::AsyncCallback::EnableStats()` and changing `OnComplete(const PerfContext*, const IOStatsContext*)` to `OnComplete()`. Callers must configure TLS statistics before each async or coroutine read. `DB::GetAsync()` and `DB::MultiGetAsync()` callbacks must copy per-operation statistics from TLS during `OnComplete()`; coroutine users must copy them from inside a `CoroStackableDB` hook before suspending again. Each operation leaves TLS statistics collection disabled.
* Added `Compressor::MaybeOverrideCompressionOptions()` (EXPERIMENTAL), an omnibus hook letting a custom compressor adjust the subset of `CompressionOptions` that the SST builder applies rather than the compressor itself: `parallel_threads`, `max_compressed_bytes_per_kb`, `auto_skip`, and `auto_skip_min_sample_every` (and potentially more in the future). This deprecates `Compressor::GetRecommendedParallelThreads()`, which it subsumes; the default implementation still honors an overridden `GetRecommendedParallelThreads()` for compatibility.
* Added experimental `CoroDB::CoGet()` and `CoroDB::CoMultiGet()` overloads for point reads from an `SstFileReader`.
* Added experimental `DB::GetWithMetadata()` and `DB::MultiGetWithMetadata()` APIs with opt-in output metadata for key timestamps and explicit-snapshot newer-version detection.
* Added a `DB::NewIterators()` overload that accepts parallel vectors of `ReadOptions` and column family handles, allowing each returned iterator to use independent read settings while sharing a consistent database state. The vectors must have the same size, all snapshots must be null or identical, and all `tailing` settings must match.
* Added `ReadOptions::custom_context`, an opaque, non-owning pointer passed through table reads so external table implementations can consume application-defined read options.
### Behavior Changes
* `ExternalTableReader::MultiGet()` is now used by RocksDB's `DB::MultiGet()` path. Previously, the external table adapter used `TableReader`'s fallback, which called `ExternalTableReader::Get()` separately for each key.
* `DB::OpenAndCompact()` now requires `output_directory` to be a simple name for a new subdirectory when the new `use_session_tmp_dir_for_remote_compaction` DB option is enabled. The option places that subdirectory under the canonical `<dbname>/session_tmp/` root, and `DB::Open()` deletes stale contents under the root after recovery. The disabled default preserves the existing `<dbname>/<output_directory>` layout without automatic cleanup. Previously, remote-compaction output directories were created directly under `<dbname>`, which made it challenging to safely identify and delete obsolete files.
* Several block-based SST table properties now better support compression benchmarking and optimization: `uncompressed_data_size` is now persisted in the file (previously it was computed only at write time and read back as 0); a new `lsm_info_at_creation` property records the LSM level and "bottommost" status at the file's creation, distinguishing "not applicable" (e.g. SstFileWriter files) from "unknown" (older files); and the `compression_options` property now records the full compression manager configuration instead of only its name.
### Bug Fixes
* Fixed a usually-minor performance bug where `Cache::ApplyToAllEntries()` used single-entry batches instead of the intended 256-entry default for `ApplyToAllEntriesOptions::average_entries_per_lock`. All of the Cache implementations (LRU and HCC) benefit from the fix, but the disfavored LRU cache was especially impacted by repeated mutex acquisition and release.
* Fixed stats-disabled coroutine and asynchronous reads inheriting enabled TLS statistics state when interleaved with stats-enabled reads on the same executor. Each read now uses its captured per-call configuration, while enabled request counters remain isolated across suspensions.
* Fixed a bug where a remote compaction (`CompactionService` / `DB::OpenAndCompact`) requested with `OpenAndCompactOptions::allow_resumption=true` but for which resumption was internally disabled (incompatible with `paranoid_file_checks` or output-iteration verification) could fail a retried attempt, because the output directory was not cleaned before starting fresh and output files left by a previously interrupted attempt collided with the reused file numbers.
* Fixed a bug where a remote compaction (`CompactionService` / `DB::OpenAndCompact`) could fail when the primary rotated its MANIFEST while the worker was opening the primary's directory read-only. The open reads `CURRENT`, and the primary publishes a new MANIFEST by renaming a freshly written `CURRENT` over the old one; on file systems without POSIX read-after-unlink semantics (remote/object stores) the replaced object is then reported as gone (`PathNotFound`/`NotFound`, or `TryAgain` for the MANIFEST equivalent) instead of remaining readable. `DB::OpenAndCompact()` now retries the secondary open a bounded, configurable number of times for those transient statuses, so a concurrent rotation no longer fails the compaction (which, for a `CompactionService` that does not fall back to a local compaction, became a background error on the primary).
* Fixed secondary DB `NewIterators()` returning iterators from different database states when `TryCatchUpWithPrimary()` runs concurrently.
* Fixed a bug where a `SeekToFirst()` on an iterator previously used for a `Seek()` could end the scan early and silently omit keys with `ReadOptions::async_io`, or otherwise run with readahead effectively disabled. Affects scans using `ReadOptions::prefix_same_as_start` together with `ReadOptions::auto_readahead_size` (enabled by default); memory-mapped reads are unaffected.
### Performance Improvements
* `DB::MultiGetEntityLazy()` now reads all keys of a batch under a single shared per-column-family SuperVersion pin, and a single `LazyWideColumnsBatch::MultiResolve()` coalesces the resulting blob reads across keys: one read per blob file (and one per SST for embedded blobs), for whole-column and byte-range reads alike, instead of reading each key's blobs separately.
## 11.10.0 (08/28/2026)
### New Features
* Added `BlockBasedTableOptions::optimize_key_common_prefix` as part of new `format_version=8`. When enabled, each data block and index block stores the common user-key prefix of its keys once instead of at every restart point, reducing block size and block-cache footprint for prefix-heavy data and speeding up Seek for bytewise-ordered keys.
* A new experimental feature spreads out periodic (time-based) compaction trigger times across a fleet of similarly-configured DBs to avoid recurring "thundering herd" triggers, especially with remote compaction. See the new experimental DB option `periodic_compaction_phase_recovery_percent`.
### Public API Changes
* `IngestExternalFileArg::atomic_replace_range` now supports a range that only partially overlaps existing files, instead of failing. Requires universal compaction and a table factory that supports range deletion; other configurations still fail on partial overlap. The range is also now specified as half-open (`start` inclusive, `limit` exclusive), resolving a previously documented ambiguity in the upper bound.
* Added an immutable `external_table_config` option that is persisted in OPTIONS files and passed to external table factories during bootstrap.
### Bug Fixes
* Fixed a liveness bug where RocksDB could remain indefinitely in recovery from a soft background error when concurrent writes continuously generated normal flush requests.
* Fixed a race in online error recovery where a concurrent higher-severity I/O error could leave a stale retry-flush plan active, prevent the required WAL switch, and create duplicate internal WAL history detected on DB reopen.
* Fix "bottommost files" infinite compaction loops (`kBottommostFiles`). This happens with the unsupported combination of leveled compaction and `preserve_internal_time_seconds` / `preclude_last_level_data_seconds`, and in an edge case with user-defined timestamps when `full_history_ts_low` is unset. The underlying logic is now more unified to reduce risk of a similar divergence in logic in the future.
* Fixed `reuse_manifest_on_open` to create a fresh MANIFEST instead of appending after recovery sees an incomplete atomic group at the MANIFEST tail.
* Starting remote compaction (`CompactionService` / `DB::OpenAndCompact`) is now more efficient and more reliable, by avoiding unnecessary file operations (latency) while using more accurate consistency / precondition checks, both avoiding some spurious failures and rejecting more legitimate inconsistencies. New DBOption `remote_compaction_manifest_floor` can be disabled in case of unexpected problems with the change.
* Fixed unbounded retention of recovered transaction state by a long-lived secondary opened with `allow_2pc`.
* Fixed stale reads on secondary instances, where entries replayed from a WAL that the primary had already flushed could shadow the newer values in the flushed files indefinitely. Also fixed a case where a secondary could stop returning such a key altogether while the primary's flushed file holding it was unreadable.
* Fixed subset checkpoints to discard tolerated MANIFEST tail corruption before appending column-family drop records.
### Performance Improvements
* Improved compaction CPU efficiency for column families using an `SstPartitioner` (such as the built-in `SstPartitionerFixedPrefix`): when the partitioner can express its next boundary as a key prefix via the new `SstPartitioner::ShouldPartitionByPrefix()` API, compaction now checks that cached prefix inline instead of copying every user key and making a virtual `ShouldPartition()` call for each key-value written to an SST.
## 11.9.0 (08/14/2026)
### New Features
* Added an integrated and improved compression auto-skip feature (new CompressionOptions, still experimental) that saves CPU by skipping compression when and where the overall ratio is inadequate. This improves on the previous auto-skip compression manager by (a) taking achieved compression ratios into account, (b) using a continuously evolving payoff estimate, and (c) combining natively with parallel compression.
@@ -1156,7 +960,7 @@ want to continue to use force enabling, they need to explicitly pass a `true` to
* Overload the API CreateColumnFamilyWithImport() to support creating ColumnFamily by importing multiple ColumnFamilies It requires that CFs should not overlap in user key range.
### Behavior Changes
* Change the default value for option `level_compaction_dynamic_level_bytes` to true. This affects users who use leveled compaction and do not set this option explicitly. These users may see additional background compactions following DB open. These compactions help to shape the LSM according to `level_compaction_dynamic_level_bytes` such that the size of each level Ln is approximately size of Ln-1 * `max_bytes_for_level_multiplier`. Turning on this option has other benefits too: see more detail in wiki: https://github.com/meta/rocksdb/wiki/Leveled-Compaction#option-level_compaction_dynamic_level_bytes-and-levels-target-size and in option comment in advanced_options.h (#11525).
* Change the default value for option `level_compaction_dynamic_level_bytes` to true. This affects users who use leveled compaction and do not set this option explicitly. These users may see additional background compactions following DB open. These compactions help to shape the LSM according to `level_compaction_dynamic_level_bytes` such that the size of each level Ln is approximately size of Ln-1 * `max_bytes_for_level_multiplier`. Turning on this option has other benefits too: see more detail in wiki: https://github.com/facebook/rocksdb/wiki/Leveled-Compaction#option-level_compaction_dynamic_level_bytes-and-levels-target-size and in option comment in advanced_options.h (#11525).
* For Leveled Compaction users, `CompactRange()` will now always try to compact to the last non-empty level. (#11468)
For Leveled Compaction users, `CompactRange()` with `bottommost_level_compaction = BottommostLevelCompaction::kIfHaveCompactionFilter` will behave similar to `kForceOptimized` in that it will skip files created during this manual compaction when compacting files in the bottommost level. (#11468)
* RocksDB will try to drop range tombstones during non-bottommost compaction when it is safe to do so. (#11459)
@@ -1201,7 +1005,7 @@ For Leveled Compaction users, `CompactRange()` with `bottommost_level_compaction
* Remove unnecessary constructor for CompressionOptions.
### Behavior changes
* Changed default block cache size from an 8MB to 32MB LRUCache, which increases the default number of cache shards from 16 to 64. This change is intended to minimize cache mutex contention under stress conditions. See https://github.com/meta/rocksdb/wiki/Block-Cache for more information.
* Changed default block cache size from an 8MB to 32MB LRUCache, which increases the default number of cache shards from 16 to 64. This change is intended to minimize cache mutex contention under stress conditions. See https://github.com/facebook/rocksdb/wiki/Block-Cache for more information.
* For level compaction with `level_compaction_dynamic_level_bytes=true`, RocksDB now trivially moves levels down to fill LSM starting from bottommost level during DB open. See more in comments for option `level_compaction_dynamic_level_bytes` (#11321).
* User-provided `ReadOptions` take effect for more reads of non-`CacheEntryRole::kDataBlock` blocks.
* For level compaction with `level_compaction_dynamic_level_bytes=true`, RocksDB now drains unnecessary levels through background compaction automatically (#11340). This together with #11321 makes it automatic to migrate other compaction settings to level compaction with `level_compaction_dynamic_level_bytes=true`. In addition, a live DB that becomes smaller will now have unnecessary levels drained which can help to reduce read and space amp.
@@ -1365,7 +1169,7 @@ For Leveled Compaction users, `CompactRange()` with `bottommost_level_compaction
* Only periodic stats dumper triggered by options.stats_dump_period_sec will update stats interval. Ones triggered by DB::GetProperty() will not update stats interval and will report based on an interval since the last time stats dump period.
### Public API changes
* Make kXXH3 checksum the new default, because it is faster on common hardware, especially with kCRC32c affected by a performance bug in some versions of clang (https://github.com/meta/rocksdb/issues/9891). DBs written with this new setting can be read by RocksDB 6.27 and newer.
* Make kXXH3 checksum the new default, because it is faster on common hardware, especially with kCRC32c affected by a performance bug in some versions of clang (https://github.com/facebook/rocksdb/issues/9891). DBs written with this new setting can be read by RocksDB 6.27 and newer.
* Refactor the classes, APIs and data structures for block cache tracing to allow a user provided trace writer to be used. Introduced an abstract BlockCacheTraceWriter class that takes a structured BlockCacheTraceRecord. The BlockCacheTraceWriter implementation can then format and log the record in whatever way it sees fit. The default BlockCacheTraceWriterImpl does file tracing using a user provided TraceWriter. More details in rocksdb/includb/block_cache_trace_writer.h.
## 7.7.0 (09/18/2022)
@@ -1485,7 +1289,7 @@ For Leveled Compaction users, `CompactRange()` with `bottommost_level_compaction
* Fix a bug in which backup/checkpoint can include a WAL deleted by RocksDB.
* Fix a bug where concurrent compactions might cause unnecessary further write stalling. In some cases, this might cause write rate to drop to minimum.
* Fix a bug in Logger where if dbname and db_log_dir are on different filesystems, dbname creation would fail wrt to db_log_dir path returning an error and fails to open the DB.
* Fix a CPU and memory efficiency issue introduce by https://github.com/meta/rocksdb/pull/8336 which made InternalKeyComparator configurable as an unintended side effect.
* Fix a CPU and memory efficiency issue introduce by https://github.com/facebook/rocksdb/pull/8336 which made InternalKeyComparator configurable as an unintended side effect.
## Behavior Change
* In leveled compaction with dynamic levelling, level multiplier is not anymore adjusted due to oversized L0. Instead, compaction score is adjusted by increasing size level target by adding incoming bytes from upper levels. This would deprioritize compactions from upper levels if more data from L0 is coming. This is to fix some unnecessary full stalling due to drastic change of level targets, while not wasting write bandwidth for compaction while writes are overloaded.
@@ -1524,7 +1328,7 @@ For Leveled Compaction users, `CompactRange()` with `bottommost_level_compaction
* Add User Defined Timestamp in C API.
* `rocksdb_comparator_with_ts_create` to create timestamp aware comparator
* Put, Get, Delete, SingleDelete, MultiGet APIs has corresponding timestamp aware APIs with suffix `with_ts`
* And Add C API's for Transaction, SstFileWriter, Compaction as mentioned [here](https://github.com/meta/rocksdb/wiki/User-defined-Timestamp-(Experimental))
* And Add C API's for Transaction, SstFileWriter, Compaction as mentioned [here](https://github.com/facebook/rocksdb/wiki/User-defined-Timestamp-(Experimental))
* The contract for implementations of Comparator::IsSameLengthImmediateSuccessor has been updated to work around a design bug in `auto_prefix_mode`.
* The API documentation for `auto_prefix_mode` now notes some corner cases in which it returns different results than `total_order_seek`, due to design bugs that are not easily fixed. Users using built-in comparators and keys at least the size of a fixed prefix length are not affected.
* Obsoleted the NUM_DATA_BLOCKS_READ_PER_LEVEL stat and introduced the NUM_LEVEL_READ_PER_MULTIGET and MULTIGET_COROUTINE_COUNT stats
@@ -1755,7 +1559,7 @@ cache at some point (i.e, causing a cache full under `LRUCacheOptions::strict_ca
* Experimental: Add support for new APIs ReadAsync in FSRandomAccessFile that reads the data asynchronously and Poll API in FileSystem that checks if requested read request has completed or not. ReadAsync takes a callback function. Poll API checks for completion of read IO requests and should call callback functions to indicate completion of read requests.
## 6.29.0 (01/21/2022)
Note: The next release will be major release 7.0. See https://github.com/meta/rocksdb/issues/9390 for more info.
Note: The next release will be major release 7.0. See https://github.com/facebook/rocksdb/issues/9390 for more info.
### Public API change
* Added values to `TraceFilterType`: `kTraceFilterIteratorSeek`, `kTraceFilterIteratorSeekForPrev`, and `kTraceFilterMultiGet`. They can be set in `TraceOptions` to filter out the operation types after which they are named.
* Added `TraceOptions::preserve_write_order`. When enabled it guarantees write records are traced in the same order they are logged to WAL and applied to the DB. By default it is disabled (false) to match the legacy behavior and prevent regression.
@@ -1904,7 +1708,7 @@ Note: The next release will be major release 7.0. See https://github.com/meta/ro
### Java API Changes
* Add Java API bindings for new integrated BlobDB options
* `keyMayExist()` supports ByteBuffer.
* Fix multiget throwing Null Pointer Exception for num of keys > 70k (https://github.com/meta/rocksdb/issues/8039).
* Fix multiget throwing Null Pointer Exception for num of keys > 70k (https://github.com/facebook/rocksdb/issues/8039).
## 6.25.0 (2021-09-20)
### Bug Fixes
@@ -2591,7 +2395,7 @@ Note: The next release will be major release 7.0. See https://github.com/meta/ro
* Errors related to the retrieval of the compression dictionary are now propagated to the user.
* db_bench adds a "benchmark" stats_history, which prints out the whole stats history.
* Overload GetAllKeyVersions() to support non-default column family.
* Added new APIs ExportColumnFamily() and CreateColumnFamilyWithImport() to support export and import of a Column Family. https://github.com/meta/rocksdb/issues/3469
* Added new APIs ExportColumnFamily() and CreateColumnFamilyWithImport() to support export and import of a Column Family. https://github.com/facebook/rocksdb/issues/3469
* ldb sometimes uses a string-append merge operator if no merge operator is passed in. This is to allow users to print keys from a DB with a merge operator.
* Replaces old Registra with ObjectRegistry to allow user to create custom object from string, also add LoadEnv() to Env.
* Added new overload of GetApproximateSizes which gets SizeApproximationOptions object and returns a Status. The older overloads are redirecting their calls to this new method and no longer assert if the include_flags doesn't have either of INCLUDE_MEMTABLES or INCLUDE_FILES bits set. It's recommended to use the new method only, as it is more type safe and returns a meaningful status in case of errors.
@@ -3178,7 +2982,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
## 4.8.0 (2016-05-02)
### Public API Change
* Allow preset compression dictionary for improved compression of block-based tables. This is supported for zlib, zstd, and lz4. The compression dictionary's size is configurable via CompressionOptions::max_dict_bytes.
* Delete deprecated classes for creating backups (BackupableDB) and restoring from backups (RestoreBackupableDB). Now, BackupEngine should be used for creating backups, and BackupEngineReadOnly should be used for restorations. For more details, see https://github.com/meta/rocksdb/wiki/How-to-backup-RocksDB%3F
* Delete deprecated classes for creating backups (BackupableDB) and restoring from backups (RestoreBackupableDB). Now, BackupEngine should be used for creating backups, and BackupEngineReadOnly should be used for restorations. For more details, see https://github.com/facebook/rocksdb/wiki/How-to-backup-RocksDB%3F
* Expose estimate of per-level compression ratio via DB property: "rocksdb.compression-ratio-at-levelN".
* Added EventListener::OnTableFileCreationStarted. EventListener::OnTableFileCreated will be called on failure case. User can check creation status via TableFileCreationInfo::status.
@@ -3188,7 +2992,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
## 4.7.0 (2016-04-08)
### Public API Change
* rename options compaction_measure_io_stats to report_bg_io_stats and include flush too.
* Change some default options. Now default options will optimize for server-workloads. Also enable slowdown and full stop triggers for pending compaction bytes. These changes may cause sub-optimal performance or significant increase of resource usage. To avoid these risks, users can open existing RocksDB with options extracted from RocksDB option files. See https://github.com/meta/rocksdb/wiki/RocksDB-Options-File for how to use RocksDB option files. Or you can call Options.OldDefaults() to recover old defaults. DEFAULT_OPTIONS_HISTORY.md will track change history of default options.
* Change some default options. Now default options will optimize for server-workloads. Also enable slowdown and full stop triggers for pending compaction bytes. These changes may cause sub-optimal performance or significant increase of resource usage. To avoid these risks, users can open existing RocksDB with options extracted from RocksDB option files. See https://github.com/facebook/rocksdb/wiki/RocksDB-Options-File for how to use RocksDB option files. Or you can call Options.OldDefaults() to recover old defaults. DEFAULT_OPTIONS_HISTORY.md will track change history of default options.
## 4.6.0 (2016-03-10)
### Public API Changes
@@ -3485,7 +3289,7 @@ Because of that, Options::disable_seek_compaction is now obsolete. It is still a
### Public API changes
* Added _LEVEL to all InfoLogLevel enums
* Deprecated ReadOptions.prefix and ReadOptions.prefix_seek. Seek() defaults to prefix-based seek when Options.prefix_extractor is supplied. More detail is documented in https://github.com/meta/rocksdb/wiki/Prefix-Seek-API-Changes
* Deprecated ReadOptions.prefix and ReadOptions.prefix_seek. Seek() defaults to prefix-based seek when Options.prefix_extractor is supplied. More detail is documented in https://github.com/facebook/rocksdb/wiki/Prefix-Seek-API-Changes
* MemTableRepFactory::CreateMemTableRep() takes info logger as an extra parameter.
### New Features
@@ -3536,8 +3340,8 @@ Because of that, Options::disable_seek_compaction is now obsolete. It is still a
### New Features
* Added [BackupableDB](https://github.com/meta/rocksdb/wiki/How-to-backup-RocksDB%3F)
* Implemented [TailingIterator](https://github.com/meta/rocksdb/wiki/Tailing-Iterator), a special type of iterator that
* Added [BackupableDB](https://github.com/facebook/rocksdb/wiki/How-to-backup-RocksDB%3F)
* Implemented [TailingIterator](https://github.com/facebook/rocksdb/wiki/Tailing-Iterator), a special type of iterator that
doesn't create a snapshot (can be used to read newly inserted data)
and is optimized for doing sequential reads.
* Added property block for table, which allows (1) a table to store
@@ -3554,8 +3358,8 @@ Because of that, Options::disable_seek_compaction is now obsolete. It is still a
* Huge benchmark performance improvements by multiple efforts. For example, increase in readonly QPS from about 530k in 2.6 release to 1.1 million in 2.7 [1]
* Speeding up a way RocksDB deleted obsolete files - no longer listing the whole directory under a lock -- decrease in p99
* Use raw pointer instead of shared pointer for statistics: [5b825d](https://github.com/meta/rocksdb/commit/5b825d6964e26ec3b4bb6faa708ebb1787f1d7bd) -- huge increase in performance -- shared pointers are slow
* Optimized locking for `Get()` -- [1fdb3f](https://github.com/meta/rocksdb/commit/1fdb3f7dc60e96394e3e5b69a46ede5d67fb976c) -- 1.5x QPS increase for some workloads
* Cache speedup - [e8d40c3](https://github.com/meta/rocksdb/commit/e8d40c31b3cca0c3e1ae9abe9b9003b1288026a9)
* Implemented autovector, which allocates first N elements on stack. Most of vectors in RocksDB are small. Also, we never want to allocate heap objects while holding a mutex. -- [c01676e4](https://github.com/meta/rocksdb/commit/c01676e46d3be08c3c140361ef1f5884f47d3b3c)
* Use raw pointer instead of shared pointer for statistics: [5b825d](https://github.com/facebook/rocksdb/commit/5b825d6964e26ec3b4bb6faa708ebb1787f1d7bd) -- huge increase in performance -- shared pointers are slow
* Optimized locking for `Get()` -- [1fdb3f](https://github.com/facebook/rocksdb/commit/1fdb3f7dc60e96394e3e5b69a46ede5d67fb976c) -- 1.5x QPS increase for some workloads
* Cache speedup - [e8d40c3](https://github.com/facebook/rocksdb/commit/e8d40c31b3cca0c3e1ae9abe9b9003b1288026a9)
* Implemented autovector, which allocates first N elements on stack. Most of vectors in RocksDB are small. Also, we never want to allocate heap objects while holding a mutex. -- [c01676e4](https://github.com/facebook/rocksdb/commit/c01676e46d3be08c3c140361ef1f5884f47d3b3c)
* Lots of efforts to move malloc, memcpy and IO outside of locks
+13 -2
View File
@@ -153,7 +153,7 @@ most processors made since roughly 2013.
* Build RocksDB from source:
cd ~
git clone https://github.com/meta/rocksdb.git
git clone https://github.com/facebook/rocksdb.git
cd rocksdb
gmake static_lib
@@ -174,7 +174,7 @@ most processors made since roughly 2013.
```bash
cd ~
git clone https://github.com/meta/rocksdb.git
git clone https://github.com/facebook/rocksdb.git
cd rocksdb
gmake static_lib
```
@@ -211,3 +211,14 @@ most processors made since roughly 2013.
export LIBPATH=/opt/freeware/lib
export JAVA_HOME=/usr/java8_64
export PATH=/opt/freeware/bin:$PATH
* **Solaris Sparc**
* Install GCC 11 and higher.
* Use these environment variables:
export CC=gcc
export EXTRA_CFLAGS=-m64
export EXTRA_CXXFLAGS=-m64
export EXTRA_LDFLAGS=-m64
export PORTABLE=1
export PLATFORM_LDFLAGS="-static-libstdc++ -static-libgcc"
+1 -1
View File
@@ -1,6 +1,6 @@
This is the list of all known third-party language bindings for RocksDB. If something is missing, please open a pull request to add it.
* Java - https://github.com/meta/rocksdb/tree/main/java
* Java - https://github.com/facebook/rocksdb/tree/main/java
* Python
* https://github.com/rocksdict/RocksDict
* http://python-rocksdb.readthedocs.io/en/latest/ (unmaintained)
+13 -15
View File
@@ -355,6 +355,10 @@ ARFLAGS = -X64 rs
STRIPFLAGS = -X64 -x
endif
ifeq ($(PLATFORM), OS_SOLARIS)
PLATFORM_CXXFLAGS += -D _GLIBCXX_USE_C99
endif
ifeq ($(LIB_MODE),shared)
# So that binaries are executable from build location, in addition to install location
EXEC_LDFLAGS += -Wl,-rpath -Wl,'$$ORIGIN'
@@ -587,7 +591,6 @@ ifneq ($(filter check-headers, $(MAKECMDGOALS)),)
DEV_HEADER_DIRS := $(sort include/ $(dir $(ALL_SOURCES)))
FOLLY_DEPENDENT_HEADERS := \
include/rocksdb/coro_db.h \
include/rocksdb/coro_external_table.h \
include/rocksdb/utilities/coro_stackable_db.h
# Some headers like in port/ are platform-specific
DEV_HEADERS_TO_CHECK := $(filter-out $(FOLLY_DEPENDENT_HEADERS), $(shell $(FIND) $(DEV_HEADER_DIRS) -type f -name '*.h' | grep -E -v 'port/|plugin/|range_tree/|secondary_index/'))
@@ -660,7 +663,6 @@ TESTS_PLATFORM_DEPENDENT := \
dynamic_bloom_test \
c_test \
checkpoint_test \
live_tuner_test \
sorted_run_builder_test \
crc32c_test \
coding_test \
@@ -1893,12 +1895,6 @@ backup_engine_test: $(OBJ_DIR)/utilities/backup/backup_engine_test.o $(TEST_LIBR
checkpoint_test: $(OBJ_DIR)/utilities/checkpoint/checkpoint_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
live_tuner_test: $(OBJ_DIR)/utilities/live_tune/live_tuner_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
db_split_merge_test: $(OBJ_DIR)/utilities/db_split_merge_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
copy_engine_test: $(OBJ_DIR)/utilities/copy_engine/copy_engine_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
@@ -2022,9 +2018,6 @@ block_fetcher_test: table/block_fetcher_test.o $(TEST_LIBRARY) $(LIBRARY)
block_test: $(OBJ_DIR)/table/block_based/block_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
builtin_index_factory_test: $(OBJ_DIR)/table/block_based/builtin_index_factory_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
data_block_hash_index_test: $(OBJ_DIR)/table/block_based/data_block_hash_index_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
@@ -2211,9 +2204,6 @@ ldb_cmd_test: $(OBJ_DIR)/tools/ldb_cmd_test.o $(TOOLS_LIBRARY) $(TEST_LIBRARY) $
ldb: $(OBJ_DIR)/tools/ldb.o $(TOOLS_LIBRARY) $(LIBRARY)
$(AM_LINK)
db_split_merge: $(OBJ_DIR)/tools/db_split_merge.o $(LIBRARY)
$(AM_LINK)
iostats_context_test: $(OBJ_DIR)/monitoring/iostats_context_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_V_CCLD)$(CXX) $^ $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
@@ -2413,7 +2403,9 @@ gen-pc:
# Jni stuff
# ---------------------------------------------------------------------------
JAVA_INCLUDE = -I$(JAVA_HOME)/include/ -I$(JAVA_HOME)/include/linux
ifeq ($(PLATFORM), OS_OPENBSD)
ifeq ($(PLATFORM), OS_SOLARIS)
ARCH := $(shell isainfo -b)
else ifeq ($(PLATFORM), OS_OPENBSD)
ifneq (,$(filter amd64 ppc64 ppc64le s390x arm64 aarch64 riscv64 sparc64 loongarch64, $(MACHINE)))
ARCH := 64
else
@@ -2494,6 +2486,12 @@ ifeq ($(PLATFORM), OS_FREEBSD)
ROCKSDBJNILIB = librocksdbjni-freebsd$(ARCH).so
ROCKSDB_JAR = rocksdbjni-$(ROCKSDB_JAVA_VERSION)-freebsd$(ARCH).jar
endif
ifeq ($(PLATFORM), OS_SOLARIS)
ROCKSDBJNILIB = librocksdbjni-solaris$(ARCH).so
ROCKSDB_JAR = rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-solaris$(ARCH).jar
JAVA_INCLUDE = -I$(JAVA_HOME)/include/ -I$(JAVA_HOME)/include/solaris
SHA256_CMD = digest -a sha256
endif
ifeq ($(PLATFORM), OS_AIX)
JAVA_INCLUDE = -I$(JAVA_HOME)/include/ -I$(JAVA_HOME)/include/aix
ROCKSDBJNILIB = librocksdbjni-aix.so
+4 -2
View File
@@ -1,5 +1,7 @@
## RocksDB: A Persistent Key-Value Store for Flash and RAM Storage
[![CircleCI Status](https://circleci.com/gh/facebook/rocksdb.svg?style=svg)](https://circleci.com/gh/facebook/rocksdb)
RocksDB is developed and maintained by Facebook Database Engineering Team.
It is built on earlier work on [LevelDB](https://github.com/google/leveldb) by Sanjay Ghemawat (sanjay@google.com)
and Jeff Dean (jeff@google.com)
@@ -12,9 +14,9 @@ and Space-Amplification-Factor (SAF). It has multi-threaded compactions,
making it especially suitable for storing multiple terabytes of data in a
single database.
Start with example usage here: https://github.com/meta/rocksdb/tree/main/examples
Start with example usage here: https://github.com/facebook/rocksdb/tree/main/examples
See the [github wiki](https://github.com/meta/rocksdb/wiki) for more explanation.
See the [github wiki](https://github.com/facebook/rocksdb/wiki) for more explanation.
The public interface is in `include/`. Callers should not include or
rely on the details of any other header files in this package. Those
+27 -96
View File
@@ -35,49 +35,6 @@ from util import ColorString
# tests to export as libraries for inclusion in other projects
_EXPORTED_TEST_LIBS = ["env_basic_test"]
_TEST_LIB_HEADERS = [
"db/db_test_util.h",
"db/db_with_timestamp_test_util.h",
"table/mock_table.h",
"test_util/mock_time_env.h",
"test_util/secondary_cache_test_util.h",
"test_util/simple_external_table_factory.h",
"test_util/testharness.h",
"test_util/testutil.h",
"tools/block_cache_analyzer/block_cache_trace_analyzer.h",
"tools/trace_analyzer_tool.h",
"utilities/agg_merge/test_agg_merge.h",
"utilities/cassandra/test_utils.h",
]
_TEST_HEADERS = {
"configurable_test": ["options/configurable_test.h"],
"persistent_cache_test": ["utilities/persistent_cache/persistent_cache_test.h"],
"point_lock_manager_test": [
"utilities/transactions/lock/point/point_lock_manager_test.h",
],
"transaction_test": ["utilities/transactions/transaction_test.h"],
}
_STRESS_LIB_HEADERS = [
"db_stress_tool/db_stress_common.h",
"db_stress_tool/db_stress_compaction_service.h",
"db_stress_tool/db_stress_compression_manager.h",
"db_stress_tool/db_stress_driver.h",
"db_stress_tool/db_stress_filters.h",
"db_stress_tool/db_stress_listener.h",
"db_stress_tool/db_stress_shared_state.h",
"db_stress_tool/db_stress_test_base.h",
"db_stress_tool/db_stress_wide_merge_operator.h",
"db_stress_tool/expected_state.h",
"db_stress_tool/expected_value.h",
"db_stress_tool/multi_ops_txns_stress.h",
"test_util/simple_external_table_factory.h",
"test_util/testutil.h",
"tools/block_cache_analyzer/block_cache_trace_analyzer.h",
"tools/trace_analyzer_tool.h"
]
# Parse src.mk files as a Dictionary of
# VAR_NAME => list of files
def parse_src_mk(repo_path):
@@ -188,22 +145,22 @@ def generate_buck(repo_path, deps_map):
# always add range_tree, it's only excluded on ppc64, which we don't use internally
src_mk["RANGE_TREE_SOURCES"] + src_mk["TOOL_LIB_SOURCES"],
deps=[
"fbsource//xplat/folly:executor",
"fbsource//xplat/folly/container:f14_hash",
"fbsource//xplat/folly/coro:baton",
"fbsource//xplat/folly/coro:blocking_wait",
"fbsource//xplat/folly/coro:collect",
"fbsource//xplat/folly/coro:coroutine",
"fbsource//xplat/folly/coro:nothrow",
"fbsource//xplat/folly/coro:task",
"fbsource//xplat/folly/executors:io_executor",
"fbsource//xplat/folly/executors:io_thread_pool_executor",
"fbsource//xplat/folly/executors/thread_factory:named_thread_factory",
"fbsource//xplat/folly/io/async:async_base",
"fbsource//xplat/folly/io/async:event_base_manager",
"fbsource//xplat/folly/io/async:io_uring_backend",
"fbsource//xplat/folly/io/async:request_context",
"fbsource//xplat/folly/synchronization:distributed_mutex",
"//folly:executor",
"//folly/container:f14_hash",
"//folly/coro:baton",
"//folly/coro:blocking_wait",
"//folly/coro:collect",
"//folly/coro:coroutine",
"//folly/coro:nothrow",
"//folly/coro:task",
"//folly/executors:io_executor",
"//folly/executors:io_thread_pool_executor",
"//folly/executors/thread_factory:named_thread_factory",
"//folly/io/async:async_base",
"//folly/io/async:event_base_manager",
"//folly/io/async:io_uring_backend",
"//folly/io/async:request_context",
"//folly/synchronization:distributed_mutex",
],
headers=LiteralValue("glob([\"**/*.h\"])")
)
@@ -226,34 +183,27 @@ def generate_buck(repo_path, deps_map):
":rocksdb_lib",
],
)
test_lib_sources = (
src_mk.get("MOCK_LIB_SOURCES", [])
+ src_mk.get("TEST_LIB_SOURCES", [])
+ src_mk.get("EXP_LIB_SOURCES", [])
+ src_mk.get("ANALYZER_LIB_SOURCES", [])
)
# Older release source lists may not compile every test helper yet.
test_lib_headers = [
header for header in _TEST_LIB_HEADERS
if header[:-2] + ".cc" in test_lib_sources
]
# rocksdb_test_lib
BUCK.add_library(
"rocksdb_test_lib",
test_lib_sources,
src_mk.get("MOCK_LIB_SOURCES", [])
+ src_mk.get("TEST_LIB_SOURCES", [])
+ src_mk.get("EXP_LIB_SOURCES", [])
+ src_mk.get("ANALYZER_LIB_SOURCES", []),
[":rocksdb_lib"],
extra_test_libs=True,
headers=test_lib_headers,
)
# rocksdb_with_faiss_test_lib
BUCK.add_library(
"rocksdb_with_faiss_test_lib",
test_lib_sources,
src_mk.get("MOCK_LIB_SOURCES", [])
+ src_mk.get("TEST_LIB_SOURCES", [])
+ src_mk.get("EXP_LIB_SOURCES", [])
+ src_mk.get("ANALYZER_LIB_SOURCES", []),
deps=[
":rocksdb_with_faiss_lib",
],
extra_test_libs=True,
headers=test_lib_headers,
)
# rocksdb_tools_lib
BUCK.add_library(
@@ -262,12 +212,6 @@ def generate_buck(repo_path, deps_map):
+ src_mk.get("ANALYZER_LIB_SOURCES", [])
+ ["test_util/testutil.cc"],
[":rocksdb_lib"],
headers=[
"test_util/testutil.h",
"tools/block_cache_analyzer/block_cache_trace_analyzer.h",
"tools/simulated_hybrid_file_system.h",
"tools/trace_analyzer_tool.h",
],
)
# rocksdb_cache_bench_tools_lib
BUCK.add_library(
@@ -281,27 +225,17 @@ def generate_buck(repo_path, deps_map):
src_mk.get("POINT_LOCK_BENCH_LIB_SOURCES", []),
[":rocksdb_lib"],
)
stress_sources = (
src_mk.get("ANALYZER_LIB_SOURCES", [])
+ src_mk.get("STRESS_LIB_SOURCES", [])
+ ["test_util/testutil.cc"]
)
# rocksdb_stress_lib
BUCK.add_rocksdb_library(
"rocksdb_stress_lib",
stress_sources,
headers=[
header for header in _STRESS_LIB_HEADERS
if header[:-2] + ".cc" in stress_sources
],
src_mk.get("ANALYZER_LIB_SOURCES", [])
+ src_mk.get("STRESS_LIB_SOURCES", [])
+ ["test_util/testutil.cc"],
)
# ldb binary
BUCK.add_binary(
"ldb", ["tools/ldb.cc"], [":rocksdb_tools_lib"]
)
BUCK.add_binary(
"db_split_merge", ["tools/db_split_merge.cc"], [":rocksdb_lib"]
)
# db_stress binary
BUCK.add_binary(
"db_stress", ["db_stress_tool/db_stress.cc"], [":rocksdb_stress_lib"]
@@ -419,7 +353,6 @@ def generate_buck(repo_path, deps_map):
test_src,
deps=json.dumps(deps["extra_deps"] + [":" + test_library]),
extra_compiler_flags=json.dumps(deps["extra_compiler_flags"]),
source_headers=_TEST_HEADERS.get(test, []),
)
else:
if with_faiss:
@@ -428,7 +361,6 @@ def generate_buck(repo_path, deps_map):
test_src,
deps=json.dumps(deps["extra_deps"] + [":rocksdb_with_faiss_test_lib"]),
extra_compiler_flags=json.dumps(deps["extra_compiler_flags"]),
source_headers=_TEST_HEADERS.get(test, []),
)
else:
BUCK.register_test(
@@ -436,7 +368,6 @@ def generate_buck(repo_path, deps_map):
test_src,
deps=json.dumps(deps["extra_deps"] + [":rocksdb_test_lib"]),
extra_compiler_flags=json.dumps(deps["extra_compiler_flags"]),
source_headers=_TEST_HEADERS.get(test, []),
)
BUCK.export_file("tools/db_crashtest.py")
BUCK.export_file("tools/fault_injection_log_parser.py")
+1 -6
View File
@@ -162,17 +162,12 @@ add_c_test_wrapper()
).encode("utf-8")
)
def register_test(self, test_name, src, deps, extra_compiler_flags, source_headers):
def register_test(self, test_name, src, deps, extra_compiler_flags):
with open(self.path, "ab") as targets_file:
targets_file.write(
targets_cfg.unittests_template.format(
test_name=test_name,
test_cc=str(src),
source_headers=(
f" source_headers=[{pretty_list(source_headers, 16)}],\n"
if source_headers
else ""
),
deps=deps,
extra_compiler_flags=extra_compiler_flags,
).encode("utf-8")
+1 -1
View File
@@ -30,7 +30,7 @@ cpp_binary_wrapper(name="{name}", srcs=[{srcs}], deps=[{deps}], extra_preprocess
unittests_template = """
cpp_unittest_wrapper(name="{test_name}",
srcs=["{test_cc}"],
{source_headers} deps={deps},
deps={deps},
extra_compiler_flags={extra_compiler_flags})
"""
+6
View File
@@ -230,6 +230,12 @@ case "$TARGET_OS" in
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lpthread -lrt -ldl"
# PORT_FILES=port/linux/linux_specific.cc
;;
SunOS)
PLATFORM=OS_SOLARIS
COMMON_FLAGS="$COMMON_FLAGS -fno-builtin-memcmp -D_REENTRANT -DOS_SOLARIS -m64"
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lpthread -lrt -static-libstdc++ -static-libgcc -m64"
# PORT_FILES=port/sunos/sunos_specific.cc
;;
AIX)
PLATFORM=OS_AIX
CC=gcc
+3 -4
View File
@@ -129,13 +129,12 @@ uncommitted_code=`git diff HEAD`
# If there's no uncommitted changes, we assume user are doing post-commit
# format check, in which case we'll try to check the modified lines vs. the
# meta/rocksdb.git main branch. Otherwise, we'll check format of the
# facebook/rocksdb.git main branch. Otherwise, we'll check format of the
# uncommitted code only.
if [ -z "$uncommitted_code" ]
then
# Attempt to get name of meta/rocksdb.git remote. Older clones might still
# use the pre-transfer facebook/rocksdb.git URL, which GitHub redirects.
[ "$FORMAT_REMOTE" ] || FORMAT_REMOTE="$(LC_ALL=POSIX LANG=POSIX git remote -v | grep -E '[:/](meta|facebook)/rocksdb\.git' | head -n 1 | cut -f 1)"
# Attempt to get name of facebook/rocksdb.git remote.
[ "$FORMAT_REMOTE" ] || FORMAT_REMOTE="$(LC_ALL=POSIX LANG=POSIX git remote -v | grep 'facebook/rocksdb.git' | head -n 1 | cut -f 1)"
# Fall back on 'origin' if that fails
[ "$FORMAT_REMOTE" ] || FORMAT_REMOTE=origin
# Use main branch from that remote
+1 -1
View File
@@ -23,7 +23,7 @@ mkdir -pv /usr/local/rocksdb-${ROCKSDB_VERSION}
ln -sfT /usr/local/rocksdb-${ROCKSDB_VERSION} /usr/local/rocksdb
wget -qO /tmp/zstd-${ZSTD_VERSION}.tar.gz https://github.com/facebook/zstd/archive/v${ZSTD_VERSION}.tar.gz
wget -qO /tmp/rocksdb-${ROCKSDB_VERSION}.tar.gz https://github.com/meta/rocksdb/archive/v${ROCKSDB_VERSION}.tar.gz
wget -qO /tmp/rocksdb-${ROCKSDB_VERSION}.tar.gz https://github.com/facebook/rocksdb/archive/v${ROCKSDB_VERSION}.tar.gz
cd /tmp
+2 -2
View File
@@ -17,8 +17,8 @@
# Then in the build_tools/ubuntu22_image directory, (bump minor version for
# random docker file updates, major version tracks Ubuntu release)
#
# $ docker build -t ghcr.io/meta/rocksdb_ubuntu:22.2
# $ docker push ghcr.io/meta/rocksdb_ubuntu:22.2
# $ docker build -t ghcr.io/facebook/rocksdb_ubuntu:22.2
# $ docker push ghcr.io/facebook/rocksdb_ubuntu:22.2
#
# Might need to change visibility to public through
# https://github.com/orgs/facebook/packages/container/rocksdb_ubuntu/settings
+2 -2
View File
@@ -17,8 +17,8 @@
# Then in the build_tools/ubuntu24_image directory, (bump minor version for
# random docker file updates, major version tracks Ubuntu release)
#
# $ docker build -t ghcr.io/meta/rocksdb_ubuntu:24.1
# $ docker push ghcr.io/meta/rocksdb_ubuntu:24.1
# $ docker build -t ghcr.io/facebook/rocksdb_ubuntu:24.1
# $ docker push ghcr.io/facebook/rocksdb_ubuntu:24.1
#
# Might need to change visibility to public through
# https://github.com/orgs/facebook/packages/container/rocksdb_ubuntu/settings
-12
View File
@@ -109,12 +109,6 @@ int rocksdb_compactionjobinfo_job_id(const rocksdb_compactionjobinfo_t* info) {
return info->rep.job_id;
}
unsigned char
rocksdb_compactionjobinfo_separate_remote_compaction_pool_selected(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.separate_remote_compaction_pool_selected;
}
int rocksdb_compactionjobinfo_num_l0_files(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.num_l0_files;
@@ -178,12 +172,6 @@ int rocksdb_subcompactionjobinfo_job_id(
return info->rep.job_id;
}
unsigned char
rocksdb_subcompactionjobinfo_separate_remote_compaction_pool_selected(
const rocksdb_subcompactionjobinfo_t* info) {
return info->rep.separate_remote_compaction_pool_selected;
}
int rocksdb_subcompactionjobinfo_subcompaction_job_id(
const rocksdb_subcompactionjobinfo_t* info) {
return info->rep.subcompaction_job_id;
-8
View File
@@ -78,10 +78,6 @@ rocksdb_compactionjobinfo_thread_id(const rocksdb_compactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API int rocksdb_compactionjobinfo_job_id(
const rocksdb_compactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_compactionjobinfo_separate_remote_compaction_pool_selected(
const rocksdb_compactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API int rocksdb_compactionjobinfo_num_l0_files(
const rocksdb_compactionjobinfo_t* info);
@@ -121,10 +117,6 @@ extern ROCKSDB_LIBRARY_API uint64_t rocksdb_subcompactionjobinfo_thread_id(
extern ROCKSDB_LIBRARY_API int rocksdb_subcompactionjobinfo_job_id(
const rocksdb_subcompactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_subcompactionjobinfo_separate_remote_compaction_pool_selected(
const rocksdb_subcompactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API int
rocksdb_subcompactionjobinfo_subcompaction_job_id(
const rocksdb_subcompactionjobinfo_t* info);
@@ -67,11 +67,6 @@ uint64_t rocksdb_table_properties_udi_is_primary_index(
return props->rep.udi_is_primary_index;
}
uint64_t rocksdb_table_properties_standard_index_is_stub(
const rocksdb_table_properties_t* props) {
return props->rep.standard_index_is_stub;
}
uint64_t rocksdb_table_properties_filter_size(
const rocksdb_table_properties_t* props) {
return props->rep.filter_size;
@@ -147,11 +142,6 @@ uint64_t rocksdb_table_properties_column_family_id(
return props->rep.column_family_id;
}
uint64_t rocksdb_table_properties_oldest_ancestor_time(
const rocksdb_table_properties_t* props) {
return props->rep.oldest_ancestor_time;
}
uint64_t rocksdb_table_properties_creation_time(
const rocksdb_table_properties_t* props) {
return props->rep.creation_time;
@@ -182,6 +172,11 @@ uint64_t rocksdb_table_properties_fast_compression_estimated_data_size(
return props->rep.fast_compression_estimated_data_size;
}
uint64_t rocksdb_table_properties_external_sst_file_global_seqno_offset(
const rocksdb_table_properties_t* props) {
return props->rep.external_sst_file_global_seqno_offset;
}
uint64_t rocksdb_table_properties_tail_start_offset(
const rocksdb_table_properties_t* props) {
return props->rep.tail_start_offset;
@@ -217,11 +212,6 @@ uint64_t rocksdb_table_properties_separate_key_value_in_data_block(
return props->rep.separate_key_value_in_data_block;
}
uint64_t rocksdb_table_properties_lsm_info_at_creation(
const rocksdb_table_properties_t* props) {
return props->rep.lsm_info_at_creation;
}
const char* rocksdb_table_properties_db_id(
const rocksdb_table_properties_t* props, size_t* size) {
*size = props->rep.db_id.size();
@@ -54,10 +54,6 @@ extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_udi_is_primary_index(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_standard_index_is_stub(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_filter_size(const rocksdb_table_properties_t* props);
@@ -106,10 +102,6 @@ rocksdb_table_properties_fixed_key_len(const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_table_properties_column_family_id(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_oldest_ancestor_time(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_creation_time(const rocksdb_table_properties_t* props);
@@ -130,6 +122,10 @@ extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_fast_compression_estimated_data_size(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_external_sst_file_global_seqno_offset(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_table_properties_tail_start_offset(
const rocksdb_table_properties_t* props);
@@ -155,10 +151,6 @@ extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_separate_key_value_in_data_block(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_lsm_info_at_creation(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API const char* rocksdb_table_properties_db_id(
const rocksdb_table_properties_t* props, size_t* size);
+30 -111
View File
@@ -741,16 +741,6 @@ unsigned char rocksdb_options_get_write_dbid_to_manifest(
return opt->rep.write_dbid_to_manifest;
}
void rocksdb_options_set_track_options_file_number_in_manifest(
rocksdb_options_t* opt, unsigned char v) {
opt->rep.track_options_file_number_in_manifest = v;
}
unsigned char rocksdb_options_get_track_options_file_number_in_manifest(
rocksdb_options_t* opt) {
return opt->rep.track_options_file_number_in_manifest;
}
void rocksdb_options_set_write_identity_file(rocksdb_options_t* opt,
unsigned char v) {
opt->rep.write_identity_file = v;
@@ -826,36 +816,6 @@ const char* rocksdb_options_get_db_host_id(rocksdb_options_t* opt,
return opt->rep.db_host_id.data();
}
void rocksdb_options_set_use_separate_remote_compaction_pool(
rocksdb_options_t* opt, unsigned char v) {
opt->rep.use_separate_remote_compaction_pool = v;
}
unsigned char rocksdb_options_get_use_separate_remote_compaction_pool(
rocksdb_options_t* opt) {
return opt->rep.use_separate_remote_compaction_pool;
}
void rocksdb_options_set_remote_compaction_manifest_floor(
rocksdb_options_t* opt, unsigned char v) {
opt->rep.remote_compaction_manifest_floor = v;
}
unsigned char rocksdb_options_get_remote_compaction_manifest_floor(
rocksdb_options_t* opt) {
return opt->rep.remote_compaction_manifest_floor;
}
void rocksdb_options_set_use_session_tmp_dir_for_remote_compaction(
rocksdb_options_t* opt, unsigned char v) {
opt->rep.use_session_tmp_dir_for_remote_compaction = v;
}
unsigned char rocksdb_options_get_use_session_tmp_dir_for_remote_compaction(
rocksdb_options_t* opt) {
return opt->rep.use_session_tmp_dir_for_remote_compaction;
}
void rocksdb_options_set_lowest_used_cache_tier(rocksdb_options_t* opt, int v) {
opt->rep.lowest_used_cache_tier =
static_cast<decltype(opt->rep.lowest_used_cache_tier)>(v);
@@ -896,16 +856,6 @@ uint64_t rocksdb_options_get_max_compaction_trigger_wakeup_seconds(
return opt->rep.max_compaction_trigger_wakeup_seconds;
}
void rocksdb_options_set_periodic_compaction_phase_recovery_percent(
rocksdb_options_t* opt, int v) {
opt->rep.periodic_compaction_phase_recovery_percent = v;
}
int rocksdb_options_get_periodic_compaction_phase_recovery_percent(
rocksdb_options_t* opt) {
return opt->rep.periodic_compaction_phase_recovery_percent;
}
void rocksdb_options_set_follower_refresh_catchup_period_ms(
rocksdb_options_t* opt, uint64_t v) {
opt->rep.follower_refresh_catchup_period_ms = v;
@@ -1929,16 +1879,6 @@ unsigned char rocksdb_trace_options_get_preserve_write_order(
return opt->rep.preserve_write_order;
}
void rocksdb_trace_options_set_include_read_write_options(
rocksdb_trace_options_t* opt, unsigned char v) {
opt->rep.include_read_write_options = v;
}
unsigned char rocksdb_trace_options_get_include_read_write_options(
rocksdb_trace_options_t* opt) {
return opt->rep.include_read_write_options;
}
/* BlockCacheTraceOptions */
void rocksdb_block_cache_trace_options_set_sampling_frequency(
@@ -2018,16 +1958,6 @@ unsigned char rocksdb_compaction_options_get_allow_trivial_move(
/* CompactRangeOptions */
void rocksdb_compactoptions_set_skip_missing_blob_files(
rocksdb_compactoptions_t* opt, unsigned char v) {
opt->rep.skip_missing_blob_files = v;
}
unsigned char rocksdb_compactoptions_get_skip_missing_blob_files(
rocksdb_compactoptions_t* opt) {
return opt->rep.skip_missing_blob_files;
}
void rocksdb_compactoptions_set_blob_garbage_collection_policy(
rocksdb_compactoptions_t* opt, int v) {
opt->rep.blob_garbage_collection_policy =
@@ -2097,6 +2027,16 @@ unsigned char rocksdb_ingestexternalfileoptions_get_ingest_behind(
return opt->rep.ingest_behind;
}
void rocksdb_ingestexternalfileoptions_set_write_global_seqno(
rocksdb_ingestexternalfileoptions_t* opt, unsigned char v) {
opt->rep.write_global_seqno = v;
}
unsigned char rocksdb_ingestexternalfileoptions_get_write_global_seqno(
rocksdb_ingestexternalfileoptions_t* opt) {
return opt->rep.write_global_seqno;
}
void rocksdb_ingestexternalfileoptions_set_verify_checksums_before_ingest(
rocksdb_ingestexternalfileoptions_t* opt, unsigned char v) {
opt->rep.verify_checksums_before_ingest = v;
@@ -2175,28 +2115,8 @@ int rocksdb_ingestexternalfileoptions_get_file_opening_threads(
return opt->rep.file_opening_threads;
}
void rocksdb_ingestexternalfileoptions_set_reject_too_old_file(
rocksdb_ingestexternalfileoptions_t* opt, unsigned char v) {
opt->rep.reject_too_old_file = v;
}
unsigned char rocksdb_ingestexternalfileoptions_get_reject_too_old_file(
rocksdb_ingestexternalfileoptions_t* opt) {
return opt->rep.reject_too_old_file;
}
/* OpenAndCompactOptions */
void rocksdb_open_and_compact_options_set_max_secondary_open_retries(
rocksdb_open_and_compact_options_t* opt, uint32_t v) {
opt->rep.max_secondary_open_retries = v;
}
uint32_t rocksdb_open_and_compact_options_get_max_secondary_open_retries(
rocksdb_open_and_compact_options_t* opt) {
return opt->rep.max_secondary_open_retries;
}
unsigned char rocksdb_open_and_compact_options_get_allow_resumption(
rocksdb_open_and_compact_options_t* opt) {
return opt->rep.allow_resumption;
@@ -2209,16 +2129,6 @@ unsigned char rocksdb_import_column_family_options_get_move_files(
return opt->rep.move_files;
}
void rocksdb_import_column_family_options_set_reject_too_old_file(
rocksdb_import_column_family_options_t* opt, unsigned char v) {
opt->rep.reject_too_old_file = v;
}
unsigned char rocksdb_import_column_family_options_get_reject_too_old_file(
rocksdb_import_column_family_options_t* opt) {
return opt->rep.reject_too_old_file;
}
/* SizeApproximationOptions */
void rocksdb_size_approximation_options_set_include_memtables(
@@ -2513,6 +2423,26 @@ unsigned char rocksdb_block_based_options_get_use_delta_encoding(
return opt->rep.use_delta_encoding;
}
void rocksdb_block_based_options_set_use_udi_as_primary_index(
rocksdb_block_based_table_options_t* opt, unsigned char v) {
opt->rep.use_udi_as_primary_index = v;
}
unsigned char rocksdb_block_based_options_get_use_udi_as_primary_index(
rocksdb_block_based_table_options_t* opt) {
return opt->rep.use_udi_as_primary_index;
}
void rocksdb_block_based_options_set_fail_if_no_udi_on_open(
rocksdb_block_based_table_options_t* opt, unsigned char v) {
opt->rep.fail_if_no_udi_on_open = v;
}
unsigned char rocksdb_block_based_options_get_fail_if_no_udi_on_open(
rocksdb_block_based_table_options_t* opt) {
return opt->rep.fail_if_no_udi_on_open;
}
void rocksdb_block_based_options_set_whole_key_filtering(
rocksdb_block_based_table_options_t* opt, unsigned char v) {
opt->rep.whole_key_filtering = v;
@@ -2584,17 +2514,6 @@ double rocksdb_block_based_options_get_uniform_cv_threshold(
return opt->rep.uniform_cv_threshold;
}
void rocksdb_block_based_options_set_optimize_key_common_prefix(
rocksdb_block_based_table_options_t* opt, int v) {
opt->rep.optimize_key_common_prefix =
static_cast<decltype(opt->rep.optimize_key_common_prefix)>(v);
}
int rocksdb_block_based_options_get_optimize_key_common_prefix(
rocksdb_block_based_table_options_t* opt) {
return static_cast<int>(opt->rep.optimize_key_common_prefix);
}
void rocksdb_block_based_options_set_enable_index_compression(
rocksdb_block_based_table_options_t* opt, unsigned char v) {
opt->rep.enable_index_compression = v;
+24 -86
View File
@@ -204,14 +204,6 @@ rocksdb_options_set_background_close_inactive_wals(rocksdb_options_t* opt,
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_options_get_background_close_inactive_wals(rocksdb_options_t* opt);
extern ROCKSDB_LIBRARY_API void
rocksdb_options_set_track_options_file_number_in_manifest(
rocksdb_options_t* opt, unsigned char v);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_options_get_track_options_file_number_in_manifest(
rocksdb_options_t* opt);
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_prefix_seek_opt_in_only(
rocksdb_options_t* opt, unsigned char v);
@@ -255,28 +247,6 @@ extern ROCKSDB_LIBRARY_API void rocksdb_options_set_db_host_id(
extern ROCKSDB_LIBRARY_API const char* rocksdb_options_get_db_host_id(
rocksdb_options_t* opt, size_t* size);
extern ROCKSDB_LIBRARY_API void
rocksdb_options_set_use_separate_remote_compaction_pool(rocksdb_options_t* opt,
unsigned char v);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_options_get_use_separate_remote_compaction_pool(rocksdb_options_t* opt);
extern ROCKSDB_LIBRARY_API void
rocksdb_options_set_remote_compaction_manifest_floor(rocksdb_options_t* opt,
unsigned char v);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_options_get_remote_compaction_manifest_floor(rocksdb_options_t* opt);
extern ROCKSDB_LIBRARY_API void
rocksdb_options_set_use_session_tmp_dir_for_remote_compaction(
rocksdb_options_t* opt, unsigned char v);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_options_get_use_session_tmp_dir_for_remote_compaction(
rocksdb_options_t* opt);
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_lowest_used_cache_tier(
rocksdb_options_t* opt, int v);
@@ -297,14 +267,6 @@ extern ROCKSDB_LIBRARY_API const char*
rocksdb_options_get_daily_offpeak_time_utc(rocksdb_options_t* opt,
size_t* size);
extern ROCKSDB_LIBRARY_API void
rocksdb_options_set_periodic_compaction_phase_recovery_percent(
rocksdb_options_t* opt, int v);
extern ROCKSDB_LIBRARY_API int
rocksdb_options_get_periodic_compaction_phase_recovery_percent(
rocksdb_options_t* opt);
extern ROCKSDB_LIBRARY_API void
rocksdb_options_set_follower_refresh_catchup_period_ms(rocksdb_options_t* opt,
uint64_t v);
@@ -715,14 +677,6 @@ extern ROCKSDB_LIBRARY_API void rocksdb_trace_options_set_preserve_write_order(
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_trace_options_get_preserve_write_order(rocksdb_trace_options_t* opt);
extern ROCKSDB_LIBRARY_API void
rocksdb_trace_options_set_include_read_write_options(
rocksdb_trace_options_t* opt, unsigned char v);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_trace_options_get_include_read_write_options(
rocksdb_trace_options_t* opt);
/* BlockCacheTraceOptions */
extern ROCKSDB_LIBRARY_API void
@@ -785,14 +739,6 @@ rocksdb_compaction_options_get_allow_trivial_move(
/* CompactRangeOptions */
extern ROCKSDB_LIBRARY_API void
rocksdb_compactoptions_set_skip_missing_blob_files(
rocksdb_compactoptions_t* opt, unsigned char v);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_compactoptions_get_skip_missing_blob_files(
rocksdb_compactoptions_t* opt);
extern ROCKSDB_LIBRARY_API void
rocksdb_compactoptions_set_blob_garbage_collection_policy(
rocksdb_compactoptions_t* opt, int v);
@@ -847,6 +793,14 @@ extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_ingestexternalfileoptions_get_ingest_behind(
rocksdb_ingestexternalfileoptions_t* opt);
extern ROCKSDB_LIBRARY_API void
rocksdb_ingestexternalfileoptions_set_write_global_seqno(
rocksdb_ingestexternalfileoptions_t* opt, unsigned char v);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_ingestexternalfileoptions_get_write_global_seqno(
rocksdb_ingestexternalfileoptions_t* opt);
extern ROCKSDB_LIBRARY_API void
rocksdb_ingestexternalfileoptions_set_verify_checksums_before_ingest(
rocksdb_ingestexternalfileoptions_t* opt, unsigned char v);
@@ -907,24 +861,8 @@ extern ROCKSDB_LIBRARY_API int
rocksdb_ingestexternalfileoptions_get_file_opening_threads(
rocksdb_ingestexternalfileoptions_t* opt);
extern ROCKSDB_LIBRARY_API void
rocksdb_ingestexternalfileoptions_set_reject_too_old_file(
rocksdb_ingestexternalfileoptions_t* opt, unsigned char v);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_ingestexternalfileoptions_get_reject_too_old_file(
rocksdb_ingestexternalfileoptions_t* opt);
/* OpenAndCompactOptions */
extern ROCKSDB_LIBRARY_API void
rocksdb_open_and_compact_options_set_max_secondary_open_retries(
rocksdb_open_and_compact_options_t* opt, uint32_t v);
extern ROCKSDB_LIBRARY_API uint32_t
rocksdb_open_and_compact_options_get_max_secondary_open_retries(
rocksdb_open_and_compact_options_t* opt);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_open_and_compact_options_get_allow_resumption(
rocksdb_open_and_compact_options_t* opt);
@@ -935,14 +873,6 @@ extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_import_column_family_options_get_move_files(
rocksdb_import_column_family_options_t* opt);
extern ROCKSDB_LIBRARY_API void
rocksdb_import_column_family_options_set_reject_too_old_file(
rocksdb_import_column_family_options_t* opt, unsigned char v);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_import_column_family_options_get_reject_too_old_file(
rocksdb_import_column_family_options_t* opt);
/* SizeApproximationOptions */
extern ROCKSDB_LIBRARY_API void
@@ -1169,6 +1099,22 @@ extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_block_based_options_get_use_delta_encoding(
rocksdb_block_based_table_options_t* opt);
extern ROCKSDB_LIBRARY_API void
rocksdb_block_based_options_set_use_udi_as_primary_index(
rocksdb_block_based_table_options_t* opt, unsigned char v);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_block_based_options_get_use_udi_as_primary_index(
rocksdb_block_based_table_options_t* opt);
extern ROCKSDB_LIBRARY_API void
rocksdb_block_based_options_set_fail_if_no_udi_on_open(
rocksdb_block_based_table_options_t* opt, unsigned char v);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_block_based_options_get_fail_if_no_udi_on_open(
rocksdb_block_based_table_options_t* opt);
extern ROCKSDB_LIBRARY_API void
rocksdb_block_based_options_set_whole_key_filtering(
rocksdb_block_based_table_options_t* opt, unsigned char v);
@@ -1224,14 +1170,6 @@ extern ROCKSDB_LIBRARY_API double
rocksdb_block_based_options_get_uniform_cv_threshold(
rocksdb_block_based_table_options_t* opt);
extern ROCKSDB_LIBRARY_API void
rocksdb_block_based_options_set_optimize_key_common_prefix(
rocksdb_block_based_table_options_t* opt, int v);
extern ROCKSDB_LIBRARY_API int
rocksdb_block_based_options_get_optimize_key_common_prefix(
rocksdb_block_based_table_options_t* opt);
extern ROCKSDB_LIBRARY_API void
rocksdb_block_based_options_set_enable_index_compression(
rocksdb_block_based_table_options_t* opt, unsigned char v);
+18 -80
View File
@@ -87,6 +87,12 @@ static void rocksdb_roundtrip_test_rocksdb_block_based_options(void) {
rocksdb_block_based_options_set_enable_index_compression(obj, 0);
CheckCondition(
rocksdb_block_based_options_get_enable_index_compression(obj) == 0);
rocksdb_block_based_options_set_fail_if_no_udi_on_open(obj, 1);
CheckCondition(rocksdb_block_based_options_get_fail_if_no_udi_on_open(obj) ==
1);
rocksdb_block_based_options_set_fail_if_no_udi_on_open(obj, 0);
CheckCondition(rocksdb_block_based_options_get_fail_if_no_udi_on_open(obj) ==
0);
rocksdb_block_based_options_set_format_version(obj, 1);
CheckCondition(rocksdb_block_based_options_get_format_version(obj) == 1);
rocksdb_block_based_options_set_format_version(obj, 0);
@@ -145,12 +151,6 @@ static void rocksdb_roundtrip_test_rocksdb_block_based_options(void) {
rocksdb_block_based_options_set_optimize_filters_for_memory(obj, 0);
CheckCondition(
rocksdb_block_based_options_get_optimize_filters_for_memory(obj) == 0);
rocksdb_block_based_options_set_optimize_key_common_prefix(obj, 1);
CheckCondition(
rocksdb_block_based_options_get_optimize_key_common_prefix(obj) == 1);
rocksdb_block_based_options_set_optimize_key_common_prefix(obj, 0);
CheckCondition(
rocksdb_block_based_options_get_optimize_key_common_prefix(obj) == 0);
rocksdb_block_based_options_set_partition_filters(obj, 1);
CheckCondition(rocksdb_block_based_options_get_partition_filters(obj) == 1);
rocksdb_block_based_options_set_partition_filters(obj, 0);
@@ -217,6 +217,12 @@ static void rocksdb_roundtrip_test_rocksdb_block_based_options(void) {
CheckCondition(rocksdb_block_based_options_get_use_delta_encoding(obj) == 1);
rocksdb_block_based_options_set_use_delta_encoding(obj, 0);
CheckCondition(rocksdb_block_based_options_get_use_delta_encoding(obj) == 0);
rocksdb_block_based_options_set_use_udi_as_primary_index(obj, 1);
CheckCondition(
rocksdb_block_based_options_get_use_udi_as_primary_index(obj) == 1);
rocksdb_block_based_options_set_use_udi_as_primary_index(obj, 0);
CheckCondition(
rocksdb_block_based_options_get_use_udi_as_primary_index(obj) == 0);
rocksdb_block_based_options_set_verify_compression(obj, 1);
CheckCondition(rocksdb_block_based_options_get_verify_compression(obj) == 1);
rocksdb_block_based_options_set_verify_compression(obj, 0);
@@ -309,10 +315,6 @@ static void rocksdb_roundtrip_test_rocksdb_compactoptions(void) {
rocksdb_compactoptions_set_blob_garbage_collection_policy(obj, 0);
CheckCondition(
rocksdb_compactoptions_get_blob_garbage_collection_policy(obj) == 0);
rocksdb_compactoptions_set_skip_missing_blob_files(obj, 1);
CheckCondition(rocksdb_compactoptions_get_skip_missing_blob_files(obj) == 1);
rocksdb_compactoptions_set_skip_missing_blob_files(obj, 0);
CheckCondition(rocksdb_compactoptions_get_skip_missing_blob_files(obj) == 0);
rocksdb_compactoptions_destroy(obj);
}
@@ -485,18 +487,6 @@ static void rocksdb_roundtrip_test_rocksdb_flushwaloptions(void) {
rocksdb_flushwaloptions_destroy(obj);
}
static void rocksdb_roundtrip_test_rocksdb_import_column_family_options(void) {
rocksdb_import_column_family_options_t* obj =
rocksdb_import_column_family_options_create();
rocksdb_import_column_family_options_set_reject_too_old_file(obj, 1);
CheckCondition(
rocksdb_import_column_family_options_get_reject_too_old_file(obj) == 1);
rocksdb_import_column_family_options_set_reject_too_old_file(obj, 0);
CheckCondition(
rocksdb_import_column_family_options_get_reject_too_old_file(obj) == 0);
rocksdb_import_column_family_options_destroy(obj);
}
static void rocksdb_roundtrip_test_rocksdb_ingestexternalfileoptions(void) {
rocksdb_ingestexternalfileoptions_t* obj =
rocksdb_ingestexternalfileoptions_create();
@@ -538,12 +528,6 @@ static void rocksdb_roundtrip_test_rocksdb_ingestexternalfileoptions(void) {
CheckCondition(
rocksdb_ingestexternalfileoptions_get_prefetch_lmax_index_and_filter_blocks(
obj) == 0);
rocksdb_ingestexternalfileoptions_set_reject_too_old_file(obj, 1);
CheckCondition(
rocksdb_ingestexternalfileoptions_get_reject_too_old_file(obj) == 1);
rocksdb_ingestexternalfileoptions_set_reject_too_old_file(obj, 0);
CheckCondition(
rocksdb_ingestexternalfileoptions_get_reject_too_old_file(obj) == 0);
rocksdb_ingestexternalfileoptions_set_verify_checksums_before_ingest(obj, 1);
CheckCondition(
rocksdb_ingestexternalfileoptions_get_verify_checksums_before_ingest(
@@ -566,6 +550,12 @@ static void rocksdb_roundtrip_test_rocksdb_ingestexternalfileoptions(void) {
rocksdb_ingestexternalfileoptions_set_verify_file_checksum(obj, 0);
CheckCondition(
rocksdb_ingestexternalfileoptions_get_verify_file_checksum(obj) == 0);
rocksdb_ingestexternalfileoptions_set_write_global_seqno(obj, 1);
CheckCondition(
rocksdb_ingestexternalfileoptions_get_write_global_seqno(obj) == 1);
rocksdb_ingestexternalfileoptions_set_write_global_seqno(obj, 0);
CheckCondition(
rocksdb_ingestexternalfileoptions_get_write_global_seqno(obj) == 0);
rocksdb_ingestexternalfileoptions_destroy(obj);
}
@@ -596,20 +586,6 @@ static void rocksdb_roundtrip_test_rocksdb_livefiles_storage_info_options(
rocksdb_livefiles_storage_info_options_destroy(obj);
}
static void rocksdb_roundtrip_test_rocksdb_open_and_compact_options(void) {
rocksdb_open_and_compact_options_t* obj =
rocksdb_open_and_compact_options_create();
rocksdb_open_and_compact_options_set_max_secondary_open_retries(obj, 1);
CheckCondition(
rocksdb_open_and_compact_options_get_max_secondary_open_retries(obj) ==
1);
rocksdb_open_and_compact_options_set_max_secondary_open_retries(obj, 0);
CheckCondition(
rocksdb_open_and_compact_options_get_max_secondary_open_retries(obj) ==
0);
rocksdb_open_and_compact_options_destroy(obj);
}
static void rocksdb_roundtrip_test_rocksdb_optimistictransactiondb_options(
void) {
rocksdb_optimistictransactiondb_options_t* obj =
@@ -857,12 +833,6 @@ static void rocksdb_roundtrip_test_rocksdb_options(void) {
CheckCondition(rocksdb_options_get_paranoid_memory_checks(obj) == 1);
rocksdb_options_set_paranoid_memory_checks(obj, 0);
CheckCondition(rocksdb_options_get_paranoid_memory_checks(obj) == 0);
rocksdb_options_set_periodic_compaction_phase_recovery_percent(obj, 1);
CheckCondition(
rocksdb_options_get_periodic_compaction_phase_recovery_percent(obj) == 1);
rocksdb_options_set_periodic_compaction_phase_recovery_percent(obj, 0);
CheckCondition(
rocksdb_options_get_periodic_compaction_phase_recovery_percent(obj) == 0);
rocksdb_options_set_persist_stats_to_disk(obj, 1);
CheckCondition(rocksdb_options_get_persist_stats_to_disk(obj) == 1);
rocksdb_options_set_persist_stats_to_disk(obj, 0);
@@ -889,12 +859,6 @@ static void rocksdb_roundtrip_test_rocksdb_options(void) {
CheckCondition(rocksdb_options_get_read_io_executor_threads(obj) == 1);
rocksdb_options_set_read_io_executor_threads(obj, 0);
CheckCondition(rocksdb_options_get_read_io_executor_threads(obj) == 0);
rocksdb_options_set_remote_compaction_manifest_floor(obj, 1);
CheckCondition(rocksdb_options_get_remote_compaction_manifest_floor(obj) ==
1);
rocksdb_options_set_remote_compaction_manifest_floor(obj, 0);
CheckCondition(rocksdb_options_get_remote_compaction_manifest_floor(obj) ==
0);
rocksdb_options_set_reuse_manifest_on_open(obj, 1);
CheckCondition(rocksdb_options_get_reuse_manifest_on_open(obj) == 1);
rocksdb_options_set_reuse_manifest_on_open(obj, 0);
@@ -923,12 +887,6 @@ static void rocksdb_roundtrip_test_rocksdb_options(void) {
CheckCondition(rocksdb_options_get_track_and_verify_wals(obj) == 1);
rocksdb_options_set_track_and_verify_wals(obj, 0);
CheckCondition(rocksdb_options_get_track_and_verify_wals(obj) == 0);
rocksdb_options_set_track_options_file_number_in_manifest(obj, 1);
CheckCondition(
rocksdb_options_get_track_options_file_number_in_manifest(obj) == 1);
rocksdb_options_set_track_options_file_number_in_manifest(obj, 0);
CheckCondition(
rocksdb_options_get_track_options_file_number_in_manifest(obj) == 0);
rocksdb_options_set_two_write_queues(obj, 1);
CheckCondition(rocksdb_options_get_two_write_queues(obj) == 1);
rocksdb_options_set_two_write_queues(obj, 0);
@@ -947,18 +905,6 @@ static void rocksdb_roundtrip_test_rocksdb_options(void) {
CheckCondition(rocksdb_options_get_use_fsync(obj) == 1);
rocksdb_options_set_use_fsync(obj, 0);
CheckCondition(rocksdb_options_get_use_fsync(obj) == 0);
rocksdb_options_set_use_separate_remote_compaction_pool(obj, 1);
CheckCondition(rocksdb_options_get_use_separate_remote_compaction_pool(obj) ==
1);
rocksdb_options_set_use_separate_remote_compaction_pool(obj, 0);
CheckCondition(rocksdb_options_get_use_separate_remote_compaction_pool(obj) ==
0);
rocksdb_options_set_use_session_tmp_dir_for_remote_compaction(obj, 1);
CheckCondition(
rocksdb_options_get_use_session_tmp_dir_for_remote_compaction(obj) == 1);
rocksdb_options_set_use_session_tmp_dir_for_remote_compaction(obj, 0);
CheckCondition(
rocksdb_options_get_use_session_tmp_dir_for_remote_compaction(obj) == 0);
rocksdb_options_set_verify_manifest_content_on_close(obj, 1);
CheckCondition(rocksdb_options_get_verify_manifest_content_on_close(obj) ==
1);
@@ -1140,12 +1086,6 @@ static void rocksdb_roundtrip_test_rocksdb_trace_options(void) {
CheckCondition(rocksdb_trace_options_get_filter(obj) == 1);
rocksdb_trace_options_set_filter(obj, 0);
CheckCondition(rocksdb_trace_options_get_filter(obj) == 0);
rocksdb_trace_options_set_include_read_write_options(obj, 1);
CheckCondition(rocksdb_trace_options_get_include_read_write_options(obj) ==
1);
rocksdb_trace_options_set_include_read_write_options(obj, 0);
CheckCondition(rocksdb_trace_options_get_include_read_write_options(obj) ==
0);
rocksdb_trace_options_set_max_trace_file_size(obj, 1);
CheckCondition(rocksdb_trace_options_get_max_trace_file_size(obj) == 1);
rocksdb_trace_options_set_max_trace_file_size(obj, 0);
@@ -1421,10 +1361,8 @@ static void rocksdb_run_generated_roundtrip_tests(void) {
rocksdb_roundtrip_test_rocksdb_fifo_compaction_options();
rocksdb_roundtrip_test_rocksdb_flushoptions();
rocksdb_roundtrip_test_rocksdb_flushwaloptions();
rocksdb_roundtrip_test_rocksdb_import_column_family_options();
rocksdb_roundtrip_test_rocksdb_ingestexternalfileoptions();
rocksdb_roundtrip_test_rocksdb_livefiles_storage_info_options();
rocksdb_roundtrip_test_rocksdb_open_and_compact_options();
rocksdb_roundtrip_test_rocksdb_optimistictransactiondb_options();
rocksdb_roundtrip_test_rocksdb_options();
rocksdb_roundtrip_test_rocksdb_readoptions();
+2 -3
View File
@@ -180,7 +180,7 @@ DEFINE_uint32(
// than random." Even with this setting, file offsets are not randomized.
DEFINE_bool(sck_randomize, false,
"(-stress_cache_key) Randomize (hash) cache key");
// See https://github.com/meta/rocksdb/pull/9058
// See https://github.com/facebook/rocksdb/pull/9058
DEFINE_bool(sck_footer_unique_id, false,
"(-stress_cache_key) Simulate using proposed footer unique id");
// ## END stress_cache_key sub-tool options ##
@@ -276,8 +276,7 @@ struct KeyGen {
// location, so we fix hash bits starting at the bottom of that word.
auto hi_hash = uint64_t{0x9e3779b97f4a7c13U} ^
(key_hash << 1 << (FLAGS_degenerate_hash_bits - 1));
uint64_t un_hi;
uint64_t un_lo;
uint64_t un_hi, un_lo;
BijectiveUnhash2x64(hi_hash, key_hash, &un_hi, &un_lo);
un_lo ^= BitwiseAnd(FLAGS_seed, INT32_MAX);
EncodeFixed64(key_data, un_lo);
+2 -7
View File
@@ -32,9 +32,9 @@ class CacheReservationManager {
// for an incremental amount of memory used (i.e, incremental_memory_used)
class CacheReservationHandle {
public:
virtual ~CacheReservationHandle() = default;
virtual ~CacheReservationHandle() {}
};
virtual ~CacheReservationManager() = default;
virtual ~CacheReservationManager() {}
virtual Status UpdateCacheReservation(std::size_t new_memory_used) = 0;
// TODO(hx235): replace the usage of
// `UpdateCacheReservation(memory_used_delta, increase)` with
@@ -235,11 +235,6 @@ class ConcurrentCacheReservationManager
cache_res_mgr_ = cache_res_mgr;
cache_res_handle_ = std::move(cache_res_handle);
}
// No copying allowed
CacheReservationHandle(const CacheReservationHandle&) = delete;
CacheReservationHandle& operator=(const CacheReservationHandle&) = delete;
CacheReservationHandle(CacheReservationHandle&&) = delete;
CacheReservationHandle& operator=(CacheReservationHandle&&) = delete;
~CacheReservationHandle() override {
std::lock_guard<std::mutex> lock(cache_res_mgr_->cache_res_mgr_mu_);
+2 -2
View File
@@ -407,8 +407,8 @@ TEST(CacheReservationHandleTest, HandleTest) {
std::size_t mem_used = 0;
const std::size_t incremental_mem_used_handle_1 = 1 * kSizeDummyEntry;
const std::size_t incremental_mem_used_handle_2 = 2 * kSizeDummyEntry;
std::unique_ptr<CacheReservationManager::CacheReservationHandle> handle_1;
std::unique_ptr<CacheReservationManager::CacheReservationHandle> handle_2;
std::unique_ptr<CacheReservationManager::CacheReservationHandle> handle_1,
handle_2;
// To test consecutive CacheReservationManager::MakeCacheReservation works
// correctly in terms of returning the handle as well as updating cache
-19
View File
@@ -948,25 +948,6 @@ TEST_P(CacheTest, GetChargeAndDeleter) {
cache_->Release(h1);
}
TEST_P(LRUCacheTest, UnchargedStandaloneGetCharge) {
auto cache = NewCache(1000, 0, true, kFullChargeCacheMetadata);
Cache::Handle* pinned = nullptr;
ASSERT_OK(
cache->Insert(EncodeKey(1), EncodeValue(1), &kHelper, 800, &pinned));
ASSERT_EQ(nullptr, cache->CreateStandalone(EncodeKey(2), EncodeValue(2),
&kHelper, 500, false));
Cache::Handle* h = cache->CreateStandalone(EncodeKey(2), EncodeValue(2),
&kHelper, 500, true);
ASSERT_NE(h, nullptr);
EXPECT_EQ(0, cache->GetCharge(h));
EXPECT_EQ(0, cache->GetUsage(h));
cache->Release(h);
cache->Release(pinned);
}
namespace {
bool AreTwoCacheKeysOrdered(Cache* cache) {
std::vector<std::string> keys;
+3 -12
View File
@@ -975,8 +975,7 @@ void FixedHyperClockTable::Erase(const UniqueId64x2& hashed_key) {
// Could be multiple entries in rare cases. Erase them all.
// Optimistically increment acquire counter
auto add_acquire = AcquireCounter::PlusTransformPromiseNoOverflow(1);
SlotMeta old_meta;
SlotMeta meta;
SlotMeta old_meta, meta;
h->meta.Apply(add_acquire, &old_meta, &meta);
// Check if it's an entry visible to lookups
if (meta.IsVisible()) {
@@ -1739,8 +1738,7 @@ inline bool MatchAndRef(const UniqueId64x2* hashed_key, const ClockHandle& h,
// Must be at least something to match
assert(hashed_key || shift > 0);
SlotMeta old_meta;
SlotMeta new_meta;
SlotMeta old_meta, new_meta;
// (Optimistically) increment acquire counter.
auto add_acquire = AcquireCounter::PlusTransformPromiseNoOverflow(1);
h.meta.Apply(add_acquire, &old_meta, &new_meta);
@@ -1858,12 +1856,6 @@ class AutoHyperClockTable::ChainRewriteLock {
assert(saved_head_.IsLocked());
}
// No copying allowed
ChainRewriteLock(const ChainRewriteLock&) = delete;
ChainRewriteLock& operator=(const ChainRewriteLock&) = delete;
ChainRewriteLock(ChainRewriteLock&&) = delete;
ChainRewriteLock& operator=(ChainRewriteLock&&) = delete;
~ChainRewriteLock() {
if (!IsEnd()) {
// Release lock
@@ -3130,8 +3122,7 @@ AutoHyperClockTable::HandleImpl* AutoHyperClockTable::Lookup(
if (probably_equal) {
// Increment acquire counter for definitive check
auto add_acquire = AcquireCounter::PlusTransformPromiseNoOverflow(1);
SlotMeta old_meta;
SlotMeta new_meta;
SlotMeta old_meta, new_meta;
h->meta.Apply(add_acquire, &old_meta, &new_meta);
// Check if it's a referencable (sharable) entry
if (LIKELY(old_meta.IsShareable())) {
-4
View File
@@ -29,10 +29,6 @@ class CompressedSecondaryCacheResultHandle : public SecondaryCacheResultHandle {
const CompressedSecondaryCacheResultHandle&) = delete;
CompressedSecondaryCacheResultHandle& operator=(
const CompressedSecondaryCacheResultHandle&) = delete;
CompressedSecondaryCacheResultHandle(CompressedSecondaryCacheResultHandle&&) =
delete;
CompressedSecondaryCacheResultHandle& operator=(
CompressedSecondaryCacheResultHandle&&) = delete;
bool IsReady() override { return true; }
+1 -2
View File
@@ -508,8 +508,7 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
std::string str1 = rnd.RandomString(1001);
auto item1 = std::make_unique<TestItem>(str1.data(), str1.length());
ASSERT_OK(cache->Insert(key1, item1.get(), GetHelper(), str1.length()));
// Appease clang-analyze "potential memory leak"
[[maybe_unused]] TestItem* released_item1 = item1.release();
item1.release(); // Appease clang-analyze "potential memory leak"
Cache::Handle* handle;
handle = cache->Lookup(key2, nullptr, this, Cache::Priority::LOW);
+1 -1
View File
@@ -574,7 +574,7 @@ LRUHandle* LRUCacheShard::CreateStandalone(const Slice& key, uint32_t hash,
if (strict_capacity_limit_ && (usage_ + e->total_charge) > capacity_) {
if (allow_uncharged) {
e->total_charge = LRUHandle::kUnchargedStandaloneTotalCharge;
e->total_charge = 0;
} else {
free(e);
e = nullptr;
-8
View File
@@ -87,10 +87,6 @@ struct LRUHandle : public Cache::Handle {
IM_IS_STANDALONE = (1 << 2),
};
// total_charge sentinel for a standalone entry that added nothing to usage_
// (strict cache full, created with allow_uncharged).
static constexpr size_t kUnchargedStandaloneTotalCharge = 0;
// Beginning of the key (MUST BE THE LAST FIELD IN THIS STRUCT!)
char key_data[1];
@@ -200,10 +196,6 @@ struct LRUHandle : public Cache::Handle {
inline size_t GetCharge(
CacheMetadataChargePolicy metadata_charge_policy) const {
if (IsStandalone() && total_charge == kUnchargedStandaloneTotalCharge) {
// Documented to report 0; no metadata was charged.
return 0;
}
size_t meta_charge = CalcuMetaCharge(metadata_charge_policy);
assert(total_charge >= meta_charge);
return total_charge - meta_charge;
+58 -72
View File
@@ -1366,8 +1366,7 @@ TEST_P(BasicSecondaryCacheTest, BasicFailTest) {
// str1.length()).IsInvalidArgument());
ASSERT_OK(
cache->Insert(k1.AsSlice(), item1.get(), GetHelper(), str1.length()));
// Appease clang-analyze "potential memory leak"
[[maybe_unused]] TestItem* released_item1 = item1.release();
item1.release(); // Appease clang-analyze "potential memory leak"
Cache::Handle* handle;
handle = cache->Lookup(k2.AsSlice(), nullptr, /*context*/ this,
@@ -1474,81 +1473,68 @@ TEST_P(BasicSecondaryCacheTest, CreateFailTest) {
}
TEST_P(BasicSecondaryCacheTest, FullCapacityTest) {
const size_t kValueSize = 700;
estimated_value_size_ = kValueSize;
for (CacheMetadataChargePolicy charge_policy :
{kDontChargeCacheMetadata, kFullChargeCacheMetadata}) {
SCOPED_TRACE(charge_policy);
for (bool strict_capacity_limit : {false, true}) {
SCOPED_TRACE(strict_capacity_limit);
std::shared_ptr<TestSecondaryCache> secondary_cache =
std::make_shared<TestSecondaryCache>(2048, true);
std::shared_ptr<Cache> cache =
NewCache(1024, [&](ShardedCacheOptions& opts) {
opts.num_shard_bits = 0;
opts.strict_capacity_limit = strict_capacity_limit;
opts.metadata_charge_policy = charge_policy;
opts.secondary_cache = secondary_cache;
});
CacheKey k1 = CacheKey::CreateUniqueForCacheLifetime(cache.get());
CacheKey k2 = CacheKey::CreateUniqueForCacheLifetime(cache.get());
for (bool strict_capacity_limit : {false, true}) {
std::shared_ptr<TestSecondaryCache> secondary_cache =
std::make_shared<TestSecondaryCache>(2048, true);
std::shared_ptr<Cache> cache =
NewCache(1024 /* capacity */, 0 /* num_shard_bits */,
strict_capacity_limit, secondary_cache);
CacheKey k1 = CacheKey::CreateUniqueForCacheLifetime(cache.get());
CacheKey k2 = CacheKey::CreateUniqueForCacheLifetime(cache.get());
Random rnd(301);
std::string str1 = rnd.RandomString(kValueSize);
TestItem* item1 = new TestItem(str1.data(), str1.length());
ASSERT_OK(cache->Insert(k1.AsSlice(), item1, GetHelper(), str1.length()));
std::string str2 = rnd.RandomString(kValueSize);
TestItem* item2 = new TestItem(str2.data(), str2.length());
// k1 should be demoted to NVM
ASSERT_OK(cache->Insert(k2.AsSlice(), item2, GetHelper(), str2.length()));
Random rnd(301);
std::string str1 = rnd.RandomString(1020);
TestItem* item1 = new TestItem(str1.data(), str1.length());
ASSERT_OK(cache->Insert(k1.AsSlice(), item1, GetHelper(), str1.length()));
std::string str2 = rnd.RandomString(1020);
TestItem* item2 = new TestItem(str2.data(), str2.length());
// k1 should be demoted to NVM
ASSERT_OK(cache->Insert(k2.AsSlice(), item2, GetHelper(), str2.length()));
Cache::Handle* handle2;
handle2 = cache->Lookup(k2.AsSlice(), GetHelper(),
/*context*/ this, Cache::Priority::LOW);
Cache::Handle* handle2;
handle2 = cache->Lookup(k2.AsSlice(), GetHelper(),
/*context*/ this, Cache::Priority::LOW);
ASSERT_NE(handle2, nullptr);
// k1 lookup fails without secondary cache support
Cache::Handle* handle1;
handle1 = cache->Lookup(
k1.AsSlice(),
GetHelper(CacheEntryRole::kDataBlock, /*secondary_compatible=*/false),
/*context*/ this, Cache::Priority::LOW);
ASSERT_EQ(handle1, nullptr);
// k1 promotion can fail with strict_capacity_limit=true, but Lookup still
// succeeds using a standalone handle
handle1 = cache->Lookup(k1.AsSlice(), GetHelper(),
/*context*/ this, Cache::Priority::LOW);
ASSERT_NE(handle1, nullptr);
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
ASSERT_EQ(secondary_cache->num_lookups(), 1u);
// Releasing k2's handle first, k2 is evicted from primary iff k1 promotion
// was charged to the cache (except HCC doesn't erase in Release() over
// capacity)
// FIXME: Insert to secondary from Release disabled
cache->Release(handle2);
cache->Release(handle1);
handle2 = cache->Lookup(
k2.AsSlice(),
GetHelper(CacheEntryRole::kDataBlock, /*secondary_compatible=*/false),
/*context*/ this, Cache::Priority::LOW);
if (strict_capacity_limit || IsHyperClock()) {
ASSERT_NE(handle2, nullptr);
// k1 lookup fails without secondary cache support
Cache::Handle* handle1;
handle1 = cache->Lookup(
k1.AsSlice(),
GetHelper(CacheEntryRole::kDataBlock, /*secondary_compatible=*/false),
/*context*/ this, Cache::Priority::LOW);
ASSERT_EQ(handle1, nullptr);
// k1 promotion can fail with strict_capacity_limit=true, but Lookup still
// succeeds using a standalone handle
handle1 = cache->Lookup(k1.AsSlice(), GetHelper(),
/*context*/ this, Cache::Priority::LOW);
ASSERT_NE(handle1, nullptr);
ASSERT_EQ(strict_capacity_limit ? 0 : str1.size(),
cache->GetUsage(handle1));
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
ASSERT_EQ(secondary_cache->num_lookups(), 1u);
// Releasing k2's handle first, k2 is evicted from primary iff k1
// promotion was charged to the cache (except HCC doesn't erase in
// Release() over capacity)
// FIXME: Insert to secondary from Release disabled
cache->Release(handle2);
cache->Release(handle1);
handle2 = cache->Lookup(
k2.AsSlice(),
GetHelper(CacheEntryRole::kDataBlock, /*secondary_compatible=*/false),
/*context*/ this, Cache::Priority::LOW);
if (strict_capacity_limit || IsHyperClock()) {
ASSERT_NE(handle2, nullptr);
cache->Release(handle2);
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
} else {
ASSERT_EQ(handle2, nullptr);
// FIXME: Insert to secondary from Release disabled
// ASSERT_EQ(secondary_cache->num_inserts(), 2u);
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
}
cache.reset();
secondary_cache.reset();
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
} else {
ASSERT_EQ(handle2, nullptr);
// FIXME: Insert to secondary from Release disabled
// ASSERT_EQ(secondary_cache->num_inserts(), 2u);
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
}
cache.reset();
secondary_cache.reset();
}
}
+3 -9
View File
@@ -90,7 +90,7 @@ class CacheShardBase {
class ShardedCacheBase : public Cache {
public:
explicit ShardedCacheBase(const ShardedCacheOptions& opts);
~ShardedCacheBase() override = default;
virtual ~ShardedCacheBase() = default;
int GetNumShardBits() const;
uint32_t GetNumShards() const;
@@ -143,13 +143,7 @@ class ShardedCache : public ShardedCacheBase {
sizeof(CacheShard) * GetNumShards()))),
destroy_shards_in_dtor_(false) {}
// No copying allowed
ShardedCache(const ShardedCache&) = delete;
ShardedCache& operator=(const ShardedCache&) = delete;
ShardedCache(ShardedCache&&) = delete;
ShardedCache& operator=(ShardedCache&&) = delete;
~ShardedCache() override {
virtual ~ShardedCache() {
if (destroy_shards_in_dtor_) {
ForEachShard([](CacheShard* cs) { cs->~CacheShard(); });
}
@@ -251,7 +245,7 @@ class ShardedCache : public ShardedCacheBase {
std::unique_ptr<size_t[]> states(new size_t[num_shards]{});
size_t aepl = opts.average_entries_per_lock;
aepl = std::max(aepl, size_t{1});
aepl = std::min(aepl, size_t{1});
bool remaining_work;
do {
+2 -2
View File
@@ -36,7 +36,7 @@ class TieredSecondaryCache : public SecondaryCacheWrapper {
#endif
}
~TieredSecondaryCache() override = default;
~TieredSecondaryCache() override {}
const char* Name() const override { return "TieredSecondaryCache"; }
@@ -75,7 +75,7 @@ class TieredSecondaryCache : public SecondaryCacheWrapper {
class ResultHandle : public SecondaryCacheResultHandle {
public:
~ResultHandle() override = default;
~ResultHandle() override {}
bool IsReady() override {
if (inner_handle_ && inner_handle_->IsReady()) {
+1 -1
View File
@@ -951,7 +951,7 @@ TEST_F(DBTieredSecondaryCacheTest, FSBufferTest) {
IODebugContext* dbg) override {
for (size_t i = 0; i < num_reqs; ++i) {
FSReadRequest& req = reqs[i];
// See https://github.com/meta/rocksdb/pull/13195 for why we
// See https://github.com/facebook/rocksdb/pull/13195 for why we
// want to set up our test implementation for FSAllocationPtr this
// way.
char* internalData = new char[req.len];
+1 -1
View File
@@ -241,7 +241,7 @@ class FullTypedCacheHelperFns : public BasicTypedCacheHelperFns<TValue> {
if (source != CacheTier::kVolatileTier) {
return Status::InvalidArgument();
}
if constexpr (!std::is_empty_v<TCreateContext>) {
if constexpr (sizeof(TCreateContext) > 0) {
TCreateContext* tcontext = static_cast<TCreateContext*>(context);
tcontext->Create(&value, out_charge, data, type, allocator);
} else {
+2 -130
View File
@@ -4,122 +4,6 @@
Thorough multi-agent code review for RocksDB commits following CLAUDE.md guidelines,
with structured codebase exploration, inter-agent debate, and verification.
## Behavioral Compatibility Review Perspective
Behavior changes are human judgment calls. They must be intentional,
well-documented (including release notes), and researched for potential
downstream production impact. Agents and reviewers must call out concrete gaps,
ambiguity, or adverse impacts in the change's intent, documentation, and
production-impact analysis.
When a diff changes how an existing accepted public or serialized input is
interpreted—including an undocumented, discouraged, or historically ignored
value—reviewers must:
- Compare the old and new observable behavior, including performance and
resource impact.
- Check that the rationale and user-facing documentation clearly describe the
change.
- Identify known direct callers and stored configurations that may depend on
the previous behavior.
- Check for appropriate regression coverage, or a documented reason why such
coverage is not practical.
A behavior change is not automatically a defect. Report a finding when the
intent, documentation, downstream-impact analysis, or mitigation has a concrete
gap or ambiguity that could lead to an unintended compatibility regression.
## DB Mutex Review Perspective
For every non-trivial change, determine whether the changed code, or any
transitive callee it adds or changes, can run while `DBImpl::mutex_` is held.
State the result explicitly, including when the DB mutex is not affected.
`DBImpl::mutex_` is the DB-wide metadata and coordination mutex. It protects
cross-object invariants involving `ColumnFamilyData`, memtable and
`SuperVersion` publication, `VersionSet`, snapshots, flush/compaction queues,
background-job state, and file lifetime. It is shared by all column families
and can be a hot lock. It is not the filesystem `db_lock_`, and it should not
replace data-path synchronization already provided by `WriteThread`,
`wal_write_mutex_`, atomics, referenced `SuperVersion`s, or component-local
synchronization.
When a DB-mutex critical section is affected:
### Locking and correctness
- Trace the full call path and mark the exact lock and unlock boundaries. Do
not inspect only the lines that directly acquire the mutex.
- State the locking contract. Helpers that require the lock should document it
and normally use `mutex_.AssertHeld()`. Check whether each caller expects the
lock to be held on return.
- Treat calls that can release and re-acquire the mutex as concurrency
boundaries. Examples include `VersionSet::LogAndApply()`, condition-variable
waits, write-thread coordination, and notification helpers. Revalidate state
inspected before such a call.
- Keep atomic metadata transitions under the mutex. Examples include moving a
memtable between mutable and immutable state, reserving compaction inputs,
installing a `Version` or `SuperVersion`, updating scheduling counters and
queues, and changing file-lifetime protection.
- For expensive work, prefer a prepare/run/install structure: reserve and pin
the required state while locked, do CPU or I/O work while unlocked, then
re-lock, validate, and publish. Before unlocking, copy mutable options and
acquire every required object reference or lifetime reservation.
- Check object lifetime across every unlocked region. Do not use a raw
`ColumnFamilyData*`, `Version*`, memtable, file metadata pointer, or DB-owned
container element after unlocking unless an explicit reference,
`SuperVersion`, job object, or pending-output reservation keeps it alive.
- Preserve the snapshot/input-selection boundary. Flush and compaction must not
miss a snapshot whose visible key versions they may remove. Missing a newly
created snapshot is a correctness bug; retaining a recently released
snapshot is conservative but safe.
- Never invoke untrusted or re-entrant code while holding the mutex. The mutex
is non-reentrant, so a callback into RocksDB can self-deadlock.
- Check lock ordering. `options_mutex_` is acquired before the DB mutex. When
both the DB mutex and `wal_write_mutex_` are needed, the DB mutex is acquired
first. Establish and document the order for other locks.
- Keep cleanup outside the critical section when possible. File deletion,
`JobContext::Clean()`, expensive `SuperVersion` destruction, logging, and
listener callbacks should normally run after protected state is detached or
snapshotted.
- Do not add the DB mutex to steady-state point reads or the normal write data
phase without strong justification. Reads normally use a referenced
`SuperVersion`; writes use `WriteThread` and component-specific
synchronization.
### Critical-section performance
- Compare the CPU and I/O performed under the mutex before and after the
change. Include call frequency, loop bounds, input-size scaling, hidden work
in callees, and worst-case cost.
- Account for filesystem or network I/O, waits, callbacks, logging and
formatting, allocation, container growth, copies, sorting, scans, reference
release and destructors, cache misses, and repeated metadata lookups.
- Do not allow a significant regression in mutex-held work without clear
justification, hold-time or contention measurements, and representative
benchmarks.
- Treat unavoidable work under the mutex as hot-path code and keep it highly
optimized. Precompute, allocate, copy, and perform I/O outside the lock;
remove repeated scans and lookups; cache or batch work where practical; and
keep locked operations O(1) or tightly bounded when possible.
- Treat new blocking I/O, waits, callbacks into user or plugin code, unbounded
loops, or work that scales with DB size, files, column families, snapshots,
or pending jobs as a blocker unless it is unavoidable and supported by strong
measurements and explicit design justification.
### Validation and evidence
- Test unlock windows with sync points rather than sleeps. Cover relevant races
with memtable switches, snapshot create/release, column-family drop,
flush/compaction install, shutdown, and background errors. Run TSAN where
appropriate.
- Distinguish wait time from hold time. `PerfContext::db_mutex_lock_nanos` and
`DB_MUTEX_WAIT_MICROS` primarily expose acquisition wait; use contention
profiles or scoped instrumentation to find long critical sections.
- Report affected critical sections, before/after cost, workload assumptions,
optimization choices, and remaining risk. If exact measurement is not
practical, provide a conservative static bound and do not claim no regression
without evidence.
IMPORTANT — Where the review goes: Your FINAL RESPONSE TEXT is the review.
The PR comment is generated verbatim from your final response, so the
complete, polished review MUST appear there in full. Do NOT end your turn with
@@ -399,9 +283,6 @@ in isolation may be a critical bug when you consider the full call chain.
- **Callee side effects**: Does the change call functions that mutate shared
state (counters, seqnos, metadata)? Are the mutations correct for the new
calling context? (Use the callee-chain analysis from context.md)
- Apply the DB Mutex Review Perspective above to locking contracts, unlocked
object lifetimes, stale-state validation, lock ordering, and snapshot
boundaries.
#### Agent: Cross-Component & Adversarial Reviewer
@@ -505,9 +386,6 @@ Write findings to `findings-caller-audit.md`.
- Loop optimization opportunities
- Branch prediction (LIKELY/UNLIKELY)
- Zero-overhead design verification
- Apply the DB Mutex Review Perspective above, with special focus on transitive
CPU and I/O, before/after cost, critical-section hold time, and benchmark
evidence
#### Agent: API & Compatibility Reviewer
- Public API backwards compatibility
@@ -515,8 +393,8 @@ Write findings to `findings-caller-audit.md`.
- Documentation completeness
- Deprecation policy compliance
- Forward compatibility (struct extensibility)
- Apply the Behavioral Compatibility Review Perspective above to changes in
observable semantics and implicit caller contracts.
- **Behavioral compatibility**: Do changed semantics (e.g., kOutOfBound vs
kUnknown) break implicit contracts with callers?
#### Agent: Serialization & Deserialization Reviewer
- Format correctness and versioning
@@ -691,8 +569,6 @@ Rules for this structure:
- [ ] Multi-component interactions (compaction, recovery, snapshots, iterators)
- [ ] Configuration dependencies and unexpected option combinations
- [ ] Potential pitfalls from caller-chain analysis
- [ ] DB-mutex reachability checked through transitive callees; affected lock
and unlock boundaries documented
### Review Phase (verified by agents)
- [ ] Database semantics preserved (snapshot isolation, key ordering)
@@ -706,10 +582,6 @@ Rules for this structure:
- [ ] Code follows RocksDB style conventions
- [ ] Behavioral contracts with upstream callers preserved
- [ ] All callers enumerated with parameter range table
- [ ] Before/after CPU and I/O under the DB mutex compared, with call frequency,
scaling, hidden callee work, and worst-case cost included
- [ ] No significant DB-mutex-held work regression; unavoidable work is highly
optimized, bounded, and supported by measurements when material
## Output Structure
Your **final response text is the primary output** — it becomes the PR comment
-106
View File
@@ -1,106 +0,0 @@
# RocksDB Diff and Pull Request Summary Guidance
This guidance applies to both internal Phabricator diff summaries and external
GitHub pull request summaries for changes under `fbcode/internal_repo_rocksdb/`.
## Goal
Write each diff or pull request summary for a reviewer who has not read the
patch. The summary must explain the behavioral story and the evidence for it,
not merely inventory the edited files or symbols.
Every new or updated diff or pull request summary must clearly cover:
1. Why the change is required.
2. What the previous behavior was, including the failure mode or limitation.
3. What the new behavior is.
4. What changed in the implementation.
5. Safety boundaries, compatibility, and unsupported or intentionally deferred
cases.
6. Tests performed and known limitations of the validation.
Use explicit headings for a broad, subtle, or high-risk change. A small,
focused change may combine these points into a few paragraphs, but none may be
left implicit when it affects a review decision.
## What Good RocksDB Summaries Make Concrete
- Name the exact conditions that activate a bug or behavior: relevant options,
call ordering, storage mode, concurrency mode, file format, platform, or
workload shape. Also state important cases that are not affected.
- Explain the causal chain from the old behavior to its externally observable
result. For correctness fixes, distinguish a symptom from the underlying
invariant violation.
- Describe the new contract before implementation mechanics. Then identify the
essential implementation choices and why they are sufficient.
- Call out API, ABI, serialized-format, behavioral, and performance
compatibility where relevant. For additive persisted fields, state how older
data is interpreted and whether older readers remain supported.
- State safety boundaries precisely: defaults, feature gates, fallback paths,
ownership and lifetime constraints, memory-ordering assumptions, and cases
deliberately postponed to a follow-up.
- Support performance claims with a baseline, workload and configuration,
measurement method, and results. Report meaningful tradeoffs, not only the
winning metric.
- Make the test plan evidence-based. Name the tests and configurations, say
what behavior each proves, and report failures, timeouts, untested platforms,
or stale pre-rebase results honestly. For a regression test, say how it fails
without the fix when that was verified.
- Separate incidental cleanup or a pre-existing test fix from the primary
change, and explain why it belongs in the diff.
Avoid vague claims such as "improves performance," "fixes an issue," or "tests
pass." Avoid copying the patch into prose, exhaustive file-by-file narration,
raw command output, and claims broader than the evidence.
## Suggested Structure
Adapt this structure to the change; do not keep empty or irrelevant sections.
```text
Summary:
Why the change is required:
<User or system impact, and why the current behavior is inadequate.>
Previous behavior:
<Old contract or mechanism, triggering conditions, and observable result.>
New behavior:
<New contract and externally visible effect.>
Implementation:
<Essential design and code changes; omit routine edit-by-edit narration.>
Safety, compatibility, and limitations:
<Defaults, unaffected paths, compatibility, unsupported cases, and follow-ups.>
Test Plan:
<Exact tests/benchmarks, configurations, what they demonstrate, and gaps.>
```
For a focused bug fix, a cohesive narrative can be clearer than six headings:
problem and root cause, trigger and unaffected cases, fix, then quantified or
counterfactual validation. For a multi-part change, labeled bullets such as
`Fix`, `Add`, and `Change` can make the implementation and review surface easy
to scan.
## Reference Pull Request Summaries
- [PR #15216](https://github.com/meta/rocksdb/pull/15216) is a strong
focused bug-fix example. It traces stale iterator state to readahead behavior
and dropped keys, identifies the exact prerequisites and unaffected mmap
case, gives before/after measurements, and explains how the regression test
fails when the fix is reverted.
- [PR #15218](https://github.com/meta/rocksdb/pull/15218) is a concise
example of explicit `Why`, `Previous behavior`, `New behavior`,
`Implementation`, and `Safety boundaries and unsupported cases` sections.
- Peter Dillinger's [PR #15231](https://github.com/meta/rocksdb/pull/15231)
is a strong recent multi-part example. Its `Fix`/`Add`/`Change` bullets
separate the behaviors, its compatibility sentence explains how old files
read back, and its test plan maps each new persisted property to concrete
coverage while explaining an adjacent test adjustment.
Use these pull requests as models for reasoning, specificity, and evidence. Do
not copy their length or headings mechanically; scale the summary to the risk
and complexity of the change.
-11
View File
@@ -25,8 +25,6 @@ CRASHTEST_PY=$(PYTHON) -u tools/db_crashtest.py --stress_cmd=$(DB_STRESS_CMD) --
crash_test_with_multiops_wup_txn \
crash_test_with_optimistic_txn \
crash_test_with_tiered_storage \
crash_test_with_full_external_table \
liveness_crash_test \
blackbox_crash_test blackbox_crash_test_with_atomic_flush \
blackbox_crash_test_with_wc_txn blackbox_crash_test_with_wp_txn \
blackbox_crash_test_with_wup_txn \
@@ -37,7 +35,6 @@ CRASHTEST_PY=$(PYTHON) -u tools/db_crashtest.py --stress_cmd=$(DB_STRESS_CMD) --
blackbox_crash_test_with_multiops_wup_txn \
blackbox_crash_test_with_optimistic_txn \
blackbox_crash_test_with_tiered_storage \
blackbox_crash_test_with_full_external_table \
whitebox_crash_test whitebox_crash_test_with_atomic_flush \
whitebox_crash_test_with_wc_txn whitebox_crash_test_with_wp_txn \
whitebox_crash_test_with_wup_txn \
@@ -88,8 +85,6 @@ crash_test_with_tiered_storage: $(DB_STRESS_CMD)
$(CRASHTEST_MAKE) whitebox_crash_test_with_tiered_storage
$(CRASHTEST_MAKE) blackbox_crash_test_with_tiered_storage
crash_test_with_full_external_table: blackbox_crash_test_with_full_external_table
crash_test_with_multiops_wc_txn: $(DB_STRESS_CMD)
$(CRASHTEST_MAKE) blackbox_crash_test_with_multiops_wc_txn
@@ -99,9 +94,6 @@ crash_test_with_multiops_wp_txn: $(DB_STRESS_CMD)
crash_test_with_multiops_wup_txn: $(DB_STRESS_CMD)
$(CRASHTEST_MAKE) blackbox_crash_test_with_multiops_wup_txn
liveness_crash_test: $(DB_STRESS_CMD)
$(CRASHTEST_PY) liveness $(CRASH_TEST_EXT_ARGS)
blackbox_crash_test: $(DB_STRESS_CMD)
$(CRASHTEST_PY) --simple blackbox $(CRASH_TEST_EXT_ARGS)
$(CRASHTEST_PY) blackbox $(CRASH_TEST_EXT_ARGS)
@@ -136,9 +128,6 @@ blackbox_crash_test_with_multiops_wup_txn: $(DB_STRESS_CMD)
blackbox_crash_test_with_tiered_storage: $(DB_STRESS_CMD)
$(CRASHTEST_PY) --test_tiered_storage blackbox $(CRASH_TEST_EXT_ARGS)
blackbox_crash_test_with_full_external_table: $(DB_STRESS_CMD)
$(CRASHTEST_PY) --test_full_external_table blackbox $(CRASH_TEST_EXT_ARGS)
blackbox_crash_test_with_optimistic_txn: $(DB_STRESS_CMD)
$(CRASHTEST_PY) --optimistic_txn blackbox $(CRASH_TEST_EXT_ARGS)
-7
View File
@@ -42,13 +42,6 @@ class ArenaWrappedDBIter : public Iterator {
std::unique_ptr<ColumnFamilyData, ColumnFamilyDataUnrefDeleter>;
public:
ArenaWrappedDBIter() = default;
// No copying allowed
ArenaWrappedDBIter(const ArenaWrappedDBIter&) = delete;
ArenaWrappedDBIter& operator=(const ArenaWrappedDBIter&) = delete;
ArenaWrappedDBIter(ArenaWrappedDBIter&&) = delete;
ArenaWrappedDBIter& operator=(ArenaWrappedDBIter&&) = delete;
~ArenaWrappedDBIter() override {
if (db_iter_ != nullptr) {
DestroyDBIter();
-2
View File
@@ -24,8 +24,6 @@ class AttributeGroupIteratorImpl : public AttributeGroupIterator {
AttributeGroupIteratorImpl(const AttributeGroupIteratorImpl&) = delete;
AttributeGroupIteratorImpl& operator=(const AttributeGroupIteratorImpl&) =
delete;
AttributeGroupIteratorImpl(AttributeGroupIteratorImpl&&) = delete;
AttributeGroupIteratorImpl& operator=(AttributeGroupIteratorImpl&&) = delete;
bool Valid() const override { return impl_.Valid(); }
void SeekToFirst() override { impl_.SeekToFirst(); }
-41
View File
@@ -5,9 +5,7 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <limits>
#include "rocksdb/rocksdb_namespace.h"
@@ -33,43 +31,4 @@ constexpr uint64_t kInvalidBlobFileNumber = 0;
constexpr uint64_t kCurrentFileBlobIndexFileNumber = kInvalidBlobFileNumber;
static_assert(kCurrentFileBlobIndexFileNumber == kInvalidBlobFileNumber);
// Sentinel range length meaning "the whole blob value" for the unified
// blob-read primitives, which take a range descriptor {range_offset,
// range_length}; range_length == kWholeBlobLength selects the whole value and
// any other value selects the strict sub-range
// [range_offset, range_offset + range_length).
//
// This is the internal counterpart of the public kLazyWholeColumn
// (include/rocksdb/lazy_wide_columns.h); both are the maximum size_t. The lazy
// resolver does not forward the public value as a range descriptor -- it
// normalizes a whole-column request to kWholeBlobLength -- so the two are not
// required to be equal, but both rely on the sentinel exceeding any real
// blob/column size so a "whole" request is never mistaken for a strict
// sub-range. Keep them in sync.
inline constexpr size_t kWholeBlobLength = std::numeric_limits<size_t>::max();
// How aggressively a blob read verifies the record's checksum. Derived once at
// the lazy read-path boundary (ReadPathBlobResolver) from
// (ReadOptions::verify_checksums, LazyColumnReadRequest::force_verify) and
// threaded down through the blob-read primitives, replacing the earlier mix of
// a verify_checksums bool and a separate force-verify method. (Not literally a
// constant, but kept here as a small shared blob-read enum rather than its own
// header.)
//
// - kVerifyIfPresent: verify the whole-record checksum whenever the format
// provides one, even if ReadOptions::verify_checksums is off. A byte-range
// request under this policy is escalated to a whole-record read (a strict
// sub-range cannot cover the checksum). This is the public force_verify.
// - kVerifyIfNoAmplification: verify when the checksummable unit is already
// being read -- a whole-value read verifies (the check is "free"), while a
// strict sub-range read skips verification rather than amplify the read to
// the whole record. Corresponds to verify_checksums on, force_verify off.
// - kSkip: never verify. Corresponds to verify_checksums off, force_verify
// off.
enum class BlobVerifyPolicy : uint8_t {
kVerifyIfPresent,
kVerifyIfNoAmplification,
kSkip,
};
} // namespace ROCKSDB_NAMESPACE
+1 -30
View File
@@ -6,7 +6,6 @@
#pragma once
#include <cassert>
#include <optional>
#include "db/blob/blob_garbage_meter.h"
#include "rocksdb/rocksdb_namespace.h"
@@ -22,14 +21,10 @@ namespace ROCKSDB_NAMESPACE {
class BlobCountingIterator : public InternalIterator {
public:
BlobCountingIterator(InternalIterator* iter,
BlobGarbageMeter* blob_garbage_meter,
bool track_current_key_inflow)
BlobGarbageMeter* blob_garbage_meter)
: iter_(iter), blob_garbage_meter_(blob_garbage_meter) {
assert(iter_);
assert(blob_garbage_meter_);
if (track_current_key_inflow) {
current_key_inflow_.emplace();
}
UpdateAndCountBlobIfNeeded();
}
@@ -132,47 +127,23 @@ class BlobCountingIterator : public InternalIterator {
return iter_->IsDeleteRangeSentinelKey();
}
const BlobGarbageMeter& GetCurrentKeyInflow() const {
assert(current_key_inflow_.has_value());
return *current_key_inflow_;
}
bool TracksCurrentKeyInflow() const {
return current_key_inflow_.has_value();
}
private:
void UpdateAndCountBlobIfNeeded() {
assert(!iter_->Valid() || iter_->status().ok());
if (!iter_->Valid()) {
status_ = iter_->status();
if (current_key_inflow_.has_value()) {
current_key_inflow_->Clear();
}
return;
}
TEST_SYNC_POINT(
"BlobCountingIterator::UpdateAndCountBlobIfNeeded:ProcessInFlow");
if (current_key_inflow_.has_value()) {
current_key_inflow_->Clear();
TEST_SYNC_POINT(
"BlobCountingIterator::UpdateAndCountBlobIfNeeded:CurrentKeyInflow");
status_ = current_key_inflow_->ProcessInFlow(key(), value());
if (!status_.ok()) {
return;
}
}
TEST_SYNC_POINT(
"BlobCountingIterator::UpdateAndCountBlobIfNeeded:AggregateInflow");
status_ = blob_garbage_meter_->ProcessInFlow(key(), value());
}
InternalIterator* iter_;
BlobGarbageMeter* blob_garbage_meter_;
std::optional<BlobGarbageMeter> current_key_inflow_;
Status status_;
};
+2 -56
View File
@@ -71,8 +71,7 @@ TEST(BlobCountingIteratorTest, CountBlobs) {
VectorIterator input(keys, values);
BlobGarbageMeter blob_garbage_meter;
BlobCountingIterator blob_counter(&input, &blob_garbage_meter,
/*track_current_key_inflow=*/false);
BlobCountingIterator blob_counter(&input, &blob_garbage_meter);
constexpr uint64_t first_expected_bytes =
first_size +
@@ -302,58 +301,6 @@ TEST(BlobCountingIteratorTest, CountBlobs) {
5 * second_expected_bytes);
}
TEST(BlobCountingIteratorTest, TracksCurrentKeyOnlyForResumableCompaction) {
constexpr size_t kNumEntries = 1000;
std::vector<std::string> keys;
std::vector<std::string> values;
keys.reserve(kNumEntries);
values.reserve(kNumEntries);
for (size_t i = 0; i < kNumEntries; ++i) {
keys.push_back(
test::KeyStr("key" + std::to_string(i), i + 1, kTypeBlobIndex));
BlobIndex::EncodeBlob(&values.emplace_back(), /*file_number=*/7,
/*offset=*/i * 100, /*size=*/100, kNoCompression);
}
for (const bool track_current_key : {false, true}) {
VectorIterator input(keys, values);
BlobGarbageMeter blob_garbage_meter;
size_t aggregate_parse_count = 0;
size_t current_key_parse_count = 0;
SyncPoint::GetInstance()->SetCallBack(
"BlobCountingIterator::UpdateAndCountBlobIfNeeded:AggregateInflow",
[&](void*) { ++aggregate_parse_count; });
SyncPoint::GetInstance()->SetCallBack(
"BlobCountingIterator::UpdateAndCountBlobIfNeeded:CurrentKeyInflow",
[&](void*) { ++current_key_parse_count; });
SyncPoint::GetInstance()->EnableProcessing();
BlobCountingIterator blob_counter(&input, &blob_garbage_meter,
track_current_key);
EXPECT_EQ(blob_counter.TracksCurrentKeyInflow(), track_current_key);
const BlobGarbageMeter* current_key_meter =
track_current_key ? &blob_counter.GetCurrentKeyInflow() : nullptr;
for (blob_counter.SeekToFirst(); blob_counter.Valid();
blob_counter.Next()) {
if (track_current_key) {
// This checks reuse of the optional meter object, not allocations made
// by its internal map while processing each key.
EXPECT_EQ(&blob_counter.GetCurrentKeyInflow(), current_key_meter);
}
}
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
ASSERT_OK(blob_counter.status());
EXPECT_EQ(aggregate_parse_count, kNumEntries);
EXPECT_EQ(current_key_parse_count, track_current_key ? kNumEntries : 0);
const auto flow = blob_garbage_meter.flows().find(7);
ASSERT_NE(flow, blob_garbage_meter.flows().end());
EXPECT_EQ(flow->second.GetInFlow().GetCount(), kNumEntries);
}
}
TEST(BlobCountingIteratorTest, CorruptBlobIndex) {
const std::vector<std::string> keys{
test::KeyStr("user_key", 1, kTypeBlobIndex)};
@@ -364,8 +311,7 @@ TEST(BlobCountingIteratorTest, CorruptBlobIndex) {
VectorIterator input(keys, values);
BlobGarbageMeter blob_garbage_meter;
BlobCountingIterator blob_counter(&input, &blob_garbage_meter,
/*track_current_key_inflow=*/false);
BlobCountingIterator blob_counter(&input, &blob_garbage_meter);
blob_counter.SeekToFirst();
ASSERT_FALSE(blob_counter.Valid());
+36 -41
View File
@@ -44,41 +44,43 @@ Status VersionBlobFetcherBase::FetchBlob(const Slice& user_key,
prefetch_buffer, blob_value, bytes_read);
}
Status VersionBlobFetcherBase::FetchBlobRange(
const Slice& user_key, const BlobIndex& blob_index, uint64_t range_offset,
size_t range_length, BlobVerifyPolicy verify_policy,
PinnableSlice* blob_value, uint64_t* bytes_read) const {
if (range_length == kWholeBlobLength) {
// Whole-value read. Delegate to FetchBlob (which handles the direct-write
// fallback). For kVerifyIfPresent, force whole-record checksum verification
// even when ReadOptions::verify_checksums is off, via a transient fetcher
// over a verify-enabled copy of the read options (both locals outlive the
// borrowed-options VersionBlobFetcher).
constexpr FilePrefetchBuffer* prefetch_buffer = nullptr;
if (verify_policy == BlobVerifyPolicy::kVerifyIfPresent &&
!read_options().verify_checksums) {
ReadOptions verify_read_options = read_options();
verify_read_options.verify_checksums = true;
VersionBlobFetcher verifying_fetcher(version_, verify_read_options,
blob_file_cache_,
allow_write_path_fallback_);
return verifying_fetcher.FetchBlob(user_key, blob_index, prefetch_buffer,
blob_value, bytes_read);
}
Status VersionBlobFetcherBase::FetchBlobRange(const Slice& user_key,
const BlobIndex& blob_index,
uint64_t range_offset,
size_t range_length,
PinnableSlice* blob_value,
uint64_t* bytes_read) const {
// Only ever called when SupportsRangeRead() holds (a Version to read through,
// no direct-write fallback); the direct-write path resolves whole records and
// has no range variant.
assert(SupportsRangeRead());
assert(version_);
return version_->GetBlobRange(read_options(), user_key, blob_index,
range_offset, range_length, blob_value,
bytes_read);
}
Status VersionBlobFetcherBase::FetchBlobForceVerify(
const Slice& user_key, const BlobIndex& blob_index,
FilePrefetchBuffer* prefetch_buffer, PinnableSlice* blob_value,
uint64_t* bytes_read) const {
if (read_options().verify_checksums) {
// Verification already happens on the normal path.
return FetchBlob(user_key, blob_index, prefetch_buffer, blob_value,
bytes_read);
}
// Strict sub-range read: value bytes only, no whole-record checksum, no cache
// fill. Only ever chosen when SupportsRangeRead() holds (a Version to read
// through, no direct-write fallback) and verification is not forced (a strict
// sub-range cannot cover the whole-record checksum).
assert(SupportsRangeRead());
assert(version_);
assert(verify_policy != BlobVerifyPolicy::kVerifyIfPresent);
return version_->GetBlobRange(read_options(), user_key, blob_index,
range_offset, range_length, blob_value,
bytes_read);
// Force verification via a transient fetcher over a verify-enabled copy of
// the read options. `verify_read_options` outlives `verifying_fetcher` (both
// are local to this call), so the borrowed-options VersionBlobFetcher is
// safe.
ReadOptions verify_read_options = read_options();
verify_read_options.verify_checksums = true;
VersionBlobFetcher verifying_fetcher(version_, verify_read_options,
blob_file_cache_,
allow_write_path_fallback_);
return verifying_fetcher.FetchBlob(user_key, blob_index, prefetch_buffer,
blob_value, bytes_read);
}
Status EmbeddedAwareBlobFetcher::FetchBlob(const Slice& user_key,
@@ -92,16 +94,9 @@ Status EmbeddedAwareBlobFetcher::FetchBlob(const Slice& user_key,
// Same-file ("embedded") blob records live in the current SST and are read
// by the SameFileBlobReader (the BlockBasedTable). A separate blob-file
// prefetch buffer and bytes_read counter do not apply here (embedded reads
// account their own BLOB_DB_* stats via BlobSource). This is a whole-value
// read; the verify policy follows read_options().verify_checksums (the
// decorator carries no separate force-verify signal).
const BlobVerifyPolicy verify_policy =
read_options().verify_checksums
? BlobVerifyPolicy::kVerifyIfNoAmplification
: BlobVerifyPolicy::kSkip;
return same_file_reader_->GetSameFileBlob(
read_options(), blob_index,
/*range_offset=*/0, kWholeBlobLength, verify_policy, blob_value);
// account their own BLOB_DB_* stats via BlobSource).
return same_file_reader_->GetSameFileBlob(read_options(), blob_index,
blob_value);
}
if (base_ == nullptr) {
+27 -23
View File
@@ -7,7 +7,6 @@
#include <utility>
#include "db/blob/blob_constants.h"
#include "rocksdb/options.h"
#include "rocksdb/status.h"
@@ -64,26 +63,37 @@ class VersionBlobFetcherBase : public BlobFetcher {
PinnableSlice* blob_value,
uint64_t* bytes_read) const override;
// Fetches the value of a separate-file blob reference into *blob_value,
// applying `verify_policy` (see BlobVerifyPolicy). range_length ==
// kWholeBlobLength reads the whole value (delegating to FetchBlob, which also
// handles the direct-write fallback, and forcing whole-record checksum
// verification for kVerifyIfPresent even when ReadOptions::verify_checksums
// is off). Any other length reads only the sub-range [range_offset,
// range_offset + range_length) directly from the blob file, reading just
// those bytes on a cache miss -- no record-header/key read, no whole-record
// checksum, no cache fill (see Version::GetBlobRange /
// BlobSource::GetBlobRange). A strict sub-range is only valid when
// SupportsRangeRead() is true and verify_policy != kVerifyIfPresent
// (verifying a strict sub-range would have to amplify the read to the whole
// record); callers check that first and take the whole-value path otherwise.
// Not routed through the same-file (embedded) decorator -- same-file
// references are resolved by the caller through a SameFileBlobReader.
// Fetches a byte sub-range [range_offset, range_offset + range_length) of an
// *uncompressed*, separate-file blob's value into *blob_value, reading only
// those bytes on a cache miss (see Version::GetBlobRange /
// BlobSource::GetBlobRange). Only valid when SupportsRangeRead() is true (a
// Version to read through and no direct-write fallback); callers check that
// first and take the whole-value FetchBlob path otherwise. Not routed through
// the same-file (embedded) decorator -- same-file range reads are a later
// phase.
Status FetchBlobRange(const Slice& user_key, const BlobIndex& blob_index,
uint64_t range_offset, size_t range_length,
BlobVerifyPolicy verify_policy,
PinnableSlice* blob_value, uint64_t* bytes_read) const;
// Fetches the whole blob with checksum verification forced on, regardless of
// this fetcher's ReadOptions::verify_checksums. Used to honor a lazy read's
// force_verify when verify_checksums is off: the caller wants the record's
// checksum checked even though the global option would skip it. When
// verify_checksums is already on this is just FetchBlob.
//
// TODO(lazy-blob-resolution-cleanup): fold FetchBlobRange /
// FetchBlobForceVerify (and the whole-vs-range GetBlob twins throughout the
// blob stack) into one parameterized primitive taking a BlobReadRequest -- a
// range descriptor ({offset, len}; whole == [0, npos)) plus a 3-valued verify
// policy (must-verify-if-present / verify-if-no-amplification / ignore). That
// retires this method and the range/whole method pairs. See the lazy blob
// resolution plan (deferred for feature velocity).
Status FetchBlobForceVerify(const Slice& user_key,
const BlobIndex& blob_index,
FilePrefetchBuffer* prefetch_buffer,
PinnableSlice* blob_value,
uint64_t* bytes_read) const;
// True if this fetcher has enough context to resolve a (non-inlined,
// non-same-file) blob reference: either a Version to read through, or the
// direct-write fallback (a blob file cache) enabled. Callers that may hold a
@@ -101,12 +111,6 @@ class VersionBlobFetcherBase : public BlobFetcher {
return version_ != nullptr && !allow_write_path_fallback_;
}
// The Version this fetcher reads separate-file blob references through (may
// be null for a direct-write-fallback-only fetcher). Used by the cross-key
// lazy coalescing path to group and dispatch reads via
// Version::MultiGetBlobLazy.
const Version* version() const { return version_; }
protected:
VersionBlobFetcherBase(const Version* version, BlobFileCache* blob_file_cache,
bool allow_write_path_fallback)
+12 -15
View File
@@ -40,16 +40,15 @@ BlobFileBuilder::BlobFileBuilder(
const std::string& column_family_name, Env::WriteLifeTimeHint write_hint,
const std::shared_ptr<IOTracer>& io_tracer,
BlobFileCompletionCallback* blob_callback,
BlobFileCreationReason creation_reason, std::string blob_output_path,
BlobFileCreationReason creation_reason,
std::vector<std::string>* output_file_paths,
std::vector<BlobFileAddition>* blob_file_additions)
: BlobFileBuilder([versions]() { return versions->NewFileNumber(); }, fs,
immutable_options, mutable_cf_options, file_options,
write_options, std::move(db_id), std::move(db_session_id),
job_id, column_family_id, column_family_name, write_hint,
write_options, db_id, db_session_id, job_id,
column_family_id, column_family_name, write_hint,
io_tracer, blob_callback, creation_reason,
std::move(blob_output_path), output_file_paths,
blob_file_additions) {}
output_file_paths, blob_file_additions) {}
BlobFileBuilder::BlobFileBuilder(
std::function<uint64_t()> file_number_generator, FileSystem* fs,
@@ -60,7 +59,7 @@ BlobFileBuilder::BlobFileBuilder(
const std::string& column_family_name, Env::WriteLifeTimeHint write_hint,
const std::shared_ptr<IOTracer>& io_tracer,
BlobFileCompletionCallback* blob_callback,
BlobFileCreationReason creation_reason, std::string blob_output_path,
BlobFileCreationReason creation_reason,
std::vector<std::string>* output_file_paths,
std::vector<BlobFileAddition>* blob_file_additions)
: file_number_generator_(std::move(file_number_generator)),
@@ -91,7 +90,6 @@ BlobFileBuilder::BlobFileBuilder(
io_tracer_(io_tracer),
blob_callback_(blob_callback),
creation_reason_(creation_reason),
blob_output_path_(std::move(blob_output_path)),
output_file_paths_(output_file_paths),
blob_file_additions_(blob_file_additions),
blob_count_(0),
@@ -104,14 +102,16 @@ BlobFileBuilder::BlobFileBuilder(
assert(output_file_paths_);
assert(output_file_paths_->empty());
assert(blob_file_additions_);
// Resumable compactions restore finalized additions before creating a new
// builder. Any newly generated blob files are appended to that state.
assert(blob_file_additions_->empty());
}
BlobFileBuilder::~BlobFileBuilder() = default;
std::string BlobFileBuilder::GetBlobFilePath(uint64_t blob_file_number) const {
return BlobFileName(blob_output_path_, blob_file_number);
assert(immutable_options_);
assert(!immutable_options_->cf_paths.empty());
return BlobFileName(immutable_options_->cf_paths.front().path,
blob_file_number);
}
Status BlobFileBuilder::Add(const Slice& key, const Slice& value,
@@ -194,9 +194,6 @@ Status BlobFileBuilder::OpenBlobFileIfNeeded() {
assert(file_number_generator_);
const uint64_t blob_file_number = file_number_generator_();
if (blob_file_number == kInvalidBlobFileNumber) {
return Status::Incomplete("Blob file number range exhausted");
}
std::string blob_file_path = GetBlobFilePath(blob_file_number);
@@ -356,7 +353,7 @@ Status BlobFileBuilder::CloseBlobFile() {
s = blob_callback_->OnBlobFileCompleted(
GetBlobFilePath(blob_file_number), column_family_name_, job_id_,
blob_file_number, creation_reason_, s, checksum_value, checksum_method,
blob_count_, blob_bytes_, /*set_bg_error_on_space_limit=*/true);
blob_count_, blob_bytes_);
}
assert(blob_file_additions_);
@@ -403,7 +400,7 @@ void BlobFileBuilder::Abandon(const Status& s) {
->OnBlobFileCompleted(GetBlobFilePath(blob_file_number),
column_family_name_, job_id_, blob_file_number,
creation_reason_, s, "", "", blob_count_,
blob_bytes_, /*set_bg_error_on_space_limit=*/true)
blob_bytes_)
.PermitUncheckedError();
}
-5
View File
@@ -48,7 +48,6 @@ class BlobFileBuilder {
const std::shared_ptr<IOTracer>& io_tracer,
BlobFileCompletionCallback* blob_callback,
BlobFileCreationReason creation_reason,
std::string blob_output_path,
std::vector<std::string>* output_file_paths,
std::vector<BlobFileAddition>* blob_file_additions);
@@ -64,14 +63,11 @@ class BlobFileBuilder {
const std::shared_ptr<IOTracer>& io_tracer,
BlobFileCompletionCallback* blob_callback,
BlobFileCreationReason creation_reason,
std::string blob_output_path,
std::vector<std::string>* output_file_paths,
std::vector<BlobFileAddition>* blob_file_additions);
BlobFileBuilder(const BlobFileBuilder&) = delete;
BlobFileBuilder& operator=(const BlobFileBuilder&) = delete;
BlobFileBuilder(BlobFileBuilder&&) = delete;
BlobFileBuilder& operator=(BlobFileBuilder&&) = delete;
~BlobFileBuilder();
@@ -114,7 +110,6 @@ class BlobFileBuilder {
std::shared_ptr<IOTracer> io_tracer_;
BlobFileCompletionCallback* blob_callback_;
BlobFileCreationReason creation_reason_;
const std::string blob_output_path_;
std::vector<std::string>* output_file_paths_;
std::vector<BlobFileAddition>* blob_file_additions_;
std::unique_ptr<BlobLogWriter> writer_;
+7 -14
View File
@@ -148,8 +148,7 @@ TEST_F(BlobFileBuilderTest, BuildAndCheckOneFile) {
&file_options_, &write_options_, "" /*db_id*/, "" /*db_session_id*/,
job_id, column_family_id, column_family_name, write_hint,
nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
BlobFileCreationReason::kFlush, immutable_options.cf_paths.front().path,
&blob_file_paths, &blob_file_additions);
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
std::vector<std::pair<std::string, std::string>> expected_key_value_pairs(
number_of_blobs);
@@ -233,8 +232,7 @@ TEST_F(BlobFileBuilderTest, BuildAndCheckMultipleFiles) {
&file_options_, &write_options_, "" /*db_id*/, "" /*db_session_id*/,
job_id, column_family_id, column_family_name, write_hint,
nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
BlobFileCreationReason::kFlush, immutable_options.cf_paths.front().path,
&blob_file_paths, &blob_file_additions);
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
std::vector<std::pair<std::string, std::string>> expected_key_value_pairs(
number_of_blobs);
@@ -321,8 +319,7 @@ TEST_F(BlobFileBuilderTest, InlinedValues) {
&file_options_, &write_options_, "" /*db_id*/, "" /*db_session_id*/,
job_id, column_family_id, column_family_name, write_hint,
nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
BlobFileCreationReason::kFlush, immutable_options.cf_paths.front().path,
&blob_file_paths, &blob_file_additions);
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
for (size_t i = 0; i < number_of_blobs; ++i) {
const std::string key = std::to_string(i);
@@ -376,8 +373,7 @@ TEST_F(BlobFileBuilderTest, Compression) {
&file_options_, &write_options_, "" /*db_id*/, "" /*db_session_id*/,
job_id, column_family_id, column_family_name, write_hint,
nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
BlobFileCreationReason::kFlush, immutable_options.cf_paths.front().path,
&blob_file_paths, &blob_file_additions);
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
const std::string key("1");
const std::string uncompressed_value(value_size, 'x');
@@ -456,8 +452,7 @@ TEST_F(BlobFileBuilderTest, CompressionError) {
&file_options_, &write_options_, "" /*db_id*/, "" /*db_session_id*/,
job_id, column_family_id, column_family_name, write_hint,
nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
BlobFileCreationReason::kFlush, immutable_options.cf_paths.front().path,
&blob_file_paths, &blob_file_additions);
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
SyncPoint::GetInstance()->SetCallBack(
"LegacyForceBuiltinCompression:TamperWithStatus", [](void* arg) {
@@ -537,8 +532,7 @@ TEST_F(BlobFileBuilderTest, Checksum) {
&file_options_, &write_options_, "" /*db_id*/, "" /*db_session_id*/,
job_id, column_family_id, column_family_name, write_hint,
nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
BlobFileCreationReason::kFlush, immutable_options.cf_paths.front().path,
&blob_file_paths, &blob_file_additions);
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
const std::string key("1");
const std::string value("deadbeef");
@@ -637,8 +631,7 @@ TEST_P(BlobFileBuilderIOErrorTest, IOError) {
&file_options_, &write_options_, "" /*db_id*/, "" /*db_session_id*/,
job_id, column_family_id, column_family_name, write_hint,
nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
BlobFileCreationReason::kFlush, immutable_options.cf_paths.front().path,
&blob_file_paths, &blob_file_additions);
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
SyncPoint::GetInstance()->SetCallBack(sync_point_, [this](void* arg) {
Status* const s = static_cast<Status*>(arg);
+24 -88
View File
@@ -38,14 +38,12 @@ BlobFileCache::BlobFileCache(Cache* cache,
}
Status BlobFileCache::GetBlobFileReader(
const ReadOptions& read_options, const BlobFileOpenInfo& blob_file,
const ReadOptions& read_options, uint64_t blob_file_number,
CacheHandleGuard<BlobFileReader>* blob_file_reader,
bool allow_footer_skip_retry) {
assert(blob_file_reader);
assert(blob_file_reader->IsEmpty());
const uint64_t blob_file_number = blob_file.file_number;
// NOTE: sharing same Cache with table_cache
const Slice key = GetSliceForKey(&blob_file_number);
@@ -76,8 +74,18 @@ Status BlobFileCache::GetBlobFileReader(
std::unique_ptr<BlobFileReader> reader;
{
Status s = OpenBlobFileReader(read_options, blob_file,
allow_footer_skip_retry, &reader);
assert(file_options_);
Status s = BlobFileReader::Create(
*immutable_options_, read_options, *file_options_, column_family_id_,
blob_file_read_hist_, blob_file_number, io_tracer_,
/*skip_footer_validation=*/false, &reader);
if (!s.ok() && s.IsCorruption() && allow_footer_skip_retry) {
reader.reset();
s = BlobFileReader::Create(
*immutable_options_, read_options, *file_options_, column_family_id_,
blob_file_read_hist_, blob_file_number, io_tracer_,
/*skip_footer_validation=*/true, &reader);
}
if (!s.ok()) {
ROCKS_LOG_WARN(immutable_options_->logger,
"BlobFileCache open failed for blob file %" PRIu64
@@ -98,7 +106,7 @@ Status BlobFileCache::GetBlobFileReader(
}
}
[[maybe_unused]] BlobFileReader* released_reader = reader.release();
reader.release();
*blob_file_reader = cache_.Guard(handle);
@@ -106,7 +114,7 @@ Status BlobFileCache::GetBlobFileReader(
}
Status BlobFileCache::OpenBlobFileReaderUncached(
const ReadOptions& read_options, const BlobFileOpenInfo& blob_file,
const ReadOptions& read_options, uint64_t blob_file_number,
std::unique_ptr<BlobFileReader>* blob_file_reader,
bool allow_footer_skip_retry) {
assert(blob_file_reader);
@@ -115,77 +123,21 @@ Status BlobFileCache::OpenBlobFileReaderUncached(
Statistics* const statistics = immutable_options_->stats;
RecordTick(statistics, NO_FILE_OPENS);
Status s = OpenBlobFileReader(read_options, blob_file,
allow_footer_skip_retry, blob_file_reader);
if (!s.ok()) {
RecordTick(statistics, NO_FILE_ERRORS);
}
return s;
}
void BlobFileCache::RegisterBlobFileChecksum(
const BlobFileOpenInfo& blob_file) {
assert(blob_file.file_checksum.empty() ==
blob_file.file_checksum_func_name.empty());
if (blob_file.file_checksum.empty()) {
return;
}
const Slice key = GetSliceForKey(&blob_file.file_number);
MutexLock cache_lock(&mutex_.Get(key));
{
MutexLock checksum_lock(&blob_file_checksums_mutex_);
BlobFileChecksum& checksum = blob_file_checksums_[blob_file.file_number];
checksum.value = blob_file.file_checksum.ToString();
checksum.function_name = blob_file.file_checksum_func_name.ToString();
}
// Readers opened while the file was active did not carry finalized metadata
// and may also have observed a footer-less file size.
cache_.get()->Erase(key);
}
void BlobFileCache::UnregisterBlobFileChecksum(uint64_t blob_file_number) {
MutexLock checksum_lock(&blob_file_checksums_mutex_);
blob_file_checksums_.erase(blob_file_number);
}
Status BlobFileCache::OpenBlobFileReader(
const ReadOptions& read_options, const BlobFileOpenInfo& blob_file,
bool allow_footer_skip_retry,
std::unique_ptr<BlobFileReader>* blob_file_reader) {
assert(file_options_);
assert(blob_file.file_checksum.empty() ==
blob_file.file_checksum_func_name.empty());
FileOptions file_options = *file_options_;
if (!blob_file.file_checksum.empty()) {
file_options.file_checksum = blob_file.file_checksum.ToString();
file_options.file_checksum_func_name =
blob_file.file_checksum_func_name.ToString();
} else {
MutexLock checksum_lock(&blob_file_checksums_mutex_);
const auto it = blob_file_checksums_.find(blob_file.file_number);
if (it != blob_file_checksums_.end()) {
file_options.file_checksum = it->second.value;
file_options.file_checksum_func_name = it->second.function_name;
} else {
file_options.file_checksum.clear();
file_options.file_checksum_func_name.clear();
}
}
Status s = BlobFileReader::Create(
*immutable_options_, read_options, file_options, column_family_id_,
blob_file_read_hist_, blob_file.file_number, io_tracer_,
*immutable_options_, read_options, *file_options_, column_family_id_,
blob_file_read_hist_, blob_file_number, io_tracer_,
/*skip_footer_validation=*/false, blob_file_reader);
if (!s.ok() && s.IsCorruption() && allow_footer_skip_retry) {
blob_file_reader->reset();
s = BlobFileReader::Create(
*immutable_options_, read_options, file_options, column_family_id_,
blob_file_read_hist_, blob_file.file_number, io_tracer_,
*immutable_options_, read_options, *file_options_, column_family_id_,
blob_file_read_hist_, blob_file_number, io_tracer_,
/*skip_footer_validation=*/true, blob_file_reader);
}
if (!s.ok()) {
RecordTick(statistics, NO_FILE_ERRORS);
}
return s;
}
@@ -226,7 +178,7 @@ Status BlobFileCache::InsertBlobFileReader(
}
Status BlobFileCache::RefreshBlobFileReader(
const BlobFileOpenInfo& blob_file,
uint64_t blob_file_number,
std::unique_ptr<BlobFileReader>* blob_file_reader,
CacheHandleGuard<BlobFileReader>* cached_blob_file_reader) {
assert(blob_file_reader);
@@ -234,26 +186,10 @@ Status BlobFileCache::RefreshBlobFileReader(
assert(cached_blob_file_reader);
assert(cached_blob_file_reader->IsEmpty());
const Slice key = GetSliceForKey(&blob_file.file_number);
const Slice key = GetSliceForKey(&blob_file_number);
MutexLock lock(&mutex_.Get(key));
TypedHandle* handle = cache_.Lookup(key);
if (blob_file.file_checksum.empty()) {
MutexLock checksum_lock(&blob_file_checksums_mutex_);
if (blob_file_checksums_.find(blob_file.file_number) !=
blob_file_checksums_.end()) {
// This reader can have been opened just before finalization registered
// checksum metadata. Never let such a racing refresh repopulate the
// cache.
if (handle) {
*cached_blob_file_reader = cache_.Guard(handle);
}
blob_file_reader->reset();
return Status::OK();
}
}
if (handle) {
BlobFileReader* const cached_reader = cache_.Value(handle);
assert(cached_reader != nullptr);
+6 -35
View File
@@ -6,12 +6,9 @@
#pragma once
#include <cinttypes>
#include <string>
#include <unordered_map>
#include "cache/typed_cache.h"
#include "db/blob/blob_file_reader.h"
#include "db/blob/blob_read_request.h"
#include "rocksdb/rocksdb_namespace.h"
#include "util/mutexlock.h"
@@ -34,35 +31,21 @@ class BlobFileCache {
BlobFileCache(const BlobFileCache&) = delete;
BlobFileCache& operator=(const BlobFileCache&) = delete;
BlobFileCache(BlobFileCache&&) = delete;
BlobFileCache& operator=(BlobFileCache&&) = delete;
~BlobFileCache() = default;
// Returns a cached reader for `blob_file`, opening and caching it on miss.
// Checksum metadata is forwarded to the FileSystem through FileOptions when
// the file is opened. If `allow_footer_skip_retry` is true, a
// footer-validation corruption retries once without requiring a footer.
// Returns a cached reader for `blob_file_number`, opening and caching it on
// miss. If `allow_footer_skip_retry` is true, a footer-validation corruption
// retries once without requiring a footer.
Status GetBlobFileReader(const ReadOptions& read_options,
const BlobFileOpenInfo& blob_file,
uint64_t blob_file_number,
CacheHandleGuard<BlobFileReader>* blob_file_reader,
bool allow_footer_skip_retry = false);
// Opens a blob file reader without inserting it into the cache.
Status OpenBlobFileReaderUncached(
const ReadOptions& read_options, const BlobFileOpenInfo& blob_file,
const ReadOptions& read_options, uint64_t blob_file_number,
std::unique_ptr<BlobFileReader>* blob_file_reader,
bool allow_footer_skip_retry = false);
// Makes finalized checksum metadata available to direct-write fallback reads
// whose pinned Version predates the blob-file addition. Any cached
// footer-less reader is evicted as part of the same per-file transition.
void RegisterBlobFileChecksum(const BlobFileOpenInfo& blob_file);
// Stops retaining checksum metadata once no direct-write fallback can refer
// to the file. Manifest-backed callers continue to provide it directly.
void UnregisterBlobFileChecksum(uint64_t blob_file_number);
// Inserts a freshly opened uncached reader unless another thread already
// cached the same blob file.
Status InsertBlobFileReader(
@@ -75,7 +58,7 @@ class BlobFileCache {
// reader instead so a racing refresh cannot reintroduce an older active-file
// view after the blob file grows.
Status RefreshBlobFileReader(
const BlobFileOpenInfo& blob_file,
uint64_t blob_file_number,
std::unique_ptr<BlobFileReader>* blob_file_reader,
CacheHandleGuard<BlobFileReader>* cached_blob_file_reader);
@@ -89,11 +72,6 @@ class BlobFileCache {
}
private:
Status OpenBlobFileReader(const ReadOptions& read_options,
const BlobFileOpenInfo& blob_file,
bool allow_footer_skip_retry,
std::unique_ptr<BlobFileReader>* blob_file_reader);
using CacheInterface =
BasicTypedCacheInterface<BlobFileReader, CacheEntryRole::kMisc>;
using TypedHandle = CacheInterface::TypedHandle;
@@ -107,13 +85,6 @@ class BlobFileCache {
HistogramImpl* blob_file_read_hist_;
std::shared_ptr<IOTracer> io_tracer_;
struct BlobFileChecksum {
std::string value;
std::string function_name;
};
std::unordered_map<uint64_t, BlobFileChecksum> blob_file_checksums_;
mutable port::Mutex blob_file_checksums_mutex_;
static constexpr size_t kNumberOfMutexStripes = 1 << 7;
};
+18 -21
View File
@@ -115,13 +115,13 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader) {
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_options,
&file_options, column_family_id,
blob_file_read_hist, nullptr /*IOTracer*/);
const BlobFileOpenInfo blob_file{blob_file_number, Slice(), Slice()};
// First try: reader should be opened and put in cache
CacheHandleGuard<BlobFileReader> first;
const ReadOptions read_options;
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options, blob_file, &first));
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options, blob_file_number,
&first));
ASSERT_NE(first.GetValue(), nullptr);
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
@@ -129,8 +129,8 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader) {
// Second try: reader should be served from cache
CacheHandleGuard<BlobFileReader> second;
ASSERT_OK(
blob_file_cache.GetBlobFileReader(read_options, blob_file, &second));
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options, blob_file_number,
&second));
ASSERT_NE(second.GetValue(), nullptr);
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
@@ -163,7 +163,6 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_Race) {
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_options,
&file_options, column_family_id,
blob_file_read_hist, nullptr /*IOTracer*/);
const BlobFileOpenInfo blob_file{blob_file_number, Slice(), Slice()};
CacheHandleGuard<BlobFileReader> first;
CacheHandleGuard<BlobFileReader> second;
@@ -173,15 +172,16 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_Race) {
"BlobFileCache::GetBlobFileReader:DoubleCheck", [&](void* /* arg */) {
// Disabling sync points to prevent infinite recursion
SyncPoint::GetInstance()->DisableProcessing();
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options, blob_file,
&second));
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options,
blob_file_number, &second));
ASSERT_NE(second.GetValue(), nullptr);
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
});
SyncPoint::GetInstance()->EnableProcessing();
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options, blob_file, &first));
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options, blob_file_number,
&first));
ASSERT_NE(first.GetValue(), nullptr);
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
@@ -245,22 +245,21 @@ TEST_F(BlobFileCacheTest, RefreshBlobFileReaderPrefersLargestObservedFileSize) {
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_options,
&file_options, column_family_id,
blob_file_read_hist, nullptr /*IOTracer*/);
const BlobFileOpenInfo blob_file{blob_file_number, Slice(), Slice()};
const ReadOptions read_options;
std::unique_ptr<BlobFileReader> stale_reader;
ASSERT_OK(blob_file_cache.OpenBlobFileReaderUncached(
read_options, blob_file, &stale_reader,
read_options, blob_file_number, &stale_reader,
/*allow_footer_skip_retry=*/true));
std::unique_ptr<BlobFileReader> initial_cached_reader;
ASSERT_OK(blob_file_cache.OpenBlobFileReaderUncached(
read_options, blob_file, &initial_cached_reader,
read_options, blob_file_number, &initial_cached_reader,
/*allow_footer_skip_retry=*/true));
CacheHandleGuard<BlobFileReader> cached_reader;
ASSERT_OK(blob_file_cache.RefreshBlobFileReader(
blob_file, &initial_cached_reader, &cached_reader));
blob_file_number, &initial_cached_reader, &cached_reader));
ASSERT_NE(cached_reader.GetValue(), nullptr);
const uint64_t initial_file_size = cached_reader.GetValue()->GetFileSize();
@@ -270,20 +269,20 @@ TEST_F(BlobFileCacheTest, RefreshBlobFileReaderPrefersLargestObservedFileSize) {
std::unique_ptr<BlobFileReader> fresh_reader;
ASSERT_OK(blob_file_cache.OpenBlobFileReaderUncached(
read_options, blob_file, &fresh_reader,
read_options, blob_file_number, &fresh_reader,
/*allow_footer_skip_retry=*/true));
ASSERT_GT(fresh_reader->GetFileSize(), initial_file_size);
CacheHandleGuard<BlobFileReader> refreshed_reader;
ASSERT_OK(blob_file_cache.RefreshBlobFileReader(blob_file, &fresh_reader,
&refreshed_reader));
ASSERT_OK(blob_file_cache.RefreshBlobFileReader(
blob_file_number, &fresh_reader, &refreshed_reader));
ASSERT_NE(refreshed_reader.GetValue(), nullptr);
ASSERT_GT(refreshed_reader.GetValue()->GetFileSize(), initial_file_size);
BlobFileReader* const largest_reader = refreshed_reader.GetValue();
CacheHandleGuard<BlobFileReader> preserved_reader;
ASSERT_OK(blob_file_cache.RefreshBlobFileReader(blob_file, &stale_reader,
&preserved_reader));
ASSERT_OK(blob_file_cache.RefreshBlobFileReader(
blob_file_number, &stale_reader, &preserved_reader));
ASSERT_NE(preserved_reader.GetValue(), nullptr);
ASSERT_EQ(preserved_reader.GetValue(), largest_reader);
ASSERT_EQ(stale_reader.get(), nullptr);
@@ -313,13 +312,12 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_IOError) {
// Note: there is no blob file with the below number
constexpr uint64_t blob_file_number = 123;
const BlobFileOpenInfo blob_file{blob_file_number, Slice(), Slice()};
CacheHandleGuard<BlobFileReader> reader;
const ReadOptions read_options;
ASSERT_TRUE(
blob_file_cache.GetBlobFileReader(read_options, blob_file, &reader)
blob_file_cache.GetBlobFileReader(read_options, blob_file_number, &reader)
.IsIOError());
ASSERT_EQ(reader.GetValue(), nullptr);
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
@@ -354,7 +352,6 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_CacheFull) {
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_options,
&file_options, column_family_id,
blob_file_read_hist, nullptr /*IOTracer*/);
const BlobFileOpenInfo blob_file{blob_file_number, Slice(), Slice()};
// Insert into cache should fail since it has zero capacity and
// strict_capacity_limit is set
@@ -362,7 +359,7 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_CacheFull) {
const ReadOptions read_options;
ASSERT_TRUE(
blob_file_cache.GetBlobFileReader(read_options, blob_file, &reader)
blob_file_cache.GetBlobFileReader(read_options, blob_file_number, &reader)
.IsMemoryLimit());
ASSERT_EQ(reader.GetValue(), nullptr);
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
+15 -62
View File
@@ -12,7 +12,6 @@
#include "db/event_helpers.h"
#include "file/sst_file_manager_impl.h"
#include "rocksdb/status.h"
#include "util/cast_util.h"
namespace ROCKSDB_NAMESPACE {
@@ -39,12 +38,14 @@ class BlobFileCompletionCallback {
job_id, creation_reason);
}
Status OnBlobFileCompleted(
const std::string& file_name, const std::string& column_family_name,
int job_id, uint64_t file_number, BlobFileCreationReason creation_reason,
const Status& report_status, const std::string& checksum_value,
const std::string& checksum_method, uint64_t blob_count,
uint64_t blob_bytes, bool set_bg_error_on_space_limit) {
Status OnBlobFileCompleted(const std::string& file_name,
const std::string& column_family_name, int job_id,
uint64_t file_number,
BlobFileCreationReason creation_reason,
const Status& report_status,
const std::string& checksum_value,
const std::string& checksum_method,
uint64_t blob_count, uint64_t blob_bytes) {
Status s;
auto sfm = static_cast<SstFileManagerImpl*>(sst_file_manager_);
@@ -55,72 +56,24 @@ class BlobFileCompletionCallback {
s = Status::SpaceLimit("Max allowed space was reached");
TEST_SYNC_POINT(
"BlobFileCompletionCallback::CallBack::MaxAllowedSpaceReached");
if (set_bg_error_on_space_limit) {
// This callback historically reports space limits as flush errors.
SetBGError(s);
}
InstrumentedMutexLock l(mutex_);
error_handler_->SetBGError(s, BackgroundErrorReason::kFlush);
}
}
NotifyCreationFinished(
file_name, column_family_name, job_id, file_number, creation_reason,
(!report_status.ok() ? report_status : s), checksum_value,
checksum_method, blob_count, blob_bytes);
return s;
}
// For a complete blob file that is kept even when the space limit is
// reached. The file is registered with the SstFileManager at `file_size`,
// which cannot fail, and its creation is reported as successful. Returns
// whether the max allowed space is reached; the caller sets the background
// error, because this does not acquire the DB mutex.
bool OnKeptBlobFileCompleted(
const std::string& file_name, const std::string& column_family_name,
int job_id, uint64_t file_number, BlobFileCreationReason creation_reason,
const std::string& checksum_value, const std::string& checksum_method,
uint64_t blob_count, uint64_t blob_bytes, uint64_t file_size) {
bool space_limit_reached = false;
SstFileManagerImpl* sfm =
static_cast_with_check<SstFileManagerImpl>(sst_file_manager_);
if (sfm) {
sfm->OnAddFile(file_name, file_size).PermitUncheckedError();
space_limit_reached = sfm->IsMaxAllowedSpaceReached();
if (space_limit_reached) {
TEST_SYNC_POINT(
"BlobFileCompletionCallback::CallBack::MaxAllowedSpaceReached");
}
}
NotifyCreationFinished(file_name, column_family_name, job_id, file_number,
creation_reason, Status::OK(), checksum_value,
checksum_method, blob_count, blob_bytes);
return space_limit_reached;
}
void SetBGError(const Status& s) {
InstrumentedMutexLock l(mutex_);
error_handler_->SetBGError(s, BackgroundErrorReason::kFlush);
}
private:
void NotifyCreationFinished(const std::string& file_name,
const std::string& column_family_name, int job_id,
uint64_t file_number,
BlobFileCreationReason creation_reason,
const Status& status,
const std::string& checksum_value,
const std::string& checksum_method,
uint64_t blob_count, uint64_t blob_bytes) {
// Notify the listeners.
EventHelpers::LogAndNotifyBlobFileCreationFinished(
event_logger_, listeners_, dbname_, column_family_name, file_name,
job_id, file_number, creation_reason, status,
job_id, file_number, creation_reason,
(!report_status.ok() ? report_status : s),
(checksum_value.empty() ? kUnknownFileChecksum : checksum_value),
(checksum_method.empty() ? kUnknownFileChecksumFuncName
: checksum_method),
blob_count, blob_bytes);
return s;
}
private:
SstFileManager* sst_file_manager_;
InstrumentedMutex* mutex_;
ErrorHandler* error_handler_;
-4
View File
@@ -52,8 +52,6 @@ class SharedBlobFileMetaData {
SharedBlobFileMetaData(SharedBlobFileMetaData&&) = delete;
SharedBlobFileMetaData& operator=(SharedBlobFileMetaData&&) = delete;
~SharedBlobFileMetaData() = default;
uint64_t GetBlobFileSize() const;
uint64_t GetBlobFileNumber() const { return blob_file_number_; }
uint64_t GetTotalBlobCount() const { return total_blob_count_; }
@@ -112,8 +110,6 @@ class BlobFileMetaData {
BlobFileMetaData(BlobFileMetaData&&) = delete;
BlobFileMetaData& operator=(BlobFileMetaData&&) = delete;
~BlobFileMetaData() = default;
const std::shared_ptr<SharedBlobFileMetaData>& GetSharedMeta() const {
return shared_meta_;
}
+50 -125
View File
@@ -27,7 +27,6 @@
#include "rocksdb/file_system.h"
#include "util/aligned_buffer.h"
#include "util/compression.h"
#include "util/defer.h"
#include "util/mutexlock.h"
namespace ROCKSDB_NAMESPACE {
@@ -159,12 +158,6 @@ BlobFilePartitionManager::~BlobFilePartitionManager() {
}
}
BlobFilePartitionManager::DeferredFile::~DeferredFile() {
// Only a later flush of the generation reads `seal_status`, and the DB may
// close before one runs.
seal_status.PermitUncheckedError();
}
void BlobFilePartitionManager::ResetPartitionState(Partition* partition) {
partition->writer.reset();
partition->file_number = 0;
@@ -178,19 +171,6 @@ void BlobFilePartitionManager::ResetPartitionState(Partition* partition) {
partition->sync_required = false;
}
BlobFilePartitionManager::DeferredFile
BlobFilePartitionManager::DetachPartitionFile(Partition* partition) {
DeferredFile deferred;
deferred.writer = std::move(partition->writer);
deferred.file_number = partition->file_number;
deferred.blob_count = partition->blob_count;
deferred.total_blob_bytes = partition->total_blob_bytes;
deferred.garbage_blob_count = partition->garbage_blob_count;
deferred.garbage_blob_bytes = partition->garbage_blob_bytes;
ResetPartitionState(partition);
return deferred;
}
void BlobFilePartitionManager::AddFilePartitionMapping(uint64_t file_number,
uint32_t partition_idx) {
WriteLock lock(&file_partition_mutex_);
@@ -275,7 +255,7 @@ Status BlobFilePartitionManager::OpenNewBlobFile(Partition* partition,
Status BlobFilePartitionManager::SealActiveBlobFile(
const WriteOptions& write_options, Partition* partition,
SealedFile* sealed_file, bool* space_limit_reached) {
SealedFile* sealed_file) {
assert(partition != nullptr);
assert(partition->writer);
assert(sealed_file != nullptr);
@@ -284,8 +264,10 @@ Status BlobFilePartitionManager::SealActiveBlobFile(
Status s =
FinalizeBlobFile(write_options, partition->writer.get(), file_number,
partition->blob_count, partition->total_blob_bytes,
&sealed_file->addition, space_limit_reached);
&sealed_file->addition);
if (!s.ok()) {
RemoveFilePartitionMapping(file_number);
ResetPartitionState(partition);
return s;
}
@@ -302,15 +284,11 @@ Status BlobFilePartitionManager::SealDeferredFile(
assert(deferred->writer);
assert(sealed_file != nullptr);
// As for its SST file, the flush sets a reached space limit as the
// background error after it commits. An error set earlier would make the
// flush roll back instead.
bool space_limit_reached = false;
Status s = FinalizeBlobFile(write_options, deferred->writer.get(),
deferred->file_number, deferred->blob_count,
deferred->total_blob_bytes,
&sealed_file->addition, &space_limit_reached);
Status s = FinalizeBlobFile(
write_options, deferred->writer.get(), deferred->file_number,
deferred->blob_count, deferred->total_blob_bytes, &sealed_file->addition);
if (!s.ok()) {
RemoveFilePartitionMapping(deferred->file_number);
return s;
}
@@ -323,10 +301,9 @@ Status BlobFilePartitionManager::SealDeferredFile(
Status BlobFilePartitionManager::FinalizeBlobFile(
const WriteOptions& write_options, BlobLogWriter* writer,
uint64_t file_number, uint64_t blob_count, uint64_t total_blob_bytes,
BlobFileAddition* addition, bool* space_limit_reached) {
BlobFileAddition* addition) {
assert(writer != nullptr);
assert(addition != nullptr);
assert(space_limit_reached != nullptr);
BlobLogFooter footer;
footer.blob_count = blob_count;
@@ -340,24 +317,19 @@ Status BlobFilePartitionManager::FinalizeBlobFile(
}
if (blob_callback_ != nullptr) {
// `mutex_` is held, and the DB mutex must not be acquired under it.
const std::string blob_file_path = BlobFileName(db_path_, file_number);
if (blob_callback_->OnKeptBlobFileCompleted(
blob_file_path, column_family_name_, /*job_id=*/0, file_number,
BlobFileCreationReason::kFlush, checksum_value, checksum_method,
blob_count, total_blob_bytes, writer->get_file_size())) {
*space_limit_reached = true;
s = blob_callback_->OnBlobFileCompleted(
blob_file_path, column_family_name_, /*job_id=*/0, file_number,
BlobFileCreationReason::kFlush, s, checksum_value, checksum_method,
blob_count, total_blob_bytes);
if (!s.ok()) {
return s;
}
}
*addition =
BlobFileAddition(file_number, blob_count, total_blob_bytes,
std::move(checksum_method), std::move(checksum_value));
if (blob_file_cache_ != nullptr) {
const BlobFileOpenInfo blob_file{file_number, addition->GetChecksumValue(),
addition->GetChecksumMethod()};
blob_file_cache_->RegisterBlobFileChecksum(blob_file);
}
return Status::OK();
}
@@ -500,15 +472,6 @@ Status BlobFilePartitionManager::WriteBlob(
value) %
num_partitions_);
bool space_limit_reached = false;
// Setting the background error takes the DB mutex, so it must run after
// `mutex_` is released.
Defer set_space_limit_error([&]() {
if (space_limit_reached) {
blob_callback_->SetBGError(
Status::SpaceLimit("Max allowed space was reached"));
}
});
{
MutexLock lock(&mutex_);
Partition* partition = partitions_[selected_partition_idx].get();
@@ -518,16 +481,11 @@ Status BlobFilePartitionManager::WriteBlob(
return Status::OK();
}
SealedFile sealed_file;
Status s = SealActiveBlobFile(write_options, partition, &sealed_file,
&space_limit_reached);
if (!s.ok()) {
DeferredFile failed = DetachPartitionFile(partition);
failed.seal_status = s;
current_generation_failed_files_.push_back(std::move(failed));
return s;
Status s = SealActiveBlobFile(write_options, partition, &sealed_file);
if (s.ok()) {
current_generation_sealed_files_.push_back(std::move(sealed_file));
}
current_generation_sealed_files_.push_back(std::move(sealed_file));
return Status::OK();
return s;
};
const uint64_t record_size =
@@ -580,41 +538,34 @@ Status BlobFilePartitionManager::WriteBlob(
return Status::OK();
}
void BlobFilePartitionManager::RotateCurrentGeneration(uint64_t memtable_id) {
void BlobFilePartitionManager::RotateCurrentGeneration() {
MutexLock lock(&mutex_);
GenerationBatch batch;
batch.memtable_id = memtable_id;
batch.sealed_files = std::move(current_generation_sealed_files_);
current_generation_sealed_files_.clear();
for (auto& failed : current_generation_failed_files_) {
batch.deferred_files.emplace_back(std::move(failed));
}
current_generation_failed_files_.clear();
for (auto& partition : partitions_) {
if (!partition->writer) {
continue;
}
batch.deferred_files.emplace_back(DetachPartitionFile(partition.get()));
DeferredFile deferred;
deferred.writer = std::move(partition->writer);
deferred.file_number = partition->file_number;
deferred.blob_count = partition->blob_count;
deferred.total_blob_bytes = partition->total_blob_bytes;
deferred.garbage_blob_count = partition->garbage_blob_count;
deferred.garbage_blob_bytes = partition->garbage_blob_bytes;
ResetPartitionState(partition.get());
batch.deferred_files.emplace_back(std::move(deferred));
}
pending_generations_.emplace_back(std::move(batch));
}
BlobFilePartitionManager::GenerationBatch*
BlobFilePartitionManager::FindPendingGeneration(uint64_t memtable_id) {
for (auto& batch : pending_generations_) {
if (batch.memtable_id == memtable_id) {
return &batch;
}
}
return nullptr;
}
Status BlobFilePartitionManager::PrepareFlushAdditions(
const WriteOptions& write_options,
const std::vector<uint64_t>& memtable_ids,
const WriteOptions& write_options, size_t num_generations,
std::vector<BlobFileAddition>* additions,
std::vector<BlobFileGarbage>* garbages,
std::vector<std::vector<uint64_t>>* generation_blob_file_numbers) {
@@ -627,26 +578,22 @@ Status BlobFilePartitionManager::PrepareFlushAdditions(
}
MutexLock lock(&mutex_);
for (const uint64_t memtable_id : memtable_ids) {
GenerationBatch* const batch_ptr = FindPendingGeneration(memtable_id);
if (batch_ptr == nullptr) {
return Status::Corruption(
"Missing blob direct write generation metadata for flush");
}
GenerationBatch& batch = *batch_ptr;
if (num_generations > pending_generations_.size()) {
return Status::Corruption(
"Missing blob direct write generation metadata for flush");
}
for (size_t i = 0; i < num_generations; ++i) {
GenerationBatch& batch = pending_generations_[i];
while (!batch.deferred_files.empty()) {
DeferredFile& deferred = batch.deferred_files.front();
if (!deferred.seal_status.ok()) {
return deferred.seal_status;
}
DeferredFile deferred = std::move(batch.deferred_files.front());
batch.deferred_files.pop_front();
SealedFile sealed_file;
Status s = SealDeferredFile(write_options, &deferred, &sealed_file);
if (!s.ok()) {
deferred.seal_status = s;
return s;
}
batch.deferred_files.pop_front();
// Keep each successfully sealed file attached to the generation
// immediately. If a later seal or the flush job itself fails, retry must
// reuse these exact on-disk files instead of finalizing replacements.
@@ -694,12 +641,6 @@ Status BlobFilePartitionManager::MarkBlobWriteAsGarbage(uint64_t file_number,
return Status::OK();
}
for (auto& failed : current_generation_failed_files_) {
if (MarkDeferredFileGarbage(&failed, file_number, blob_count, blob_bytes)) {
return Status::OK();
}
}
for (auto& batch : pending_generations_) {
for (auto& deferred : batch.deferred_files) {
if (MarkDeferredFileGarbage(&deferred, file_number, blob_count,
@@ -722,22 +663,12 @@ Status BlobFilePartitionManager::MarkBlobWriteAsGarbage(uint64_t file_number,
return Status::Corruption(message);
}
void BlobFilePartitionManager::CommitFlushedGenerations(
uint64_t earliest_unflushed_memtable_id) {
std::vector<uint64_t> sealed_file_numbers;
{
MutexLock lock(&mutex_);
while (!pending_generations_.empty() &&
pending_generations_.front().memtable_id <
earliest_unflushed_memtable_id) {
for (const auto& sealed_file :
pending_generations_.front().sealed_files) {
sealed_file_numbers.push_back(sealed_file.addition.GetBlobFileNumber());
}
pending_generations_.pop_front();
}
void BlobFilePartitionManager::CommitPreparedGenerations(
size_t num_generations) {
MutexLock lock(&mutex_);
while (num_generations-- > 0 && !pending_generations_.empty()) {
pending_generations_.pop_front();
}
RemoveFilePartitionMappings(sealed_file_numbers);
}
Status BlobFilePartitionManager::SyncAllOpenFiles(
@@ -822,9 +753,6 @@ void BlobFilePartitionManager::UnprotectSealedBlobFileNumbers(
// manifest-visible path. Evict it here so delayed protection does not
// leave an obsolete blob reader behind until DB close.
blob_file_cache_->Evict(file_number);
if (file_to_partition_.find(file_number) == file_to_partition_.end()) {
blob_file_cache_->UnregisterBlobFileChecksum(file_number);
}
}
}
}
@@ -844,10 +772,6 @@ void BlobFilePartitionManager::RemoveFilePartitionMappings(
// this file before it was sealed. Drop it now so future manifest-visible
// reads reopen against the finalized on-disk size and footer state.
blob_file_cache_->Evict(file_number);
if (protected_blob_file_refs_.find(file_number) ==
protected_blob_file_refs_.end()) {
blob_file_cache_->UnregisterBlobFileChecksum(file_number);
}
}
}
}
@@ -885,9 +809,9 @@ Status BlobFilePartitionManager::ResolveBlobDirectWriteIndex(
}
Status s;
const BlobFileOpenInfo blob_file{blob_idx.file_number(), Slice(), Slice()};
CacheHandleGuard<BlobFileReader> reader;
s = blob_file_cache->GetBlobFileReader(read_options, blob_file, &reader,
s = blob_file_cache->GetBlobFileReader(read_options, blob_idx.file_number(),
&reader,
/*allow_footer_skip_retry=*/true);
if (!s.ok()) {
return s;
@@ -912,7 +836,7 @@ Status BlobFilePartitionManager::ResolveBlobDirectWriteIndex(
std::unique_ptr<BlobFileReader> fresh_reader;
s = blob_file_cache->OpenBlobFileReaderUncached(
read_options, blob_file, &fresh_reader,
read_options, blob_idx.file_number(), &fresh_reader,
/*allow_footer_skip_retry=*/true);
if (!s.ok()) {
return s;
@@ -926,7 +850,8 @@ Status BlobFilePartitionManager::ResolveBlobDirectWriteIndex(
if (s.ok()) {
blob_value->PinSelf(fresh_contents->data());
CacheHandleGuard<BlobFileReader> ignored;
blob_file_cache->RefreshBlobFileReader(blob_file, &fresh_reader, &ignored)
blob_file_cache
->RefreshBlobFileReader(blob_idx.file_number(), &fresh_reader, &ignored)
.PermitUncheckedError();
}
+17 -42
View File
@@ -88,31 +88,27 @@ class BlobFilePartitionManager {
const Slice& key,
const WideColumns& columns) const;
// Move the current active partition files into the generation of the
// memtable `memtable_id`, which just became immutable. Called from
// SwitchMemtable() while DB mutex is held. No I/O is performed here.
void RotateCurrentGeneration(uint64_t memtable_id);
// Move the current active partition files into the next immutable
// memtable-generation batch. Called from SwitchMemtable() while DB mutex is
// held. No I/O is performed here.
void RotateCurrentGeneration();
// Seal the queued generations of the memtables `memtable_ids` and return all
// blob additions and initial-garbage updates that must be registered with
// the flush of those memtables. Concurrent flushes pick different
// memtables, so generations are matched by memtable ID, not by queue
// position. Generations stay queued until CommitFlushedGenerations() removes
// them. If `generation_blob_file_numbers` is non-null, it receives the
// sealed blob file numbers for each memtable, in `memtable_ids` order.
// Seal the first `num_generations` queued immutable generations and return
// all blob additions and initial-garbage updates that must be registered
// with the matching flush. Generations stay queued until
// CommitPreparedGenerations() is called. If `generation_blob_file_numbers`
// is non-null, it receives the sealed blob file numbers for each prepared
// generation in the same FIFO order.
Status PrepareFlushAdditions(const WriteOptions& write_options,
const std::vector<uint64_t>& memtable_ids,
size_t num_generations,
std::vector<BlobFileAddition>* additions,
std::vector<BlobFileGarbage>* garbages,
std::vector<std::vector<uint64_t>>*
generation_blob_file_numbers = nullptr);
// Remove the generations of the memtables older than
// `earliest_unflushed_memtable_id`, whose flush results are installed, and
// stop tracking their sealed files as manager-owned. A flush that finishes
// before an older one is installed with it, or rolled back if it fails, so
// the flush that prepared a generation cannot remove it on its own.
void CommitFlushedGenerations(uint64_t earliest_unflushed_memtable_id);
// Remove the first `num_generations` prepared immutable generations after
// their blob-file additions and garbage edits were committed to MANIFEST.
void CommitPreparedGenerations(size_t num_generations);
// In v1 flush-on-write mode each AddRecord flushes to the OS immediately, so
// there is nothing extra to do for the non-sync write path.
@@ -183,11 +179,6 @@ class BlobFilePartitionManager {
};
struct DeferredFile {
DeferredFile() = default;
DeferredFile(DeferredFile&&) = default;
DeferredFile& operator=(DeferredFile&&) = default;
~DeferredFile();
// Blob writer moved out of an active partition at memtable rotation.
std::unique_ptr<BlobLogWriter> writer;
// Metadata preserved until flush preparation seals the file.
@@ -197,10 +188,6 @@ class BlobFilePartitionManager {
// Failed transformed writes already appended before the file is sealed.
uint64_t garbage_blob_count = 0;
uint64_t garbage_blob_bytes = 0;
// Set once sealing fails. The writer cannot be finalized again, and
// memtables still reference the file, so every flush of its generation
// fails with this status.
Status seal_status;
};
struct SealedFile {
@@ -213,8 +200,6 @@ class BlobFilePartitionManager {
};
struct GenerationBatch {
// The memtable that became immutable when this generation was rotated.
uint64_t memtable_id = 0;
// Files that were still open when the memtable became immutable.
std::deque<DeferredFile> deferred_files;
// Files already sealed for this memtable generation.
@@ -223,24 +208,17 @@ class BlobFilePartitionManager {
// Clears all active-file bookkeeping from a partition after sealing.
void ResetPartitionState(Partition* partition);
// Returns the queued generation of `memtable_id`, or null.
GenerationBatch* FindPendingGeneration(uint64_t memtable_id);
// Moves a partition's open file out of the partition and resets it.
DeferredFile DetachPartitionFile(Partition* partition);
// Marks a blob file as manager-owned until it is committed or abandoned.
void AddFilePartitionMapping(uint64_t file_number, uint32_t partition_idx);
// Stops tracking a blob file after open failure.
// Stops tracking a blob file after open/seal failure.
void RemoveFilePartitionMapping(uint64_t file_number);
// Opens a new active blob file for one partition.
Status OpenNewBlobFile(Partition* partition, uint32_t column_family_id,
CompressionType compression, uint32_t partition_idx);
// Finalizes one active partition file and returns the MANIFEST addition.
// Sets `*space_limit_reached` if the SstFileManager's max allowed space is
// reached; the seal still succeeds.
Status SealActiveBlobFile(const WriteOptions& write_options,
Partition* partition, SealedFile* sealed_file,
bool* space_limit_reached);
Partition* partition, SealedFile* sealed_file);
// Finalizes one deferred file that was carried over to flush preparation.
Status SealDeferredFile(const WriteOptions& write_options,
DeferredFile* deferred, SealedFile* sealed_file);
@@ -249,8 +227,7 @@ class BlobFilePartitionManager {
Status FinalizeBlobFile(const WriteOptions& write_options,
BlobLogWriter* writer, uint64_t file_number,
uint64_t blob_count, uint64_t total_blob_bytes,
BlobFileAddition* addition,
bool* space_limit_reached);
BlobFileAddition* addition);
// Appends one initial-garbage record to the flush output if any failed
// transformed writes targeted the sealed blob file.
static void AddSealedFileGarbage(const SealedFile& sealed_file,
@@ -309,8 +286,6 @@ class BlobFilePartitionManager {
mutable port::Mutex mutex_;
// Files already sealed while the current mutable memtable was active.
std::vector<SealedFile> current_generation_sealed_files_;
// Files whose seal failed while the current mutable memtable was active.
std::vector<DeferredFile> current_generation_failed_files_;
// FIFO immutable memtable generations waiting to be flushed.
std::deque<GenerationBatch> pending_generations_;
+14 -211
View File
@@ -278,31 +278,25 @@ Status BlobFileReader::ReadFromFile(const RandomAccessFileReader* file_reader,
return s;
}
FSReadRequest read_req;
read_req.offset = read_offset;
read_req.len = read_size;
if (file_reader->use_direct_io()) {
read_req.scratch = nullptr;
constexpr char* scratch = nullptr;
AlignedBufferAllocationContext direct_io_context{direct_io_buffer};
file_reader->Read(io_options, &read_req, &direct_io_context, &dbg);
s = file_reader->Read(io_options, read_offset, read_size, slice, scratch,
&direct_io_context, &dbg);
} else {
buf->reset(new char[read_size]);
read_req.scratch = buf->get();
file_reader->Read(io_options, &read_req, nullptr, &dbg);
s = file_reader->Read(io_options, read_offset, read_size, slice, buf->get(),
nullptr, &dbg);
}
s = std::move(read_req.status);
if (!s.ok()) {
return s;
}
*slice = read_req.result;
if (slice->size() != read_size) {
return Status::Corruption("Incomplete blob read from " +
file_reader->file_name() + " at offset " +
std::to_string(read_offset) + ": expected " +
std::to_string(read_size) + " bytes, got " +
std::to_string(slice->size()));
return Status::Corruption("Failed to read data from blob file");
}
return Status::OK();
@@ -336,10 +330,7 @@ Status BlobFileReader::GetBlob(
if (!IsValidBlobOffset(offset, key_size, value_size, file_size_,
has_footer_)) {
return Status::Corruption(
"Invalid blob offset " + std::to_string(offset) + " for value size " +
std::to_string(value_size) + " in " + file_reader_->file_name() +
" (file size " + std::to_string(file_size_) + ")");
return Status::Corruption("Invalid blob offset");
}
if (compression_type != compression_type_) {
@@ -447,18 +438,12 @@ Status BlobFileReader::GetBlobRange(const ReadOptions& read_options,
// performs); this guards the sub-range read below.
if (!IsValidBlobOffset(offset, key_size, value_size, file_size_,
has_footer_)) {
return Status::Corruption(
"Invalid blob offset " + std::to_string(offset) + " for value size " +
std::to_string(value_size) + " in " + file_reader_->file_name() +
" (file size " + std::to_string(file_size_) + ")");
return Status::Corruption("Invalid blob offset");
}
// The requested sub-range must lie within the value.
if (range_offset > value_size || range_length > value_size - range_offset) {
return Status::InvalidArgument(
"Blob range [" + std::to_string(range_offset) + ", +" +
std::to_string(range_length) + ") out of bounds for value size " +
std::to_string(value_size) + " in " + file_reader_->file_name());
return Status::InvalidArgument("Blob range out of bounds");
}
// Read only the requested bytes, at the value's file position plus the range
@@ -533,10 +518,7 @@ void BlobFileReader::MultiGetBlob(
if (!IsValidBlobOffset(offset, key_size, value_size, file_size_,
has_footer_)) {
*req->status = Status::Corruption(
"Invalid blob offset " + std::to_string(offset) + " for value size " +
std::to_string(value_size) + " in " + file_reader_->file_name() +
" (file size " + std::to_string(file_size_) + ")");
*req->status = Status::Corruption("Invalid blob offset");
continue;
}
if (req->compression != compression_type_) {
@@ -630,11 +612,8 @@ void BlobFileReader::MultiGetBlob(
auto& read_req = read_reqs[j++];
const auto& record_slice = read_req.result;
if (read_req.status.ok() && record_slice.size() != read_req.len) {
read_req.status = IOStatus::Corruption(
"Incomplete blob read from " + file_reader_->file_name() +
" at offset " + std::to_string(read_req.offset) + ": expected " +
std::to_string(read_req.len) + " bytes, got " +
std::to_string(record_slice.size()));
read_req.status =
IOStatus::Corruption("Failed to read data from blob file");
}
*req->status = read_req.status;
@@ -665,182 +644,6 @@ void BlobFileReader::MultiGetBlob(
}
}
void BlobFileReader::MultiGetBlobRange(
const ReadOptions& read_options,
autovector<std::pair<BlobRangeReadRequest*, std::unique_ptr<BlobContents>>>&
blob_reqs,
uint64_t* bytes_read) const {
const size_t num_blobs = blob_reqs.size();
assert(num_blobs > 0);
assert(num_blobs <= MultiGetContext::MAX_BATCH_SIZE);
// Range reads are only issued for uncompressed blob references (a strict
// sub-range of a compressed record cannot be decompressed in isolation).
// Version::MultiGetBlobLazy rejects a compressed blob index before it reaches
// here, so every request's blob index is uncompressed by contract; a
// compressed blob file therefore means the blob index disagrees with the
// file. Report that as the compression-type mismatch it is -- exactly like
// the whole-value MultiGetBlob path -- rather than as a usage error. (Unlike
// the single-read path, BlobSource has no per-request compression to check
// for a range read, so this reader-level check is the range multi path's
// compression backstop.)
if (compression_type_ != kNoCompression) {
for (auto& blob_req : blob_reqs) {
assert(blob_req.first);
assert(blob_req.first->status);
*blob_req.first->status =
Status::Corruption("Compression type mismatch when reading a blob");
}
return;
}
#ifndef NDEBUG
for (size_t i = 0; i < num_blobs - 1; ++i) {
assert(blob_reqs[i].first->offset + blob_reqs[i].first->range_offset <=
blob_reqs[i + 1].first->offset +
blob_reqs[i + 1].first->range_offset);
}
#endif // !NDEBUG
std::vector<FSReadRequest> read_reqs;
uint64_t total_len = 0;
read_reqs.reserve(num_blobs);
for (size_t i = 0; i < num_blobs; ++i) {
BlobRangeReadRequest* const req = blob_reqs[i].first;
assert(req);
assert(req->status);
const size_t key_size = req->user_key.size();
// Validate that the full value region is within the file (same check
// GetBlob performs); this guards the sub-range read below.
if (!IsValidBlobOffset(req->offset, key_size, req->value_size, file_size_,
has_footer_)) {
*req->status = Status::Corruption(
"Invalid blob offset " + std::to_string(req->offset) +
" for value size " + std::to_string(req->value_size) + " in " +
file_reader_->file_name() + " (file size " +
std::to_string(file_size_) + ")");
continue;
}
// The requested sub-range must lie within the value.
if (req->range_offset > req->value_size ||
req->range_length > req->value_size - req->range_offset) {
*req->status = Status::InvalidArgument(
"Blob range [" + std::to_string(req->range_offset) + ", +" +
std::to_string(req->range_length) +
") out of bounds for value size " + std::to_string(req->value_size) +
" in " + file_reader_->file_name());
continue;
}
// Read only the requested bytes, at the value's file position plus the
// range offset -- no record-header adjustment, no whole-record checksum.
FSReadRequest read_req;
read_req.offset = req->offset + req->range_offset;
read_req.len = req->range_length;
total_len += read_req.len;
read_reqs.emplace_back(std::move(read_req));
}
RecordTick(statistics_, BLOB_DB_BLOB_FILE_BYTES_READ, total_len);
if (read_reqs.empty()) {
if (bytes_read) {
*bytes_read = 0;
}
return;
}
Buffer buf;
AlignedBuffer direct_io_buffer;
Status s;
bool direct_io = file_reader_->use_direct_io();
if (direct_io) {
for (size_t i = 0; i < read_reqs.size(); ++i) {
read_reqs[i].scratch = nullptr;
}
} else {
buf.reset(new char[total_len]);
std::ptrdiff_t pos = 0;
for (size_t i = 0; i < read_reqs.size(); ++i) {
read_reqs[i].scratch = buf.get() + pos;
pos += read_reqs[i].len;
}
}
TEST_SYNC_POINT("BlobFileReader::MultiGetBlobRange:ReadFromFile");
PERF_COUNTER_ADD(blob_read_count, num_blobs);
PERF_COUNTER_ADD(blob_read_byte, total_len);
IOOptions opts;
IODebugContext dbg;
s = file_reader_->PrepareIOOptions(read_options, opts, &dbg);
if (s.ok()) {
AlignedBufferAllocationContext direct_io_context{&direct_io_buffer};
s = file_reader_->MultiRead(opts, read_reqs.data(), read_reqs.size(),
&direct_io_context, &dbg);
}
if (!s.ok()) {
// Batch read failed; s is written to every request's status below, so these
// per-request FS statuses carry nothing extra.
for (auto& req : read_reqs) {
req.status.PermitUncheckedError();
}
for (auto& blob_req : blob_reqs) {
BlobRangeReadRequest* const req = blob_req.first;
assert(req);
assert(req->status);
if (!req->status->IsCorruption() && !req->status->IsInvalidArgument()) {
// Avoid overwriting per-request validation errors.
*req->status = s;
}
}
return;
}
assert(s.ok());
TEST_SYNC_POINT_CALLBACK("BlobFileReader::MultiGetBlobRange:TamperWithResult",
&read_reqs);
uint64_t total_bytes = 0;
for (size_t i = 0, j = 0; i < num_blobs; ++i) {
BlobRangeReadRequest* const req = blob_reqs[i].first;
assert(req);
assert(req->status);
if (!req->status->ok()) {
continue;
}
assert(j < read_reqs.size());
auto& read_req = read_reqs[j++];
const Slice& range_slice = read_req.result;
if (read_req.status.ok() && range_slice.size() != read_req.len) {
read_req.status = IOStatus::Corruption(
"Incomplete blob range read from " + file_reader_->file_name() +
" at offset " + std::to_string(read_req.offset) + ": expected " +
std::to_string(read_req.len) + " bytes, got " +
std::to_string(range_slice.size()));
}
*req->status = read_req.status;
if (!req->status->ok()) {
continue;
}
// The blob is uncompressed, so the bytes read are exactly the requested
// value bytes; copy them into an owned BlobContents (no decompression).
BlobContentsCreator::Create(&blob_reqs[i].second, /*out_charge=*/nullptr,
range_slice, kNoCompression,
/*allocator=*/nullptr);
total_bytes += range_slice.size();
}
if (bytes_read) {
*bytes_read = total_bytes;
}
}
Status BlobFileReader::VerifyBlob(const Slice& record_slice,
const Slice& user_key, uint64_t value_size) {
PERF_TIMER_GUARD(blob_checksum_time);
-17
View File
@@ -53,8 +53,6 @@ class BlobFileReader {
BlobFileReader(const BlobFileReader&) = delete;
BlobFileReader& operator=(const BlobFileReader&) = delete;
BlobFileReader(BlobFileReader&&) = delete;
BlobFileReader& operator=(BlobFileReader&&) = delete;
~BlobFileReader();
@@ -90,21 +88,6 @@ class BlobFileReader {
blob_reqs,
uint64_t* bytes_read) const;
// Byte-range (partial) multi-read counterpart of MultiGetBlob: for each
// request reads only its sub-range [offset + range_offset, +range_length) of
// an *uncompressed* blob value, coalescing all requests into a single
// MultiRead. Unlike MultiGetBlob it reads no record header/key, verifies no
// whole-record checksum (a strict sub-range cannot cover it), and never
// decompresses -- so it is only valid when this reader's blob file is
// uncompressed (checked). Each result owns a copy of its requested bytes.
// Requests must be sorted ascending by effective read offset (offset +
// range_offset) by the caller.
void MultiGetBlobRange(
const ReadOptions& read_options,
autovector<std::pair<BlobRangeReadRequest*,
std::unique_ptr<BlobContents>>>& blob_reqs,
uint64_t* bytes_read) const;
CompressionType GetCompressionType() const { return compression_type_; }
uint64_t GetFileSize() const { return file_size_; }
-23
View File
@@ -59,14 +59,6 @@ class BlobGarbageMeter {
out_flow_.Add(bytes);
assert(IsValid());
}
void AddOutFlow(uint64_t count, uint64_t bytes) {
out_flow_.Add(count, bytes);
assert(IsValid());
}
void AddGarbage(uint64_t count, uint64_t bytes) {
in_flow_.Add(count, bytes);
assert(IsValid());
}
const BlobStats& GetInFlow() const { return in_flow_; }
const BlobStats& GetOutFlow() const { return out_flow_; }
@@ -98,21 +90,6 @@ class BlobGarbageMeter {
Status ProcessInFlow(const Slice& key, const Slice& value);
Status ProcessOutFlow(const Slice& key, const Slice& value);
void ProcessOutFlow(const BlobGarbageMeter& inflow) {
for (const auto& [file_number, flow] : inflow.flows()) {
const BlobStats& stats = flow.GetInFlow();
flows_[file_number].AddOutFlow(stats.GetCount(), stats.GetBytes());
}
}
// Imports a garbage delta computed by another compaction process.
void AddGarbage(uint64_t blob_file_number, uint64_t count, uint64_t bytes) {
// A garbage delta is equivalent to inflow with no matching outflow.
flows_[blob_file_number].AddGarbage(count, bytes);
}
void Clear() { flows_.clear(); }
const std::unordered_map<uint64_t, BlobInOutFlow>& flows() const {
return flows_;
}
+21 -296
View File
@@ -5,11 +5,9 @@
#include "db/blob/blob_gen2_format.h"
#include <algorithm>
#include <array>
#include <cstring>
#include <string>
#include <vector>
#include "file/random_access_file_reader.h"
#include "file/writable_file_writer.h"
@@ -18,56 +16,9 @@
#include "table/format.h"
#include "util/cast_util.h"
#include "util/coding.h"
#include "util/crc32c.h"
namespace ROCKSDB_NAMESPACE {
namespace {
// Verifies a SimpleGen2Blob record's builtin checksum: the stored 4-byte
// checksum (at record[payload_size + 1 ..], context-modified by the record's
// file offset) against the checksum computed over the payload plus its 1-byte
// compression marker. Returns OK, or a Corruption carrying debugging
// information -- stored (with the per-offset context modifier removed),
// computed, and checksum type, with CRC values unmasked so they can be compared
// against a reference. Mirrors VerifyBlockChecksum() for the block-based
// format, since a SimpleGen2Blob record's trailer uses the same
// builtin-checksum scheme; shared by the scalar and batched readers so both
// report the same diagnostics.
Status VerifySimpleGen2BlobChecksum(ChecksumType checksum_type,
uint32_t base_context_checksum,
const char* record, size_t payload_size,
uint64_t record_offset,
const std::string& file_name) {
uint32_t stored = DecodeFixed32(record + payload_size + 1);
const uint32_t modifier =
ChecksumModifierForContext(base_context_checksum, record_offset);
stored -= modifier;
const uint32_t computed = ComputeBuiltinChecksumWithLastByte(
checksum_type, record, payload_size, record[payload_size]);
if (stored == computed) {
return Status::OK();
}
// Unmask CRC values (as VerifyBlockChecksum does) so a reader of the error
// can compare against a reference checksum.
uint32_t stored_for_msg = stored;
uint32_t computed_for_msg = computed;
if (checksum_type == kCRC32c) {
stored_for_msg = crc32c::Unmask(stored);
computed_for_msg = crc32c::Unmask(computed);
}
return Status::Corruption("SimpleGen2 blob record checksum mismatch: stored" +
std::string(modifier ? "(context removed)" : "") +
" = " + std::to_string(stored_for_msg) +
", computed = " + std::to_string(computed_for_msg) +
", type = " + std::to_string(checksum_type) +
" in " + file_name + " offset " +
std::to_string(record_offset) + " size " +
std::to_string(payload_size));
}
} // namespace
Status ReadAndVerifySimpleGen2BlobRecord(
const ReadOptions& read_options, RandomAccessFileReader* file,
uint64_t record_offset, size_t payload_size, size_t record_size,
@@ -82,17 +33,14 @@ Status ReadAndVerifySimpleGen2BlobRecord(
IODebugContext dbg;
Status s = file->PrepareIOOptions(read_options, opts, &dbg);
if (s.ok()) {
s = file->Read(record_offset, record_size, opts, &result, buf, &dbg);
s = file->Read(opts, record_offset, record_size, &result, buf, nullptr,
&dbg);
}
if (!s.ok()) {
return s;
}
if (result.size() != record_size) {
return Status::Corruption("Incomplete blob record read from " +
file->file_name() + " at offset " +
std::to_string(record_offset) + ": expected " +
std::to_string(record_size) + " bytes, got " +
std::to_string(result.size()));
return Status::Corruption("Could not read complete blob record");
}
// With mmap reads the data lands outside `buf`; copy it in so the caller can
// rely on `buf` owning the bytes (this is the only copy on the mmap path).
@@ -106,32 +54,24 @@ Status ReadAndVerifySimpleGen2BlobRecord(
static_cast<CompressionType>(record[payload_size]);
if (compression != expected_compression) {
return Status::Corruption(
"Blob record compression mismatch in " + file->file_name() +
" at offset " + std::to_string(record_offset) + ": index expects " +
std::to_string(static_cast<int>(expected_compression)) +
", record has " + std::to_string(static_cast<int>(compression)));
"Blob record compression does not match blob index");
}
if (read_options.verify_checksums) {
const Status checksum_status = VerifySimpleGen2BlobChecksum(
checksum_type, base_context_checksum, record, payload_size,
record_offset, file->file_name());
if (!checksum_status.ok()) {
return checksum_status;
uint32_t stored = DecodeFixed32(record + payload_size + 1);
stored -= ChecksumModifierForContext(base_context_checksum, record_offset);
const uint32_t computed = ComputeBuiltinChecksumWithLastByte(
checksum_type, record, payload_size, record[payload_size]);
if (stored != computed) {
return Status::Corruption("Blob record checksum mismatch in " +
file->file_name() + " offset " +
std::to_string(record_offset) + " size " +
std::to_string(payload_size));
}
}
// The compression marker matched the blob index above, but SimpleGen2Blob
// payloads are currently written uncompressed only (see
// WriteSimpleGen2BlobRecord), so a record that actually carries a compression
// type is not readable here. This guards a corrupt or forward-incompatible
// record whose (compressed) marker happened to agree with a (compressed)
// index.
if (compression != kNoCompression) {
return Status::Corruption("Blob record compression " +
std::to_string(static_cast<int>(compression)) +
" is not supported in " + file->file_name() +
" at offset " + std::to_string(record_offset));
return Status::Corruption("Blob record compression is not supported");
}
return Status::OK();
}
@@ -148,21 +88,14 @@ Status ReadSimpleGen2BlobRange(const ReadOptions& read_options,
// A strict sub-range of a compressed payload cannot be decompressed in
// isolation; callers resolve such payloads whole and slice instead.
if (expected_compression != kNoCompression) {
return Status::Corruption(
"Cannot range-read a compressed blob (compression " +
std::to_string(static_cast<int>(expected_compression)) + ") in " +
file->file_name() + " at offset " + std::to_string(record_offset));
return Status::Corruption("Cannot range-read a compressed blob");
}
// The requested sub-range must lie within the payload (the trailer that
// follows the payload is never part of a range read).
if (range_offset > payload_size ||
range_length > payload_size - range_offset) {
return Status::InvalidArgument(
"Blob range [" + std::to_string(range_offset) + ", +" +
std::to_string(range_length) + ") out of bounds for payload size " +
std::to_string(payload_size) + " in " + file->file_name() +
" at offset " + std::to_string(record_offset));
return Status::InvalidArgument("Blob range out of bounds");
}
Slice result;
@@ -170,18 +103,14 @@ Status ReadSimpleGen2BlobRange(const ReadOptions& read_options,
IODebugContext dbg;
Status s = file->PrepareIOOptions(read_options, opts, &dbg);
if (s.ok()) {
s = file->Read(record_offset + range_offset, range_length, opts, &result,
buf, &dbg);
s = file->Read(opts, record_offset + range_offset, range_length, &result,
buf, nullptr, &dbg);
}
if (!s.ok()) {
return s;
}
if (result.size() != range_length) {
return Status::Corruption("Incomplete blob range read from " +
file->file_name() + " at offset " +
std::to_string(record_offset + range_offset) +
": expected " + std::to_string(range_length) +
" bytes, got " + std::to_string(result.size()));
return Status::Corruption("Could not read complete blob range");
}
// With mmap reads the data lands outside `buf`; copy it in so the caller can
// rely on `buf` owning the bytes (the only copy on the mmap path).
@@ -195,210 +124,6 @@ Status ReadSimpleGen2BlobRange(const ReadOptions& read_options,
return Status::OK();
}
namespace {
// One record's read description for the batched SimpleGen2Blob readers below:
// where/how much to read, where to put it, and where to report the outcome.
struct Gen2ReadSlot {
uint64_t offset = 0;
size_t len = 0;
char* buf = nullptr;
Status* status = nullptr;
};
// Issues a single MultiRead for the records described by `slots` and copies
// each result into its buf, setting *slots[i].status. Shared by the
// whole-record and range batch readers; per-record format checks (compression
// marker / checksum) are done by the whole-record caller afterwards from the
// (now populated) buf.
void MultiReadGen2(const ReadOptions& read_options,
RandomAccessFileReader* file,
std::vector<Gen2ReadSlot>& slots) {
assert(file != nullptr);
assert(!slots.empty());
// RandomAccessFileReader::MultiRead requires the requests sorted ascending by
// file offset -- it asserts this, and in direct-I/O mode it aligns and merges
// only consecutive requests, so unsorted input reads the wrong bytes. The
// callers group records by SST but not by offset, so sort here. Each slot
// carries its own output buf/status pointer, so sorting does not disturb
// result routing.
std::sort(slots.begin(), slots.end(),
[](const Gen2ReadSlot& a, const Gen2ReadSlot& b) {
return a.offset < b.offset;
});
const size_t num = slots.size();
std::vector<FSReadRequest> read_reqs;
read_reqs.reserve(num);
for (size_t i = 0; i < num; ++i) {
FSReadRequest read_req;
read_req.offset = slots[i].offset;
read_req.len = slots[i].len;
read_reqs.emplace_back(std::move(read_req));
}
AlignedBuffer direct_io_buffer;
const bool direct_io = file->use_direct_io();
if (direct_io) {
for (size_t i = 0; i < num; ++i) {
read_reqs[i].scratch = nullptr;
}
} else {
for (size_t i = 0; i < num; ++i) {
read_reqs[i].scratch = slots[i].buf;
}
}
IOOptions opts;
IODebugContext dbg;
Status s = file->PrepareIOOptions(read_options, opts, &dbg);
if (s.ok()) {
AlignedBufferAllocationContext direct_io_context{&direct_io_buffer};
s = file->MultiRead(opts, read_reqs.data(), read_reqs.size(),
&direct_io_context, &dbg);
}
if (!s.ok()) {
// Batch read failed; s is written to every slot's status below, so these
// per-request FS statuses carry nothing extra.
for (auto& read_req : read_reqs) {
read_req.status.PermitUncheckedError();
}
for (size_t i = 0; i < num; ++i) {
*slots[i].status = s;
}
return;
}
for (size_t i = 0; i < num; ++i) {
FSReadRequest& read_req = read_reqs[i];
if (read_req.status.ok() && read_req.result.size() != read_req.len) {
read_req.status = IOStatus::Corruption(
"Incomplete blob record read from " + file->file_name() +
" at offset " + std::to_string(read_req.offset) + ": expected " +
std::to_string(read_req.len) + " bytes, got " +
std::to_string(read_req.result.size()));
}
*slots[i].status = read_req.status;
if (!slots[i].status->ok()) {
continue;
}
// With mmap/direct reads the data lands outside the caller's buf; copy it
// in so the caller can rely on buf owning the bytes.
if (read_req.result.data() != slots[i].buf) {
memcpy(slots[i].buf, read_req.result.data(), read_req.len);
}
}
}
} // namespace
void ReadAndVerifySimpleGen2BlobRecords(const ReadOptions& read_options,
RandomAccessFileReader* file,
ChecksumType checksum_type,
uint32_t base_context_checksum,
size_t num_records,
SimpleGen2RecordReadRequest* reqs) {
assert(file != nullptr);
assert(num_records > 0);
std::vector<Gen2ReadSlot> slots;
slots.reserve(num_records);
for (size_t i = 0; i < num_records; ++i) {
assert(reqs[i].buf != nullptr);
assert(reqs[i].status != nullptr);
assert(reqs[i].record_size ==
reqs[i].payload_size + kSimpleGen2BlobTrailerSize);
slots.push_back({reqs[i].record_offset, reqs[i].record_size, reqs[i].buf,
reqs[i].status});
}
MultiReadGen2(read_options, file, slots);
for (size_t i = 0; i < num_records; ++i) {
if (!reqs[i].status->ok()) {
continue;
}
const char* record = reqs[i].buf;
const size_t payload_size = reqs[i].payload_size;
const CompressionType compression =
static_cast<CompressionType>(record[payload_size]);
if (compression != reqs[i].expected_compression) {
*reqs[i].status = Status::Corruption(
"Blob record compression mismatch in " + file->file_name() +
" at offset " + std::to_string(reqs[i].record_offset) +
": index expects " +
std::to_string(static_cast<int>(reqs[i].expected_compression)) +
", record has " + std::to_string(static_cast<int>(compression)));
continue;
}
if (read_options.verify_checksums) {
*reqs[i].status = VerifySimpleGen2BlobChecksum(
checksum_type, base_context_checksum, record, payload_size,
reqs[i].record_offset, file->file_name());
if (!reqs[i].status->ok()) {
continue;
}
}
// See ReadAndVerifySimpleGen2BlobRecord: the marker matched the index, but
// only uncompressed SimpleGen2Blob payloads are currently supported.
if (compression != kNoCompression) {
*reqs[i].status = Status::Corruption(
"Blob record compression " +
std::to_string(static_cast<int>(compression)) +
" is not supported in " + file->file_name() + " at offset " +
std::to_string(reqs[i].record_offset));
continue;
}
}
}
void ReadSimpleGen2BlobRanges(const ReadOptions& read_options,
RandomAccessFileReader* file, size_t num_records,
SimpleGen2RangeReadRequest* reqs) {
assert(file != nullptr);
assert(num_records > 0);
std::vector<Gen2ReadSlot> slots;
slots.reserve(num_records);
for (size_t i = 0; i < num_records; ++i) {
assert(reqs[i].buf != nullptr);
assert(reqs[i].status != nullptr);
// A strict sub-range of a compressed payload cannot be decompressed in
// isolation; callers resolve such payloads whole and slice instead.
if (reqs[i].expected_compression != kNoCompression) {
*reqs[i].status = Status::Corruption(
"Cannot range-read a compressed blob (compression " +
std::to_string(static_cast<int>(reqs[i].expected_compression)) +
") in " + file->file_name() + " at offset " +
std::to_string(reqs[i].record_offset));
continue;
}
// The requested sub-range must lie within the payload (the trailer that
// follows the payload is never part of a range read).
if (reqs[i].range_offset > reqs[i].payload_size ||
reqs[i].range_length > reqs[i].payload_size - reqs[i].range_offset) {
*reqs[i].status = Status::InvalidArgument(
"Blob range [" + std::to_string(reqs[i].range_offset) + ", +" +
std::to_string(reqs[i].range_length) +
") out of bounds for payload size " +
std::to_string(reqs[i].payload_size) + " in " + file->file_name() +
" at offset " + std::to_string(reqs[i].record_offset));
continue;
}
slots.push_back({reqs[i].record_offset + reqs[i].range_offset,
reqs[i].range_length, reqs[i].buf, reqs[i].status});
}
if (slots.empty()) {
return;
}
// slots only covers requests that passed validation above; a failed
// validation left *reqs[i].status non-OK and is skipped here.
MultiReadGen2(read_options, file, slots);
}
IOStatus WriteSimpleGen2BlobRecord(WritableFileWriter* file,
const WriteOptions& write_options,
ChecksumType checksum_type,
@@ -423,12 +148,12 @@ IOStatus WriteSimpleGen2BlobRecord(WritableFileWriter* file,
return io_s;
}
if (!payload.empty()) {
io_s = file->Append(payload, opts);
io_s = file->Append(opts, payload);
if (!io_s.ok()) {
return io_s;
}
}
return file->Append(Slice(trailer.data(), trailer.size()), opts);
return file->Append(opts, Slice(trailer.data(), trailer.size()));
}
} // namespace ROCKSDB_NAMESPACE
-51
View File
@@ -91,57 +91,6 @@ Status ReadSimpleGen2BlobRange(const ReadOptions& read_options,
uint64_t range_offset, size_t range_length,
CompressionType expected_compression, char* buf);
// One whole-record request in a batched SimpleGen2Blob read (see
// ReadAndVerifySimpleGen2BlobRecords). Mirrors the scalar
// ReadAndVerifySimpleGen2BlobRecord inputs, with the read bytes going into
// caller-provided `buf` (capacity >= record_size) and the per-record outcome in
// `*status`.
struct SimpleGen2RecordReadRequest {
uint64_t record_offset = 0;
size_t payload_size = 0;
size_t record_size = 0; // == payload_size + kSimpleGen2BlobTrailerSize
CompressionType expected_compression = kNoCompression;
char* buf = nullptr;
Status* status = nullptr;
};
// One sub-range request in a batched SimpleGen2Blob range read (see
// ReadSimpleGen2BlobRanges). Mirrors the scalar ReadSimpleGen2BlobRange inputs.
struct SimpleGen2RangeReadRequest {
uint64_t record_offset = 0;
size_t payload_size = 0;
uint64_t range_offset = 0;
size_t range_length = 0;
CompressionType expected_compression = kNoCompression;
char* buf = nullptr;
Status* status = nullptr;
};
// Batch counterpart of ReadAndVerifySimpleGen2BlobRecord: reads `num_records`
// whole records from `file` in a single MultiRead, then per record verifies the
// compression marker and (when read_options.verify_checksums) the
// context-modified checksum, copying the record into reqs[i].buf. Per-record
// outcome is written to *reqs[i].status; on success reqs[i].buf[0,
// payload_size) holds the verified payload (the trailer sits unused at the
// tail). Requests may be given in any order; they are sorted internally, as
// RandomAccessFileReader::MultiRead requires ascending offsets.
void ReadAndVerifySimpleGen2BlobRecords(const ReadOptions& read_options,
RandomAccessFileReader* file,
ChecksumType checksum_type,
uint32_t base_context_checksum,
size_t num_records,
SimpleGen2RecordReadRequest* reqs);
// Batch counterpart of ReadSimpleGen2BlobRange: reads the requested sub-range
// of `num_records` *uncompressed* payloads from `file` in a single MultiRead
// (no trailer read, no checksum). Per-record outcome is in *reqs[i].status; on
// success reqs[i].buf[0, range_length) holds the requested bytes. Requests may
// be given in any order; they are sorted internally, as
// RandomAccessFileReader::MultiRead requires ascending offsets.
void ReadSimpleGen2BlobRanges(const ReadOptions& read_options,
RandomAccessFileReader* file, size_t num_records,
SimpleGen2RangeReadRequest* reqs);
// Writes a SimpleGen2Blob record for `payload` at the current end of `file`,
// which the caller asserts is byte offset `record_offset`. Appends the payload
// bytes followed by the 5-byte trailer (compression marker + context-modified
+1 -7
View File
@@ -55,10 +55,6 @@ class BlobIndex {
BlobIndex(const BlobIndex&) = default;
BlobIndex& operator=(const BlobIndex&) = default;
BlobIndex(BlobIndex&&) = default;
BlobIndex& operator=(BlobIndex&&) = default;
~BlobIndex() = default;
bool IsInlined() const { return type_ == Type::kInlinedTTL; }
@@ -106,9 +102,7 @@ class BlobIndex {
Status DecodeFrom(Slice slice) {
const char* kErrorMessage = "Error while decoding blob index";
if (slice.empty()) {
return Status::Corruption(kErrorMessage, "Empty blob index");
}
assert(slice.size() > 0);
type_ = static_cast<Type>(*slice.data());
if (type_ >= Type::kUnknown) {
return Status::Corruption(kErrorMessage,
+1 -1
View File
@@ -29,7 +29,7 @@ Status BlobLogSequentialReader::ReadSlice(uint64_t size, Slice* slice,
StopWatch read_sw(clock_, statistics_, BLOB_DB_BLOB_FILE_READ_MICROS);
// TODO: rate limit `BlobLogSequentialReader` reads (it appears unused?)
Status s = file_->Read(next_byte_, static_cast<size_t>(size), IOOptions(),
Status s = file_->Read(IOOptions(), next_byte_, static_cast<size_t>(size),
slice, buf, nullptr);
next_byte_ += size;
if (!s.ok()) {
-2
View File
@@ -43,8 +43,6 @@ class BlobLogSequentialReader {
// No copying allowed
BlobLogSequentialReader(const BlobLogSequentialReader&) = delete;
BlobLogSequentialReader& operator=(const BlobLogSequentialReader&) = delete;
BlobLogSequentialReader(BlobLogSequentialReader&&) = delete;
BlobLogSequentialReader& operator=(BlobLogSequentialReader&&) = delete;
~BlobLogSequentialReader();
+6 -6
View File
@@ -40,7 +40,7 @@ Status BlobLogWriter::Sync(const WriteOptions& write_options) {
IOOptions opts;
Status s = WritableFileWriter::PrepareIOOptions(write_options, opts);
if (s.ok()) {
s = use_fsync_ ? dest_->Fsync(opts) : dest_->Sync(opts);
s = dest_->Sync(opts, use_fsync_);
}
if (s.ok()) {
RecordTick(statistics_, BLOB_DB_BLOB_FILE_SYNCED);
@@ -58,7 +58,7 @@ Status BlobLogWriter::WriteHeader(const WriteOptions& write_options,
IOOptions opts;
Status s = WritableFileWriter::PrepareIOOptions(write_options, opts);
if (s.ok()) {
s = dest_->Append(Slice(str), opts);
s = dest_->Append(opts, Slice(str));
}
if (s.ok()) {
block_offset_ += str.size();
@@ -92,7 +92,7 @@ Status BlobLogWriter::AppendFooter(const WriteOptions& write_options,
IOOptions opts;
s = WritableFileWriter::PrepareIOOptions(write_options, opts);
if (s.ok()) {
s = dest_->Append(Slice(str), opts);
s = dest_->Append(opts, Slice(str));
}
if (s.ok()) {
block_offset_ += str.size();
@@ -181,13 +181,13 @@ Status BlobLogWriter::EmitPhysicalRecord(const WriteOptions& write_options,
IOOptions opts;
Status s = WritableFileWriter::PrepareIOOptions(write_options, opts);
if (s.ok()) {
s = dest_->Append(Slice(headerbuf), opts);
s = dest_->Append(opts, Slice(headerbuf));
}
if (s.ok()) {
s = dest_->Append(key, opts);
s = dest_->Append(opts, key);
}
if (s.ok()) {
s = dest_->Append(val, opts);
s = dest_->Append(opts, val);
}
if (do_flush_ && s.ok()) {
s = dest_->Flush(opts);
-4
View File
@@ -37,8 +37,6 @@ class BlobLogWriter {
// No copying allowed
BlobLogWriter(const BlobLogWriter&) = delete;
BlobLogWriter& operator=(const BlobLogWriter&) = delete;
BlobLogWriter(BlobLogWriter&&) = delete;
BlobLogWriter& operator=(BlobLogWriter&&) = delete;
~BlobLogWriter();
@@ -70,8 +68,6 @@ class BlobLogWriter {
uint64_t get_log_number() const { return log_number_; }
uint64_t get_file_size() const { return block_offset_; }
Status Sync(const WriteOptions& write_options);
private:
+1 -59
View File
@@ -14,21 +14,10 @@
namespace ROCKSDB_NAMESPACE {
// Non-owning metadata needed when opening a blob file for reading. The
// checksum slices must remain valid while this object is in use. A file without
// finalized checksum metadata, such as an active direct-write blob file, uses
// empty slices for both checksum fields.
struct BlobFileOpenInfo {
uint64_t file_number = 0;
Slice file_checksum;
Slice file_checksum_func_name;
};
// A read Blob request structure for use in BlobSource::MultiGetBlob and
// BlobFileReader::MultiGetBlob.
struct BlobReadRequest {
// User key to lookup the paired blob
// TODO: consider simplifying to copied Slice?
const Slice* user_key = nullptr;
// File offset in bytes
@@ -60,57 +49,10 @@ struct BlobReadRequest {
BlobReadRequest() = default;
BlobReadRequest(const BlobReadRequest& other) = default;
BlobReadRequest& operator=(const BlobReadRequest& other) = default;
BlobReadRequest(BlobReadRequest&& other) = default;
BlobReadRequest& operator=(BlobReadRequest&& other) = default;
~BlobReadRequest() = default;
};
using BlobFileReadRequests =
std::tuple<BlobFileOpenInfo, uint64_t /* file_size */,
std::tuple<uint64_t /* file_number */, uint64_t /* file_size */,
autovector<BlobReadRequest>>;
// A byte-range (partial) blob read request for the lazy blob-read multi
// primitives (BlobFileReader::MultiGetBlobRange /
// BlobSource::MultiGetBlobRange). Unlike BlobReadRequest (whole value), it
// reads only the sub-range [range_offset, range_offset + range_length) of an
// *uncompressed* blob value, skipping whole-record checksum verification and
// blob-cache population -- the multi-read counterpart of
// BlobSource::GetBlobRange / BlobFileReader::GetBlobRange.
struct BlobRangeReadRequest {
// User key that pairs with the blob value (used for offset validation).
Slice user_key;
// The blob value's file offset and full (logical == on-disk, uncompressed)
// size, from the BlobIndex.
uint64_t offset = 0;
uint64_t value_size = 0;
// The requested sub-range within the value. `range_length` is the number of
// bytes to read; the caller must ensure range_offset + range_length <=
// value_size (it clamps before building the request).
uint64_t range_offset = 0;
size_t range_length = 0;
// Output parameter set to point to the requested bytes, and the read status.
PinnableSlice* result = nullptr;
Status* status = nullptr;
BlobRangeReadRequest(Slice _user_key, uint64_t _offset, uint64_t _value_size,
uint64_t _range_offset, size_t _range_length,
PinnableSlice* _result, Status* _status)
: user_key(_user_key),
offset(_offset),
value_size(_value_size),
range_offset(_range_offset),
range_length(_range_length),
result(_result),
status(_status) {}
BlobRangeReadRequest() = default;
};
using BlobFileRangeReadRequests =
std::tuple<BlobFileOpenInfo, uint64_t /* file_size */,
autovector<BlobRangeReadRequest>>;
} // namespace ROCKSDB_NAMESPACE
+25 -365
View File
@@ -7,7 +7,6 @@
#include <cassert>
#include <string>
#include <vector>
#include "cache/cache_reservation_manager.h"
#include "cache/charged_cache.h"
@@ -50,10 +49,9 @@ Status AppendBlobRefreshRetryFailure(const Status& stale_status,
template <typename ReadFn>
Status ReadBlobWithReaderRetry(BlobFileCache* blob_file_cache,
const ReadOptions& read_options,
const BlobFileOpenInfo& blob_file,
const ReadFn& read) {
uint64_t file_number, const ReadFn& read) {
CacheHandleGuard<BlobFileReader> blob_file_reader;
Status s = blob_file_cache->GetBlobFileReader(read_options, blob_file,
Status s = blob_file_cache->GetBlobFileReader(read_options, file_number,
&blob_file_reader);
if (!s.ok()) {
return s;
@@ -67,11 +65,11 @@ Status ReadBlobWithReaderRetry(BlobFileCache* blob_file_cache,
const Status stale_status = s;
blob_file_reader.Reset();
blob_file_cache->Evict(blob_file.file_number);
blob_file_cache->Evict(file_number);
std::unique_ptr<BlobFileReader> fresh_reader;
s = blob_file_cache->OpenBlobFileReaderUncached(
read_options, blob_file, &fresh_reader,
read_options, file_number, &fresh_reader,
/*allow_footer_skip_retry=*/false);
if (!s.ok()) {
return AppendBlobRefreshRetryFailure(stale_status, s);
@@ -84,7 +82,7 @@ Status ReadBlobWithReaderRetry(BlobFileCache* blob_file_cache,
CacheHandleGuard<BlobFileReader> ignored_reader;
blob_file_cache
->RefreshBlobFileReader(blob_file, &fresh_reader, &ignored_reader)
->RefreshBlobFileReader(file_number, &fresh_reader, &ignored_reader)
.PermitUncheckedError();
return s;
}
@@ -202,7 +200,7 @@ Status BlobSource::PutBlobIntoCache(
const Status s = InsertEntryIntoCache(cache_key, blob->get(), &cache_handle,
Cache::Priority::BOTTOM);
if (s.ok()) {
[[maybe_unused]] BlobContents* released_blob = blob->release();
blob->release();
assert(cache_handle != nullptr);
*cached_blob =
@@ -273,9 +271,9 @@ Status BlobSource::InsertEntryIntoCache(const Slice& key, BlobContents* value,
}
Status BlobSource::GetBlob(const ReadOptions& read_options,
const Slice& user_key,
const BlobFileOpenInfo& blob_file, uint64_t offset,
uint64_t file_size, uint64_t value_size,
const Slice& user_key, uint64_t file_number,
uint64_t offset, uint64_t file_size,
uint64_t value_size,
CompressionType compression_type,
FilePrefetchBuffer* prefetch_buffer,
PinnableSlice* value, uint64_t* bytes_read) {
@@ -283,8 +281,7 @@ Status BlobSource::GetBlob(const ReadOptions& read_options,
Status s;
const CacheKey cache_key =
GetCacheKey(blob_file.file_number, file_size, offset);
const CacheKey cache_key = GetCacheKey(file_number, file_size, offset);
CacheHandleGuard<BlobContents> blob_handle;
@@ -334,7 +331,8 @@ Status BlobSource::GetBlob(const ReadOptions& read_options,
uint64_t read_size = 0;
s = ReadBlobWithReaderRetry(
blob_file_cache_, read_options, blob_file, [&](BlobFileReader* reader) {
blob_file_cache_, read_options, file_number,
[&](BlobFileReader* reader) {
if (compression_type != reader->GetCompressionType()) {
return Status::Corruption(
"Compression type mismatch when reading blob");
@@ -378,8 +376,7 @@ Status BlobSource::GetBlob(const ReadOptions& read_options,
}
Status BlobSource::GetBlobRange(const ReadOptions& read_options,
const Slice& user_key,
const BlobFileOpenInfo& blob_file,
const Slice& user_key, uint64_t file_number,
uint64_t offset, uint64_t file_size,
uint64_t value_size,
CompressionType compression_type,
@@ -396,8 +393,7 @@ Status BlobSource::GetBlobRange(const ReadOptions& read_options,
Status s;
const CacheKey cache_key =
GetCacheKey(blob_file.file_number, file_size, offset);
const CacheKey cache_key = GetCacheKey(file_number, file_size, offset);
CacheHandleGuard<BlobContents> blob_handle;
@@ -420,8 +416,7 @@ Status BlobSource::GetBlobRange(const ReadOptions& read_options,
const bool no_io = read_options.read_tier == kBlockCacheTier;
if (no_io) {
s = Status::Incomplete("Cannot read blob(s): no disk I/O allowed");
return s;
return Status::Incomplete("Cannot read blob(s): no disk I/O allowed");
}
// Cache miss: read only the requested sub-range from the file. The result is
@@ -438,7 +433,8 @@ Status BlobSource::GetBlobRange(const ReadOptions& read_options,
// case (offset/size mismatch surfacing as Corruption), not a payload
// checksum failure.
s = ReadBlobWithReaderRetry(
blob_file_cache_, read_options, blob_file, [&](BlobFileReader* reader) {
blob_file_cache_, read_options, file_number,
[&](BlobFileReader* reader) {
if (compression_type != reader->GetCompressionType()) {
return Status::Corruption(
"Compression type mismatch when reading blob");
@@ -505,8 +501,7 @@ Status BlobSource::GetSimpleGen2Blob(
const bool no_io = read_options.read_tier == kBlockCacheTier;
if (no_io) {
s = Status::Incomplete("Cannot read blob(s): no disk I/O allowed");
return s;
return Status::Incomplete("Cannot read blob(s): no disk I/O allowed");
}
// Cache miss (or no cache configured). Read the record into a buffer
@@ -606,8 +601,7 @@ Status BlobSource::GetSimpleGen2BlobRange(
const bool no_io = read_options.read_tier == kBlockCacheTier;
if (no_io) {
s = Status::Incomplete("Cannot read blob(s): no disk I/O allowed");
return s;
return Status::Incomplete("Cannot read blob(s): no disk I/O allowed");
}
// Cache miss: read only the requested sub-range from the file. The result is
@@ -639,188 +633,6 @@ Status BlobSource::GetSimpleGen2BlobRange(
return s;
}
void BlobSource::MultiGetSimpleGen2Blob(
const ReadOptions& read_options, const OffsetableCacheKey& base_cache_key,
RandomAccessFileReader* file, ChecksumType checksum_type,
uint32_t base_context_checksum, size_t num_records,
SimpleGen2BlobReadRequest* reqs) {
assert(file);
if (num_records == 0) {
return;
}
const bool no_io = read_options.read_tier == kBlockCacheTier;
MemoryAllocator* const allocator = (blob_cache_ && read_options.fill_cache)
? blob_cache_.get()->memory_allocator()
: nullptr;
// Records that missed the cache and must be read from disk, in read order;
// each owns its buffer until we build a BlobContents from it below.
struct Pending {
size_t idx;
CacheAllocationPtr buf;
uint64_t record_size;
};
std::vector<Pending> pending;
std::vector<SimpleGen2RecordReadRequest> read_reqs;
for (size_t i = 0; i < num_records; ++i) {
SimpleGen2BlobReadRequest& req = reqs[i];
assert(req.result);
assert(req.status);
const uint64_t record_size = req.payload_size + kSimpleGen2BlobTrailerSize;
const CacheKey cache_key =
GetSimpleGen2BlobCacheKey(base_cache_key, req.record_offset);
if (blob_cache_) {
CacheHandleGuard<BlobContents> blob_handle;
const Status cs = GetBlobFromCache(cache_key.AsSlice(), &blob_handle);
if (cs.ok()) {
*req.status = cs;
PinCachedBlob(&blob_handle, req.result);
continue;
}
}
if (no_io) {
*req.status =
Status::Incomplete("Cannot read blob(s): no disk I/O allowed");
continue;
}
CacheAllocationPtr buf =
AllocateBlock(static_cast<size_t>(record_size), allocator);
read_reqs.push_back(SimpleGen2RecordReadRequest{
req.record_offset, static_cast<size_t>(req.payload_size),
static_cast<size_t>(record_size), req.expected_compression, buf.get(),
req.status});
pending.push_back(Pending{i, std::move(buf), record_size});
}
if (read_reqs.empty()) {
return;
}
ReadAndVerifySimpleGen2BlobRecords(read_options, file, checksum_type,
base_context_checksum, read_reqs.size(),
read_reqs.data());
for (size_t k = 0; k < pending.size(); ++k) {
SimpleGen2BlobReadRequest& req = reqs[pending[k].idx];
if (!req.status->ok()) {
continue;
}
const uint64_t record_size = pending[k].record_size;
std::unique_ptr<BlobContents> blob_contents(new BlobContents(
std::move(pending[k].buf), static_cast<size_t>(req.payload_size)));
RecordTick(statistics_, BLOB_DB_BLOB_FILE_BYTES_READ, record_size);
PERF_COUNTER_ADD(blob_read_count, 1);
PERF_COUNTER_ADD(blob_read_byte, record_size);
if (read_options.io_activity == Env::IOActivity::kLazyResolve) {
RecordLazyRead(statistics_, record_size);
}
if (blob_cache_ && read_options.fill_cache) {
const CacheKey cache_key =
GetSimpleGen2BlobCacheKey(base_cache_key, req.record_offset);
CacheHandleGuard<BlobContents> blob_handle;
const Status ps =
PutBlobIntoCache(cache_key.AsSlice(), &blob_contents, &blob_handle);
if (!ps.ok()) {
*req.status = ps;
} else {
PinCachedBlob(&blob_handle, req.result);
}
} else {
PinOwnedBlob(&blob_contents, req.result);
}
}
}
void BlobSource::MultiGetSimpleGen2BlobRange(
const ReadOptions& read_options, const OffsetableCacheKey& base_cache_key,
RandomAccessFileReader* file, size_t num_records,
SimpleGen2BlobRangeReadRequest* reqs) {
assert(file);
// Range reads are only issued while resolving a lazy result (partial reads
// are lazy-only), so the lazy read stats below are recorded unconditionally.
assert(read_options.io_activity == Env::IOActivity::kLazyResolve);
if (num_records == 0) {
return;
}
const bool no_io = read_options.read_tier == kBlockCacheTier;
struct Pending {
size_t idx;
CacheAllocationPtr buf;
};
std::vector<Pending> pending;
std::vector<SimpleGen2RangeReadRequest> read_reqs;
for (size_t i = 0; i < num_records; ++i) {
SimpleGen2BlobRangeReadRequest& req = reqs[i];
assert(req.result);
assert(req.status);
const CacheKey cache_key =
GetSimpleGen2BlobCacheKey(base_cache_key, req.record_offset);
// Probe the whole-payload cache; on a hit slice the requested sub-range.
if (blob_cache_) {
CacheHandleGuard<BlobContents> blob_handle;
const Status cs = GetBlobFromCache(cache_key.AsSlice(), &blob_handle);
if (cs.ok()) {
*req.status = cs;
PinCacheHitSubRange(&blob_handle, req.range_offset, req.range_length,
req.result);
continue;
}
}
if (no_io) {
*req.status =
Status::Incomplete("Cannot read blob(s): no disk I/O allowed");
continue;
}
// No cache-fill allocator: a partial payload is never inserted.
CacheAllocationPtr buf =
AllocateBlock(req.range_length, /*allocator=*/nullptr);
read_reqs.push_back(SimpleGen2RangeReadRequest{
req.record_offset, static_cast<size_t>(req.payload_size),
req.range_offset, req.range_length, req.expected_compression, buf.get(),
req.status});
pending.push_back(Pending{i, std::move(buf)});
}
if (read_reqs.empty()) {
return;
}
ReadSimpleGen2BlobRanges(read_options, file, read_reqs.size(),
read_reqs.data());
for (size_t k = 0; k < pending.size(); ++k) {
SimpleGen2BlobRangeReadRequest& req = reqs[pending[k].idx];
if (!req.status->ok()) {
continue;
}
std::unique_ptr<BlobContents> blob_contents(
new BlobContents(std::move(pending[k].buf), req.range_length));
RecordTick(statistics_, BLOB_DB_BLOB_FILE_BYTES_READ, req.range_length);
PERF_COUNTER_ADD(blob_read_count, 1);
PERF_COUNTER_ADD(blob_read_byte, req.range_length);
RecordLazyRead(statistics_, req.range_length);
RecordLazyPartialRead(statistics_, req.range_length, req.payload_size);
PinOwnedBlob(&blob_contents, req.result);
}
}
void BlobSource::MultiGetBlob(const ReadOptions& read_options,
autovector<BlobFileReadRequests>& blob_reqs,
uint64_t* bytes_read) {
@@ -829,7 +641,7 @@ void BlobSource::MultiGetBlob(const ReadOptions& read_options,
uint64_t total_bytes_read = 0;
uint64_t bytes_read_in_file = 0;
for (auto& [blob_file, file_size, blob_reqs_in_file] : blob_reqs) {
for (auto& [file_number, file_size, blob_reqs_in_file] : blob_reqs) {
// sort blob_reqs_in_file by file offset.
std::sort(
blob_reqs_in_file.begin(), blob_reqs_in_file.end(),
@@ -837,7 +649,7 @@ void BlobSource::MultiGetBlob(const ReadOptions& read_options,
return lhs.offset < rhs.offset;
});
MultiGetBlobFromOneFile(read_options, blob_file, file_size,
MultiGetBlobFromOneFile(read_options, file_number, file_size,
blob_reqs_in_file, &bytes_read_in_file);
total_bytes_read += bytes_read_in_file;
@@ -849,7 +661,7 @@ void BlobSource::MultiGetBlob(const ReadOptions& read_options,
}
void BlobSource::MultiGetBlobFromOneFile(const ReadOptions& read_options,
const BlobFileOpenInfo& blob_file,
uint64_t file_number,
uint64_t /*file_size*/,
autovector<BlobReadRequest>& blob_reqs,
uint64_t* bytes_read) {
@@ -867,7 +679,6 @@ void BlobSource::MultiGetBlobFromOneFile(const ReadOptions& read_options,
Mask cache_hit_mask = 0;
uint64_t total_bytes = 0;
const uint64_t file_number = blob_file.file_number;
const OffsetableCacheKey base_cache_key(db_id_, db_session_id_, file_number);
if (blob_cache_) {
@@ -939,7 +750,7 @@ void BlobSource::MultiGetBlobFromOneFile(const ReadOptions& read_options,
}
CacheHandleGuard<BlobFileReader> blob_file_reader;
Status s = blob_file_cache_->GetBlobFileReader(read_options, blob_file,
Status s = blob_file_cache_->GetBlobFileReader(read_options, file_number,
&blob_file_reader);
if (!s.ok()) {
for (size_t i = 0; i < _blob_reqs.size(); ++i) {
@@ -979,7 +790,7 @@ void BlobSource::MultiGetBlobFromOneFile(const ReadOptions& read_options,
std::unique_ptr<BlobFileReader> fresh_reader;
s = blob_file_cache_->OpenBlobFileReaderUncached(
read_options, blob_file, &fresh_reader,
read_options, file_number, &fresh_reader,
/*allow_footer_skip_retry=*/false);
if (!s.ok()) {
for (const auto& blob_req : _blob_reqs) {
@@ -1040,7 +851,7 @@ void BlobSource::MultiGetBlobFromOneFile(const ReadOptions& read_options,
if (install_fresh_reader) {
CacheHandleGuard<BlobFileReader> ignored_reader;
blob_file_cache_
->RefreshBlobFileReader(blob_file, &fresh_reader, &ignored_reader)
->RefreshBlobFileReader(file_number, &fresh_reader, &ignored_reader)
.PermitUncheckedError();
}
}
@@ -1073,24 +884,6 @@ void BlobSource::MultiGetBlobFromOneFile(const ReadOptions& read_options,
}
}
// Whole-column reads on the lazy resolve path count as lazy reads (mirrors
// BlobSource::GetBlob, which records only on a disk read, not a cache hit).
// Gated on the activity so regular MultiGet is unaffected; the misses read
// above are exactly the disk reads for these blob columns.
if (read_options.io_activity == Env::IOActivity::kLazyResolve) {
for (auto& [req, blob_contents] : _blob_reqs) {
assert(req);
if (req->status->ok()) {
const uint64_t adjustment =
read_options.verify_checksums
? BlobLogRecord::CalculateAdjustmentForRecordHeader(
req->user_key->size())
: 0;
RecordLazyRead(statistics_, req->len + adjustment);
}
}
}
total_bytes += _bytes_read;
if (bytes_read) {
*bytes_read = total_bytes;
@@ -1098,139 +891,6 @@ void BlobSource::MultiGetBlobFromOneFile(const ReadOptions& read_options,
}
}
void BlobSource::MultiGetBlobRange(
const ReadOptions& read_options,
autovector<BlobFileRangeReadRequests>& blob_reqs, uint64_t* bytes_read) {
assert(blob_reqs.size() > 0);
uint64_t total_bytes_read = 0;
uint64_t bytes_read_in_file = 0;
for (auto& [blob_file, file_size, blob_reqs_in_file] : blob_reqs) {
// Sort by effective read offset so the file system layer can coalesce
// adjacent sub-range reads within one MultiRead.
std::sort(blob_reqs_in_file.begin(), blob_reqs_in_file.end(),
[](const BlobRangeReadRequest& lhs,
const BlobRangeReadRequest& rhs) -> bool {
return lhs.offset + lhs.range_offset <
rhs.offset + rhs.range_offset;
});
MultiGetBlobRangeFromOneFile(read_options, blob_file, file_size,
blob_reqs_in_file, &bytes_read_in_file);
total_bytes_read += bytes_read_in_file;
bytes_read_in_file = 0; // reset: some early-return paths do not write it
}
if (bytes_read) {
*bytes_read = total_bytes_read;
}
}
void BlobSource::MultiGetBlobRangeFromOneFile(
const ReadOptions& read_options, const BlobFileOpenInfo& blob_file,
uint64_t /*file_size*/, autovector<BlobRangeReadRequest>& blob_reqs,
uint64_t* bytes_read) {
const size_t num_blobs = blob_reqs.size();
assert(num_blobs > 0);
assert(num_blobs <= MultiGetContext::MAX_BATCH_SIZE);
// Range reads are only issued while resolving a lazy result (partial reads
// are lazy-only), so the lazy read stats below are recorded unconditionally.
assert(read_options.io_activity == Env::IOActivity::kLazyResolve);
using Mask = uint64_t;
Mask cache_hit_mask = 0;
uint64_t total_bytes = 0;
const uint64_t file_number = blob_file.file_number;
const OffsetableCacheKey base_cache_key(db_id_, db_session_id_, file_number);
// Probe the whole-value cache per request; on a hit, slice the requested
// sub-range (zero-copy, no disk read). A partial read is never cache-filled.
if (blob_cache_) {
size_t cached_blob_count = 0;
for (size_t i = 0; i < num_blobs; ++i) {
BlobRangeReadRequest& req = blob_reqs[i];
assert(req.status);
CacheHandleGuard<BlobContents> blob_handle;
const CacheKey cache_key = base_cache_key.WithOffset(req.offset);
const Status s = GetBlobFromCache(cache_key.AsSlice(), &blob_handle);
if (s.ok()) {
*req.status = s;
PinCacheHitSubRange(&blob_handle, req.range_offset, req.range_length,
req.result);
++cached_blob_count;
cache_hit_mask |= (Mask{1} << i); // hit -> no disk read
}
}
if (cached_blob_count == num_blobs) {
if (bytes_read) {
*bytes_read = 0;
}
return;
}
}
const bool no_io = read_options.read_tier == kBlockCacheTier;
if (no_io) {
for (size_t i = 0; i < num_blobs; ++i) {
if (!(cache_hit_mask & (Mask{1} << i))) {
assert(blob_reqs[i].status);
*blob_reqs[i].status =
Status::Incomplete("Cannot read blob(s): no disk I/O allowed");
}
}
return;
}
autovector<std::pair<BlobRangeReadRequest*, std::unique_ptr<BlobContents>>>
_blob_reqs;
for (size_t i = 0; i < num_blobs; ++i) {
if (!(cache_hit_mask & (Mask{1} << i))) {
_blob_reqs.emplace_back(&blob_reqs[i], std::unique_ptr<BlobContents>());
}
}
// Lazy range reads run against a pinned SuperVersion (max_open_files == -1),
// and blob file numbers are never reused, so the stale cached-reader race
// that MultiGetBlobFromOneFile guards against cannot occur here; a Corruption
// is simply surfaced per request. Hence no reader-refresh retry and no
// cache-fill (a partial value cannot represent the whole-record cache entry).
CacheHandleGuard<BlobFileReader> blob_file_reader;
Status s = blob_file_cache_->GetBlobFileReader(read_options, blob_file,
&blob_file_reader);
if (!s.ok()) {
for (auto& blob_req : _blob_reqs) {
assert(blob_req.first);
assert(blob_req.first->status);
*blob_req.first->status = s;
}
return;
}
assert(blob_file_reader.GetValue());
uint64_t _bytes_read = 0;
blob_file_reader.GetValue()->MultiGetBlobRange(read_options, _blob_reqs,
&_bytes_read);
for (auto& [req, blob_contents] : _blob_reqs) {
assert(req);
if (req->status->ok()) {
PinOwnedBlob(&blob_contents, req->result);
RecordLazyRead(statistics_, req->range_length);
RecordLazyPartialRead(statistics_, req->range_length, req->value_size);
}
}
total_bytes += _bytes_read;
if (bytes_read) {
*bytes_read = total_bytes;
}
}
bool BlobSource::TEST_BlobInCache(uint64_t file_number, uint64_t file_size,
uint64_t offset, size_t* charge) const {
const CacheKey cache_key = GetCacheKey(file_number, file_size, offset);
+9 -84
View File
@@ -28,30 +28,6 @@ class Slice;
class RandomAccessFileReader;
enum ChecksumType : char;
// One whole-record request for BlobSource::MultiGetSimpleGen2Blob (the batched
// counterpart of GetSimpleGen2Blob). The per-file context (base cache key,
// file, checksum context) is passed once to the batch call; these are the
// per-record inputs and outputs.
struct SimpleGen2BlobReadRequest {
uint64_t record_offset = 0;
uint64_t payload_size = 0;
CompressionType expected_compression = kNoCompression;
PinnableSlice* result = nullptr;
Status* status = nullptr;
};
// One sub-range request for BlobSource::MultiGetSimpleGen2BlobRange (the
// batched counterpart of GetSimpleGen2BlobRange).
struct SimpleGen2BlobRangeReadRequest {
uint64_t record_offset = 0;
uint64_t payload_size = 0;
uint64_t range_offset = 0;
size_t range_length = 0;
CompressionType expected_compression = kNoCompression;
PinnableSlice* result = nullptr;
Status* status = nullptr;
};
// BlobSource is a class that provides universal access to blobs, regardless of
// whether they are in the blob cache, secondary cache, or (remote) storage.
// Depending on user settings, it always fetch blobs from multi-tier cache and
@@ -67,8 +43,6 @@ class BlobSource {
BlobSource(const BlobSource&) = delete;
BlobSource& operator=(const BlobSource&) = delete;
BlobSource(BlobSource&&) = delete;
BlobSource& operator=(BlobSource&&) = delete;
~BlobSource();
@@ -82,9 +56,8 @@ class BlobSource {
// sets "*bytes_read" to the size of on-disk (possibly compressed) blob
// record.
Status GetBlob(const ReadOptions& read_options, const Slice& user_key,
const BlobFileOpenInfo& blob_file, uint64_t offset,
uint64_t file_size, uint64_t value_size,
CompressionType compression_type,
uint64_t file_number, uint64_t offset, uint64_t file_size,
uint64_t value_size, CompressionType compression_type,
FilePrefetchBuffer* prefetch_buffer, PinnableSlice* value,
uint64_t* bytes_read);
@@ -108,11 +81,10 @@ class BlobSource {
// *bytes_read (when non-null) is the number of bytes read from the file; on a
// hit it is 0.
Status GetBlobRange(const ReadOptions& read_options, const Slice& user_key,
const BlobFileOpenInfo& blob_file, uint64_t offset,
uint64_t file_size, uint64_t value_size,
CompressionType compression_type, uint64_t range_offset,
size_t range_length, PinnableSlice* value,
uint64_t* bytes_read);
uint64_t file_number, uint64_t offset, uint64_t file_size,
uint64_t value_size, CompressionType compression_type,
uint64_t range_offset, size_t range_length,
PinnableSlice* value, uint64_t* bytes_read);
// Reads a SimpleGen2Blob payload (see db/blob/blob_gen2_format.h) through the
// blob value cache and BLOB_DB_* statistics. This is the counterpart to
@@ -174,33 +146,6 @@ class BlobSource {
uint64_t range_offset, size_t range_length,
PinnableSlice* value, uint64_t* bytes_read);
// Batched counterpart of GetSimpleGen2Blob: resolves `num_records` whole
// embedded (SimpleGen2Blob) records from one file, coalescing all cache-miss
// reads into a single MultiRead. The per-file context (base cache key, file,
// checksum type, base context checksum) is shared by all records; per-record
// inputs/outputs are in `reqs`. Each record independently probes the blob
// cache and, on a miss, is read + verified from the file; whole records are
// cache-filled (when configured and fill_cache) exactly as GetSimpleGen2Blob.
// Per-record outcome is written to *reqs[i].status.
void MultiGetSimpleGen2Blob(const ReadOptions& read_options,
const OffsetableCacheKey& base_cache_key,
RandomAccessFileReader* file,
ChecksumType checksum_type,
uint32_t base_context_checksum,
size_t num_records,
SimpleGen2BlobReadRequest* reqs);
// Batched counterpart of GetSimpleGen2BlobRange: resolves `num_records`
// uncompressed embedded sub-ranges from one file, coalescing cache-miss reads
// into a single MultiRead. Like GetSimpleGen2BlobRange, each record probes
// the whole-payload cache (slicing on a hit) and a partial read is never
// inserted into the cache. Per-record outcome is in *reqs[i].status.
void MultiGetSimpleGen2BlobRange(const ReadOptions& read_options,
const OffsetableCacheKey& base_cache_key,
RandomAccessFileReader* file,
size_t num_records,
SimpleGen2BlobRangeReadRequest* reqs);
// Read multiple blobs from the underlying cache or blob file(s).
//
// If successful, returns ok and sets "result" in the elements of "blob_reqs"
@@ -236,34 +181,14 @@ class BlobSource {
// "*bytes_read" to the total size of on-disk (possibly compressed) blob
// records.
void MultiGetBlobFromOneFile(const ReadOptions& read_options,
const BlobFileOpenInfo& blob_file,
uint64_t file_size,
uint64_t file_number, uint64_t file_size,
autovector<BlobReadRequest>& blob_reqs,
uint64_t* bytes_read);
// Byte-range (partial) multi-read counterpart of MultiGetBlob: for each
// request reads only its sub-range of an *uncompressed* blob value, from the
// blob cache (whole-value hit, sliced) or coalesced disk reads (one MultiRead
// per file). Like GetBlobRange, a partial read never fills the blob cache.
// Groups requests by file and dispatches each file via
// MultiGetBlobRangeFromOneFile. Per-request outcome is in
// *blob_reqs[..].status.
void MultiGetBlobRange(const ReadOptions& read_options,
autovector<BlobFileRangeReadRequests>& blob_reqs,
uint64_t* bytes_read);
// Byte-range (partial) multi-read counterpart of MultiGetBlobFromOneFile for
// a single blob file. See MultiGetBlobRange.
void MultiGetBlobRangeFromOneFile(const ReadOptions& read_options,
const BlobFileOpenInfo& blob_file,
uint64_t file_size,
autovector<BlobRangeReadRequest>& blob_reqs,
uint64_t* bytes_read);
inline Status GetBlobFileReader(
const ReadOptions& read_options, const BlobFileOpenInfo& blob_file,
const ReadOptions& read_options, uint64_t blob_file_number,
CacheHandleGuard<BlobFileReader>* blob_file_reader) {
return blob_file_cache_->GetBlobFileReader(read_options, blob_file,
return blob_file_cache_->GetBlobFileReader(read_options, blob_file_number,
blob_file_reader);
}
+75 -93
View File
@@ -104,10 +104,6 @@ void WriteBlobFile(const ImmutableOptions& immutable_options,
&checksum_method, &checksum_value));
}
BlobFileOpenInfo OpenInfoWithoutChecksum(uint64_t file_number) {
return BlobFileOpenInfo{file_number, Slice(), Slice()};
}
} // anonymous namespace
class BlobSourceTest : public DBTestBase {
@@ -216,10 +212,10 @@ TEST_F(BlobSourceTest, GetBlobsFromCache) {
ASSERT_FALSE(blob_source.TEST_BlobInCache(blob_file_number, file_size,
blob_offsets[i]));
ASSERT_OK(blob_source.GetBlob(
read_options, keys[i], OpenInfoWithoutChecksum(blob_file_number),
blob_offsets[i], file_size, blob_sizes[i], kNoCompression,
prefetch_buffer, &values[i], &bytes_read));
ASSERT_OK(blob_source.GetBlob(read_options, keys[i], blob_file_number,
blob_offsets[i], file_size, blob_sizes[i],
kNoCompression, prefetch_buffer, &values[i],
&bytes_read));
ASSERT_EQ(values[i], blobs[i]);
ASSERT_TRUE(values[i].IsPinned());
ASSERT_EQ(bytes_read,
@@ -255,10 +251,10 @@ TEST_F(BlobSourceTest, GetBlobsFromCache) {
ASSERT_FALSE(blob_source.TEST_BlobInCache(blob_file_number, file_size,
blob_offsets[i]));
ASSERT_OK(blob_source.GetBlob(
read_options, keys[i], OpenInfoWithoutChecksum(blob_file_number),
blob_offsets[i], file_size, blob_sizes[i], kNoCompression,
prefetch_buffer, &values[i], &bytes_read));
ASSERT_OK(blob_source.GetBlob(read_options, keys[i], blob_file_number,
blob_offsets[i], file_size, blob_sizes[i],
kNoCompression, prefetch_buffer, &values[i],
&bytes_read));
ASSERT_EQ(values[i], blobs[i]);
ASSERT_TRUE(values[i].IsPinned());
ASSERT_EQ(bytes_read,
@@ -303,10 +299,10 @@ TEST_F(BlobSourceTest, GetBlobsFromCache) {
ASSERT_TRUE(blob_source.TEST_BlobInCache(blob_file_number, file_size,
blob_offsets[i]));
ASSERT_OK(blob_source.GetBlob(
read_options, keys[i], OpenInfoWithoutChecksum(blob_file_number),
blob_offsets[i], file_size, blob_sizes[i], kNoCompression,
prefetch_buffer, &values[i], &bytes_read));
ASSERT_OK(blob_source.GetBlob(read_options, keys[i], blob_file_number,
blob_offsets[i], file_size, blob_sizes[i],
kNoCompression, prefetch_buffer, &values[i],
&bytes_read));
ASSERT_EQ(values[i], blobs[i]);
ASSERT_TRUE(values[i].IsPinned());
ASSERT_EQ(bytes_read,
@@ -343,10 +339,10 @@ TEST_F(BlobSourceTest, GetBlobsFromCache) {
ASSERT_TRUE(blob_source.TEST_BlobInCache(blob_file_number, file_size,
blob_offsets[i]));
ASSERT_OK(blob_source.GetBlob(
read_options, keys[i], OpenInfoWithoutChecksum(blob_file_number),
blob_offsets[i], file_size, blob_sizes[i], kNoCompression,
prefetch_buffer, &values[i], &bytes_read));
ASSERT_OK(blob_source.GetBlob(read_options, keys[i], blob_file_number,
blob_offsets[i], file_size, blob_sizes[i],
kNoCompression, prefetch_buffer, &values[i],
&bytes_read));
ASSERT_EQ(values[i], blobs[i]);
ASSERT_TRUE(values[i].IsPinned());
ASSERT_EQ(bytes_read,
@@ -389,8 +385,7 @@ TEST_F(BlobSourceTest, GetBlobsFromCache) {
blob_offsets[i]));
ASSERT_TRUE(blob_source
.GetBlob(read_options, keys[i],
OpenInfoWithoutChecksum(blob_file_number),
.GetBlob(read_options, keys[i], blob_file_number,
blob_offsets[i], file_size, blob_sizes[i],
kNoCompression, prefetch_buffer, &values[i],
&bytes_read)
@@ -432,8 +427,7 @@ TEST_F(BlobSourceTest, GetBlobsFromCache) {
blob_offsets[i]));
ASSERT_TRUE(blob_source
.GetBlob(read_options, keys[i],
OpenInfoWithoutChecksum(file_number),
.GetBlob(read_options, keys[i], file_number,
blob_offsets[i], file_size, blob_sizes[i],
kNoCompression, prefetch_buffer, &values[i],
&bytes_read)
@@ -528,8 +522,8 @@ TEST_F(BlobSourceTest, GetCompressedBlobs) {
compression, blob_offsets, blob_sizes);
CacheHandleGuard<BlobFileReader> blob_file_reader;
ASSERT_OK(blob_source.GetBlobFileReader(
read_options, OpenInfoWithoutChecksum(file_number), &blob_file_reader));
ASSERT_OK(blob_source.GetBlobFileReader(read_options, file_number,
&blob_file_reader));
ASSERT_NE(blob_file_reader.GetValue(), nullptr);
const uint64_t file_size = blob_file_reader.GetValue()->GetFileSize();
@@ -547,10 +541,10 @@ TEST_F(BlobSourceTest, GetCompressedBlobs) {
for (size_t i = 0; i < num_blobs; ++i) {
ASSERT_FALSE(blob_source.TEST_BlobInCache(file_number, file_size,
blob_offsets[i]));
ASSERT_OK(blob_source.GetBlob(
read_options, keys[i], OpenInfoWithoutChecksum(file_number),
blob_offsets[i], file_size, blob_sizes[i], compression,
nullptr /*prefetch_buffer*/, &values[i], &bytes_read));
ASSERT_OK(blob_source.GetBlob(read_options, keys[i], file_number,
blob_offsets[i], file_size, blob_sizes[i],
compression, nullptr /*prefetch_buffer*/,
&values[i], &bytes_read));
ASSERT_EQ(values[i], blobs[i] /*uncompressed blob*/);
ASSERT_NE(values[i].size(), blob_sizes[i] /*compressed size*/);
ASSERT_EQ(bytes_read,
@@ -570,10 +564,10 @@ TEST_F(BlobSourceTest, GetCompressedBlobs) {
blob_offsets[i]));
// Compressed blob size is passed in GetBlob
ASSERT_OK(blob_source.GetBlob(
read_options, keys[i], OpenInfoWithoutChecksum(file_number),
blob_offsets[i], file_size, blob_sizes[i], compression,
nullptr /*prefetch_buffer*/, &values[i], &bytes_read));
ASSERT_OK(blob_source.GetBlob(read_options, keys[i], file_number,
blob_offsets[i], file_size, blob_sizes[i],
compression, nullptr /*prefetch_buffer*/,
&values[i], &bytes_read));
ASSERT_EQ(values[i], blobs[i] /*uncompressed blob*/);
ASSERT_NE(values[i].size(), blob_sizes[i] /*compressed size*/);
ASSERT_EQ(bytes_read,
@@ -680,8 +674,7 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromMultiFiles) {
keys[j], blob_offsets[j], blob_sizes[j], kNoCompression,
&value_buf[i * num_blobs + j], &statuses_buf[i * num_blobs + j]);
}
blob_reqs.emplace_back(OpenInfoWithoutChecksum(file_number), file_size,
blob_reqs_in_file[i]);
blob_reqs.emplace_back(file_number, file_size, blob_reqs_in_file[i]);
}
get_perf_context()->Reset();
@@ -738,8 +731,7 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromMultiFiles) {
}
// Add a fake multi-get blob request.
blob_reqs.emplace_back(OpenInfoWithoutChecksum(fake_file_number), file_size,
fake_blob_reqs_in_file);
blob_reqs.emplace_back(fake_file_number, file_size, fake_blob_reqs_in_file);
blob_source.MultiGetBlob(read_options, blob_reqs, &bytes_read);
@@ -873,9 +865,8 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromCache) {
statistics->Reset().PermitUncheckedError();
// Get half of blobs
blob_source.MultiGetBlobFromOneFile(
read_options, OpenInfoWithoutChecksum(blob_file_number), file_size,
blob_reqs, &bytes_read);
blob_source.MultiGetBlobFromOneFile(read_options, blob_file_number,
file_size, blob_reqs, &bytes_read);
uint64_t fs_read_bytes = 0;
uint64_t ca_read_bytes = 0;
@@ -920,10 +911,10 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromCache) {
ASSERT_FALSE(blob_source.TEST_BlobInCache(blob_file_number, file_size,
blob_offsets[i]));
ASSERT_OK(blob_source.GetBlob(
read_options, keys[i], OpenInfoWithoutChecksum(blob_file_number),
blob_offsets[i], file_size, blob_sizes[i], kNoCompression,
prefetch_buffer, &value_buf[i], &bytes_read));
ASSERT_OK(blob_source.GetBlob(read_options, keys[i], blob_file_number,
blob_offsets[i], file_size, blob_sizes[i],
kNoCompression, prefetch_buffer,
&value_buf[i], &bytes_read));
ASSERT_EQ(value_buf[i], blobs[i]);
ASSERT_TRUE(value_buf[i].IsPinned());
ASSERT_EQ(bytes_read,
@@ -944,9 +935,8 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromCache) {
kNoCompression, &value_buf[i], &statuses_buf[i]);
}
blob_source.MultiGetBlobFromOneFile(
read_options, OpenInfoWithoutChecksum(blob_file_number), file_size,
blob_reqs, &bytes_read);
blob_source.MultiGetBlobFromOneFile(read_options, blob_file_number,
file_size, blob_reqs, &bytes_read);
uint64_t blob_bytes = 0;
for (size_t i = 0; i < num_blobs; ++i) {
@@ -995,9 +985,8 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromCache) {
get_perf_context()->Reset();
statistics->Reset().PermitUncheckedError();
blob_source.MultiGetBlobFromOneFile(
read_options, OpenInfoWithoutChecksum(blob_file_number), file_size,
blob_reqs, &bytes_read);
blob_source.MultiGetBlobFromOneFile(read_options, blob_file_number,
file_size, blob_reqs, &bytes_read);
for (size_t i = 0; i < num_blobs; ++i) {
ASSERT_TRUE(statuses_buf[i].IsIncomplete());
@@ -1040,9 +1029,8 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromCache) {
get_perf_context()->Reset();
statistics->Reset().PermitUncheckedError();
blob_source.MultiGetBlobFromOneFile(
read_options, OpenInfoWithoutChecksum(non_existing_file_number),
file_size, blob_reqs, &bytes_read);
blob_source.MultiGetBlobFromOneFile(read_options, non_existing_file_number,
file_size, blob_reqs, &bytes_read);
for (size_t i = 0; i < num_blobs; ++i) {
ASSERT_TRUE(statuses_buf[i].IsIOError());
@@ -1116,9 +1104,8 @@ TEST_F(BlobSourceTest, GetBlobPreservesCorruptionDetailsWhenRefreshOpenFails) {
PinnableSlice value;
uint64_t bytes_read = 0;
ASSERT_OK(blob_source.GetBlob(
read_options, keys[0], OpenInfoWithoutChecksum(blob_file_number),
blob_offsets[0], file_size, blob_sizes[0], kNoCompression,
prefetch_buffer, &value, &bytes_read));
read_options, keys[0], blob_file_number, blob_offsets[0], file_size,
blob_sizes[0], kNoCompression, prefetch_buffer, &value, &bytes_read));
ASSERT_EQ(value, blobs[0]);
const std::string blob_file_path =
@@ -1127,10 +1114,10 @@ TEST_F(BlobSourceTest, GetBlobPreservesCorruptionDetailsWhenRefreshOpenFails) {
PinnableSlice invalid_value;
bytes_read = 0;
Status s = blob_source.GetBlob(
read_options, keys[0], OpenInfoWithoutChecksum(blob_file_number),
file_size, file_size, blob_sizes[0], kNoCompression, prefetch_buffer,
&invalid_value, &bytes_read);
Status s =
blob_source.GetBlob(read_options, keys[0], blob_file_number, file_size,
file_size, blob_sizes[0], kNoCompression,
prefetch_buffer, &invalid_value, &bytes_read);
ASSERT_TRUE(s.IsCorruption());
ASSERT_EQ(bytes_read, 0);
@@ -1191,9 +1178,8 @@ TEST_F(BlobSourceTest,
PinnableSlice value;
uint64_t bytes_read = 0;
ASSERT_OK(blob_source.GetBlob(
read_options, keys[0], OpenInfoWithoutChecksum(blob_file_number),
blob_offsets[0], file_size, blob_sizes[0], kNoCompression,
prefetch_buffer, &value, &bytes_read));
read_options, keys[0], blob_file_number, blob_offsets[0], file_size,
blob_sizes[0], kNoCompression, prefetch_buffer, &value, &bytes_read));
ASSERT_EQ(value, blobs[0]);
const std::string blob_file_path =
@@ -1207,9 +1193,8 @@ TEST_F(BlobSourceTest,
&values[0], &statuses[0]);
bytes_read = 0;
blob_source.MultiGetBlobFromOneFile(read_options,
OpenInfoWithoutChecksum(blob_file_number),
file_size, blob_reqs, &bytes_read);
blob_source.MultiGetBlobFromOneFile(read_options, blob_file_number, file_size,
blob_reqs, &bytes_read);
ASSERT_TRUE(statuses[0].IsCorruption());
ASSERT_EQ(bytes_read, 0);
@@ -1318,8 +1303,8 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
CacheHandleGuard<BlobFileReader> file_reader;
ReadOptions read_options;
ASSERT_OK(blob_source.GetBlobFileReader(
read_options, OpenInfoWithoutChecksum(file_number), &file_reader));
ASSERT_OK(
blob_source.GetBlobFileReader(read_options, file_number, &file_reader));
ASSERT_NE(file_reader.GetValue(), nullptr);
const uint64_t file_size = file_reader.GetValue()->GetFileSize();
ASSERT_EQ(file_reader.GetValue()->GetCompressionType(), kNoCompression);
@@ -1337,8 +1322,7 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
get_perf_context()->Reset();
// key0 should be filled to the primary cache from the blob file.
ASSERT_OK(blob_source.GetBlob(read_options, keys[0],
OpenInfoWithoutChecksum(file_number),
ASSERT_OK(blob_source.GetBlob(read_options, keys[0], file_number,
blob_offsets[0], file_size, blob_sizes[0],
kNoCompression, nullptr /* prefetch_buffer */,
values.data(), nullptr /* bytes_read */));
@@ -1347,18 +1331,17 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
// key0 should be evicted and key0's dummy item is inserted into secondary
// cache. key1 should be filled to the primary cache from the blob file.
ASSERT_OK(blob_source.GetBlob(
read_options, keys[1], OpenInfoWithoutChecksum(file_number),
blob_offsets[1], file_size, blob_sizes[1], kNoCompression,
nullptr /* prefetch_buffer */, &values[1], nullptr /* bytes_read */));
ASSERT_OK(blob_source.GetBlob(read_options, keys[1], file_number,
blob_offsets[1], file_size, blob_sizes[1],
kNoCompression, nullptr /* prefetch_buffer */,
&values[1], nullptr /* bytes_read */));
// Release cache handle
values[1].Reset();
// key0 should be filled to the primary cache from the blob file. key1
// should be evicted and key1's dummy item is inserted into secondary cache.
ASSERT_OK(blob_source.GetBlob(read_options, keys[0],
OpenInfoWithoutChecksum(file_number),
ASSERT_OK(blob_source.GetBlob(read_options, keys[0], file_number,
blob_offsets[0], file_size, blob_sizes[0],
kNoCompression, nullptr /* prefetch_buffer */,
values.data(), nullptr /* bytes_read */));
@@ -1371,10 +1354,10 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
// key0 should be evicted and is inserted into secondary cache.
// key1 should be filled to the primary cache from the blob file.
ASSERT_OK(blob_source.GetBlob(
read_options, keys[1], OpenInfoWithoutChecksum(file_number),
blob_offsets[1], file_size, blob_sizes[1], kNoCompression,
nullptr /* prefetch_buffer */, &values[1], nullptr /* bytes_read */));
ASSERT_OK(blob_source.GetBlob(read_options, keys[1], file_number,
blob_offsets[1], file_size, blob_sizes[1],
kNoCompression, nullptr /* prefetch_buffer */,
&values[1], nullptr /* bytes_read */));
ASSERT_EQ(values[1], blobs[1]);
ASSERT_TRUE(
blob_source.TEST_BlobInCache(file_number, file_size, blob_offsets[1]));
@@ -1439,10 +1422,9 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
// fetch key0 from the blob file to the primary cache.
// key1 is evicted and inserted into the secondary cache.
ASSERT_OK(blob_source.GetBlob(
read_options, keys[0], OpenInfoWithoutChecksum(file_number),
blob_offsets[0], file_size, blob_sizes[0], kNoCompression,
nullptr /* prefetch_buffer */, values.data(),
nullptr /* bytes_read */));
read_options, keys[0], file_number, blob_offsets[0], file_size,
blob_sizes[0], kNoCompression, nullptr /* prefetch_buffer */,
values.data(), nullptr /* bytes_read */));
ASSERT_EQ(values[0], blobs[0]);
// Release cache handle
@@ -1624,8 +1606,8 @@ TEST_F(BlobSourceCacheReservationTest, SimpleCacheReservation) {
for (size_t i = 0; i < kNumBlobs; ++i) {
ASSERT_OK(blob_source.GetBlob(
read_options, keys_[i], OpenInfoWithoutChecksum(kBlobFileNumber),
blob_offsets[i], blob_file_size_, blob_sizes[i], kNoCompression,
read_options, keys_[i], kBlobFileNumber, blob_offsets[i],
blob_file_size_, blob_sizes[i], kNoCompression,
nullptr /* prefetch_buffer */, &values[i], nullptr /* bytes_read */));
ASSERT_EQ(cache_res_mgr->GetTotalReservedCacheSize(), 0);
ASSERT_EQ(cache_res_mgr->GetTotalMemoryUsed(), 0);
@@ -1643,8 +1625,8 @@ TEST_F(BlobSourceCacheReservationTest, SimpleCacheReservation) {
uint64_t blob_bytes = 0;
for (size_t i = 0; i < kNumBlobs; ++i) {
ASSERT_OK(blob_source.GetBlob(
read_options, keys_[i], OpenInfoWithoutChecksum(kBlobFileNumber),
blob_offsets[i], blob_file_size_, blob_sizes[i], kNoCompression,
read_options, keys_[i], kBlobFileNumber, blob_offsets[i],
blob_file_size_, blob_sizes[i], kNoCompression,
nullptr /* prefetch_buffer */, &values[i], nullptr /* bytes_read */));
size_t charge = 0;
@@ -1746,8 +1728,8 @@ TEST_F(BlobSourceCacheReservationTest, IncreaseCacheReservation) {
for (size_t i = 0; i < kNumBlobs; ++i) {
ASSERT_OK(blob_source.GetBlob(
read_options, keys_[i], OpenInfoWithoutChecksum(kBlobFileNumber),
blob_offsets[i], blob_file_size_, blob_sizes[i], kNoCompression,
read_options, keys_[i], kBlobFileNumber, blob_offsets[i],
blob_file_size_, blob_sizes[i], kNoCompression,
nullptr /* prefetch_buffer */, &values[i], nullptr /* bytes_read */));
ASSERT_EQ(cache_res_mgr->GetTotalReservedCacheSize(), 0);
ASSERT_EQ(cache_res_mgr->GetTotalMemoryUsed(), 0);
@@ -1762,8 +1744,8 @@ TEST_F(BlobSourceCacheReservationTest, IncreaseCacheReservation) {
uint64_t blob_bytes = 0;
for (size_t i = 0; i < kNumBlobs; ++i) {
ASSERT_OK(blob_source.GetBlob(
read_options, keys_[i], OpenInfoWithoutChecksum(kBlobFileNumber),
blob_offsets[i], blob_file_size_, blob_sizes[i], kNoCompression,
read_options, keys_[i], kBlobFileNumber, blob_offsets[i],
blob_file_size_, blob_sizes[i], kNoCompression,
nullptr /* prefetch_buffer */, &values[i], nullptr /* bytes_read */));
// Release cache handle
+1 -664
View File
@@ -7,7 +7,6 @@
#include <array>
#include <atomic>
#include <cstdint>
#include <mutex>
#include <set>
#include <sstream>
#include <string>
@@ -20,19 +19,15 @@
#include "db/column_family.h"
#include "db/db_test_util.h"
#include "db/db_with_timestamp_test_util.h"
#include "env/composite_env_wrapper.h"
#include "file/filename.h"
#include "file/random_access_file_reader.h"
#include "port/stack_trace.h"
#include "rocksdb/convenience.h"
#include "rocksdb/file_system.h"
#include "rocksdb/trace_reader_writer.h"
#include "rocksdb/trace_record.h"
#include "rocksdb/utilities/replayer.h"
#include "test_util/sync_point.h"
#include "util/compression.h"
#include "util/defer.h"
#include "util/file_checksum_helper.h"
#include "utilities/fault_injection_env.h"
namespace ROCKSDB_NAMESPACE {
@@ -41,116 +36,6 @@ class DBBlobBasicTest : public DBTestBase {
protected:
DBBlobBasicTest()
: DBTestBase("db_blob_basic_test", /* env_do_fsync */ false) {}
Options MissingBlobRepairOptions() {
Options options = GetDefaultOptions();
options.create_if_missing = true;
options.enable_blob_files = true;
options.min_blob_size = 0;
options.enable_blob_garbage_collection = false;
options.disable_auto_compactions = true;
options.num_levels = 3;
options.level_compaction_dynamic_level_bytes = false;
return options;
}
void AssertBlobReference(const Slice& key, uint64_t blob_file_number) {
PinnableSlice value;
bool is_blob_index = false;
DBImpl::GetImplOptions get_options;
get_options.column_family = db_->DefaultColumnFamily();
get_options.value = &value;
get_options.is_blob_index = &is_blob_index;
ASSERT_OK(dbfull()->GetImpl(ReadOptions(), key, get_options));
ASSERT_TRUE(is_blob_index);
BlobIndex blob_index;
ASSERT_OK(blob_index.DecodeFrom(value));
ASSERT_EQ(blob_index.file_number(), blob_file_number);
}
void RecoverMissingNonOldestBlob(const Options& options,
bool preserve_older_value,
uint64_t* missing_blob_file) {
DestroyAndReopen(options);
ASSERT_OK(Put("a", "va"));
ASSERT_OK(Put("c", "vc"));
if (preserve_older_value) {
ASSERT_OK(Put("b", "old"));
}
ASSERT_OK(Flush());
const Snapshot* snapshot =
preserve_older_value ? db_->GetSnapshot() : nullptr;
{
Defer release_snapshot([this, snapshot]() {
if (snapshot) {
db_->ReleaseSnapshot(snapshot);
}
});
ASSERT_OK(Put("b", "vb"));
ASSERT_OK(Put("d", "vd"));
ASSERT_OK(Flush());
const std::vector<uint64_t> blob_files = GetBlobFileNumbers();
ASSERT_EQ(blob_files.size(), 2);
*missing_blob_file = blob_files.back();
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_EQ(TotalTableFiles(), 1);
}
Close();
ASSERT_OK(env_->DeleteFile(BlobFileName(dbname_, *missing_blob_file)));
Options recovery_options = options;
recovery_options.best_efforts_recovery = true;
Reopen(recovery_options);
const std::vector<uint64_t> live_blob_files = GetBlobFileNumbers();
ASSERT_EQ(live_blob_files.size(), 1);
ASSERT_NE(live_blob_files.front(), *missing_blob_file);
ASSERT_EQ(TotalTableFiles(), 1);
ASSERT_NO_FATAL_FAILURE(AssertBlobReference("b", *missing_blob_file));
ASSERT_NO_FATAL_FAILURE(AssertBlobReference("d", *missing_blob_file));
}
};
class BlobFileChecksumCapturingFS : public FileSystemWrapper {
public:
explicit BlobFileChecksumCapturingFS(const std::shared_ptr<FileSystem>& base)
: FileSystemWrapper(base) {}
static const char* kClassName() { return "BlobFileChecksumCapturingFS"; }
const char* Name() const override { return kClassName(); }
IOStatus NewRandomAccessFile(const std::string& fname,
const FileOptions& opts,
std::unique_ptr<FSRandomAccessFile>* result,
IODebugContext* dbg) override {
if (fname.find(".blob") != std::string::npos) {
std::lock_guard<std::mutex> lock(mu_);
file_checksum_ = opts.file_checksum;
file_checksum_func_name_ = opts.file_checksum_func_name;
++capture_count_;
}
return target()->NewRandomAccessFile(fname, opts, result, dbg);
}
std::string GetFileChecksum() {
std::lock_guard<std::mutex> lock(mu_);
return file_checksum_;
}
std::string GetFileChecksumFuncName() {
std::lock_guard<std::mutex> lock(mu_);
return file_checksum_func_name_;
}
int GetCaptureCount() {
std::lock_guard<std::mutex> lock(mu_);
return capture_count_;
}
private:
std::mutex mu_;
std::string file_checksum_;
std::string file_checksum_func_name_;
int capture_count_ = 0;
};
TEST_F(DBBlobBasicTest, GetBlob) {
@@ -180,41 +65,6 @@ TEST_F(DBBlobBasicTest, GetBlob) {
.IsIncomplete());
}
TEST_F(DBBlobBasicTest, BlobFileChecksumInFileOptions) {
auto capturing_fs =
std::make_shared<BlobFileChecksumCapturingFS>(env_->GetFileSystem());
std::unique_ptr<Env> env(new CompositeEnvWrapper(env_, capturing_fs));
Options options = GetDefaultOptions();
options.create_if_missing = true;
options.env = env.get();
options.enable_blob_files = true;
options.min_blob_size = 0;
options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
Defer close_db([this]() { Close(); });
Reopen(options);
constexpr char key[] = "key";
constexpr char blob_value[] = "blob_value";
ASSERT_OK(Put(key, blob_value));
ASSERT_OK(Flush());
std::vector<ColumnFamilyMetaData> column_family_metadata;
db_->GetAllColumnFamilyMetaData(&column_family_metadata);
ASSERT_EQ(column_family_metadata.size(), 1);
ASSERT_EQ(column_family_metadata[0].blob_files.size(), 1);
const BlobMetaData& blob_metadata = column_family_metadata[0].blob_files[0];
ASSERT_FALSE(blob_metadata.checksum_value.empty());
ASSERT_EQ(blob_metadata.checksum_method, "FileChecksumCrc32c");
ASSERT_EQ(Get(key), blob_value);
ASSERT_GT(capturing_fs->GetCaptureCount(), 0);
ASSERT_EQ(capturing_fs->GetFileChecksum(), blob_metadata.checksum_value);
ASSERT_EQ(capturing_fs->GetFileChecksumFuncName(),
blob_metadata.checksum_method);
}
TEST_F(DBBlobBasicTest, BlobFileWritableFileMaxBufferSize) {
constexpr uint64_t kBlobWriterBufferSize = 128 * 1024;
@@ -904,15 +754,6 @@ TEST_F(DBBlobBasicTest, MultiGetBlobsFromCache) {
ASSERT_EQ(values[2], third_value);
}
// Load the SST blocks into the block cache. The inlined key does not read
// the blob file, so the blob cache stays empty.
{
ReadOptions fill_block_cache_options;
std::string value;
ASSERT_OK(db_->Get(fill_block_cache_options, first_key, &value));
ASSERT_EQ(value, first_value);
}
// Try again with no I/O allowed. The first (inlined) value should be
// successfully read; however, the two blob values could only be read from the
// blob file, so for those the read should return Incomplete.
@@ -1550,335 +1391,6 @@ TEST_F(DBBlobBasicTest, BestEffortsRecovery_MissingNewestBlobFile) {
ASSERT_EQ("value" + std::to_string(kNumTableFiles - 2), value);
}
TEST_F(DBBlobBasicTest, RepairMissingNonOldestBlobAfterBestEffortsRecovery) {
Options options = MissingBlobRepairOptions();
options.statistics = CreateDBStatistics();
uint64_t missing_blob_file = 0;
ASSERT_NO_FATAL_FAILURE(
RecoverMissingNonOldestBlob(options, false, &missing_blob_file));
const std::vector<uint64_t> healthy_blob_files = GetBlobFileNumbers();
ColumnFamilyMetaData before;
db_->GetColumnFamilyMetaData(db_->DefaultColumnFamily(), &before);
CompactRangeOptions repair;
repair.skip_missing_blob_files = true;
repair.bottommost_level_compaction = BottommostLevelCompaction::kSkip;
repair.max_subcompactions = 2;
repair.blob_garbage_collection_policy = BlobGarbageCollectionPolicy::kDisable;
ASSERT_OK(options.statistics->Reset());
ASSERT_OK(db_->CompactRange(repair, nullptr, nullptr));
ASSERT_GT(options.statistics->getTickerCount(COMPACT_READ_BYTES), 0);
ASSERT_EQ(options.statistics->getTickerCount(BLOB_DB_BLOB_FILE_BYTES_READ),
0);
ColumnFamilyMetaData after;
db_->GetColumnFamilyMetaData(db_->DefaultColumnFamily(), &after);
std::set<std::string> input_files;
for (const LevelMetaData& before_level : before.levels) {
for (const SstFileMetaData& input : before_level.files) {
input_files.insert(input.name);
}
}
for (const LevelMetaData& after_level : after.levels) {
for (const SstFileMetaData& output : after_level.files) {
ASSERT_EQ(input_files.count(output.name), 0);
}
}
ASSERT_EQ(GetBlobFileNumbers(), healthy_blob_files);
ASSERT_EQ(Get("a"), "va");
ASSERT_EQ(Get("c"), "vc");
std::string value;
ASSERT_EQ(db_->Get(ReadOptions(), "b", &value).code(), Status::kNotFound);
ASSERT_EQ(db_->Get(ReadOptions(), "d", &value).code(), Status::kNotFound);
Reopen(options);
ASSERT_EQ(Get("a"), "va");
ASSERT_EQ(Get("c"), "vc");
ASSERT_EQ(db_->Get(ReadOptions(), "b", &value).code(), Status::kNotFound);
ASSERT_EQ(db_->Get(ReadOptions(), "d", &value).code(), Status::kNotFound);
}
TEST_F(DBBlobBasicTest, MissingBlobRepairIsScopedToCompactRange) {
Options options = MissingBlobRepairOptions();
options.paranoid_checks = false;
uint64_t missing_blob_file = 0;
ASSERT_NO_FATAL_FAILURE(
RecoverMissingNonOldestBlob(options, false, &missing_blob_file));
ASSERT_OK(Put("b", "replacement"));
ASSERT_OK(Flush());
const Status normal_compaction =
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
ASSERT_EQ(normal_compaction.code(), Status::kCorruption);
ASSERT_NE(normal_compaction.ToString().find("unknown blob file"),
std::string::npos);
CompactRangeOptions repair;
repair.skip_missing_blob_files = true;
ASSERT_OK(db_->CompactRange(repair, nullptr, nullptr));
ASSERT_EQ(Get("b"), "replacement");
ASSERT_EQ(Get("a"), "va");
std::string value;
ASSERT_EQ(db_->Get(ReadOptions(), "d", &value).code(), Status::kNotFound);
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
Reopen(options);
ASSERT_EQ(Get("b"), "replacement");
const uint64_t later_missing_blob = GetBlobFileNumbers().back();
Close();
ASSERT_OK(env_->DeleteFile(BlobFileName(dbname_, later_missing_blob)));
Reopen(options);
const Status later_corruption = db_->Get(ReadOptions(), "b", &value);
ASSERT_FALSE(later_corruption.ok());
ASSERT_TRUE(later_corruption.IsCorruption() || later_corruption.IsIOError() ||
later_corruption.IsNotFound())
<< later_corruption.ToString();
}
TEST_F(DBBlobBasicTest, RepairMissingBlobsAcrossSubcompactions) {
Options options = MissingBlobRepairOptions();
options.statistics = CreateDBStatistics();
options.compression = kNoCompression;
options.target_file_size_base = 1024 * 1024;
BlockBasedTableOptions table_options;
table_options.block_size = 256;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
DestroyAndReopen(options);
constexpr int kNumKeys = 512;
for (int key = 0; key < kNumKeys; key += 2) {
ASSERT_OK(Put(Key(key), "healthy"));
}
ASSERT_OK(Flush());
const uint64_t healthy_blob_file = GetBlobFileNumbers().back();
for (int key = 1; key < kNumKeys; key += 2) {
ASSERT_OK(Put(Key(key), "lost"));
}
ASSERT_OK(Flush());
const uint64_t missing_blob_file = GetBlobFileNumbers().back();
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_EQ(TotalTableFiles(), 1);
Close();
ASSERT_OK(env_->DeleteFile(BlobFileName(dbname_, missing_blob_file)));
Options recovery_options = options;
recovery_options.best_efforts_recovery = true;
Reopen(recovery_options);
ASSERT_EQ(GetBlobFileNumbers(), std::vector<uint64_t>{healthy_blob_file});
ASSERT_NO_FATAL_FAILURE(AssertBlobReference(Key(1), missing_blob_file));
ASSERT_OK(db_->SetOptions({{"target_file_size_base", "1024"}}));
CompactRangeOptions repair;
repair.skip_missing_blob_files = true;
repair.max_subcompactions = 4;
ASSERT_OK(options.statistics->Reset());
ASSERT_OK(db_->CompactRange(repair, nullptr, nullptr));
HistogramData subcompactions;
options.statistics->histogramData(NUM_SUBCOMPACTIONS_SCHEDULED,
&subcompactions);
ASSERT_GT(subcompactions.max, 1);
ASSERT_EQ(options.statistics->getTickerCount(BLOB_DB_BLOB_FILE_BYTES_READ),
0);
std::string value;
for (int key = 0; key < kNumKeys; ++key) {
if (key % 2 == 0) {
ASSERT_OK(db_->Get(ReadOptions(), Key(key), &value));
ASSERT_EQ(value, "healthy");
} else {
ASSERT_EQ(db_->Get(ReadOptions(), Key(key), &value).code(),
Status::kNotFound);
}
}
}
TEST_F(DBBlobBasicTest, RepairPropagatesKnownBlobFileIoFailureDuringGc) {
Options options = MissingBlobRepairOptions();
DestroyAndReopen(options);
ASSERT_OK(Put("key", "value"));
ASSERT_OK(Flush());
const std::vector<uint64_t> blob_files = GetBlobFileNumbers();
ASSERT_EQ(blob_files.size(), 1);
Close();
ASSERT_OK(env_->DeleteFile(BlobFileName(dbname_, blob_files.front())));
Reopen(options);
ASSERT_EQ(GetBlobFileNumbers(), blob_files);
ASSERT_NO_FATAL_FAILURE(AssertBlobReference("key", blob_files.front()));
std::atomic<size_t> blob_file_opens{0};
SyncPoint::GetInstance()->SetCallBack(
"BlobFileReader::OpenFile:NewRandomAccessFile",
[&blob_file_opens](void*) { ++blob_file_opens; });
Defer clear_sync_points([]() {
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
});
SyncPoint::GetInstance()->EnableProcessing();
CompactRangeOptions repair;
repair.skip_missing_blob_files = true;
repair.blob_garbage_collection_policy = BlobGarbageCollectionPolicy::kForce;
repair.blob_garbage_collection_age_cutoff = 1.0;
const Status status = db_->CompactRange(repair, nullptr, nullptr);
ASSERT_FALSE(status.ok());
ASSERT_TRUE(status.IsIOError() || status.IsCorruption() ||
status.IsNotFound())
<< status.ToString();
ASSERT_GT(blob_file_opens.load(), 0);
ASSERT_EQ(GetBlobFileNumbers(), blob_files);
ASSERT_NO_FATAL_FAILURE(AssertBlobReference("key", blob_files.front()));
}
TEST_F(DBBlobBasicTest, RepairMissingBlobPreservesSnapshotDeletionSemantics) {
for (const bool use_merge : {false, true}) {
SCOPED_TRACE("use_merge=" + std::to_string(use_merge));
Options options = MissingBlobRepairOptions();
if (use_merge) {
options.merge_operator = MergeOperators::CreateStringAppendOperator();
}
uint64_t missing_blob_file = 0;
ASSERT_NO_FATAL_FAILURE(
RecoverMissingNonOldestBlob(options, true, &missing_blob_file));
{
const Snapshot* snapshot = db_->GetSnapshot();
Defer release_snapshot(
[this, snapshot]() { db_->ReleaseSnapshot(snapshot); });
if (use_merge) {
ASSERT_OK(Merge("b", "replacement"));
} else {
ASSERT_OK(Put("b", "replacement"));
}
ASSERT_OK(Flush());
CompactRangeOptions repair;
repair.skip_missing_blob_files = true;
ASSERT_OK(db_->CompactRange(repair, nullptr, nullptr));
ASSERT_EQ(Get("b"), "replacement");
ReadOptions read;
read.snapshot = snapshot;
std::string value;
ASSERT_EQ(db_->Get(read, "b", &value).code(), Status::kNotFound);
ASSERT_OK(db_->Get(read, "a", &value));
ASSERT_EQ(value, "va");
}
Reopen(options);
ASSERT_EQ(Get("b"), "replacement");
}
}
TEST_F(DBBlobBasicTest, RepairMissingBlobPairedWithSingleDelete) {
Options options = MissingBlobRepairOptions();
uint64_t missing_blob_file = 0;
ASSERT_NO_FATAL_FAILURE(
RecoverMissingNonOldestBlob(options, false, &missing_blob_file));
{
const Snapshot* snapshot = db_->GetSnapshot();
Defer release_snapshot(
[this, snapshot]() { db_->ReleaseSnapshot(snapshot); });
ASSERT_OK(SingleDelete("b"));
ASSERT_OK(Flush());
CompactRangeOptions repair;
repair.skip_missing_blob_files = true;
ASSERT_OK(db_->CompactRange(repair, nullptr, nullptr));
std::string value;
ASSERT_EQ(db_->Get(ReadOptions(), "b", &value).code(), Status::kNotFound);
ReadOptions read;
read.snapshot = snapshot;
ASSERT_EQ(db_->Get(read, "b", &value).code(), Status::kNotFound);
ASSERT_OK(db_->Get(read, "a", &value));
ASSERT_EQ(value, "va");
}
CompactRangeOptions normal;
normal.bottommost_level_compaction = BottommostLevelCompaction::kForce;
ASSERT_OK(db_->CompactRange(normal, nullptr, nullptr));
Reopen(options);
std::string value;
ASSERT_EQ(db_->Get(ReadOptions(), "b", &value).code(), Status::kNotFound);
ASSERT_EQ(db_->Get(ReadOptions(), "d", &value).code(), Status::kNotFound);
ASSERT_EQ(Get("a"), "va");
ASSERT_EQ(Get("c"), "vc");
}
TEST_F(DBBlobBasicTest, RepairMissingBlobWithNoLiveBlobMetadata) {
Options options = MissingBlobRepairOptions();
uint64_t missing_blob_file = 0;
ASSERT_NO_FATAL_FAILURE(
RecoverMissingNonOldestBlob(options, false, &missing_blob_file));
ASSERT_OK(Delete("a"));
ASSERT_OK(Delete("c"));
ASSERT_OK(Flush());
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_TRUE(GetBlobFileNumbers().empty());
ASSERT_NO_FATAL_FAILURE(AssertBlobReference("b", missing_blob_file));
ASSERT_NO_FATAL_FAILURE(AssertBlobReference("d", missing_blob_file));
std::string value;
ASSERT_EQ(db_->Get(ReadOptions(), "b", &value).code(), Status::kCorruption);
CompactRangeOptions repair;
repair.skip_missing_blob_files = true;
ASSERT_OK(db_->CompactRange(repair, nullptr, nullptr));
ASSERT_EQ(db_->Get(ReadOptions(), "b", &value).code(), Status::kNotFound);
ASSERT_EQ(db_->Get(ReadOptions(), "d", &value).code(), Status::kNotFound);
Reopen(options);
ASSERT_EQ(db_->Get(ReadOptions(), "b", &value).code(), Status::kNotFound);
ASSERT_EQ(db_->Get(ReadOptions(), "d", &value).code(), Status::kNotFound);
}
TEST_F(DBBlobBasicTest, RepairRetainsHealthyBlobsReferencedByUntouchedSsts) {
Options options = MissingBlobRepairOptions();
options.sst_partitioner_factory = NewSstPartitionerFixedPrefixFactory(1);
DestroyAndReopen(options);
ASSERT_OK(Put("a_anchor", "anchor"));
ASSERT_OK(Put("z_anchor", "anchor"));
ASSERT_OK(Flush());
ASSERT_OK(Put("a_lost", "lost"));
ASSERT_OK(Flush());
const uint64_t missing_blob_file = GetBlobFileNumbers().back();
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_EQ(TotalTableFiles(), 2);
Close();
ASSERT_OK(env_->DeleteFile(BlobFileName(dbname_, missing_blob_file)));
Options recovery_options = options;
recovery_options.best_efforts_recovery = true;
Reopen(recovery_options);
ASSERT_OK(Put("a_live", "healthy"));
ASSERT_OK(Flush());
const uint64_t healthy_blob_file = GetBlobFileNumbers().back();
ASSERT_OK(Delete("a_anchor"));
ASSERT_OK(Flush());
const Slice a_begin("a");
const Slice a_end("b");
CompactRangeOptions rewrite;
rewrite.bottommost_level_compaction = BottommostLevelCompaction::kForce;
ASSERT_OK(db_->CompactRange(rewrite, &a_begin, &a_end));
ASSERT_EQ(Get("a_live"), "healthy");
ASSERT_OK(Delete("z_anchor"));
ASSERT_OK(Flush());
const Slice z_begin("z");
CompactRangeOptions repair;
repair.skip_missing_blob_files = true;
ASSERT_OK(db_->CompactRange(repair, &z_begin, nullptr));
ASSERT_NO_FATAL_FAILURE(AssertBlobReference("a_lost", missing_blob_file));
ASSERT_NO_FATAL_FAILURE(AssertBlobReference("a_live", healthy_blob_file));
ASSERT_EQ(Get("a_live"), "healthy");
const std::vector<uint64_t> retained_blob_files = GetBlobFileNumbers();
ASSERT_NE(std::find(retained_blob_files.begin(), retained_blob_files.end(),
healthy_blob_file),
retained_blob_files.end());
std::string value;
ASSERT_EQ(db_->Get(ReadOptions(), "a_lost", &value).code(),
Status::kCorruption);
ASSERT_OK(db_->CompactRange(repair, &a_begin, &a_end));
ASSERT_EQ(db_->Get(ReadOptions(), "a_lost", &value).code(),
Status::kNotFound);
ASSERT_EQ(Get("a_live"), "healthy");
Reopen(options);
ASSERT_EQ(db_->Get(ReadOptions(), "a_lost", &value).code(),
Status::kNotFound);
ASSERT_EQ(Get("a_live"), "healthy");
}
TEST_F(DBBlobBasicTest, GetMergeBlobWithPut) {
Options options = GetDefaultOptions();
options.merge_operator = MergeOperators::CreateStringAppendOperator();
@@ -2125,13 +1637,11 @@ TEST_F(DBBlobBasicTest, PropertiesMultiVersion) {
class DBBlobBasicIOErrorTest : public DBBlobBasicTest,
public testing::WithParamInterface<std::string> {
public:
~DBBlobBasicIOErrorTest() override { Close(); }
protected:
DBBlobBasicIOErrorTest() : sync_point_(GetParam()) {
fault_injection_env_.reset(new FaultInjectionTestEnv(env_));
}
~DBBlobBasicIOErrorTest() { Close(); }
std::unique_ptr<FaultInjectionTestEnv> fault_injection_env_;
std::string sync_point_;
@@ -2711,179 +2221,6 @@ class DBBlobWithTimestampTest : public DBBasicTestWithTimestampBase {
: DBBasicTestWithTimestampBase("db_blob_with_timestamp_test") {}
};
TEST_F(DBBlobWithTimestampTest, RepairPreservesTimestampHistoryAfterRecovery) {
Options options = GetDefaultOptions();
options.create_if_missing = true;
options.enable_blob_files = true;
options.min_blob_size = 0;
options.enable_blob_garbage_collection = false;
options.disable_auto_compactions = true;
options.num_levels = 3;
options.level_compaction_dynamic_level_bytes = false;
TestComparator test_cmp(Timestamp(0, 0).size());
options.comparator = &test_cmp;
DestroyAndReopen(options);
Defer close_db([this]() { Close(); });
const std::string old_timestamp = Timestamp(1, 0);
const std::string middle_timestamp = Timestamp(2, 0);
const std::string lost_timestamp = Timestamp(3, 0);
const std::string latest_timestamp = Timestamp(4, 0);
const Slice old_ts(old_timestamp);
const Slice middle_ts(middle_timestamp);
const Slice lost_ts(lost_timestamp);
const Slice latest_ts(latest_timestamp);
ReadOptions healthy_read;
healthy_read.verify_checksums = true;
healthy_read.timestamp = &latest_ts;
std::string healthy_value;
ASSERT_OK(db_->Put(WriteOptions(), "a", old_ts, "healthy"));
ASSERT_OK(db_->Put(WriteOptions(), "b", old_ts, "older"));
const SequenceNumber older_sequence = db_->GetLatestSequenceNumber();
ASSERT_OK(Flush());
const uint64_t healthy_blob_file = GetBlobFileNumbers().back();
ASSERT_OK(db_->Get(healthy_read, "a", &healthy_value));
ASSERT_EQ(healthy_value, "healthy");
ASSERT_OK(db_->Put(WriteOptions(), "b", lost_ts, "lost"));
const SequenceNumber lost_sequence = db_->GetLatestSequenceNumber();
ASSERT_OK(db_->Put(WriteOptions(), "d", lost_ts, "lost"));
const SequenceNumber other_lost_sequence = db_->GetLatestSequenceNumber();
ASSERT_OK(Flush());
const uint64_t missing_blob_file = GetBlobFileNumbers().back();
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_EQ(TotalTableFiles(), 1);
Close();
ASSERT_OK(env_->DeleteFile(BlobFileName(dbname_, missing_blob_file)));
Options recovery_options = options;
recovery_options.best_efforts_recovery = true;
Reopen(recovery_options);
ASSERT_EQ(TotalTableFiles(), 1);
ASSERT_EQ(GetBlobFileNumbers(), std::vector<uint64_t>{healthy_blob_file});
ASSERT_OK(db_->Get(healthy_read, "a", &healthy_value));
ASSERT_EQ(healthy_value, "healthy");
{
ReadOptions read;
read.timestamp = &lost_ts;
PinnableSlice value;
bool is_blob_index = false;
DBImpl::GetImplOptions get_options;
get_options.column_family = db_->DefaultColumnFamily();
get_options.value = &value;
get_options.is_blob_index = &is_blob_index;
ASSERT_OK(dbfull()->GetImpl(read, "b", get_options));
ASSERT_TRUE(is_blob_index);
BlobIndex blob_index;
ASSERT_OK(blob_index.DecodeFrom(value));
ASSERT_EQ(blob_index.file_number(), missing_blob_file);
}
CompactRangeOptions repair;
repair.skip_missing_blob_files = true;
repair.full_history_ts_low = &old_ts;
ASSERT_OK(db_->CompactRange(repair, nullptr, nullptr));
for (int reopen = 0; reopen < 2; ++reopen) {
SCOPED_TRACE(reopen);
if (reopen != 0) {
Reopen(options);
}
std::string full_history_ts_low;
ASSERT_OK(db_->GetFullHistoryTsLow(db_->DefaultColumnFamily(),
&full_history_ts_low));
ASSERT_EQ(full_history_ts_low, old_timestamp);
ReadOptions read;
read.verify_checksums = true;
std::string value;
read.timestamp = &old_ts;
ASSERT_OK(db_->Get(read, "b", &value));
ASSERT_EQ(value, "older");
read.timestamp = &middle_ts;
ASSERT_OK(db_->Get(read, "b", &value));
ASSERT_EQ(value, "older");
read.timestamp = &lost_ts;
ASSERT_EQ(db_->Get(read, "b", &value).code(), Status::kNotFound);
ASSERT_EQ(db_->Get(read, "d", &value).code(), Status::kNotFound);
read.timestamp = &latest_ts;
ASSERT_EQ(db_->Get(read, "b", &value).code(), Status::kNotFound);
ASSERT_OK(db_->Get(read, "a", &value));
ASSERT_EQ(value, "healthy");
read.iter_start_ts = &old_ts;
std::unique_ptr<Iterator> history(db_->NewIterator(read));
history->Seek("b");
ASSERT_NO_FATAL_FAILURE(CheckIterEntry(history.get(), "b", lost_sequence,
kTypeDeletionWithTimestamp, Slice(),
lost_ts));
history->Next();
ASSERT_NO_FATAL_FAILURE(CheckIterEntry(history.get(), "b", older_sequence,
kTypeBlobIndex, "older", old_ts));
ASSERT_EQ(history->value(), "older");
history->Next();
ASSERT_NO_FATAL_FAILURE(
CheckIterEntry(history.get(), "d", other_lost_sequence,
kTypeDeletionWithTimestamp, Slice(), lost_ts));
history->Next();
ASSERT_FALSE(history->Valid());
ASSERT_OK(history->status());
}
CompactRangeOptions normal;
normal.bottommost_level_compaction = BottommostLevelCompaction::kForce;
normal.full_history_ts_low = &latest_ts;
ASSERT_OK(db_->Get(healthy_read, "a", &healthy_value));
ASSERT_EQ(healthy_value, "healthy");
ASSERT_OK(db_->CompactRange(normal, nullptr, nullptr));
ASSERT_OK(db_->Get(healthy_read, "a", &healthy_value));
ASSERT_EQ(healthy_value, "healthy");
Reopen(options);
ReadOptions read;
read.verify_checksums = true;
read.timestamp = &latest_ts;
std::string value;
ASSERT_EQ(db_->Get(read, "b", &value).code(), Status::kNotFound);
ASSERT_EQ(db_->Get(read, "d", &value).code(), Status::kNotFound);
ASSERT_OK(db_->Get(read, "a", &value));
ASSERT_EQ(value, "healthy");
read.timestamp = &old_ts;
ASSERT_EQ(db_->Get(read, "b", &value).code(), Status::kInvalidArgument);
}
TEST_F(DBBlobWithTimestampTest, HistoryCompactionPreservesHealthyBlobKey) {
Options options = GetDefaultOptions();
options.create_if_missing = true;
options.enable_blob_files = true;
options.min_blob_size = 0;
options.enable_blob_garbage_collection = false;
options.disable_auto_compactions = true;
options.num_levels = 3;
options.level_compaction_dynamic_level_bytes = false;
TestComparator test_cmp(Timestamp(0, 0).size());
options.comparator = &test_cmp;
DestroyAndReopen(options);
Defer close_db([this]() { Close(); });
const std::string old_timestamp = Timestamp(1, 0);
const std::string history_floor = Timestamp(4, 0);
const Slice old_ts(old_timestamp);
const Slice history_floor_ts(history_floor);
ASSERT_OK(db_->Put(WriteOptions(), "a", old_ts, "healthy"));
ASSERT_OK(Flush());
ASSERT_EQ(GetBlobFileNumbers().size(), 1);
ReadOptions read;
read.verify_checksums = true;
read.timestamp = &history_floor_ts;
std::string value;
ASSERT_OK(db_->Get(read, "a", &value));
ASSERT_EQ(value, "healthy");
CompactRangeOptions normal;
normal.bottommost_level_compaction = BottommostLevelCompaction::kForce;
normal.full_history_ts_low = &history_floor_ts;
ASSERT_OK(db_->CompactRange(normal, nullptr, nullptr));
ASSERT_OK(db_->Get(read, "a", &value));
ASSERT_EQ(value, "healthy");
Reopen(options);
ASSERT_OK(db_->Get(read, "a", &value));
ASSERT_EQ(value, "healthy");
}
TEST_F(DBBlobWithTimestampTest, GetBlob) {
Options options = GetDefaultOptions();
options.create_if_missing = true;
+2 -633
View File
@@ -10,15 +10,12 @@
#include <algorithm>
#include <array>
#include <atomic>
#include <functional>
#include <future>
#include <limits>
#include <mutex>
#include <set>
#include <sstream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
@@ -38,14 +35,11 @@
#include "port/stack_trace.h"
#include "rocksdb/convenience.h"
#include "rocksdb/file_system.h"
#include "rocksdb/sst_file_manager.h"
#include "rocksdb/trace_reader_writer.h"
#include "rocksdb/trace_record.h"
#include "rocksdb/utilities/replayer.h"
#include "test_util/sync_point.h"
#include "util/compression.h"
#include "util/defer.h"
#include "util/file_checksum_helper.h"
#include "utilities/fault_injection_env.h"
namespace ROCKSDB_NAMESPACE {
@@ -127,59 +121,6 @@ bool IsBlobFilePath(const std::string& fname) {
file_type == kBlobFile;
}
class BlobFileChecksumCapturingFileSystem : public FileSystemWrapper {
public:
explicit BlobFileChecksumCapturingFileSystem(
const std::shared_ptr<FileSystem>& fs)
: FileSystemWrapper(fs) {}
static const char* kClassName() {
return "BlobFileChecksumCapturingFileSystem";
}
const char* Name() const override { return kClassName(); }
IOStatus NewRandomAccessFile(const std::string& fname,
const FileOptions& opts,
std::unique_ptr<FSRandomAccessFile>* result,
IODebugContext* dbg) override {
if (IsBlobFilePath(fname)) {
std::lock_guard<std::mutex> lock(mu_);
file_checksum_ = opts.file_checksum;
file_checksum_func_name_ = opts.file_checksum_func_name;
++capture_count_;
}
return target()->NewRandomAccessFile(fname, opts, result, dbg);
}
void Reset() {
std::lock_guard<std::mutex> lock(mu_);
file_checksum_.clear();
file_checksum_func_name_.clear();
capture_count_ = 0;
}
std::string GetFileChecksum() const {
std::lock_guard<std::mutex> lock(mu_);
return file_checksum_;
}
std::string GetFileChecksumFuncName() const {
std::lock_guard<std::mutex> lock(mu_);
return file_checksum_func_name_;
}
int GetCaptureCount() const {
std::lock_guard<std::mutex> lock(mu_);
return capture_count_;
}
private:
mutable std::mutex mu_;
std::string file_checksum_;
std::string file_checksum_func_name_;
int capture_count_ = 0;
};
// Keeps track of when an active blob file stops being writer-visible so the
// test filesystem can model current-size visibility only while the file remains
// active.
@@ -1464,9 +1405,7 @@ TEST_F(DBBlobDirectWriteTest,
std::vector<BlobFileGarbage> garbages1;
// Simulate a flush retry: prepare once, skip commit, then prepare the same
// generation again and verify it reuses the already sealed blob file.
const std::vector<uint64_t> memtable_ids = {
cfh->cfd()->imm()->GetEarliestMemTableID()};
ASSERT_OK(mgr->PrepareFlushAdditions(WriteOptions(), memtable_ids,
ASSERT_OK(mgr->PrepareFlushAdditions(WriteOptions(), /*num_generations=*/1,
&additions1, &garbages1));
ASSERT_EQ(additions1.size(), 1U);
ASSERT_TRUE(garbages1.empty());
@@ -1475,7 +1414,7 @@ TEST_F(DBBlobDirectWriteTest,
std::vector<BlobFileAddition> additions2;
std::vector<BlobFileGarbage> garbages2;
ASSERT_OK(mgr->PrepareFlushAdditions(WriteOptions(), memtable_ids,
ASSERT_OK(mgr->PrepareFlushAdditions(WriteOptions(), /*num_generations=*/1,
&additions2, &garbages2));
ASSERT_EQ(additions2.size(), 1U);
ASSERT_TRUE(garbages2.empty());
@@ -1816,54 +1755,6 @@ TEST_F(
ASSERT_TRUE(iter->Valid());
ASSERT_EQ(iter->value().ToString(), values[target_idx]);
}
TEST_F(DBBlobDirectWriteTest, DirectWriteDelayedReadUsesFinalizedFileChecksum) {
auto capturing_fs = std::make_shared<BlobFileChecksumCapturingFileSystem>(
env_->GetFileSystem());
std::unique_ptr<Env> env(new CompositeEnvWrapper(env_, capturing_fs));
Options options = GetDirectWriteOptions();
options.env = env.get();
options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
Defer close_db([this]() { Close(); });
Reopen(options);
const std::string key = "delayed-read-key";
const std::string value(96, 'v');
ASSERT_OK(Put(key, value));
ReadOptions read_options;
read_options.allow_unprepared_value = true;
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
iter->Seek(key);
ASSERT_TRUE(iter->Valid());
ASSERT_EQ(iter->key(), key);
ASSERT_TRUE(iter->value().empty());
ASSERT_OK(Flush());
std::vector<ColumnFamilyMetaData> column_family_metadata;
db_->GetAllColumnFamilyMetaData(&column_family_metadata);
ASSERT_EQ(column_family_metadata.size(), 1);
ASSERT_EQ(column_family_metadata[0].blob_files.size(), 1);
const BlobMetaData& blob_metadata = column_family_metadata[0].blob_files[0];
ASSERT_FALSE(blob_metadata.checksum_value.empty());
ASSERT_EQ(blob_metadata.checksum_method, "FileChecksumCrc32c");
capturing_fs->Reset();
ASSERT_TRUE(iter->PrepareValue());
ASSERT_TRUE(iter->Valid());
ASSERT_EQ(iter->value().ToString(), value);
ASSERT_GT(capturing_fs->GetCaptureCount(), 0);
ASSERT_EQ(capturing_fs->GetFileChecksum(), blob_metadata.checksum_value);
ASSERT_EQ(capturing_fs->GetFileChecksumFuncName(),
blob_metadata.checksum_method);
const int capture_count = capturing_fs->GetCaptureCount();
ASSERT_EQ(Get(key), value);
ASSERT_EQ(capturing_fs->GetCaptureCount(), capture_count);
}
TEST_F(DBBlobDirectWriteTest, OrderedTraceUsesLogicalBatchForBlobDirectWrite) {
AssertOrderedTraceStoresLogicalPut(GetDirectWriteOptions(),
/*expect_blob_files=*/true);
@@ -1881,528 +1772,6 @@ TEST_F(DBBlobDirectWriteTest,
AssertOrderedTraceStoresLogicalPut(options, /*expect_blob_files=*/false);
}
TEST_F(DBBlobDirectWriteTest, OverlappingFlushesPublishTheirOwnBlobFiles) {
for (bool atomic_flush : {false, true}) {
SCOPED_TRACE("atomic_flush=" + std::to_string(atomic_flush));
Options options = GetDirectWriteOptions();
options.atomic_flush = atomic_flush;
options.max_background_jobs = 8;
options.max_write_buffer_number = 4;
options.disable_auto_compactions = true;
DestroyAndReopen(options);
const std::string value(1024, 'v');
// The flush of the memtable holding "key1" waits while it writes its
// table file, until a second flush, of the memtable holding "key2", has
// finished its own table file.
std::atomic<int> table_writes{0};
std::atomic<bool> released{false};
std::promise<void> first_flush_writing;
std::promise<void> release_first_flush;
std::shared_future<void> release_first_flush_future =
release_first_flush.get_future().share();
SyncPoint::GetInstance()->SetCallBack(
"FlushJob::WriteLevel0Table:num_memtables", [&](void*) {
if (table_writes.fetch_add(1) == 0) {
first_flush_writing.set_value();
release_first_flush_future.wait();
}
});
SyncPoint::GetInstance()->SetCallBack(
atomic_flush ? "DBImpl::AtomicFlushMemTablesToOutputFiles:WaitCV"
: "DBImpl::FlushMemTableToOutputFile:Finish",
[&](void*) {
if (!released.exchange(true)) {
release_first_flush.set_value();
}
});
SyncPoint::GetInstance()->EnableProcessing();
FlushOptions no_wait;
no_wait.wait = false;
no_wait.allow_write_stall = true;
ASSERT_OK(Put("key1", value));
ASSERT_OK(db_->Flush(no_wait));
first_flush_writing.get_future().wait();
ASSERT_OK(Put("key2", value));
ASSERT_OK(db_->Flush(no_wait));
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
ASSERT_OK(dbfull()->TEST_GetBGError());
ASSERT_EQ(2, NumTableFilesAtLevel(0));
Reopen(options);
ASSERT_EQ(value, Get("key1"));
ASSERT_EQ(value, Get("key2"));
}
}
TEST_F(DBBlobDirectWriteTest, RetryAfterOlderOverlappingFlushFails) {
Options options = GetDirectWriteOptions();
options.max_background_jobs = 8;
options.max_write_buffer_number = 4;
options.disable_auto_compactions = true;
options.max_bgerror_resume_count = 0;
DestroyAndReopen(options);
const std::string value(1024, 'v');
// The flush of the memtable holding "key2" finishes while the older flush,
// of the memtable holding "key1", is still writing its table file. The
// newer flush's results wait to be installed after the older one's, but the
// older flush fails, which rolls the newer memtable back too.
std::atomic<int> table_writes{0};
std::atomic<bool> released{false};
std::atomic<bool> failed{false};
std::promise<void> first_flush_writing;
std::promise<void> release_first_flush;
std::shared_future<void> release_first_flush_future =
release_first_flush.get_future().share();
SyncPoint::GetInstance()->SetCallBack(
"FlushJob::WriteLevel0Table:num_memtables", [&](void*) {
if (table_writes.fetch_add(1) == 0) {
first_flush_writing.set_value();
release_first_flush_future.wait();
}
});
SyncPoint::GetInstance()->SetCallBack(
"DBImpl::FlushMemTableToOutputFile:Finish", [&](void*) {
if (!released.exchange(true)) {
release_first_flush.set_value();
}
});
SyncPoint::GetInstance()->SetCallBack(
"FlushJob::WriteLevel0Table:s", [&](void* arg) {
if (released.load() && !failed.exchange(true)) {
IOStatus injected = IOStatus::IOError("injected");
injected.SetRetryable(true);
*static_cast<Status*>(arg) = injected;
}
});
SyncPoint::GetInstance()->EnableProcessing();
FlushOptions no_wait;
no_wait.wait = false;
no_wait.allow_write_stall = true;
ASSERT_OK(Put("key1", value));
ASSERT_OK(db_->Flush(no_wait));
first_flush_writing.get_future().wait();
ASSERT_OK(Put("key2", value));
ASSERT_OK(db_->Flush(no_wait));
ASSERT_NOK(dbfull()->TEST_WaitForFlushMemTable());
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
ASSERT_TRUE(failed.load());
// Retrying the flush must still find the blob files of both memtables.
ASSERT_OK(dbfull()->Resume());
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
ASSERT_OK(dbfull()->TEST_GetBGError());
Reopen(options);
ASSERT_EQ(value, Get("key1"));
ASSERT_EQ(value, Get("key2"));
}
class BlobFileCreationStatusListener : public EventListener {
public:
void OnBlobFileCreated(const BlobFileCreationInfo& info) override {
std::lock_guard<std::mutex> lock(mutex_);
statuses_.push_back(info.status.ToString());
}
void OnErrorRecoveryBegin(BackgroundErrorReason /*reason*/, Status bg_error,
bool* /*auto_recovery*/) override {
bg_error.PermitUncheckedError();
}
std::vector<std::string> GetStatuses() {
std::lock_guard<std::mutex> lock(mutex_);
return statuses_;
}
private:
std::mutex mutex_;
std::vector<std::string> statuses_;
};
TEST_F(DBBlobDirectWriteTest, SpaceLimitWhileSealingKeepsBlobFile) {
Options options = GetDirectWriteOptions();
std::shared_ptr<SstFileManager> sst_file_manager(NewSstFileManager(env_));
options.sst_file_manager = sst_file_manager;
auto listener = std::make_shared<BlobFileCreationStatusListener>();
options.listeners.emplace_back(listener);
DestroyAndReopen(options);
const std::string value(1024, 'v');
ASSERT_OK(Put("key", value));
// The flush seals the blob file holding "key" and reaches the space limit.
// It still commits the file, and then writes stop. As for an SST file,
// Flush() returns the background error set after the commit.
sst_file_manager->SetMaxAllowedSpaceUsage(1);
ASSERT_EQ(Status::kSpaceLimit, Flush().subcode());
ASSERT_EQ(Status::kSpaceLimit, dbfull()->TEST_GetBGError().subcode());
ASSERT_NOK(Put("key2", "x"));
ColumnFamilyMetaData cf_meta;
db_->GetColumnFamilyMetaData(&cf_meta);
ASSERT_EQ(1U, cf_meta.blob_files.size());
ASSERT_EQ(value, Get("key"));
ASSERT_EQ(std::vector<std::string>{"OK"}, listener->GetStatuses());
}
TEST_F(DBBlobDirectWriteTest,
SpaceLimitWhileSealingDoesNotBlockMemtableSwitch) {
Options options = GetDirectWriteOptions();
options.max_write_buffer_number = 4;
std::shared_ptr<SstFileManager> sst_file_manager(NewSstFileManager(env_));
options.sst_file_manager = sst_file_manager;
DestroyAndReopen(options);
const std::string value(1024, 'v');
ASSERT_OK(Put("key1", value));
// While the flush reports the space limit for the blob file holding "key1",
// a concurrent memtable switch holds the DB mutex and rotates the blob
// generation.
port::Thread switch_thread;
std::atomic<bool> switch_started{false};
std::promise<void> switch_holds_db_mutex;
SyncPoint::GetInstance()->LoadDependency(
{{"DBBlobDirectWriteTest::SpaceLimitWhileSealing:SecondPut",
"DBImpl::BackgroundCallFlush:start"}});
SyncPoint::GetInstance()->SetCallBack(
"DBImpl::SwitchMemtable:BeforeRotateBlobGeneration", [&](void*) {
if (switch_started.exchange(false)) {
switch_holds_db_mutex.set_value();
}
});
SyncPoint::GetInstance()->SetCallBack(
"BlobFileCompletionCallback::CallBack::MaxAllowedSpaceReached",
[&](void*) {
if (switch_thread.joinable()) {
return;
}
switch_started = true;
switch_thread = port::Thread([&]() {
FlushOptions no_wait;
no_wait.wait = false;
no_wait.allow_write_stall = true;
db_->Flush(no_wait).PermitUncheckedError();
});
switch_holds_db_mutex.get_future().wait();
});
SyncPoint::GetInstance()->EnableProcessing();
sst_file_manager->SetMaxAllowedSpaceUsage(1);
FlushOptions no_wait;
no_wait.wait = false;
ASSERT_OK(db_->Flush(no_wait));
ASSERT_OK(Put("key2", value));
TEST_SYNC_POINT("DBBlobDirectWriteTest::SpaceLimitWhileSealing:SecondPut");
dbfull()->TEST_WaitForBackgroundWork().PermitUncheckedError();
ASSERT_TRUE(switch_thread.joinable());
switch_thread.join();
dbfull()->TEST_WaitForBackgroundWork().PermitUncheckedError();
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
ASSERT_EQ(value, Get("key1"));
ASSERT_EQ(value, Get("key2"));
}
class IgnoreFlushNoWALErrorListener : public EventListener {
public:
void OnBackgroundError(BackgroundErrorReason reason,
Status* bg_error) override {
std::lock_guard<std::mutex> lock(mutex_);
reasons_.push_back(reason);
if (reason == BackgroundErrorReason::kFlushNoWAL) {
*bg_error = Status::OK();
}
}
void OnErrorRecoveryBegin(BackgroundErrorReason /*reason*/, Status bg_error,
bool* /*auto_recovery*/) override {
bg_error.PermitUncheckedError();
}
std::vector<BackgroundErrorReason> GetReasons() {
std::lock_guard<std::mutex> lock(mutex_);
return reasons_;
}
private:
std::mutex mutex_;
std::vector<BackgroundErrorReason> reasons_;
};
TEST_F(DBBlobDirectWriteTest, SpaceLimitWhileSealingStopsWrites) {
Options options = GetDirectWriteOptions();
std::shared_ptr<SstFileManager> sst_file_manager(NewSstFileManager(env_));
options.sst_file_manager = sst_file_manager;
auto listener = std::make_shared<IgnoreFlushNoWALErrorListener>();
options.listeners.emplace_back(listener);
DestroyAndReopen(options);
const std::string value(1024, 'v');
ASSERT_OK(Put("key1", value));
// The space limit is reported for the blob file the flush seals, with the
// reason kFlush, so a listener that ignores kFlushNoWAL errors still stops
// writes.
sst_file_manager->SetMaxAllowedSpaceUsage(1);
FlushOptions no_wait;
no_wait.wait = false;
ASSERT_OK(db_->Flush(no_wait));
dbfull()->TEST_WaitForBackgroundWork().PermitUncheckedError();
const std::vector<BackgroundErrorReason> reasons = listener->GetReasons();
ASSERT_GT(
std::count(reasons.begin(), reasons.end(), BackgroundErrorReason::kFlush),
0);
ASSERT_NOK(Put("key2", value));
}
TEST_F(DBBlobDirectWriteTest, SpaceLimitWhileWriteSealsBlobFile) {
Options options = GetDirectWriteOptions();
options.blob_file_size = 2048;
std::shared_ptr<SstFileManager> sst_file_manager(NewSstFileManager(env_));
options.sst_file_manager = sst_file_manager;
auto listener = std::make_shared<BlobFileCreationStatusListener>();
options.listeners.emplace_back(listener);
DestroyAndReopen(options);
const std::string value(1024, 'v');
ASSERT_OK(Put("key1", value));
// "key2" does not fit in the blob file holding "key1", so the write seals
// that file and reaches the space limit. The write still succeeds, and then
// writes stop.
sst_file_manager->SetMaxAllowedSpaceUsage(1);
ASSERT_OK(Put("key2", value));
ASSERT_EQ(Status::kSpaceLimit, dbfull()->TEST_GetBGError().subcode());
ASSERT_NOK(Put("key3", "x"));
dbfull()->TEST_DeleteObsoleteFiles();
ASSERT_EQ(2U, CountBlobFiles());
ASSERT_EQ(value, Get("key1"));
ASSERT_EQ(value, Get("key2"));
ASSERT_EQ(std::vector<std::string>{"OK"}, listener->GetStatuses());
}
class FailNthBlobFileOpenFileSystem : public FileSystemWrapper {
public:
FailNthBlobFileOpenFileSystem(const std::shared_ptr<FileSystem>& fs,
int failing_open)
: FileSystemWrapper(fs), failing_open_(failing_open) {}
static const char* kClassName() { return "FailNthBlobFileOpenFileSystem"; }
const char* Name() const override { return kClassName(); }
IOStatus NewWritableFile(const std::string& fname,
const FileOptions& file_opts,
std::unique_ptr<FSWritableFile>* result,
IODebugContext* dbg) override {
if (IsBlobFilePath(fname) && ++blob_file_opens_ == failing_open_) {
return IOStatus::IOError("Injected blob file open error");
}
return target()->NewWritableFile(fname, file_opts, result, dbg);
}
private:
const int failing_open_;
std::atomic<int> blob_file_opens_{0};
};
TEST_F(DBBlobDirectWriteTest, FailedBatchAfterSealCountsGarbageInSealedFile) {
auto fs = std::make_shared<FailNthBlobFileOpenFileSystem>(
env_->GetFileSystem(), /*failing_open=*/3);
std::unique_ptr<Env> env(new CompositeEnvWrapper(env_, fs));
Options options = GetDirectWriteOptions();
options.env = env.get();
options.blob_file_size = 2048;
Defer close_db([this]() { Close(); });
DestroyAndReopen(options);
const std::string value(1024, 'v');
// An SST refers to the first blob file, so a later blob file that no SST
// refers to is dropped only once all its blobs are garbage.
ASSERT_OK(Put("x", value));
ASSERT_OK(Flush());
// "b" does not fit in the blob file holding "a" from the same batch, so the
// batch seals that file and then fails to open the next one. "a" is garbage
// in the sealed file, so the flush drops that file.
WriteBatch batch;
ASSERT_OK(batch.Put("a", value));
ASSERT_OK(batch.Put("b", value));
ASSERT_TRUE(db_->Write(WriteOptions(), &batch).IsIOError());
ASSERT_OK(Put("c", value));
ASSERT_OK(Flush());
ColumnFamilyMetaData cf_meta;
db_->GetColumnFamilyMetaData(&cf_meta);
ASSERT_EQ(2U, cf_meta.blob_files.size());
ASSERT_EQ("NOT_FOUND", Get("a"));
ASSERT_EQ(value, Get("x"));
ASSERT_EQ(value, Get("c"));
}
class FailBlobFileSizeFileSystem : public FileSystemWrapper {
public:
explicit FailBlobFileSizeFileSystem(const std::shared_ptr<FileSystem>& fs)
: FileSystemWrapper(fs) {}
static const char* kClassName() { return "FailBlobFileSizeFileSystem"; }
const char* Name() const override { return kClassName(); }
IOStatus GetFileSize(const std::string& fname, const IOOptions& options,
uint64_t* file_size, IODebugContext* dbg) override {
if (IsBlobFilePath(fname)) {
return IOStatus::IOError("Injected blob file size error");
}
return target()->GetFileSize(fname, options, file_size, dbg);
}
};
TEST_F(DBBlobDirectWriteTest, SealedBlobFilesTrackedWhenSizeLookupFails) {
Options options = GetDirectWriteOptions();
options.blob_file_size = 2048;
// Only the SstFileManager fails to look up blob file sizes.
std::shared_ptr<SstFileManager> sst_file_manager(NewSstFileManager(
env_,
std::make_shared<FailBlobFileSizeFileSystem>(env_->GetFileSystem())));
options.sst_file_manager = sst_file_manager;
DestroyAndReopen(options);
const std::string value(1024, 'v');
// "key2" does not fit in the blob file holding "key1", so the write seals
// that file, and the flush seals the one holding "key2".
ASSERT_OK(Put("key1", value));
ASSERT_OK(Put("key2", value));
ASSERT_OK(Flush());
ASSERT_EQ(2U, CountBlobFiles());
std::unordered_map<std::string, uint64_t> blob_file_sizes;
std::vector<std::string> files;
ASSERT_OK(env_->GetChildren(dbname_, &files));
for (const auto& file : files) {
const std::string path = dbname_ + "/" + file;
if (IsBlobFilePath(path)) {
ASSERT_OK(env_->GetFileSize(path, &blob_file_sizes[path]));
}
}
std::unordered_map<std::string, uint64_t> tracked_blob_file_sizes;
for (const auto& [path, size] : sst_file_manager->GetTrackedFiles()) {
if (IsBlobFilePath(path)) {
tracked_blob_file_sizes[path] = size;
}
}
ASSERT_EQ(blob_file_sizes, tracked_blob_file_sizes);
Reopen(options);
ASSERT_EQ(value, Get("key1"));
ASSERT_EQ(value, Get("key2"));
}
class DisableAutoRecoveryListener : public EventListener {
public:
void OnErrorRecoveryBegin(BackgroundErrorReason /*reason*/, Status bg_error,
bool* auto_recovery) override {
bg_error.PermitUncheckedError();
*auto_recovery = false;
}
};
TEST_F(DBBlobDirectWriteTest, FailedBlobSealLeavesMemtableFlushable) {
std::unique_ptr<FaultInjectionTestEnv> fault_env(
new FaultInjectionTestEnv(env_));
Options options = GetDirectWriteOptions();
options.env = fault_env.get();
options.listeners.emplace_back(
std::make_shared<DisableAutoRecoveryListener>());
for (bool atomic_flush : {false, true}) {
SCOPED_TRACE("atomic_flush=" + std::to_string(atomic_flush));
options.atomic_flush = atomic_flush;
DestroyAndReopen(options);
Defer close_db([this]() { Close(); });
const std::string value(1024, 'v');
ASSERT_OK(Put("key", value));
// The flush fails to sync the footer of the blob file holding "key".
SyncPoint::GetInstance()->SetCallBack("BlobLogWriter::Sync", [&](void*) {
fault_env->SetFilesystemActive(false, Status::NoSpace("injected"));
});
SyncPoint::GetInstance()->EnableProcessing();
ASSERT_NOK(Flush());
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
fault_env->SetFilesystemActive(true);
uint64_t flush_pending = 0;
ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kMemTableFlushPending,
&flush_pending));
ASSERT_EQ(1U, flush_pending);
}
}
TEST_F(DBBlobDirectWriteTest, FailedBlobFooterSyncFailsRetriedFlush) {
std::unique_ptr<FaultInjectionTestEnv> fault_env(
new FaultInjectionTestEnv(env_));
Options options = GetDirectWriteOptions();
options.env = fault_env.get();
options.listeners.emplace_back(
std::make_shared<DisableAutoRecoveryListener>());
DestroyAndReopen(options);
Defer close_db([this]() { Close(); });
const std::string value(1024, 'v');
ASSERT_OK(Put("key", value));
// The flush fails to sync the footer of the blob file holding "key".
SyncPoint::GetInstance()->SetCallBack("BlobLogWriter::Sync", [&](void*) {
fault_env->SetFilesystemActive(false, Status::NoSpace("injected"));
});
SyncPoint::GetInstance()->EnableProcessing();
ASSERT_NOK(Flush());
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
fault_env->SetFilesystemActive(true);
// That blob file can no longer be sealed, so retrying the flush must fail
// rather than install a table file that references it.
ASSERT_NOK(dbfull()->Resume());
ASSERT_EQ(0, NumTableFilesAtLevel(0));
ASSERT_EQ(value, Get("key"));
}
TEST_F(DBBlobDirectWriteTest, CloseAfterFailedBlobSeal) {
std::unique_ptr<FaultInjectionTestEnv> fault_env(
new FaultInjectionTestEnv(env_));
Options options = GetDirectWriteOptions();
options.env = fault_env.get();
options.blob_file_size = 2048;
options.listeners.emplace_back(
std::make_shared<DisableAutoRecoveryListener>());
for (bool seal_on_write : {false, true}) {
SCOPED_TRACE("seal_on_write=" + std::to_string(seal_on_write));
DestroyAndReopen(options);
Defer close_db([this]() { Close(); });
const std::string value(1024, 'v');
ASSERT_OK(Put("key1", value));
// Sealing the blob file holding "key1" fails, either in the write that
// does not fit in it or in the flush. The DB closes without retrying.
SyncPoint::GetInstance()->SetCallBack("BlobLogWriter::Sync", [&](void*) {
fault_env->SetFilesystemActive(false, Status::NoSpace("injected"));
});
SyncPoint::GetInstance()->EnableProcessing();
ASSERT_NOK(seal_on_write ? Put("key2", value) : Flush());
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
fault_env->SetFilesystemActive(true);
}
}
class DBBlobDirectWriteWithTimestampTest : public DBBasicTestWithTimestampBase {
protected:
DBBlobDirectWriteWithTimestampTest()
-52
View File
@@ -57,11 +57,6 @@ TEST(BlobIndexTest, SameFileBlobIndex) {
EXPECT_NE(blob_index.DebugString(false).find("file:same"), std::string::npos);
}
TEST(BlobIndexTest, EmptyBlobIndexIsCorruption) {
BlobIndex blob_index;
ASSERT_EQ(blob_index.DecodeFrom(Slice()).code(), Status::Code::kCorruption);
}
// kTypeBlobIndex is a value type used by BlobDB only. The base rocksdb
// should accept the value type on write, and report not supported value
// for reads, unless caller request for it explicitly. The base rocksdb
@@ -1037,53 +1032,6 @@ TEST_F(DBBlobIndexTest, EmbeddedBlobWideColumnGetEntityBlockCacheTier) {
ASSERT_TRUE(s.IsIncomplete()) << s.ToString();
}
// Companion to the test above (genuine miss -> Incomplete): with a blob cache
// configured, a warm read populates the embedded record into the blob cache, so
// a subsequent kBlockCacheTier GetEntity serves it from cache instead of
// returning Incomplete. This exercises the non-lazy Get/GetEntity embedded path
// (get_context -> EmbeddedAwareBlobFetcher -> GetSameFileBlob whole ->
// ResolveEmbeddedBlobCached), which shares the block-cache-tier behavior with
// the lazy resolver (a same-file column already in the blob cache is served).
TEST_F(DBBlobIndexTest,
EmbeddedBlobWideColumnGetEntityBlockCacheTierServesCached) {
Options options = GetTestOptions();
options.create_if_missing = true;
LRUCacheOptions co;
co.capacity = 8 << 20;
options.blob_cache = NewLRUCache(co);
DestroyAndReopen(options);
const std::string sst_path = dbname_ + "/embedded_wc_blockcache_hit.sst";
SstFileWriterEmbeddedBlobOptions embedded_blob_options;
embedded_blob_options.min_blob_size = 8;
const std::string big1(1024, 'a');
const WideColumns columns{{kDefaultWideColumnName, "d"}, {"big", big1}};
SstFileWriter writer(EnvOptions(), options);
ASSERT_OK(writer.OpenWithEmbeddedBlobs(sst_path, embedded_blob_options));
ASSERT_OK(writer.PutEntity("k", columns));
ASSERT_OK(writer.Finish());
ASSERT_OK(db_->IngestExternalFile({sst_path}, IngestExternalFileOptions()));
// Warm both the block cache (data block) and the blob cache (embedded record)
// with a full read.
{
PinnableWideColumns result;
ASSERT_OK(db_->GetEntity(ReadOptions(), db_->DefaultColumnFamily(), "k",
&result));
ASSERT_EQ(result.columns(), columns);
}
// The embedded record is now cached, so a block-cache-only read serves it.
ReadOptions read_options;
read_options.read_tier = kBlockCacheTier;
PinnableWideColumns result;
ASSERT_OK(
db_->GetEntity(read_options, db_->DefaultColumnFamily(), "k", &result));
ASSERT_EQ(result.columns(), columns);
}
// A corrupt embedded blob record backing a wide-column column must surface an
// error on GetEntity, not expose the raw same-file BlobIndex.
// A failure resolving an embedded blob record backing a wide-column column
-284
View File
@@ -1,284 +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).
#pragma once
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <string>
#include <utility>
#include <vector>
#include "db/blob/blob_index.h"
#include "db/version_set.h"
#include "db/wide/wide_column_serialization.h"
#include "table/internal_iterator.h"
namespace ROCKSDB_NAMESPACE {
// Replaces references absent from an immutable input Version with deletions
// or entities containing the surviving columns. Input values must already be
// prepared, as they are for compaction input. Neither input is owned.
class MissingBlobRepairIterator : public InternalIterator {
public:
MissingBlobRepairIterator(InternalIterator* input,
const VersionStorageInfo* input_storage,
size_t timestamp_size)
: input_(input),
input_storage_(input_storage),
timestamp_size_(timestamp_size) {
assert(input_);
assert(input_storage_);
Update();
}
bool Valid() const override { return input_->Valid() && status_.ok(); }
void SeekToFirst() override {
input_->SeekToFirst();
Update();
}
void SeekToLast() override {
input_->SeekToLast();
Update();
}
void Seek(const Slice& target) override {
// A rewritten type can sort after its original type at the same sequence.
const std::string target_copy = target.ToString();
ParsedInternalKey parsed;
status_ = ParseInternalKey(target_copy, &parsed, false);
if (!status_.ok()) {
return;
}
InternalKey seek_key(parsed.user_key, parsed.sequence, kValueTypeForSeek);
input_->Seek(seek_key.Encode());
Update();
while (Valid() && input_storage_->InternalComparator()->Compare(
key(), target_copy) < 0) {
Next();
}
}
void SeekForPrev(const Slice& target) override {
const std::string target_copy = target.ToString();
input_->SeekForPrev(target_copy);
Update();
while (Valid() && input_storage_->InternalComparator()->Compare(
key(), target_copy) > 0) {
Prev();
}
}
void Next() override {
assert(Valid());
input_->Next();
Update();
}
void Prev() override {
assert(Valid());
input_->Prev();
Update();
}
Slice key() const override {
assert(Valid());
return repaired_key_.empty() ? input_->key() : Slice(repaired_key_);
}
Slice value() const override {
assert(Valid());
return value_rewritten_ ? Slice(repaired_value_) : input_->value();
}
uint64_t write_unix_time() const override {
return input_->write_unix_time();
}
Status status() const override { return status_; }
bool PrepareValue() override {
assert(Valid());
return true;
}
void SetPinnedItersMgr(PinnedIteratorsManager* manager) override {
input_->SetPinnedItersMgr(manager);
}
bool IsKeyPinned() const override {
return repaired_key_.empty() && input_->IsKeyPinned();
}
bool IsValuePinned() const override {
return !value_rewritten_ && input_->IsValuePinned();
}
bool IsSafeToDropWhilePinned() const override {
return !value_rewritten_ && input_->IsSafeToDropWhilePinned();
}
bool IsDeleteRangeSentinelKey() const override {
return input_->IsDeleteRangeSentinelKey();
}
bool MayBeOutOfLowerBound() override {
return input_->MayBeOutOfLowerBound();
}
IterBoundCheck UpperBoundCheckResult() override {
return input_->UpperBoundCheckResult();
}
void SetRangeDelReadSeqno(SequenceNumber sequence) override {
input_->SetRangeDelReadSeqno(sequence);
}
void GetReadaheadState(ReadaheadFileInfo* state) override {
input_->GetReadaheadState(state);
}
void SetReadaheadState(ReadaheadFileInfo* state) override {
input_->SetReadaheadState(state);
}
Status GetProperty(std::string name, std::string* value) override {
return input_->GetProperty(std::move(name), value);
}
bool IsRepairDeletion() const { return repair_deletion_; }
private:
// Only external, non-TTL indexes use the input Version's blob file set.
// Absence is checked in metadata; inline, same-file and legacy TTL indexes
// retain their existing resolution rules.
bool IsMissing(const BlobIndex& index) const {
if (index.IsInlined() || index.HasTTL() || index.IsSameFile()) {
return false;
}
const VersionStorageInfo::BlobFiles::const_iterator it =
input_storage_->GetBlobFileMetaDataLB(index.file_number());
return it == input_storage_->GetBlobFiles().end() ||
(*it)->GetBlobFileNumber() != index.file_number();
}
// Preserve the sequence and user-key timestamp so the lost value continues
// to hide older versions, including versions retained for snapshots.
void SetDeletion(const ParsedInternalKey& parsed) {
repaired_key_ = input_->key().ToString();
const ValueType type =
timestamp_size_ == 0 ? kTypeDeletion : kTypeDeletionWithTimestamp;
UpdateInternalKey(&repaired_key_, parsed.sequence, type);
value_rewritten_ = true;
repair_deletion_ = true;
}
// Inspect column indexes without resolving blob payloads. Removing columns
// changes the positions of surviving blob indexes, so rebuild those positions
// before serialization. Only loss of every column becomes a deletion.
Status RepairEntity(const ParsedInternalKey& parsed) {
columns_.clear();
blob_columns_.clear();
Status s = WideColumnSerialization::Deserialize(input_->value(), columns_,
&blob_columns_);
if (!s.ok()) {
return s;
}
bool has_missing = false;
for (const std::pair<size_t, BlobIndex>& column : blob_columns_) {
has_missing |= IsMissing(column.second);
}
if (!has_missing) {
return Status::OK();
}
surviving_columns_.clear();
surviving_blobs_.clear();
size_t blob_position = 0;
for (size_t i = 0; i < columns_.size(); ++i) {
const BlobIndex* index = nullptr;
if (blob_position < blob_columns_.size() &&
blob_columns_[blob_position].first == i) {
index = &blob_columns_[blob_position++].second;
}
if (index && IsMissing(*index)) {
continue;
}
if (index) {
surviving_blobs_.emplace_back(surviving_columns_.size(), *index);
}
surviving_columns_.push_back(columns_[i]);
}
if (surviving_columns_.empty()) {
SetDeletion(parsed);
return Status::OK();
}
s = surviving_blobs_.empty()
? WideColumnSerialization::Serialize(surviving_columns_,
repaired_value_)
: WideColumnSerialization::SerializeV2(
surviving_columns_, surviving_blobs_, repaired_value_);
if (s.ok()) {
value_rewritten_ = true;
}
return s;
}
// Reset rewritten buffers on every movement. Preserve input errors and range
// deletion sentinels before decoding point values; malformed indexes fail.
void Update() {
repaired_key_.clear();
repaired_value_.clear();
value_rewritten_ = false;
repair_deletion_ = false;
status_ = input_->status();
if (!status_.ok() || !input_->Valid() ||
input_->IsDeleteRangeSentinelKey()) {
return;
}
ParsedInternalKey parsed;
status_ = ParseInternalKey(input_->key(), &parsed, false);
if (!status_.ok() || (parsed.type != kTypeBlobIndex &&
parsed.type != kTypeWideColumnEntity)) {
return;
}
BlobIndex index;
if (parsed.user_key.size() < timestamp_size_) {
status_ =
Status::Corruption("Blob reference key has an invalid timestamp");
} else if (parsed.type == kTypeWideColumnEntity) {
status_ = RepairEntity(parsed);
} else {
status_ = index.DecodeFrom(input_->value());
}
if (!status_.ok()) {
return;
}
if (parsed.type == kTypeBlobIndex && IsMissing(index)) {
SetDeletion(parsed);
}
}
InternalIterator* input_;
const VersionStorageInfo* input_storage_;
size_t timestamp_size_;
std::string repaired_key_;
std::string repaired_value_;
WideColumns columns_;
std::vector<std::pair<size_t, BlobIndex>> blob_columns_;
WideColumns surviving_columns_;
std::vector<std::pair<size_t, BlobIndex>> surviving_blobs_;
Status status_;
bool value_rewritten_ = false;
bool repair_deletion_ = false;
};
} // namespace ROCKSDB_NAMESPACE
+18 -51
View File
@@ -8,7 +8,6 @@
#include <cstddef>
#include <cstdint>
#include "db/blob/blob_constants.h"
#include "rocksdb/rocksdb_namespace.h"
#include "rocksdb/status.h"
@@ -18,18 +17,6 @@ class BlobIndex;
class PinnableSlice;
struct ReadOptions;
// One request for SameFileBlobReader::MultiGetSameFileBlob (the batched
// counterpart of GetSameFileBlob). See GetSameFileBlob for the per-field
// semantics (range_length == kWholeBlobLength selects the whole value).
struct SameFileBlobReadRequest {
const BlobIndex* blob_index = nullptr;
uint64_t range_offset = 0;
size_t range_length = kWholeBlobLength;
BlobVerifyPolicy verify_policy = BlobVerifyPolicy::kVerifyIfNoAmplification;
PinnableSlice* result = nullptr;
Status* status = nullptr;
};
// Narrow interface for resolving a same-file ("embedded") blob reference
// against the physical file that currently holds the table entry. Implemented
// by BlockBasedTable, which owns the reader context (file, footer, blob cache)
@@ -43,48 +30,28 @@ class SameFileBlobReader {
public:
virtual ~SameFileBlobReader() = default;
// Resolves a same-file ("embedded") blob reference (one for which
// blob_index.IsSameFile() is true) into `value`, pinning the payload
// zero-copy (from the blob cache or an owned buffer).
//
// range_length == kWholeBlobLength resolves the whole value. Any other length
// resolves only the sub-range [range_offset, range_offset + range_length),
// reading just those bytes on a cache miss -- skipping whole-record checksum
// verification (a strict sub-range cannot cover it) and never populating the
// blob cache; valid only for an uncompressed record and a verify_policy other
// than kVerifyIfPresent, with range_offset + range_length <=
// blob_index.size().
//
// verify_policy controls whole-record checksum verification (see
// BlobVerifyPolicy); a whole read under kVerifyIfPresent verifies even when
// ReadOptions::verify_checksums is off. read_tier == kBlockCacheTier without
// a cached record yields Status::Incomplete; a bad record yields
// Status::Corruption.
// Resolves a same-file blob reference (one for which blob_index.IsSameFile()
// is true) into `value`, pinning the payload zero-copy (from the blob cache
// or an owned buffer). read_tier == kBlockCacheTier without a cached record
// yields Status::Incomplete; a bad record yields Status::Corruption.
virtual Status GetSameFileBlob(const ReadOptions& read_options,
const BlobIndex& blob_index,
uint64_t range_offset, size_t range_length,
BlobVerifyPolicy verify_policy,
PinnableSlice* value) const = 0;
// Batched counterpart of GetSameFileBlob: resolves `num_reads` same-file blob
// references (whole or sub-range, mixed) against this reader, writing each
// outcome to *reqs[i].status. Implementations may coalesce the underlying
// disk reads into a single MultiRead. The default implementation simply loops
// over GetSameFileBlob (correct, but not coalesced); BlockBasedTable
// overrides it to coalesce reads of embedded records in the same SST.
virtual void MultiGetSameFileBlob(const ReadOptions& read_options,
size_t num_reads,
SameFileBlobReadRequest* reqs) const {
for (size_t i = 0; i < num_reads; ++i) {
SameFileBlobReadRequest& req = reqs[i];
const Status s =
GetSameFileBlob(read_options, *req.blob_index, req.range_offset,
req.range_length, req.verify_policy, req.result);
if (req.status) {
*req.status = s;
}
}
}
// Resolves a byte sub-range [range_offset, range_offset + range_length) of an
// *uncompressed* same-file blob reference into `value`, reading only those
// bytes on a cache miss (the embedded counterpart of a separate-file blob
// range read). Pins the payload zero-copy (a sliced cached record, or an
// owned sub-range buffer). Unlike GetSameFileBlob it skips whole-record
// checksum verification (a strict sub-range cannot cover it) and never
// populates the blob cache. The caller must ensure range_offset +
// range_length <= blob_index.size(). read_tier == kBlockCacheTier without a
// cached record yields Status::Incomplete.
virtual Status GetSameFileBlobRange(const ReadOptions& read_options,
const BlobIndex& blob_index,
uint64_t range_offset,
size_t range_length,
PinnableSlice* value) const = 0;
};
} // namespace ROCKSDB_NAMESPACE
+7 -19
View File
@@ -116,8 +116,7 @@ Status BuildTable(
// compaction path so the manifest learns about the new garbage.
blob_garbage_meter.reset(new BlobGarbageMeter());
blob_counting_iter.reset(
new BlobCountingIterator(iter, blob_garbage_meter.get(),
/*track_current_key_inflow=*/false));
new BlobCountingIterator(iter, blob_garbage_meter.get()));
input = blob_counting_iter.get();
}
@@ -162,13 +161,6 @@ Status BuildTable(
context.reason = tboptions.reason;
compaction_filter =
ioptions.compaction_filter_factory->CreateCompactionFilter(context);
CompactionFilter::MergeOrder order;
s = GetCompactionFilterMergeOrder(
compaction_filter.get(), ioptions.compaction_filter_factory.get(),
tboptions.reason, &order);
if (!s.ok()) {
return s;
}
if (compaction_filter != nullptr &&
!compaction_filter->IgnoreSnapshots()) {
s.PermitUncheckedError();
@@ -215,8 +207,7 @@ Status BuildTable(
std::move(file), fname, file_options, ioptions.clock, io_tracer,
ioptions.stats, Histograms::SST_WRITE_MICROS, ioptions.listeners,
ioptions.file_checksum_gen_factory.get(),
tmp_set.Contains(FileType::kTableFile),
tmp_set.Contains(FileType::kTableFile)));
tmp_set.Contains(FileType::kTableFile), false));
builder = NewTableBuilder(tboptions, file_writer.get());
}
@@ -224,8 +215,7 @@ Status BuildTable(
auto ucmp = tboptions.internal_comparator.user_comparator();
MergeHelper merge(
env, ucmp, ioptions.merge_operator.get(), compaction_filter.get(),
CompactionFilter::MergeOrder::kFilterBeforeMerge, ioptions.logger,
true /* internal key corruption is not ok */,
ioptions.logger, true /* internal key corruption is not ok */,
snapshots.empty() ? 0 : snapshots.back(), snapshot_checker);
std::unique_ptr<BlobFileBuilder> blob_file_builder(
@@ -238,8 +228,7 @@ Status BuildTable(
&(tboptions.write_options), tboptions.db_id,
tboptions.db_session_id, job_id, tboptions.column_family_id,
tboptions.column_family_name, write_hint, io_tracer,
blob_callback, blob_creation_reason,
ioptions.cf_paths.front().path, &blob_file_paths,
blob_callback, blob_creation_reason, &blob_file_paths,
blob_file_additions)
: nullptr);
@@ -400,13 +389,13 @@ Status BuildTable(
std::min(meta->fd.smallest_seqno, smallest_preferred_seqno),
meta->fd.largest_seqno);
relevant_mapping.SetCapacity(kMaxSeqnoTimePairsPerSST);
relevant_mapping.Enforce(meta->file_creation_time);
relevant_mapping.Enforce(tboptions.file_creation_time);
}
builder->SetSeqnoTimeTableProperties(
relevant_mapping,
ioptions.compaction_style == CompactionStyle::kCompactionStyleFIFO
? meta->file_creation_time
: meta->oldest_ancestor_time);
: meta->oldest_ancester_time);
s = builder->Finish();
}
if (io_status->ok()) {
@@ -462,8 +451,7 @@ Status BuildTable(
WritableFileWriter::PrepareIOOptions(tboptions.write_options, opts);
if (s.ok() && io_status->ok() && !empty) {
StopWatch sw(ioptions.clock, ioptions.stats, TABLE_SYNC_MICROS);
*io_status = ioptions.use_fsync ? file_writer->Fsync(opts)
: file_writer->Sync(opts);
*io_status = file_writer->Sync(opts, ioptions.use_fsync);
}
TEST_SYNC_POINT("BuildTable:BeforeCloseTableFile");
if (s.ok() && io_status->ok() && !empty) {
+60 -596
View File
@@ -58,7 +58,6 @@
#include "rocksdb/wal_filter.h"
#include "rocksdb/write_batch.h"
#include "rocksdb/write_buffer_manager.h"
#include "util/cast_util.h"
#include "util/stderr_logger.h"
#include "utilities/merge_operators.h"
@@ -196,7 +195,6 @@ using ROCKSDB_NAMESPACE::WALRecoveryMode;
using ROCKSDB_NAMESPACE::WritableFile;
using ROCKSDB_NAMESPACE::WriteBatch;
using ROCKSDB_NAMESPACE::WriteBatchWithIndex;
using ROCKSDB_NAMESPACE::WriteBufferFlushPolicy;
using ROCKSDB_NAMESPACE::WriteBufferManager;
using ROCKSDB_NAMESPACE::WriteOptions;
using ROCKSDB_NAMESPACE::WriteStallCondition;
@@ -420,13 +418,6 @@ struct rocksdb_readoptions_t {
std::function<bool(const TableProperties&)> table_filter_func;
std::shared_ptr<UserDefinedIndexFactory> table_index_factory;
rocksdb_readoptions_t() = default;
// No copying allowed
rocksdb_readoptions_t(const rocksdb_readoptions_t&) = delete;
rocksdb_readoptions_t& operator=(const rocksdb_readoptions_t&) = delete;
rocksdb_readoptions_t(rocksdb_readoptions_t&&) = delete;
rocksdb_readoptions_t& operator=(rocksdb_readoptions_t&&) = delete;
~rocksdb_readoptions_t() { ClearTableFilter(); }
void ClearTableFilter() {
@@ -475,23 +466,6 @@ struct rocksdb_compactoptions_t {
struct rocksdb_block_based_table_options_t {
BlockBasedTableOptions rep;
std::shared_ptr<UserDefinedIndexFactory> user_defined_index_factory;
bool use_udi_as_primary_index = false;
bool fail_if_no_udi_on_open = false;
// Set once rocksdb_block_based_options_set_index_mode() has been called.
// Mirrors the C++ precedence rule in GetEffectiveIndexMode(): an explicit
// index_mode wins over the deprecated bools, so a stale
// use_udi_as_primary_index cannot silently escalate a rollback back into a
// custom-index mode.
bool index_mode_explicit = false;
void SyncIndexModeFromLegacyUdiOptions() {
if (index_mode_explicit) {
return;
}
rep.index_mode = BlockBasedTableOptions::IndexMode::kStandardDefault;
rep.use_udi_as_primary_index = use_udi_as_primary_index;
rep.fail_if_no_udi_on_open = fail_if_no_udi_on_open;
}
};
struct rocksdb_block_cache_trace_options_t {
BlockCacheTraceOptions rep;
@@ -830,22 +804,14 @@ struct rocksdb_open_and_compact_options_t {
};
struct rocksdb_compactionfilter_t : public CompactionFilter {
void* state_ = nullptr;
void (*destructor_)(void*) = nullptr;
void* state_;
void (*destructor_)(void*);
unsigned char (*filter_)(void*, int level, const char* key, size_t key_length,
const char* existing_value, size_t value_length,
char** new_value, size_t* new_value_length,
unsigned char* value_changed) = nullptr;
const char* (*name_)(void*) = nullptr;
unsigned char ignore_snapshots_ = true;
rocksdb_compactionfilter_t() = default;
// No copying allowed
rocksdb_compactionfilter_t(const rocksdb_compactionfilter_t&) = delete;
rocksdb_compactionfilter_t& operator=(const rocksdb_compactionfilter_t&) =
delete;
rocksdb_compactionfilter_t(rocksdb_compactionfilter_t&&) = delete;
rocksdb_compactionfilter_t& operator=(rocksdb_compactionfilter_t&&) = delete;
unsigned char* value_changed);
const char* (*name_)(void*);
unsigned char ignore_snapshots_;
~rocksdb_compactionfilter_t() override { (*destructor_)(state_); }
@@ -871,22 +837,11 @@ struct rocksdb_compactionfilter_t : public CompactionFilter {
};
struct rocksdb_compactionfilterfactory_t : public CompactionFilterFactory {
void* state_ = nullptr;
void (*destructor_)(void*) = nullptr;
void* state_;
void (*destructor_)(void*);
rocksdb_compactionfilter_t* (*create_compaction_filter_)(
void*, rocksdb_compactionfiltercontext_t* context) = nullptr;
const char* (*name_)(void*) = nullptr;
rocksdb_compactionfilterfactory_t() = default;
// No copying allowed
rocksdb_compactionfilterfactory_t(const rocksdb_compactionfilterfactory_t&) =
delete;
rocksdb_compactionfilterfactory_t& operator=(
const rocksdb_compactionfilterfactory_t&) = delete;
rocksdb_compactionfilterfactory_t(rocksdb_compactionfilterfactory_t&&) =
delete;
rocksdb_compactionfilterfactory_t& operator=(
rocksdb_compactionfilterfactory_t&&) = delete;
void*, rocksdb_compactionfiltercontext_t* context);
const char* (*name_)(void*);
~rocksdb_compactionfilterfactory_t() override { (*destructor_)(state_); }
@@ -917,12 +872,6 @@ struct rocksdb_comparator_t : public Comparator {
rocksdb_comparator_t(size_t ts_size) : Comparator(ts_size) {}
// No copying allowed
rocksdb_comparator_t(const rocksdb_comparator_t&) = delete;
rocksdb_comparator_t& operator=(const rocksdb_comparator_t&) = delete;
rocksdb_comparator_t(rocksdb_comparator_t&&) = delete;
rocksdb_comparator_t& operator=(rocksdb_comparator_t&&) = delete;
~rocksdb_comparator_t() override { (*destructor_)(state_); }
int Compare(const Slice& a, const Slice& b) const override {
@@ -954,16 +903,9 @@ struct rocksdb_comparator_t : public Comparator {
};
struct rocksdb_filterpolicy_t : public FilterPolicy {
void* state_ = nullptr;
void (*destructor_)(void*) = nullptr;
const char* (*name_)(void*) = nullptr;
rocksdb_filterpolicy_t() = default;
// No copying allowed
rocksdb_filterpolicy_t(const rocksdb_filterpolicy_t&) = delete;
rocksdb_filterpolicy_t& operator=(const rocksdb_filterpolicy_t&) = delete;
rocksdb_filterpolicy_t(rocksdb_filterpolicy_t&&) = delete;
rocksdb_filterpolicy_t& operator=(rocksdb_filterpolicy_t&&) = delete;
void* state_;
void (*destructor_)(void*);
const char* (*name_)(void*);
~rocksdb_filterpolicy_t() override { (*destructor_)(state_); }
@@ -971,29 +913,19 @@ struct rocksdb_filterpolicy_t : public FilterPolicy {
};
struct rocksdb_mergeoperator_t : public MergeOperator {
void* state_ = nullptr;
void (*destructor_)(void*) = nullptr;
const char* (*name_)(void*) = nullptr;
void* state_;
void (*destructor_)(void*);
const char* (*name_)(void*);
char* (*full_merge_)(void*, const char* key, size_t key_length,
const char* existing_value, size_t existing_value_length,
const char* const* operands_list,
const size_t* operands_list_length, int num_operands,
unsigned char* success,
size_t* new_value_length) = nullptr;
unsigned char* success, size_t* new_value_length);
char* (*partial_merge_)(void*, const char* key, size_t key_length,
const char* const* operands_list,
const size_t* operands_list_length, int num_operands,
unsigned char* success,
size_t* new_value_length) = nullptr;
void (*delete_value_)(void*, const char* value,
size_t value_length) = nullptr;
rocksdb_mergeoperator_t() = default;
// No copying allowed
rocksdb_mergeoperator_t(const rocksdb_mergeoperator_t&) = delete;
rocksdb_mergeoperator_t& operator=(const rocksdb_mergeoperator_t&) = delete;
rocksdb_mergeoperator_t(rocksdb_mergeoperator_t&&) = delete;
rocksdb_mergeoperator_t& operator=(rocksdb_mergeoperator_t&&) = delete;
unsigned char* success, size_t* new_value_length);
void (*delete_value_)(void*, const char* value, size_t value_length);
~rocksdb_mergeoperator_t() override { (*destructor_)(state_); }
@@ -1075,19 +1007,12 @@ struct rocksdb_env_t {
};
struct rocksdb_slicetransform_t : public SliceTransform {
void* state_ = nullptr;
void (*destructor_)(void*) = nullptr;
const char* (*name_)(void*) = nullptr;
void* state_;
void (*destructor_)(void*);
const char* (*name_)(void*);
char* (*transform_)(void*, const char* key, size_t length,
size_t* dst_length) = nullptr;
unsigned char (*in_domain_)(void*, const char* key, size_t length) = nullptr;
rocksdb_slicetransform_t() = default;
// No copying allowed
rocksdb_slicetransform_t(const rocksdb_slicetransform_t&) = delete;
rocksdb_slicetransform_t& operator=(const rocksdb_slicetransform_t&) = delete;
rocksdb_slicetransform_t(rocksdb_slicetransform_t&&) = delete;
rocksdb_slicetransform_t& operator=(rocksdb_slicetransform_t&&) = delete;
size_t* dst_length);
unsigned char (*in_domain_)(void*, const char* key, size_t length);
~rocksdb_slicetransform_t() override { (*destructor_)(state_); }
@@ -1411,14 +1336,6 @@ struct rocksdb_compactionservice_t : public CompactionService {
cancel_awaiting_jobs_(cancel_awaiting_jobs),
on_installation_(on_installation) {}
// No copying allowed
rocksdb_compactionservice_t(const rocksdb_compactionservice_t&) = delete;
rocksdb_compactionservice_t& operator=(const rocksdb_compactionservice_t&) =
delete;
rocksdb_compactionservice_t(rocksdb_compactionservice_t&&) = delete;
rocksdb_compactionservice_t& operator=(rocksdb_compactionservice_t&&) =
delete;
~rocksdb_compactionservice_t() override {
if (destructor_) {
(*destructor_)(state_);
@@ -2706,59 +2623,6 @@ char* rocksdb_get(rocksdb_t* db, const rocksdb_readoptions_t* options,
return result;
}
static char* rocksdb_get_with_metadata_impl(
rocksdb_t* db, const rocksdb_readoptions_t* options,
ColumnFamilyHandle* column_family, const char* key, size_t keylen,
size_t* vallen, char** timestamp, size_t* timestamp_len,
unsigned char* newer_version_present, char** errptr) {
ROCKSDB_NAMESPACE::OutputMetadata output_metadata;
if (timestamp != nullptr && timestamp_len != nullptr) {
output_metadata.WantTimestamp();
}
if (newer_version_present != nullptr) {
output_metadata.WantNewerVersionPresent();
}
PinnableSlice pinnable_val;
const Status s =
db->rep->GetWithMetadata(options->rep, column_family, Slice(key, keylen),
&pinnable_val, &output_metadata);
if (newer_version_present != nullptr) {
*newer_version_present = *output_metadata.newer_version_present ? 1 : 0;
}
char* result = nullptr;
if (s.ok()) {
*vallen = pinnable_val.size();
result = CopyString(pinnable_val);
if (output_metadata.timestamp.has_value()) {
*timestamp_len = output_metadata.timestamp->size();
*timestamp = CopyString(*output_metadata.timestamp);
}
} else {
*vallen = 0;
if (output_metadata.timestamp.has_value()) {
*timestamp = nullptr;
*timestamp_len = 0;
}
if (!s.IsNotFound()) {
SaveError(errptr, s);
}
}
return result;
}
char* rocksdb_get_with_metadata(rocksdb_t* db,
const rocksdb_readoptions_t* options,
const char* key, size_t keylen, size_t* vallen,
char** timestamp, size_t* timestamp_len,
unsigned char* newer_version_present,
char** errptr) {
return rocksdb_get_with_metadata_impl(
db, options, db->rep->DefaultColumnFamily(), key, keylen, vallen,
timestamp, timestamp_len, newer_version_present, errptr);
}
char* rocksdb_get_cf(rocksdb_t* db, const rocksdb_readoptions_t* options,
rocksdb_column_family_handle_t* column_family,
const char* key, size_t keylen, size_t* vallen,
@@ -2781,16 +2645,6 @@ char* rocksdb_get_cf(rocksdb_t* db, const rocksdb_readoptions_t* options,
return result;
}
char* rocksdb_get_cf_with_metadata(
rocksdb_t* db, const rocksdb_readoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, size_t* vallen, char** timestamp, size_t* timestamp_len,
unsigned char* newer_version_present, char** errptr) {
return rocksdb_get_with_metadata_impl(
db, options, column_family->rep, key, keylen, vallen, timestamp,
timestamp_len, newer_version_present, errptr);
}
char* rocksdb_get_with_ts(rocksdb_t* db, const rocksdb_readoptions_t* options,
const char* key, size_t keylen, size_t* vallen,
char** ts, size_t* tslen, char** errptr) {
@@ -2882,72 +2736,6 @@ void rocksdb_multi_get(rocksdb_t* db, const rocksdb_readoptions_t* options,
}
}
static void rocksdb_multi_get_with_metadata_impl(
rocksdb_t* db, const rocksdb_readoptions_t* options,
ColumnFamilyHandle* const* column_families, size_t num_keys,
const char* const* keys_list, const size_t* keys_list_sizes,
char** values_list, size_t* values_list_sizes, char** timestamp_list,
size_t* timestamp_list_sizes, unsigned char* newer_version_present,
char** errs) {
std::unique_ptr<Slice[]> keys(new Slice[num_keys]);
std::vector<PinnableSlice> values(num_keys);
std::vector<Status> statuses(num_keys);
for (size_t i = 0; i < num_keys; ++i) {
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
ROCKSDB_NAMESPACE::MultiGetOutputMetadata output_metadata;
if (timestamp_list != nullptr && timestamp_list_sizes != nullptr) {
output_metadata.WantTimestamps();
}
if (newer_version_present != nullptr) {
output_metadata.WantNewerVersionPresent();
}
db->rep->MultiGetWithMetadata(options->rep, num_keys, column_families,
keys.get(), values.data(), statuses.data(),
&output_metadata);
for (size_t i = 0; i < num_keys; ++i) {
if (newer_version_present != nullptr) {
newer_version_present[i] =
(*output_metadata.newer_version_present)[i] ? 1 : 0;
}
if (statuses[i].ok()) {
values_list[i] = CopyString(values[i]);
values_list_sizes[i] = values[i].size();
if (output_metadata.timestamps.has_value()) {
timestamp_list[i] = CopyString((*output_metadata.timestamps)[i]);
timestamp_list_sizes[i] = (*output_metadata.timestamps)[i].size();
}
errs[i] = nullptr;
} else {
values_list[i] = nullptr;
values_list_sizes[i] = 0;
if (output_metadata.timestamps.has_value()) {
timestamp_list[i] = nullptr;
timestamp_list_sizes[i] = 0;
}
errs[i] = statuses[i].IsNotFound()
? nullptr
: strdup(statuses[i].ToString().c_str());
}
}
}
void rocksdb_multi_get_with_metadata(
rocksdb_t* db, const rocksdb_readoptions_t* options, size_t num_keys,
const char* const* keys_list, const size_t* keys_list_sizes,
char** values_list, size_t* values_list_sizes, char** timestamp_list,
size_t* timestamp_list_sizes, unsigned char* newer_version_present,
char** errs) {
std::vector<ColumnFamilyHandle*> column_families(
num_keys, db->rep->DefaultColumnFamily());
rocksdb_multi_get_with_metadata_impl(
db, options, column_families.data(), num_keys, keys_list, keys_list_sizes,
values_list, values_list_sizes, timestamp_list, timestamp_list_sizes,
newer_version_present, errs);
}
void rocksdb_multi_get_with_ts(rocksdb_t* db,
const rocksdb_readoptions_t* options,
size_t num_keys, const char* const* keys_list,
@@ -3023,24 +2811,6 @@ void rocksdb_multi_get_cf(
}
}
void rocksdb_multi_get_cf_with_metadata(
rocksdb_t* db, const rocksdb_readoptions_t* options,
const rocksdb_column_family_handle_t* const* column_families,
size_t num_keys, const char* const* keys_list,
const size_t* keys_list_sizes, char** values_list,
size_t* values_list_sizes, char** timestamp_list,
size_t* timestamp_list_sizes, unsigned char* newer_version_present,
char** errs) {
std::vector<ColumnFamilyHandle*> cfs(num_keys);
for (size_t i = 0; i < num_keys; ++i) {
cfs[i] = column_families[i]->rep;
}
rocksdb_multi_get_with_metadata_impl(
db, options, cfs.data(), num_keys, keys_list, keys_list_sizes,
values_list, values_list_sizes, timestamp_list, timestamp_list_sizes,
newer_version_present, errs);
}
void rocksdb_multi_get_cf_with_ts(
rocksdb_t* db, const rocksdb_readoptions_t* options,
const rocksdb_column_family_handle_t* const* column_families,
@@ -4785,60 +4555,12 @@ const char* rocksdb_block_based_options_get_user_defined_index_factory_name(
return name;
}
void rocksdb_block_based_options_set_use_udi_as_primary_index(
rocksdb_block_based_table_options_t* opt, unsigned char v) {
opt->use_udi_as_primary_index = v != 0;
opt->SyncIndexModeFromLegacyUdiOptions();
}
unsigned char rocksdb_block_based_options_get_use_udi_as_primary_index(
rocksdb_block_based_table_options_t* opt) {
return opt->use_udi_as_primary_index;
}
void rocksdb_block_based_options_set_fail_if_no_udi_on_open(
rocksdb_block_based_table_options_t* opt, unsigned char v) {
opt->fail_if_no_udi_on_open = v != 0;
opt->SyncIndexModeFromLegacyUdiOptions();
}
unsigned char rocksdb_block_based_options_get_fail_if_no_udi_on_open(
rocksdb_block_based_table_options_t* opt) {
return opt->fail_if_no_udi_on_open;
}
void rocksdb_block_based_options_set_index_mode(
rocksdb_block_based_table_options_t* opt, int v) {
opt->rep.index_mode = static_cast<BlockBasedTableOptions::IndexMode>(v);
opt->index_mode_explicit = true;
opt->rep.use_udi_as_primary_index = false;
opt->rep.fail_if_no_udi_on_open = false;
}
int rocksdb_block_based_options_get_index_mode(
rocksdb_block_based_table_options_t* opt) {
return ROCKSDB_NAMESPACE::lossless_cast<int>(
opt->rep.GetEffectiveIndexMode());
}
void rocksdb_options_set_block_based_table_factory(
rocksdb_options_t* opt,
rocksdb_block_based_table_options_t* table_options) {
if (table_options) {
opt->rep.table_factory.reset(
ROCKSDB_NAMESPACE::NewBlockBasedTableFactory(table_options->rep));
if (table_options->index_mode_explicit &&
table_options->rep.index_mode ==
BlockBasedTableOptions::IndexMode::kStandardDefault) {
// The default struct value cannot carry an explicit selection. Preserve
// that selection when transferring the C wrapper into a C++ factory.
ConfigOptions config;
config.invoke_prepare_options = false;
Status s = opt->rep.table_factory->ConfigureOption(config, "index_mode",
"kStandardDefault");
assert(s.ok());
s.PermitUncheckedError();
}
}
}
@@ -4952,12 +4674,6 @@ int rocksdb_compactionjobinfo_job_id(const rocksdb_compactionjobinfo_t* info) {
return info->rep.job_id;
}
unsigned char
rocksdb_compactionjobinfo_separate_remote_compaction_pool_selected(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.separate_remote_compaction_pool_selected;
}
int rocksdb_compactionjobinfo_num_l0_files(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.num_l0_files;
@@ -5021,12 +4737,6 @@ int rocksdb_subcompactionjobinfo_job_id(
return info->rep.job_id;
}
unsigned char
rocksdb_subcompactionjobinfo_separate_remote_compaction_pool_selected(
const rocksdb_subcompactionjobinfo_t* info) {
return info->rep.separate_remote_compaction_pool_selected;
}
int rocksdb_subcompactionjobinfo_subcompaction_job_id(
const rocksdb_subcompactionjobinfo_t* info) {
return info->rep.subcompaction_job_id;
@@ -5265,11 +4975,6 @@ uint64_t rocksdb_table_properties_udi_is_primary_index(
return props->rep.udi_is_primary_index;
}
uint64_t rocksdb_table_properties_standard_index_is_stub(
const rocksdb_table_properties_t* props) {
return props->rep.standard_index_is_stub;
}
uint64_t rocksdb_table_properties_filter_size(
const rocksdb_table_properties_t* props) {
return props->rep.filter_size;
@@ -5345,11 +5050,6 @@ uint64_t rocksdb_table_properties_column_family_id(
return props->rep.column_family_id;
}
uint64_t rocksdb_table_properties_oldest_ancestor_time(
const rocksdb_table_properties_t* props) {
return props->rep.oldest_ancestor_time;
}
uint64_t rocksdb_table_properties_creation_time(
const rocksdb_table_properties_t* props) {
return props->rep.creation_time;
@@ -5380,6 +5080,11 @@ uint64_t rocksdb_table_properties_fast_compression_estimated_data_size(
return props->rep.fast_compression_estimated_data_size;
}
uint64_t rocksdb_table_properties_external_sst_file_global_seqno_offset(
const rocksdb_table_properties_t* props) {
return props->rep.external_sst_file_global_seqno_offset;
}
uint64_t rocksdb_table_properties_tail_start_offset(
const rocksdb_table_properties_t* props) {
return props->rep.tail_start_offset;
@@ -5415,11 +5120,6 @@ uint64_t rocksdb_table_properties_separate_key_value_in_data_block(
return props->rep.separate_key_value_in_data_block;
}
uint64_t rocksdb_table_properties_lsm_info_at_creation(
const rocksdb_table_properties_t* props) {
return props->rep.lsm_info_at_creation;
}
const char* rocksdb_table_properties_db_id(
const rocksdb_table_properties_t* props, size_t* size) {
*size = props->rep.db_id.size();
@@ -5735,16 +5435,6 @@ uint64_t rocksdb_blob_file_garbage_info_garbage_blob_bytes(
}
// END generated: c_generated_metadata_structs_auto.cc.inc
unsigned char rocksdb_compactionjobinfo_use_separate_remote_compaction_pool(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.separate_remote_compaction_pool_selected;
}
unsigned char rocksdb_subcompactionjobinfo_use_separate_remote_compaction_pool(
const rocksdb_subcompactionjobinfo_t* info) {
return info->rep.separate_remote_compaction_pool_selected;
}
rocksdb_compactionjobinfo_t* rocksdb_compactionjobinfo_create() {
return new rocksdb_compactionjobinfo_t;
}
@@ -7120,12 +6810,7 @@ rocksdb_filterpolicy_t* rocksdb_filterpolicy_create_bloom_format(
// they delegate to a NewBloomFilterPolicy() instead of user
// supplied C functions.
struct Wrapper : public rocksdb_filterpolicy_t {
const FilterPolicy* rep_ = nullptr;
Wrapper() = default;
Wrapper(const Wrapper&) = delete;
Wrapper& operator=(const Wrapper&) = delete;
Wrapper(Wrapper&&) = delete;
Wrapper& operator=(Wrapper&&) = delete;
const FilterPolicy* rep_;
~Wrapper() override { delete rep_; }
const char* Name() const override { return rep_->Name(); }
const char* CompatibilityName() const override {
@@ -7165,12 +6850,7 @@ rocksdb_filterpolicy_t* rocksdb_filterpolicy_create_ribbon_format(
// they delegate to a NewRibbonFilterPolicy() instead of user
// supplied C functions.
struct Wrapper : public rocksdb_filterpolicy_t {
const FilterPolicy* rep_ = nullptr;
Wrapper() = default;
Wrapper(const Wrapper&) = delete;
Wrapper& operator=(const Wrapper&) = delete;
Wrapper(Wrapper&&) = delete;
Wrapper& operator=(Wrapper&&) = delete;
const FilterPolicy* rep_;
~Wrapper() override { delete rep_; }
const char* Name() const override { return rep_->Name(); }
const char* CompatibilityName() const override {
@@ -7606,7 +7286,6 @@ void rocksdb_readoptions_set_table_index_factory_from_string(
char** errptr) {
opt->table_index_factory.reset();
opt->rep.table_index_factory = nullptr;
opt->rep.read_index = ReadOptions::ReadIndex::kDefault;
if (value == nullptr) {
return;
}
@@ -7618,21 +7297,11 @@ void rocksdb_readoptions_set_table_index_factory_from_string(
}
opt->table_index_factory = std::move(factory);
opt->rep.table_index_factory = opt->table_index_factory.get();
opt->rep.read_index = ReadOptions::ReadIndex::kPreferCustom;
}
void rocksdb_readoptions_clear_table_index_factory(rocksdb_readoptions_t* opt) {
opt->table_index_factory.reset();
opt->rep.table_index_factory = nullptr;
opt->rep.read_index = ReadOptions::ReadIndex::kDefault;
}
void rocksdb_readoptions_set_read_index(rocksdb_readoptions_t* opt, int v) {
opt->rep.read_index = static_cast<ReadOptions::ReadIndex>(v);
}
int rocksdb_readoptions_get_read_index(const rocksdb_readoptions_t* opt) {
return static_cast<int>(opt->rep.read_index);
}
const char* rocksdb_readoptions_get_table_index_factory_name(
@@ -7940,35 +7609,6 @@ rocksdb_write_buffer_manager_t* rocksdb_write_buffer_manager_create(
return wbm;
}
static bool rocksdb_write_buffer_manager_parse_flush_policy(
int value, WriteBufferFlushPolicy* policy) {
switch (value) {
case rocksdb_write_buffer_manager_flush_policy_oldest:
*policy = WriteBufferFlushPolicy::kFlushOldest;
return true;
case rocksdb_write_buffer_manager_flush_policy_largest:
*policy = WriteBufferFlushPolicy::kFlushLargest;
return true;
case rocksdb_write_buffer_manager_flush_policy_largest_across_dbs:
*policy = WriteBufferFlushPolicy::kFlushLargestAcrossDBs;
return true;
default:
return false;
}
}
rocksdb_write_buffer_manager_t* rocksdb_write_buffer_manager_create_with_policy(
size_t buffer_size, bool allow_stall, int flush_policy) {
WriteBufferFlushPolicy policy;
if (!rocksdb_write_buffer_manager_parse_flush_policy(flush_policy, &policy)) {
return nullptr;
}
rocksdb_write_buffer_manager_t* wbm = new rocksdb_write_buffer_manager_t;
wbm->rep = std::make_shared<WriteBufferManager>(
buffer_size, std::shared_ptr<Cache>{}, allow_stall, policy);
return wbm;
}
rocksdb_write_buffer_manager_t* rocksdb_write_buffer_manager_create_with_cache(
size_t buffer_size, const rocksdb_cache_t* cache, bool allow_stall) {
rocksdb_write_buffer_manager_t* wbm = new rocksdb_write_buffer_manager_t;
@@ -7977,20 +7617,6 @@ rocksdb_write_buffer_manager_t* rocksdb_write_buffer_manager_create_with_cache(
return wbm;
}
rocksdb_write_buffer_manager_t*
rocksdb_write_buffer_manager_create_with_cache_and_policy(
size_t buffer_size, const rocksdb_cache_t* cache, bool allow_stall,
int flush_policy) {
WriteBufferFlushPolicy policy;
if (!rocksdb_write_buffer_manager_parse_flush_policy(flush_policy, &policy)) {
return nullptr;
}
rocksdb_write_buffer_manager_t* wbm = new rocksdb_write_buffer_manager_t;
wbm->rep = std::make_shared<WriteBufferManager>(buffer_size, cache->rep,
allow_stall, policy);
return wbm;
}
void rocksdb_write_buffer_manager_destroy(rocksdb_write_buffer_manager_t* wbm) {
delete wbm;
}
@@ -8031,19 +7657,6 @@ ROCKSDB_LIBRARY_API void rocksdb_write_buffer_manager_set_allow_stall(
wbm->rep->SetAllowStall(new_allow_stall);
}
int rocksdb_write_buffer_manager_flush_policy(
rocksdb_write_buffer_manager_t* wbm) {
return static_cast<int>(wbm->rep->flush_policy());
}
void rocksdb_write_buffer_manager_set_flush_policy(
rocksdb_write_buffer_manager_t* wbm, int flush_policy) {
WriteBufferFlushPolicy policy;
if (rocksdb_write_buffer_manager_parse_flush_policy(flush_policy, &policy)) {
wbm->rep->SetFlushPolicy(policy);
}
}
rocksdb_sst_file_manager_t* rocksdb_sst_file_manager_create(
rocksdb_env_t* env) {
rocksdb_sst_file_manager_t* sfm = new rocksdb_sst_file_manager_t;
@@ -10195,16 +9808,6 @@ unsigned char rocksdb_options_get_write_dbid_to_manifest(
return opt->rep.write_dbid_to_manifest;
}
void rocksdb_options_set_track_options_file_number_in_manifest(
rocksdb_options_t* opt, unsigned char v) {
opt->rep.track_options_file_number_in_manifest = v;
}
unsigned char rocksdb_options_get_track_options_file_number_in_manifest(
rocksdb_options_t* opt) {
return opt->rep.track_options_file_number_in_manifest;
}
void rocksdb_options_set_write_identity_file(rocksdb_options_t* opt,
unsigned char v) {
opt->rep.write_identity_file = v;
@@ -10280,36 +9883,6 @@ const char* rocksdb_options_get_db_host_id(rocksdb_options_t* opt,
return opt->rep.db_host_id.data();
}
void rocksdb_options_set_use_separate_remote_compaction_pool(
rocksdb_options_t* opt, unsigned char v) {
opt->rep.use_separate_remote_compaction_pool = v;
}
unsigned char rocksdb_options_get_use_separate_remote_compaction_pool(
rocksdb_options_t* opt) {
return opt->rep.use_separate_remote_compaction_pool;
}
void rocksdb_options_set_remote_compaction_manifest_floor(
rocksdb_options_t* opt, unsigned char v) {
opt->rep.remote_compaction_manifest_floor = v;
}
unsigned char rocksdb_options_get_remote_compaction_manifest_floor(
rocksdb_options_t* opt) {
return opt->rep.remote_compaction_manifest_floor;
}
void rocksdb_options_set_use_session_tmp_dir_for_remote_compaction(
rocksdb_options_t* opt, unsigned char v) {
opt->rep.use_session_tmp_dir_for_remote_compaction = v;
}
unsigned char rocksdb_options_get_use_session_tmp_dir_for_remote_compaction(
rocksdb_options_t* opt) {
return opt->rep.use_session_tmp_dir_for_remote_compaction;
}
void rocksdb_options_set_lowest_used_cache_tier(rocksdb_options_t* opt, int v) {
opt->rep.lowest_used_cache_tier =
static_cast<decltype(opt->rep.lowest_used_cache_tier)>(v);
@@ -10350,16 +9923,6 @@ uint64_t rocksdb_options_get_max_compaction_trigger_wakeup_seconds(
return opt->rep.max_compaction_trigger_wakeup_seconds;
}
void rocksdb_options_set_periodic_compaction_phase_recovery_percent(
rocksdb_options_t* opt, int v) {
opt->rep.periodic_compaction_phase_recovery_percent = v;
}
int rocksdb_options_get_periodic_compaction_phase_recovery_percent(
rocksdb_options_t* opt) {
return opt->rep.periodic_compaction_phase_recovery_percent;
}
void rocksdb_options_set_follower_refresh_catchup_period_ms(
rocksdb_options_t* opt, uint64_t v) {
opt->rep.follower_refresh_catchup_period_ms = v;
@@ -11383,16 +10946,6 @@ unsigned char rocksdb_trace_options_get_preserve_write_order(
return opt->rep.preserve_write_order;
}
void rocksdb_trace_options_set_include_read_write_options(
rocksdb_trace_options_t* opt, unsigned char v) {
opt->rep.include_read_write_options = v;
}
unsigned char rocksdb_trace_options_get_include_read_write_options(
rocksdb_trace_options_t* opt) {
return opt->rep.include_read_write_options;
}
/* BlockCacheTraceOptions */
void rocksdb_block_cache_trace_options_set_sampling_frequency(
@@ -11472,16 +11025,6 @@ unsigned char rocksdb_compaction_options_get_allow_trivial_move(
/* CompactRangeOptions */
void rocksdb_compactoptions_set_skip_missing_blob_files(
rocksdb_compactoptions_t* opt, unsigned char v) {
opt->rep.skip_missing_blob_files = v;
}
unsigned char rocksdb_compactoptions_get_skip_missing_blob_files(
rocksdb_compactoptions_t* opt) {
return opt->rep.skip_missing_blob_files;
}
void rocksdb_compactoptions_set_blob_garbage_collection_policy(
rocksdb_compactoptions_t* opt, int v) {
opt->rep.blob_garbage_collection_policy =
@@ -11551,6 +11094,16 @@ unsigned char rocksdb_ingestexternalfileoptions_get_ingest_behind(
return opt->rep.ingest_behind;
}
void rocksdb_ingestexternalfileoptions_set_write_global_seqno(
rocksdb_ingestexternalfileoptions_t* opt, unsigned char v) {
opt->rep.write_global_seqno = v;
}
unsigned char rocksdb_ingestexternalfileoptions_get_write_global_seqno(
rocksdb_ingestexternalfileoptions_t* opt) {
return opt->rep.write_global_seqno;
}
void rocksdb_ingestexternalfileoptions_set_verify_checksums_before_ingest(
rocksdb_ingestexternalfileoptions_t* opt, unsigned char v) {
opt->rep.verify_checksums_before_ingest = v;
@@ -11629,28 +11182,8 @@ int rocksdb_ingestexternalfileoptions_get_file_opening_threads(
return opt->rep.file_opening_threads;
}
void rocksdb_ingestexternalfileoptions_set_reject_too_old_file(
rocksdb_ingestexternalfileoptions_t* opt, unsigned char v) {
opt->rep.reject_too_old_file = v;
}
unsigned char rocksdb_ingestexternalfileoptions_get_reject_too_old_file(
rocksdb_ingestexternalfileoptions_t* opt) {
return opt->rep.reject_too_old_file;
}
/* OpenAndCompactOptions */
void rocksdb_open_and_compact_options_set_max_secondary_open_retries(
rocksdb_open_and_compact_options_t* opt, uint32_t v) {
opt->rep.max_secondary_open_retries = v;
}
uint32_t rocksdb_open_and_compact_options_get_max_secondary_open_retries(
rocksdb_open_and_compact_options_t* opt) {
return opt->rep.max_secondary_open_retries;
}
unsigned char rocksdb_open_and_compact_options_get_allow_resumption(
rocksdb_open_and_compact_options_t* opt) {
return opt->rep.allow_resumption;
@@ -11663,16 +11196,6 @@ unsigned char rocksdb_import_column_family_options_get_move_files(
return opt->rep.move_files;
}
void rocksdb_import_column_family_options_set_reject_too_old_file(
rocksdb_import_column_family_options_t* opt, unsigned char v) {
opt->rep.reject_too_old_file = v;
}
unsigned char rocksdb_import_column_family_options_get_reject_too_old_file(
rocksdb_import_column_family_options_t* opt) {
return opt->rep.reject_too_old_file;
}
/* SizeApproximationOptions */
void rocksdb_size_approximation_options_set_include_memtables(
@@ -11967,6 +11490,26 @@ unsigned char rocksdb_block_based_options_get_use_delta_encoding(
return opt->rep.use_delta_encoding;
}
void rocksdb_block_based_options_set_use_udi_as_primary_index(
rocksdb_block_based_table_options_t* opt, unsigned char v) {
opt->rep.use_udi_as_primary_index = v;
}
unsigned char rocksdb_block_based_options_get_use_udi_as_primary_index(
rocksdb_block_based_table_options_t* opt) {
return opt->rep.use_udi_as_primary_index;
}
void rocksdb_block_based_options_set_fail_if_no_udi_on_open(
rocksdb_block_based_table_options_t* opt, unsigned char v) {
opt->rep.fail_if_no_udi_on_open = v;
}
unsigned char rocksdb_block_based_options_get_fail_if_no_udi_on_open(
rocksdb_block_based_table_options_t* opt) {
return opt->rep.fail_if_no_udi_on_open;
}
void rocksdb_block_based_options_set_whole_key_filtering(
rocksdb_block_based_table_options_t* opt, unsigned char v) {
opt->rep.whole_key_filtering = v;
@@ -12038,17 +11581,6 @@ double rocksdb_block_based_options_get_uniform_cv_threshold(
return opt->rep.uniform_cv_threshold;
}
void rocksdb_block_based_options_set_optimize_key_common_prefix(
rocksdb_block_based_table_options_t* opt, int v) {
opt->rep.optimize_key_common_prefix =
static_cast<decltype(opt->rep.optimize_key_common_prefix)>(v);
}
int rocksdb_block_based_options_get_optimize_key_common_prefix(
rocksdb_block_based_table_options_t* opt) {
return static_cast<int>(opt->rep.optimize_key_common_prefix);
}
void rocksdb_block_based_options_set_enable_index_compression(
rocksdb_block_based_table_options_t* opt, unsigned char v) {
opt->rep.enable_index_compression = v;
@@ -13539,74 +13071,6 @@ void rocksdb_transactiondb_multi_get_cf(
}
}
void rocksdb_transactiondb_batched_multi_get_cf(
rocksdb_transactiondb_t* txn_db, const rocksdb_readoptions_t* options,
rocksdb_column_family_handle_t* column_family, size_t num_keys,
const char* const* keys_list, const size_t* keys_list_sizes,
rocksdb_pinnableslice_t** values, char** errs, const bool sorted_input) {
Slice* key_slices = new Slice[num_keys];
PinnableSlice* value_slices = new PinnableSlice[num_keys];
Status* statuses = new Status[num_keys];
for (size_t i = 0; i < num_keys; ++i) {
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
}
txn_db->rep->MultiGet(options->rep, column_family->rep, num_keys, key_slices,
value_slices, statuses, sorted_input);
for (size_t i = 0; i < num_keys; ++i) {
if (statuses[i].ok()) {
values[i] = new (rocksdb_pinnableslice_t);
values[i]->rep = std::move(value_slices[i]);
errs[i] = nullptr;
} else {
values[i] = nullptr;
if (!statuses[i].IsNotFound()) {
errs[i] = strdup(statuses[i].ToString().c_str());
} else {
errs[i] = nullptr;
}
}
}
delete[] key_slices;
delete[] value_slices;
delete[] statuses;
}
void rocksdb_transactiondb_batched_multi_get_cf_slice(
rocksdb_transactiondb_t* txn_db, const rocksdb_readoptions_t* options,
rocksdb_column_family_handle_t* column_family, size_t num_keys,
const rocksdb_slice_t* keys_list, rocksdb_pinnableslice_t** values,
char** errs, const bool sorted_input) {
PinnableSlice* value_slices = new PinnableSlice[num_keys];
Status* statuses = new Status[num_keys];
// Cast rocksdb_slice_t* to Slice* - they have identical memory layout
const Slice* key_slices = reinterpret_cast<const Slice*>(keys_list);
txn_db->rep->MultiGet(options->rep, column_family->rep, num_keys, key_slices,
value_slices, statuses, sorted_input);
for (size_t i = 0; i < num_keys; ++i) {
if (statuses[i].ok()) {
values[i] = new (rocksdb_pinnableslice_t);
values[i]->rep = std::move(value_slices[i]);
errs[i] = nullptr;
} else {
values[i] = nullptr;
if (!statuses[i].IsNotFound()) {
errs[i] = strdup(statuses[i].ToString().c_str());
} else {
errs[i] = nullptr;
}
}
}
delete[] value_slices;
delete[] statuses;
}
void rocksdb_transaction_set_commit_timestamp(rocksdb_transaction_t* txn,
uint64_t commit_timestamp) {
txn->rep->SetCommitTimestamp(commit_timestamp);
+3 -248
View File
@@ -240,138 +240,6 @@ static void CheckPinnableMultiGetError(
CheckCondition(strstr(error, expected) != NULL);
}
static void CheckTxnDBBatchedMultiGet(rocksdb_transactiondb_t* db,
const rocksdb_readoptions_t* options,
rocksdb_column_family_handle_t* cf,
const char* expected_a) {
for (int sorted = 0; sorted <= 1; ++sorted) {
const char* keys[4] = {"b", "a", "b", "missing"};
const size_t sizes[4] = {1, 1, 1, 7};
const char* expected[4] = {"", expected_a, "", NULL};
if (sorted) {
keys[0] = "a";
keys[1] = "b";
expected[0] = expected_a;
expected[1] = "";
}
for (int slices = 0; slices <= 1; ++slices) {
rocksdb_pinnableslice_t* values[4];
char* errs[4];
if (slices) {
rocksdb_slice_t key_slices[4];
for (size_t i = 0; i < 4; ++i) {
key_slices[i].data = keys[i];
key_slices[i].size = sizes[i];
}
rocksdb_transactiondb_batched_multi_get_cf_slice(
db, options, cf, 4, key_slices, values, errs, sorted);
} else {
rocksdb_transactiondb_batched_multi_get_cf(db, options, cf, 4, keys,
sizes, values, errs, sorted);
}
for (size_t i = 0; i < 4; ++i) {
CheckNoError(errs[i]);
if (expected[i] == NULL) {
CheckCondition(values[i] == NULL);
} else {
size_t size;
CheckCondition(values[i] != NULL);
const char* value = rocksdb_pinnableslice_value(values[i], &size);
CheckEqual(expected[i], value, size);
}
rocksdb_pinnableslice_destroy(values[i]);
}
}
}
rocksdb_transactiondb_batched_multi_get_cf(db, options, cf, 0, NULL, NULL,
NULL, NULL, false);
rocksdb_transactiondb_batched_multi_get_cf_slice(db, options, cf, 0, NULL,
NULL, NULL, true);
}
static void TestTxnDBBatchedMultiGet(void) {
rocksdb_options_t* options = rocksdb_options_create();
rocksdb_readoptions_t* ro = rocksdb_readoptions_create();
rocksdb_writeoptions_t* wo = rocksdb_writeoptions_create();
rocksdb_transaction_options_t* to = rocksdb_transaction_options_create();
rocksdb_transactiondb_options_t* dbo = rocksdb_transactiondb_options_create();
char path[200];
char* err = NULL;
snprintf(path, sizeof(path), "%s/rocksdb_c_test-batched-txn-%" PRIu64,
GetTempDir(), GetTestId());
rocksdb_options_set_create_if_missing(options, 1);
rocksdb_transaction_options_set_skip_prepare(to, 0);
for (int policy = rocksdb_txndb_write_policy_write_committed;
policy <= rocksdb_txndb_write_policy_write_prepared; ++policy) {
rocksdb_destroy_db(options, path, &err);
CheckNoError(err);
rocksdb_transactiondb_options_set_write_policy(dbo, policy);
rocksdb_transactiondb_t* db =
rocksdb_transactiondb_open(options, dbo, path, &err);
CheckNoError(err);
rocksdb_column_family_handle_t* cf =
rocksdb_transactiondb_create_column_family(db, options, "batched",
&err);
CheckNoError(err);
rocksdb_transactiondb_put_cf(db, wo, cf, "a", 1, "old", 3, &err);
CheckNoError(err);
rocksdb_transactiondb_put_cf(db, wo, cf, "b", 1, "", 0, &err);
CheckNoError(err);
CheckTxnDBBatchedMultiGet(db, ro, cf, "old");
rocksdb_transaction_t* txn = rocksdb_transaction_begin(db, wo, to, NULL);
rocksdb_transaction_set_name(txn, "batched", 7, &err);
CheckNoError(err);
rocksdb_transaction_put_cf(txn, cf, "a", 1, "new", 3, &err);
CheckNoError(err);
rocksdb_transaction_prepare(txn, &err);
CheckNoError(err);
// WRITE_PREPARED puts the new value in the base DB before commit. Reads
// must go through TransactionDB to keep that value invisible.
CheckTxnDBBatchedMultiGet(db, ro, cf, "old");
const rocksdb_snapshot_t* snapshot =
rocksdb_transactiondb_create_snapshot(db);
rocksdb_transaction_commit(txn, &err);
CheckNoError(err);
CheckTxnDBBatchedMultiGet(db, ro, cf, "new");
rocksdb_readoptions_set_snapshot(ro, snapshot);
CheckTxnDBBatchedMultiGet(db, ro, cf, "old");
rocksdb_readoptions_set_snapshot(ro, NULL);
rocksdb_transactiondb_release_snapshot(db, snapshot);
// A timestamp on a non-timestamped column family must return an error,
// rather than being reported as a missing key.
rocksdb_readoptions_set_timestamp(ro, "timestamp", 9);
for (int slices = 0; slices <= 1; ++slices) {
const char* keys[1] = {"a"};
const size_t sizes[1] = {1};
const rocksdb_slice_t key_slices[1] = {{"a", 1}};
rocksdb_pinnableslice_t* values[1];
char* errs[1];
if (slices) {
rocksdb_transactiondb_batched_multi_get_cf_slice(
db, ro, cf, 1, key_slices, values, errs, true);
} else {
rocksdb_transactiondb_batched_multi_get_cf(db, ro, cf, 1, keys, sizes,
values, errs, true);
}
CheckCondition(values[0] == NULL);
CheckCondition(errs[0] != NULL);
rocksdb_free(errs[0]);
}
rocksdb_readoptions_set_timestamp(ro, NULL, 0);
rocksdb_transaction_destroy(txn);
rocksdb_column_family_handle_destroy(cf);
rocksdb_transactiondb_close(db);
rocksdb_destroy_db(options, path, &err);
CheckNoError(err);
}
rocksdb_transactiondb_options_destroy(dbo);
rocksdb_transaction_options_destroy(to);
rocksdb_writeoptions_destroy(wo);
rocksdb_readoptions_destroy(ro);
rocksdb_options_destroy(options);
}
static void CheckIter(rocksdb_iterator_t* iter, const char* key,
const char* val) {
size_t len;
@@ -456,6 +324,7 @@ static void CheckPutCF(void* ptr, uint32_t cfid, const char* k, size_t klen,
break;
default:
CheckCondition(false);
break;
}
(*state)++;
}
@@ -474,6 +343,7 @@ static void CheckDelCF(void* ptr, uint32_t cfid, const char* k, size_t klen) {
break;
default:
CheckCondition(false);
break;
}
(*state)++;
}
@@ -495,6 +365,7 @@ static void CheckMergeCF(void* ptr, uint32_t cfid, const char* k, size_t klen,
break;
default:
CheckCondition(false);
break;
}
(*state)++;
}
@@ -2603,35 +2474,6 @@ int main(int argc, char** argv) {
CheckNoError(err);
rocksdb_readoptions_set_snapshot(roptions, snap);
CheckGet(db, roptions, "foo", "hello");
size_t metadata_value_len = 0;
unsigned char newer_version_present = 0;
char* metadata_value =
rocksdb_get_with_metadata(db, roptions, "foo", 3, &metadata_value_len,
NULL, NULL, &newer_version_present, &err);
// NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
CheckNoError(err);
CheckEqual("hello", metadata_value, metadata_value_len);
// NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
CheckCondition(newer_version_present == 1);
Free(&metadata_value);
const char* metadata_keys[] = {"foo"};
const size_t metadata_key_sizes[] = {3};
char* metadata_values[1];
size_t metadata_value_sizes[1];
unsigned char newer_versions[1];
char* metadata_errors[1];
rocksdb_multi_get_with_metadata(
db, roptions, 1, metadata_keys, metadata_key_sizes, metadata_values,
metadata_value_sizes, NULL, NULL, newer_versions, metadata_errors);
// NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
CheckCondition(metadata_errors[0] == NULL);
CheckEqual("hello", metadata_values[0], metadata_value_sizes[0]);
// NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
CheckCondition(newer_versions[0] == 1);
Free(&metadata_values[0]);
rocksdb_readoptions_set_snapshot(roptions, NULL);
CheckGet(db, roptions, "foo", NULL);
rocksdb_release_snapshot(db, snap);
@@ -3759,42 +3601,6 @@ int main(int argc, char** argv) {
bbo, &factory_name_len);
CheckCondition(factory_name == NULL);
CheckCondition(factory_name_len == 0);
CheckCondition(
rocksdb_block_based_options_get_use_udi_as_primary_index(bbo) == 0);
rocksdb_block_based_options_set_use_udi_as_primary_index(bbo, 1);
CheckCondition(
rocksdb_block_based_options_get_use_udi_as_primary_index(bbo) == 1);
rocksdb_block_based_options_set_use_udi_as_primary_index(bbo, 0);
CheckCondition(
rocksdb_block_based_options_get_use_udi_as_primary_index(bbo) == 0);
CheckCondition(
rocksdb_block_based_options_get_fail_if_no_udi_on_open(bbo) == 0);
rocksdb_block_based_options_set_fail_if_no_udi_on_open(bbo, 1);
CheckCondition(
rocksdb_block_based_options_get_fail_if_no_udi_on_open(bbo) == 1);
rocksdb_block_based_options_set_fail_if_no_udi_on_open(bbo, 0);
CheckCondition(
rocksdb_block_based_options_get_fail_if_no_udi_on_open(bbo) == 0);
// index_mode defaults to standard_default and tracks the deprecated
// bools until it is set explicitly; after that an explicit value wins no
// matter what order the deprecated setters are called in.
CheckCondition(rocksdb_block_based_table_index_mode_standard_default ==
rocksdb_block_based_options_get_index_mode(bbo));
rocksdb_block_based_options_set_use_udi_as_primary_index(bbo, 1);
CheckCondition(rocksdb_block_based_table_index_mode_custom_default ==
rocksdb_block_based_options_get_index_mode(bbo));
rocksdb_block_based_options_set_index_mode(
bbo, rocksdb_block_based_table_index_mode_custom_only);
CheckCondition(rocksdb_block_based_table_index_mode_custom_only ==
rocksdb_block_based_options_get_index_mode(bbo));
rocksdb_block_based_options_set_index_mode(
bbo, rocksdb_block_based_table_index_mode_standard_only);
rocksdb_block_based_options_set_fail_if_no_udi_on_open(bbo, 1);
CheckCondition(rocksdb_block_based_table_index_mode_standard_only ==
rocksdb_block_based_options_get_index_mode(bbo));
rocksdb_block_based_options_set_use_udi_as_primary_index(bbo, 0);
rocksdb_block_based_options_set_fail_if_no_udi_on_open(bbo, 0);
rocksdb_block_based_options_destroy(bbo);
}
@@ -4842,24 +4648,6 @@ int main(int argc, char** argv) {
CheckCondition(table_index_factory_name == NULL);
CheckCondition(table_index_factory_name_len == 0);
// read_index: set_table_index_factory_from_string implies prefer_custom;
// clearing it returns to default; the explicit setter reaches builtin,
// which the legacy factory shim cannot express.
CheckCondition(rocksdb_readoptions_read_index_default ==
rocksdb_readoptions_get_read_index(ro));
rocksdb_readoptions_set_read_index(ro,
rocksdb_readoptions_read_index_builtin);
CheckCondition(rocksdb_readoptions_read_index_builtin ==
rocksdb_readoptions_get_read_index(ro));
rocksdb_readoptions_set_table_index_factory_from_string(ro, "trie_index",
10, &err);
CheckNoError(err);
CheckCondition(rocksdb_readoptions_read_index_prefer_custom ==
rocksdb_readoptions_get_read_index(ro));
rocksdb_readoptions_clear_table_index_factory(ro);
CheckCondition(rocksdb_readoptions_read_index_default ==
rocksdb_readoptions_get_read_index(ro));
// optimize_multiget_for_io defaults to true (1); flip to 0 to verify the
// setter is wired through to the underlying C++ ReadOptions field.
CheckCondition(1 == rocksdb_readoptions_get_optimize_multiget_for_io(ro));
@@ -5770,9 +5558,6 @@ int main(int argc, char** argv) {
CheckNoError(err);
}
StartPhase("transactiondb_batched_multi_get");
TestTxnDBBatchedMultiGet();
StartPhase("transactions");
{
rocksdb_close(db);
@@ -6902,36 +6687,6 @@ int main(int argc, char** argv) {
300 == rocksdb_write_buffer_manager_buffer_size(write_buffer_manager));
rocksdb_write_buffer_manager_destroy(write_buffer_manager);
write_buffer_manager = rocksdb_write_buffer_manager_create_with_policy(
200, true,
rocksdb_write_buffer_manager_flush_policy_largest_across_dbs);
CheckCondition(write_buffer_manager != NULL);
CheckCondition(
rocksdb_write_buffer_manager_flush_policy_largest_across_dbs ==
rocksdb_write_buffer_manager_flush_policy(write_buffer_manager));
rocksdb_write_buffer_manager_set_flush_policy(
write_buffer_manager,
rocksdb_write_buffer_manager_flush_policy_largest);
CheckCondition(
rocksdb_write_buffer_manager_flush_policy_largest ==
rocksdb_write_buffer_manager_flush_policy(write_buffer_manager));
rocksdb_write_buffer_manager_set_flush_policy(write_buffer_manager, 999);
CheckCondition(
rocksdb_write_buffer_manager_flush_policy_largest ==
rocksdb_write_buffer_manager_flush_policy(write_buffer_manager));
rocksdb_write_buffer_manager_destroy(write_buffer_manager);
write_buffer_manager =
rocksdb_write_buffer_manager_create_with_cache_and_policy(
200, lru, true,
rocksdb_write_buffer_manager_flush_policy_largest_across_dbs);
CheckCondition(write_buffer_manager != NULL);
rocksdb_write_buffer_manager_destroy(write_buffer_manager);
write_buffer_manager =
rocksdb_write_buffer_manager_create_with_policy(200, true, 999);
CheckCondition(write_buffer_manager == NULL);
rocksdb_cache_destroy(lru);
}
-2
View File
@@ -26,8 +26,6 @@ class CoalescingIterator : public Iterator {
// No copy allowed
CoalescingIterator(const CoalescingIterator&) = delete;
CoalescingIterator& operator=(const CoalescingIterator&) = delete;
CoalescingIterator(CoalescingIterator&&) = delete;
CoalescingIterator& operator=(CoalescingIterator&&) = delete;
bool Valid() const override { return impl_.Valid(); }
void SeekToFirst() override { impl_.SeekToFirst(); }
+22 -171
View File
@@ -33,7 +33,6 @@
#include "db/write_controller.h"
#include "file/sst_file_manager_impl.h"
#include "logging/logging.h"
#include "memtable/flush_initiator.h"
#include "monitoring/thread_status_util.h"
#include "options/options_helper.h"
#include "port/port.h"
@@ -475,9 +474,6 @@ ColumnFamilyOptions SanitizeCfOptions(const ImmutableDBOptions& db_options,
result.preclude_last_level_data_seconds > 0)) {
// With no writes coming in, we don't need periodic SeqnoToTime entries.
// Existing SST files may or may not have that info associated with them.
// Remote compaction runs in a secondary (read-only) instance but writes
// files, so it restores both from the primary. See
// DBImplSecondary::CompactWithoutInstallation().
ROCKS_LOG_WARN(
db_options.info_log.get(),
"preserve_internal_time_seconds and preclude_last_level_data_seconds "
@@ -616,8 +612,7 @@ ColumnFamilyData::ColumnFamilyData(
const FileOptions* file_options, ColumnFamilySet* column_family_set,
BlockCacheTracer* const block_cache_tracer,
const std::shared_ptr<IOTracer>& io_tracer, const std::string& db_id,
const std::string& db_session_id, bool read_only,
bool options_are_sanitized, bool fast_sst_open)
const std::string& db_session_id, bool read_only, bool fast_sst_open)
: id_(id),
name_(name),
dummy_versions_(_dummy_versions),
@@ -627,16 +622,11 @@ ColumnFamilyData::ColumnFamilyData(
dropped_(false),
flush_skip_reschedule_(false),
internal_comparator_(cf_options.comparator),
initial_cf_options_(
options_are_sanitized
? cf_options
: SanitizeCfOptions(db_options, read_only, cf_options)),
initial_cf_options_(SanitizeCfOptions(db_options, read_only, cf_options)),
ioptions_(db_options, initial_cf_options_),
mutable_cf_options_(initial_cf_options_),
is_delete_range_supported_(
cf_options.table_factory->IsDeleteRangeSupported()),
is_table_builder_supported_(
cf_options.table_factory->IsTableBuilderSupported()),
write_buffer_manager_(write_buffer_manager),
mem_(nullptr),
imm_(ioptions_.min_write_buffer_number_to_merge,
@@ -682,7 +672,7 @@ ColumnFamilyData::ColumnFamilyData(
new InternalStats(ioptions_.num_levels, ioptions_.clock, this));
table_cache_.reset(new TableCache(ioptions_, file_options, _table_cache,
block_cache_tracer, io_tracer,
db_session_id, fast_sst_open, name_));
db_session_id, fast_sst_open));
blob_file_cache_.reset(
new BlobFileCache(_table_cache, &ioptions(), soptions(), id_,
internal_stats_->GetBlobFileReadHist(), io_tracer));
@@ -844,11 +834,6 @@ void ColumnFamilyData::SetDropped() {
// can't drop default CF
assert(id_ != 0);
dropped_ = true;
// A tracker can still contain accounting published while a different WBM
// policy was active. Cleanup therefore cannot depend on the current policy.
if (mem_ != nullptr) {
mem_->StopWBMTracking();
}
write_controller_token_.reset();
// remove from column_family_set
@@ -1027,16 +1012,13 @@ ColumnFamilyData::GetWriteStallConditionAndCause(
int num_unflushed_memtables, int num_l0_files,
uint64_t num_compaction_needed_bytes,
const MutableCFOptions& mutable_cf_options,
const ImmutableCFOptions& immutable_cf_options,
bool table_builder_supported) {
const bool auto_compactions =
table_builder_supported && !mutable_cf_options.disable_auto_compactions;
const ImmutableCFOptions& immutable_cf_options) {
if (num_unflushed_memtables >= mutable_cf_options.max_write_buffer_number) {
return {WriteStallCondition::kStopped, WriteStallCause::kMemtableLimit};
} else if (auto_compactions &&
} else if (!mutable_cf_options.disable_auto_compactions &&
num_l0_files >= mutable_cf_options.level0_stop_writes_trigger) {
return {WriteStallCondition::kStopped, WriteStallCause::kL0FileCountLimit};
} else if (auto_compactions &&
} else if (!mutable_cf_options.disable_auto_compactions &&
mutable_cf_options.hard_pending_compaction_bytes_limit > 0 &&
num_compaction_needed_bytes >=
mutable_cf_options.hard_pending_compaction_bytes_limit) {
@@ -1048,12 +1030,12 @@ ColumnFamilyData::GetWriteStallConditionAndCause(
num_unflushed_memtables - 1 >=
immutable_cf_options.min_write_buffer_number_to_merge) {
return {WriteStallCondition::kDelayed, WriteStallCause::kMemtableLimit};
} else if (auto_compactions &&
} else if (!mutable_cf_options.disable_auto_compactions &&
mutable_cf_options.level0_slowdown_writes_trigger >= 0 &&
num_l0_files >=
mutable_cf_options.level0_slowdown_writes_trigger) {
return {WriteStallCondition::kDelayed, WriteStallCause::kL0FileCountLimit};
} else if (auto_compactions &&
} else if (!mutable_cf_options.disable_auto_compactions &&
mutable_cf_options.soft_pending_compaction_bytes_limit > 0 &&
num_compaction_needed_bytes >=
mutable_cf_options.soft_pending_compaction_bytes_limit) {
@@ -1075,7 +1057,7 @@ WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
auto write_stall_condition_and_cause = GetWriteStallConditionAndCause(
imm()->NumNotFlushed(), vstorage->l0_delay_trigger_count(),
vstorage->estimated_compaction_needed_bytes(), mutable_cf_options,
ioptions(), is_table_builder_supported_);
ioptions());
write_stall_condition = write_stall_condition_and_cause.first;
auto write_stall_cause = write_stall_condition_and_cause.second;
@@ -1264,10 +1246,7 @@ uint64_t ColumnFamilyData::GetLiveSstFilesSize() const {
MemTable* ColumnFamilyData::ConstructNewMemtable(
const MutableCFOptions& mutable_cf_options, SequenceNumber earliest_seq) {
return new MemTable(internal_comparator_, ioptions_, mutable_cf_options,
write_buffer_manager_, earliest_seq, id_,
column_family_set_ != nullptr
? column_family_set_->flush_initiator()
: nullptr);
write_buffer_manager_, earliest_seq, id_);
}
void ColumnFamilyData::CreateNewMemtable(SequenceNumber earliest_seq) {
@@ -1281,8 +1260,7 @@ void ColumnFamilyData::CreateNewMemtable(SequenceNumber earliest_seq) {
}
bool ColumnFamilyData::NeedsCompaction() const {
return is_table_builder_supported_ &&
!mutable_cf_options_.disable_auto_compactions &&
return !mutable_cf_options_.disable_auto_compactions &&
compaction_picker_->NeedsCompaction(current_->storage_info());
}
@@ -1303,15 +1281,15 @@ Compaction* ColumnFamilyData::PickCompaction(
}
bool ColumnFamilyData::RangeOverlapWithCompaction(
const Slice& smallest_user_key, const Slice& largest_user_key, int level,
bool range_limit_exclusive) const {
const Slice& smallest_user_key, const Slice& largest_user_key,
int level) const {
return compaction_picker_->RangeOverlapWithCompaction(
smallest_user_key, largest_user_key, level, range_limit_exclusive);
smallest_user_key, largest_user_key, level);
}
Status ColumnFamilyData::RangesOverlapWithMemtables(
const autovector<UserKeyRange>& ranges, SuperVersion* super_version,
bool allow_data_in_errors, bool* overlap, bool range_limit_exclusive) {
bool allow_data_in_errors, bool* overlap) {
assert(overlap != nullptr);
*overlap = false;
// Create an InternalIterator over all unflushed memtables
@@ -1356,15 +1334,12 @@ Status ColumnFamilyData::RangesOverlapWithMemtables(
}
if (status.ok()) {
const int limit_cmp = memtable_iter->Valid()
? ucmp->CompareWithoutTimestamp(
seek_result.user_key, ranges[i].limit)
: 1;
if (memtable_iter->Valid() &&
(range_limit_exclusive ? limit_cmp < 0 : limit_cmp <= 0)) {
ucmp->CompareWithoutTimestamp(seek_result.user_key,
ranges[i].limit) <= 0) {
*overlap = true;
} else if (range_del_agg.IsRangeOverlapped(
ranges[i].start, ranges[i].limit, range_limit_exclusive)) {
} else if (range_del_agg.IsRangeOverlapped(ranges[i].start,
ranges[i].limit)) {
*overlap = true;
}
}
@@ -1547,13 +1522,6 @@ Status ColumnFamilyData::ValidateOptions(
return s;
}
if (!cf_options.table_factory->IsTableBuilderSupported() &&
!cf_options.disallow_memtable_writes) {
return Status::InvalidArgument(
"A table factory that cannot build tables requires "
"disallow_memtable_writes");
}
if (cf_options.ttl > 0 && cf_options.ttl != kDefaultTtl) {
if (!cf_options.table_factory->IsInstanceOf(
TableFactory::kBlockBasedTableName())) {
@@ -1754,21 +1722,10 @@ Status ColumnFamilyData::SetOptions(
if (s.ok()) {
mutable_cf_options_ = MutableCFOptions(cf_opts);
mutable_cf_options_.RefreshDerivedOptions(ioptions_);
options_install_pending_ = true;
}
return s;
}
bool ColumnFamilyData::SetOptionsIsNoOp(
const DBOptions& db_opts,
const std::unordered_map<std::string, std::string>& options_map) const {
// SetOptions() validates the resulting options, so a request that changes
// nothing must pass the same check to return the same status.
return !IsDropped() && !options_install_pending_ &&
MutableCFOptionsUnchangedByMap(mutable_cf_options_, options_map) &&
ValidateOptions(db_opts, GetLatestCFOptions()).ok();
}
Status ColumnFamilyData::AddDirectories(
std::map<std::string, std::shared_ptr<FSDirectory>>* created_dirs) {
Status s;
@@ -1869,7 +1826,7 @@ ColumnFamilySet::ColumnFamilySet(
ColumnFamilyData::kDummyColumnFamilyDataId, "", nullptr, nullptr,
nullptr, ColumnFamilyOptions(), *db_options, &file_options_, nullptr,
block_cache_tracer, io_tracer, db_id, db_session_id,
/*read_only=*/true, /*options_are_sanitized=*/false)),
/*read_only*/ true)),
default_cfd_cache_(nullptr),
db_name_(dbname),
db_options_(db_options),
@@ -1939,112 +1896,15 @@ size_t ColumnFamilySet::NumberOfColumnFamilies() const {
return column_families_.size();
}
void ColumnFamilySet::RefreshFlushableMemAccounting() {
if (flush_initiator_ == nullptr || write_buffer_manager_ == nullptr ||
!write_buffer_manager_->ShouldTrackFlushInitiator()) {
return;
}
if (bulk_flushable_mem_accounting_depth_ > 0) {
bulk_flushable_mem_accounting_dirty_ = true;
flush_initiator_->InvalidateLargestFlushableCFMem();
return;
}
// Keep candidate readers from consuming a mixture of the previous snapshot
// and the values published by this rebuild.
flush_initiator_->InvalidateLargestFlushableCFMem();
constexpr int kMaxRefreshAttempts = 3;
for (int attempt = 0; attempt < kMaxRefreshAttempts; ++attempt) {
const uint64_t update_seq =
flush_initiator_->GetFlushableMemUpdateSequence();
bool has_mutable_mem = false;
bool has_ineligible_cf = false;
size_t largest = 0;
size_t waiting_immutable_mem = 0;
for (auto cfd : *this) {
if (cfd->IsDropped() || !cfd->initialized()) {
continue;
}
const bool mutable_not_empty = !cfd->mem()->IsEmpty();
if (mutable_not_empty) {
cfd->mem()->RefreshWBMTracking();
}
const bool has_mutable =
mutable_not_empty && cfd->mem()->IsWBMTrackingActive();
has_mutable_mem = has_mutable_mem || has_mutable;
if (flush_initiator_->UsesTotalMutableMem()) {
waiting_immutable_mem +=
cfd->imm()->WBMTrackedUnstartedMemTablesMemoryUsage();
continue;
}
if (cfd->imm()->IsFlushPendingOrRunning()) {
has_ineligible_cf = true;
continue;
}
const size_t mutable_mem =
has_mutable ? cfd->mem()->WBMTrackedMemoryUsage() : 0;
const size_t immutable_mem =
cfd->imm()->WBMTrackedUnflushedMemTablesMemoryUsage();
waiting_immutable_mem += immutable_mem;
largest = std::max(largest, mutable_mem + immutable_mem);
}
if (flush_initiator_->UsesTotalMutableMem()) {
flush_initiator_->SetHasIneligibleCF(false);
flush_initiator_->SetHasFlushableCF(has_mutable_mem ||
waiting_immutable_mem > 0);
if (flush_initiator_->TrySetLargestFlushableCFMem(
0, waiting_immutable_mem, update_seq)) {
write_buffer_manager_->NotifyFlushInitiatorChanged();
return;
}
continue;
}
flush_initiator_->SetHasIneligibleCF(has_ineligible_cf);
flush_initiator_->SetHasFlushableCF(largest > 0);
if (flush_initiator_->TrySetLargestFlushableCFMem(
largest, waiting_immutable_mem, update_seq)) {
write_buffer_manager_->NotifyFlushInitiatorChanged();
return;
}
}
flush_initiator_->InvalidateLargestFlushableCFMem();
}
void ColumnFamilySet::BeginBulkFlushableMemAccountingUpdate() {
++bulk_flushable_mem_accounting_depth_;
if (bulk_flushable_mem_accounting_depth_ == 1) {
bulk_flushable_mem_accounting_dirty_ = true;
if (flush_initiator_ != nullptr && write_buffer_manager_ != nullptr &&
write_buffer_manager_->ShouldTrackFlushInitiator()) {
flush_initiator_->InvalidateLargestFlushableCFMem();
write_buffer_manager_->NotifyFlushInitiatorChanged();
}
}
}
void ColumnFamilySet::EndBulkFlushableMemAccountingUpdate() {
assert(bulk_flushable_mem_accounting_depth_ > 0);
--bulk_flushable_mem_accounting_depth_;
if (bulk_flushable_mem_accounting_depth_ == 0 &&
bulk_flushable_mem_accounting_dirty_) {
bulk_flushable_mem_accounting_dirty_ = false;
TEST_SYNC_POINT(
"ColumnFamilySet::EndBulkFlushableMemAccountingUpdate:Refresh");
RefreshFlushableMemAccounting();
}
}
// under a DB mutex AND write thread
ColumnFamilyData* ColumnFamilySet::CreateColumnFamily(
const std::string& name, uint32_t id, Version* dummy_versions,
const ColumnFamilyOptions& options, bool read_only,
bool options_are_sanitized) {
const ColumnFamilyOptions& options, bool read_only) {
assert(column_families_.find(name) == column_families_.end());
ColumnFamilyData* new_cfd = new ColumnFamilyData(
id, name, dummy_versions, table_cache_, write_buffer_manager_, options,
*db_options_, &file_options_, this, block_cache_tracer_, io_tracer_,
db_id_, db_session_id_, read_only, options_are_sanitized, fast_sst_open_);
db_id_, db_session_id_, read_only, fast_sst_open_);
column_families_.insert({name, id});
column_family_data_.insert({id, new_cfd});
auto ucmp = new_cfd->user_comparator();
@@ -2076,15 +1936,6 @@ void ColumnFamilySet::RemoveColumnFamily(ColumnFamilyData* cfd) {
column_families_.erase(cfd->GetName());
running_ts_sz_.erase(cf_id);
ts_sz_for_record_.erase(cf_id);
if (bulk_flushable_mem_accounting_depth_ > 0) {
bulk_flushable_mem_accounting_dirty_ = true;
if (flush_initiator_ != nullptr) {
flush_initiator_->InvalidateLargestFlushableCFMem();
}
return;
}
TEST_SYNC_POINT("ColumnFamilySet::RemoveColumnFamily:Refresh");
RefreshFlushableMemAccounting();
}
// under a DB mutex OR from a write thread
+21 -72
View File
@@ -52,7 +52,6 @@ struct SuperVersionContext;
class BlobFileCache;
class BlobFilePartitionManager;
class BlobSource;
class FlushInitiator;
extern const double kIncSlowdownRatio;
// This file contains a list of data structures for managing column family
@@ -171,7 +170,7 @@ class ColumnFamilyHandleImpl : public ColumnFamilyHandle {
ColumnFamilyHandleImpl(ColumnFamilyData* cfd, DBImpl* db,
InstrumentedMutex* mutex);
// destroy without mutex
~ColumnFamilyHandleImpl() override;
virtual ~ColumnFamilyHandleImpl();
virtual ColumnFamilyData* cfd() const { return cfd_; }
virtual DBImpl* db() const { return db_; }
@@ -368,12 +367,6 @@ class ColumnFamilyData {
bool is_delete_range_supported() { return is_delete_range_supported_; }
// False when the table factory cannot write tables, so the column family is
// never compacted.
bool is_table_builder_supported() const {
return is_table_builder_supported_;
}
// Validate CF options against DB options
static Status ValidateOptions(const DBOptions& db_options,
const ColumnFamilyOptions& cf_options);
@@ -381,19 +374,6 @@ class ColumnFamilyData {
Status SetOptions(
const DBOptions& db_options,
const std::unordered_map<std::string, std::string>& options_map);
// REQUIRES: DB mutex held
// Returns true if the column family is not dropped, SetOptions(db_options,
// options_map) would succeed without changing any option, and options from
// an earlier SetOptions() are not still waiting for MarkOptionsInstalled().
// Can return false for a request that changes nothing, for example one that
// sets a table factory field.
bool SetOptionsIsNoOp(
const DBOptions& db_options,
const std::unordered_map<std::string, std::string>& options_map) const;
// REQUIRES: DB mutex held
// Call after the options from SetOptions() are in the current SuperVersion
// and every other user of them is updated.
void MarkOptionsInstalled() { options_install_pending_ = false; }
InternalStats* internal_stats() { return internal_stats_.get(); }
@@ -465,8 +445,8 @@ class ColumnFamilyData {
// Check if the passed range overlap with any running compactions.
// REQUIRES: DB mutex held
bool RangeOverlapWithCompaction(const Slice& smallest_user_key,
const Slice& largest_user_key, int level,
bool range_limit_exclusive) const;
const Slice& largest_user_key,
int level) const;
// Check if the passed ranges overlap with any unflushed memtables
// (immutable or mutable).
@@ -477,8 +457,7 @@ class ColumnFamilyData {
// Thread-safe
Status RangesOverlapWithMemtables(const autovector<UserKeyRange>& ranges,
SuperVersion* super_version,
bool allow_data_in_errors, bool* overlap,
bool range_limit_exclusive);
bool allow_data_in_errors, bool* overlap);
// A flag to tell a manual compaction is to compact all levels together
// instead of a specific level.
@@ -568,15 +547,12 @@ class ColumnFamilyData {
--num_in_flight_compactions_;
}
// L0 files and pending compaction bytes stall writes only when the column
// family can be compacted automatically, which requires
// 'table_builder_supported' and no disable_auto_compactions.
static std::pair<WriteStallCondition, WriteStallCause>
GetWriteStallConditionAndCause(int num_unflushed_memtables, int num_l0_files,
uint64_t num_compaction_needed_bytes,
const MutableCFOptions& mutable_cf_options,
const ImmutableCFOptions& immutable_cf_options,
bool table_builder_supported);
GetWriteStallConditionAndCause(
int num_unflushed_memtables, int num_l0_files,
uint64_t num_compaction_needed_bytes,
const MutableCFOptions& mutable_cf_options,
const ImmutableCFOptions& immutable_cf_options);
// Recalculate some stall conditions, which are changed only during
// compaction, adding new memtable and/or recalculation of compaction score.
@@ -676,15 +652,17 @@ class ColumnFamilyData {
private:
friend class ColumnFamilySet;
ColumnFamilyData(
uint32_t id, const std::string& name, Version* dummy_versions,
Cache* table_cache, WriteBufferManager* write_buffer_manager,
const ColumnFamilyOptions& options, const ImmutableDBOptions& db_options,
const FileOptions* file_options, ColumnFamilySet* column_family_set,
BlockCacheTracer* const block_cache_tracer,
const std::shared_ptr<IOTracer>& io_tracer, const std::string& db_id,
const std::string& db_session_id, bool read_only,
bool options_are_sanitized, bool fast_sst_open = false);
ColumnFamilyData(uint32_t id, const std::string& name,
Version* dummy_versions, Cache* table_cache,
WriteBufferManager* write_buffer_manager,
const ColumnFamilyOptions& options,
const ImmutableDBOptions& db_options,
const FileOptions* file_options,
ColumnFamilySet* column_family_set,
BlockCacheTracer* const block_cache_tracer,
const std::shared_ptr<IOTracer>& io_tracer,
const std::string& db_id, const std::string& db_session_id,
bool read_only, bool fast_sst_open = false);
std::vector<std::string> GetDbPaths() const;
@@ -714,12 +692,6 @@ class ColumnFamilyData {
MutableCFOptions mutable_cf_options_;
const bool is_delete_range_supported_;
const bool is_table_builder_supported_;
// True from a successful SetOptions() until MarkOptionsInstalled(). A
// DB::SetOptions() call that fails on a later column family leaves it set
// for the column families it already changed.
bool options_install_pending_ = false;
std::unique_ptr<TableCache> table_cache_;
std::unique_ptr<BlobFileCache> blob_file_cache_;
@@ -876,8 +848,7 @@ class ColumnFamilySet {
ColumnFamilyData* CreateColumnFamily(const std::string& name, uint32_t id,
Version* dummy_version,
const ColumnFamilyOptions& options,
bool read_only,
bool options_are_sanitized);
bool read_only);
const UnorderedMap<uint32_t, size_t>& GetRunningColumnFamiliesTimestampSize()
const {
@@ -899,19 +870,6 @@ class ColumnFamilySet {
WriteBufferManager* write_buffer_manager() { return write_buffer_manager_; }
void SetFlushInitiator(FlushInitiator* flush_initiator) {
flush_initiator_ = flush_initiator;
}
FlushInitiator* flush_initiator() const { return flush_initiator_; }
void RefreshFlushableMemAccounting();
// Coalesces accounting rebuilds while a caller removes multiple column
// families. Both methods require the DB mutex.
void BeginBulkFlushableMemAccountingUpdate();
void EndBulkFlushableMemAccountingUpdate();
WriteController* write_controller() { return write_controller_; }
private:
@@ -939,9 +897,6 @@ class ColumnFamilySet {
// column families with non-zero user-defined timestamp size.
UnorderedMap<uint32_t, size_t> ts_sz_for_record_;
int bulk_flushable_mem_accounting_depth_ = 0;
bool bulk_flushable_mem_accounting_dirty_ = false;
uint32_t max_column_family_;
const FileOptions file_options_;
@@ -956,7 +911,6 @@ class ColumnFamilySet {
const ImmutableDBOptions* const db_options_;
Cache* table_cache_;
WriteBufferManager* write_buffer_manager_;
FlushInitiator* flush_initiator_ = nullptr;
WriteController* write_controller_;
BlockCacheTracer* const block_cache_tracer_;
std::shared_ptr<IOTracer> io_tracer_;
@@ -978,11 +932,6 @@ class RefedColumnFamilySet {
explicit iterator(ColumnFamilySet::iterator wrapped) : wrapped_(wrapped) {
MaybeRef(*wrapped_);
}
// No copying allowed
iterator(const iterator&) = delete;
iterator& operator=(const iterator&) = delete;
iterator(iterator&&) = delete;
iterator& operator=(iterator&&) = delete;
~iterator() { MaybeUnref(*wrapped_); }
inline void MaybeRef(ColumnFamilyData* cfd) {
if (cfd->GetID() != ColumnFamilyData::kDummyColumnFamilyDataId) {
+39 -565
View File
@@ -25,7 +25,6 @@
#include "rocksdb/iterator.h"
#include "rocksdb/listener.h"
#include "rocksdb/utilities/object_registry.h"
#include "rocksdb/utilities/options_util.h"
#include "test_util/sync_point.h"
#include "test_util/testharness.h"
#include "test_util/testutil.h"
@@ -33,7 +32,6 @@
#include "util/defer.h"
#include "util/string_util.h"
#include "utilities/fault_injection_env.h"
#include "utilities/fault_injection_fs.h"
#include "utilities/merge_operators.h"
namespace ROCKSDB_NAMESPACE {
@@ -115,8 +113,7 @@ class ColumnFamilyTestBase : public testing::Test {
}
void Build(int base, int n, int flush_every = 0) {
std::string key_space;
std::string value_space;
std::string key_space, value_space;
WriteBatch batch;
for (int i = 0; i < n; i++) {
@@ -182,35 +179,6 @@ class ColumnFamilyTestBase : public testing::Test {
db_.reset();
}
void AssertColumnFamilyNames(const std::vector<std::string>& expected) {
std::vector<std::string> column_families;
ASSERT_OK(DB::ListColumnFamilies(db_options_, dbname_, &column_families));
ASSERT_EQ(expected, column_families);
}
void LoadLatestColumnFamilyDescriptors(
std::vector<ColumnFamilyDescriptor>* descriptors) {
ConfigOptions config_options;
config_options.env = db_options_.env;
DBOptions loaded_db_options;
ASSERT_OK(LoadLatestOptions(config_options, dbname_, &loaded_db_options,
descriptors));
}
size_t CountOptionsFiles() {
std::vector<std::string> children;
EXPECT_OK(db_options_.env->GetChildren(dbname_, &children));
size_t count = 0;
for (const auto& child : children) {
uint64_t number = 0;
FileType type;
if (ParseFileName(child, &number, &type) && type == kOptionsFile) {
++count;
}
}
return count;
}
Status TryOpen(std::vector<std::string> cf,
std::vector<ColumnFamilyOptions> options = {}) {
std::vector<ColumnFamilyDescriptor> column_families;
@@ -468,6 +436,7 @@ class ColumnFamilyTestBase : public testing::Test {
}
}
return ret;
return 0;
}
void AssertCountLiveLogFiles(int value) {
@@ -545,9 +514,8 @@ class ColumnFamilyTest
INSTANTIATE_TEST_CASE_P(FormatDef, ColumnFamilyTest,
testing::Values(test::kDefaultFormatVersion));
INSTANTIATE_TEST_CASE_P(
FormatLatest, ColumnFamilyTest,
testing::Values(BlockBasedTableOptions::kLatestSupportedFormatVersion));
INSTANTIATE_TEST_CASE_P(FormatLatest, ColumnFamilyTest,
testing::Values(kLatestBbtFormatVersion));
TEST_P(ColumnFamilyTest, DontReuseColumnFamilyID) {
for (int iter = 0; iter < 3; ++iter) {
@@ -737,11 +705,8 @@ INSTANTIATE_TEST_CASE_P(
std::make_tuple(test::kDefaultFormatVersion, false)));
INSTANTIATE_TEST_CASE_P(
FormatLatest, FlushEmptyCFTestWithParam,
testing::Values(
std::make_tuple(BlockBasedTableOptions::kLatestSupportedFormatVersion,
true),
std::make_tuple(BlockBasedTableOptions::kLatestSupportedFormatVersion,
false)));
testing::Values(std::make_tuple(kLatestBbtFormatVersion, true),
std::make_tuple(kLatestBbtFormatVersion, false)));
TEST_P(ColumnFamilyTest, AddDrop) {
Open();
@@ -805,32 +770,10 @@ TEST_P(ColumnFamilyTest, EmptyNameRejected) {
ASSERT_TRUE(handles.empty());
}
TEST_P(ColumnFamilyTest, CreateRejectsOptionsThatOpenRejects) {
// CreateColumnFamily() used to skip the table factory checks of DB::Open, so
// it persisted a column family that made the next DB::Open fail.
Open();
ColumnFamilyOptions hash_index_options = column_family_options_;
BlockBasedTableOptions table_options;
table_options.index_type = BlockBasedTableOptions::kHashSearch;
hash_index_options.table_factory.reset(
NewBlockBasedTableFactory(table_options));
hash_index_options.prefix_extractor.reset();
ColumnFamilyHandle* handle = nullptr;
Status s = db_->CreateColumnFamily(hash_index_options, "hash_index", &handle);
ASSERT_TRUE(s.IsInvalidArgument()) << s.ToString();
ASSERT_EQ(handle, nullptr);
Close();
AssertColumnFamilyNames({kDefaultColumnFamilyName});
ASSERT_OK(TryOpen({kDefaultColumnFamilyName}));
}
TEST_P(ColumnFamilyTest, BulkAddDrop) {
constexpr int kNumCF = 1000;
ColumnFamilyOptions cf_options;
WriteOptions write_options;
db_options_.write_buffer_manager = std::make_shared<WriteBufferManager>(
1 << 30, nullptr, false, WriteBufferFlushPolicy::kFlushLargestAcrossDBs);
Open();
std::vector<std::string> cf_names;
std::vector<ColumnFamilyHandle*> cf_handles;
@@ -841,22 +784,7 @@ TEST_P(ColumnFamilyTest, BulkAddDrop) {
for (int i = 1; i <= kNumCF; i++) {
ASSERT_OK(db_->Put(write_options, cf_handles[i - 1], "foo", "bar"));
}
std::atomic<int> per_removal_refreshes{0};
std::atomic<int> bulk_refreshes{0};
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"ColumnFamilySet::RemoveColumnFamily:Refresh",
[&](void*) { ++per_removal_refreshes; });
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"ColumnFamilySet::EndBulkFlushableMemAccountingUpdate:Refresh",
[&](void*) { ++bulk_refreshes; });
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
Defer cleanup_sync_points([] {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
});
ASSERT_OK(db_->DropColumnFamilies(cf_handles));
EXPECT_EQ(0, per_removal_refreshes.load());
EXPECT_EQ(1, bulk_refreshes.load());
std::vector<ColumnFamilyDescriptor> cf_descriptors;
for (auto* handle : cf_handles) {
delete handle;
@@ -870,10 +798,7 @@ TEST_P(ColumnFamilyTest, BulkAddDrop) {
for (int i = 1; i <= kNumCF; i++) {
ASSERT_OK(db_->Put(write_options, cf_handles[i - 1], "foo", "bar"));
}
bulk_refreshes.store(0);
ASSERT_OK(db_->DropColumnFamilies(cf_handles));
EXPECT_EQ(0, per_removal_refreshes.load());
EXPECT_EQ(1, bulk_refreshes.load());
for (auto* handle : cf_handles) {
delete handle;
}
@@ -972,9 +897,7 @@ TEST_P(ColumnFamilyTest, IgnoreRecoveredLog) {
CreateColumnFamilies({"cf1", "cf2"});
// fill up the DB
std::string one;
std::string two;
std::string three;
std::string one, two, three;
PutFixed64(&one, 1);
PutFixed64(&two, 2);
PutFixed64(&three, 3);
@@ -1182,10 +1105,7 @@ TEST_P(ColumnFamilyTest, DifferentWriteBufferSizes) {
db_options_.max_total_wal_size = std::numeric_limits<uint64_t>::max();
Open();
CreateColumnFamilies({"one", "two", "three"});
ColumnFamilyOptions default_cf;
ColumnFamilyOptions one;
ColumnFamilyOptions two;
ColumnFamilyOptions three;
ColumnFamilyOptions default_cf, one, two, three;
// setup options. all column families have max_write_buffer_number setup to 10
// "default" -> 100KB memtable, start flushing immediately
// "one" -> 200KB memtable, start flushing with two immutable memtables
@@ -1337,9 +1257,7 @@ TEST_P(ColumnFamilyTest, GetComparator) {
// Add three column families - one with no comparator and two
// with comparators specified
ColumnFamilyOptions second;
ColumnFamilyOptions third;
ColumnFamilyOptions fourth;
ColumnFamilyOptions second, third, fourth;
second.comparator = &third_comparator;
third.comparator = &fourth_comparator;
CreateColumnFamilies({"second", "third", "fourth"}, {second, third, fourth});
@@ -1352,16 +1270,12 @@ TEST_P(ColumnFamilyTest, GetComparator) {
TEST_P(ColumnFamilyTest, DifferentMergeOperators) {
Open();
CreateColumnFamilies({"first", "second"});
ColumnFamilyOptions default_cf;
ColumnFamilyOptions first;
ColumnFamilyOptions second;
ColumnFamilyOptions default_cf, first, second;
first.merge_operator = MergeOperators::CreateUInt64AddOperator();
second.merge_operator = MergeOperators::CreateStringAppendOperator();
Reopen({default_cf, first, second});
std::string one;
std::string two;
std::string three;
std::string one, two, three;
PutFixed64(&one, 1);
PutFixed64(&two, 2);
PutFixed64(&three, 3);
@@ -1386,9 +1300,7 @@ TEST_P(ColumnFamilyTest, DifferentMergeOperators) {
TEST_P(ColumnFamilyTest, DifferentCompactionStyles) {
Open();
CreateColumnFamilies({"one", "two"});
ColumnFamilyOptions default_cf;
ColumnFamilyOptions one;
ColumnFamilyOptions two;
ColumnFamilyOptions default_cf, one, two;
db_options_.max_open_files = 20; // only 10 files in file cache
default_cf.level_compaction_dynamic_level_bytes = false;
@@ -1460,9 +1372,7 @@ TEST_P(ColumnFamilyTest, DifferentCompactionStyles) {
TEST_P(ColumnFamilyTest, MultipleManualCompactions) {
Open();
CreateColumnFamilies({"one", "two"});
ColumnFamilyOptions default_cf;
ColumnFamilyOptions one;
ColumnFamilyOptions two;
ColumnFamilyOptions default_cf, one, two;
db_options_.max_open_files = 20; // only 10 files in file cache
db_options_.max_background_compactions = 3;
@@ -1562,9 +1472,7 @@ TEST_P(ColumnFamilyTest, MultipleManualCompactions) {
TEST_P(ColumnFamilyTest, AutomaticAndManualCompactions) {
Open();
CreateColumnFamilies({"one", "two"});
ColumnFamilyOptions default_cf;
ColumnFamilyOptions one;
ColumnFamilyOptions two;
ColumnFamilyOptions default_cf, one, two;
db_options_.max_open_files = 20; // only 10 files in file cache
db_options_.max_background_compactions = 3;
@@ -1661,9 +1569,7 @@ TEST_P(ColumnFamilyTest, AutomaticAndManualCompactions) {
TEST_P(ColumnFamilyTest, ManualAndAutomaticCompactions) {
Open();
CreateColumnFamilies({"one", "two"});
ColumnFamilyOptions default_cf;
ColumnFamilyOptions one;
ColumnFamilyOptions two;
ColumnFamilyOptions default_cf, one, two;
db_options_.max_open_files = 20; // only 10 files in file cache
db_options_.max_background_compactions = 3;
@@ -1762,8 +1668,7 @@ TEST_P(ColumnFamilyTest, ManualAndAutomaticCompactions) {
TEST_P(ColumnFamilyTest, SameCFManualManualCompactions) {
Open();
CreateColumnFamilies({"one"});
ColumnFamilyOptions default_cf;
ColumnFamilyOptions one;
ColumnFamilyOptions default_cf, one;
db_options_.max_open_files = 20; // only 10 files in file cache
db_options_.max_background_compactions = 3;
@@ -1863,8 +1768,7 @@ TEST_P(ColumnFamilyTest, SameCFManualManualCompactions) {
TEST_P(ColumnFamilyTest, SameCFManualAutomaticCompactions) {
Open();
CreateColumnFamilies({"one"});
ColumnFamilyOptions default_cf;
ColumnFamilyOptions one;
ColumnFamilyOptions default_cf, one;
db_options_.max_open_files = 20; // only 10 files in file cache
db_options_.max_background_compactions = 3;
@@ -1955,8 +1859,7 @@ TEST_P(ColumnFamilyTest, SameCFManualAutomaticCompactions) {
TEST_P(ColumnFamilyTest, SameCFManualAutomaticCompactionsLevel) {
Open();
CreateColumnFamilies({"one"});
ColumnFamilyOptions default_cf;
ColumnFamilyOptions one;
ColumnFamilyOptions default_cf, one;
db_options_.max_open_files = 20; // only 10 files in file cache
db_options_.max_background_compactions = 3;
@@ -2053,8 +1956,7 @@ TEST_P(ColumnFamilyTest, SameCFManualAutomaticCompactionsLevel) {
TEST_P(ColumnFamilyTest, SameCFAutomaticManualCompactions) {
Open();
CreateColumnFamilies({"one"});
ColumnFamilyOptions default_cf;
ColumnFamilyOptions one;
ColumnFamilyOptions default_cf, one;
db_options_.max_open_files = 20; // only 10 files in file cache
db_options_.max_background_compactions = 3;
@@ -2267,9 +2169,7 @@ TEST_P(ColumnFamilyTest, DontRollEmptyLogs) {
TEST_P(ColumnFamilyTest, FlushStaleColumnFamilies) {
Open();
CreateColumnFamilies({"one", "two"});
ColumnFamilyOptions default_cf;
ColumnFamilyOptions one;
ColumnFamilyOptions two;
ColumnFamilyOptions default_cf, one, two;
default_cf.write_buffer_size = 100000; // small write buffer size
default_cf.arena_block_size = 4096;
default_cf.disable_auto_compactions = true;
@@ -2330,378 +2230,8 @@ struct CountOptionsFilesFs : public FileSystemWrapper {
std::atomic<int> options_files_created{};
};
struct FailOptionsFilesFs : public FileSystemWrapper {
explicit FailOptionsFilesFs(const std::shared_ptr<FileSystem>& t)
: FileSystemWrapper(t) {}
const char* Name() const override { return "FailOptionsFilesFs"; }
IOStatus NewWritableFile(const std::string& f, const FileOptions& file_opts,
std::unique_ptr<FSWritableFile>* r,
IODebugContext* dbg) override {
if (fail_options_writes.load(std::memory_order_relaxed) &&
f.find("OPTIONS-") != std::string::npos) {
return IOStatus::IOError("injected OPTIONS-file creation failure");
}
return FileSystemWrapper::NewWritableFile(f, file_opts, r, dbg);
}
std::atomic<bool> fail_options_writes{false};
};
} // namespace
TEST_P(ColumnFamilyTest, TrackedOptionsWriteFailureDoesNotChangeManifest) {
db_options_.track_options_file_number_in_manifest = true;
db_options_.max_manifest_file_size = 1;
db_options_.max_manifest_space_amp_pct = 0;
auto fail_fs =
std::make_shared<FailOptionsFilesFs>(db_options_.env->GetFileSystem());
auto fail_env =
std::make_unique<CompositeEnvWrapper>(db_options_.env, fail_fs);
SaveAndRestore<Env*> save_restore_env(&db_options_.env, fail_env.get());
Open();
OptionsFileManifestState before_failure;
ASSERT_OK(VersionSet::GetOptionsFileManifestState(
dbname_, db_options_.env->GetFileSystem().get(), &before_failure));
fail_fs->fail_options_writes.store(true, std::memory_order_relaxed);
ColumnFamilyHandle* handle = nullptr;
ASSERT_NOK(db_->CreateColumnFamily(ColumnFamilyOptions(), "one", &handle));
ASSERT_EQ(nullptr, handle);
fail_fs->fail_options_writes.store(false, std::memory_order_relaxed);
OptionsFileManifestState after_failure;
ASSERT_OK(VersionSet::GetOptionsFileManifestState(
dbname_, db_options_.env->GetFileSystem().get(), &after_failure));
ASSERT_EQ(before_failure.has_committed_options_file_number,
after_failure.has_committed_options_file_number);
ASSERT_EQ(before_failure.committed_options_file_number,
after_failure.committed_options_file_number);
std::vector<ColumnFamilyDescriptor> loaded_cfs;
LoadLatestColumnFamilyDescriptors(&loaded_cfs);
ASSERT_EQ(1U, loaded_cfs.size());
ASSERT_EQ(kDefaultColumnFamilyName, loaded_cfs.front().name);
const uint64_t old_manifest_number =
dbfull()->GetVersionSet()->manifest_file_number();
ASSERT_OK(db_->Put(WriteOptions(), "key", "value"));
ASSERT_OK(db_->Flush(FlushOptions()));
ASSERT_NE(old_manifest_number,
dbfull()->GetVersionSet()->manifest_file_number());
OptionsFileManifestState after_rotation;
ASSERT_OK(VersionSet::GetOptionsFileManifestState(
dbname_, db_options_.env->GetFileSystem().get(), &after_rotation));
ASSERT_EQ(before_failure.has_committed_options_file_number,
after_rotation.has_committed_options_file_number);
ASSERT_EQ(before_failure.committed_options_file_number,
after_rotation.committed_options_file_number);
Close();
AssertColumnFamilyNames({kDefaultColumnFamilyName});
}
TEST_P(ColumnFamilyTest, TrackedDropFailsBeforeManifestOnOptionsError) {
db_options_.track_options_file_number_in_manifest = true;
auto fail_fs =
std::make_shared<FailOptionsFilesFs>(db_options_.env->GetFileSystem());
auto fail_env =
std::make_unique<CompositeEnvWrapper>(db_options_.env, fail_fs);
SaveAndRestore<Env*> save_restore_env(&db_options_.env, fail_env.get());
Open();
ColumnFamilyHandle* handle = nullptr;
ASSERT_OK(db_->CreateColumnFamily(ColumnFamilyOptions(), "one", &handle));
ASSERT_NE(nullptr, handle);
fail_fs->fail_options_writes.store(true, std::memory_order_relaxed);
ASSERT_NOK(db_->DropColumnFamily(handle));
fail_fs->fail_options_writes.store(false, std::memory_order_relaxed);
ASSERT_OK(db_->DestroyColumnFamilyHandle(handle));
Close();
AssertColumnFamilyNames({kDefaultColumnFamilyName, "one"});
}
TEST_P(ColumnFamilyTest, TrackedManifestFailureKeepsPreviousOptions) {
db_options_.track_options_file_number_in_manifest = true;
auto fault_fs =
std::make_shared<FaultInjectionTestFS>(db_options_.env->GetFileSystem());
std::unique_ptr<Env> fault_env(NewCompositeEnv(fault_fs));
SaveAndRestore<Env*> save_restore_env(&db_options_.env, fault_env.get());
Open();
std::string selected_options_before;
ASSERT_OK(GetLatestOptionsFileName(dbname_, db_options_.env,
&selected_options_before));
std::atomic<bool> inject_error{true};
SyncPoint::GetInstance()->SetCallBack(
"VersionSet::LogAndApply:WriteManifest", [&](void*) {
if (inject_error.exchange(false)) {
fault_fs->SetThreadLocalErrorContext(
FaultInjectionIOType::kWrite, /*seed=*/0, /*one_in=*/1,
/*retryable=*/false, /*has_data_loss=*/false);
fault_fs->EnableThreadLocalErrorInjection(
FaultInjectionIOType::kWrite);
}
});
SyncPoint::GetInstance()->EnableProcessing();
ColumnFamilyHandle* handle = nullptr;
ASSERT_NOK(db_->CreateColumnFamily(ColumnFamilyOptions(), "one", &handle));
ASSERT_EQ(nullptr, handle);
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
fault_fs->DisableThreadLocalErrorInjection(FaultInjectionIOType::kWrite);
std::string selected_options_after;
ASSERT_OK(GetLatestOptionsFileName(dbname_, db_options_.env,
&selected_options_after));
ASSERT_EQ(selected_options_before, selected_options_after);
Close();
AssertColumnFamilyNames({kDefaultColumnFamilyName});
}
TEST_P(ColumnFamilyTest, FirstTrackedCreateFailureIgnoresUncommittedOptions) {
// Create a legacy DB without a committed number, then enable tracking while
// Open also
// needs to create a missing CF. The current topology must be bound before
// the proposed topology OPTIONS file can become a durable orphan.
Open();
Close();
db_options_.track_options_file_number_in_manifest = true;
db_options_.create_missing_column_families = true;
auto fault_fs =
std::make_shared<FaultInjectionTestFS>(db_options_.env->GetFileSystem());
std::unique_ptr<Env> fault_env(NewCompositeEnv(fault_fs));
SaveAndRestore<Env*> save_restore_env(&db_options_.env, fault_env.get());
bool injected = false;
SyncPoint::GetInstance()->SetCallBack(
"DBImpl::CreateColumnFamilyImpl:AfterPersistOptions", [&](void*) {
if (!injected) {
injected = true;
fault_fs->SetThreadLocalErrorContext(
FaultInjectionIOType::kWrite, /*seed=*/0, /*one_in=*/1,
/*retryable=*/false, /*has_data_loss=*/false);
fault_fs->EnableThreadLocalErrorInjection(
FaultInjectionIOType::kWrite);
}
});
SyncPoint::GetInstance()->EnableProcessing();
ASSERT_NOK(TryOpen({kDefaultColumnFamilyName, "one"}));
ASSERT_TRUE(injected);
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
fault_fs->DisableThreadLocalErrorInjection(FaultInjectionIOType::kWrite);
std::vector<ColumnFamilyDescriptor> loaded_cfs;
LoadLatestColumnFamilyDescriptors(&loaded_cfs);
ASSERT_EQ(1U, loaded_cfs.size());
ASSERT_EQ(kDefaultColumnFamilyName, loaded_cfs.front().name);
OptionsFileManifestState protocol_state;
ASSERT_OK(VersionSet::GetOptionsFileManifestState(
dbname_, db_options_.env->GetFileSystem().get(), &protocol_state));
ASSERT_TRUE(protocol_state.has_committed_options_file_number);
std::string selected_options;
ASSERT_OK(
GetLatestOptionsFileName(dbname_, db_options_.env, &selected_options));
ASSERT_EQ(OptionsFileName(protocol_state.committed_options_file_number),
selected_options);
AssertColumnFamilyNames({kDefaultColumnFamilyName});
}
TEST_P(ColumnFamilyTest, TrackedCreatePersistsCommittedOptions) {
db_options_.track_options_file_number_in_manifest = true;
Open();
ColumnFamilyOptions requested;
requested.arena_block_size = 0;
ColumnFamilyHandle* handle = nullptr;
ASSERT_OK(db_->CreateColumnFamily(requested, "one", &handle));
const ColumnFamilyOptions effective = db_->GetOptions(handle);
ASSERT_GT(effective.arena_block_size, 0U);
std::vector<ColumnFamilyDescriptor> loaded_cfs;
LoadLatestColumnFamilyDescriptors(&loaded_cfs);
ASSERT_EQ(2U, loaded_cfs.size());
ASSERT_EQ("one", loaded_cfs[1].name);
ASSERT_EQ(effective.arena_block_size, loaded_cfs[1].options.arena_block_size);
ASSERT_OK(db_->DestroyColumnFamilyHandle(handle));
}
TEST_P(ColumnFamilyTest, TrackedBatchTopologyWritesOneOptionsSnapshot) {
db_options_.track_options_file_number_in_manifest = true;
auto count_fs =
std::make_shared<CountOptionsFilesFs>(db_options_.env->GetFileSystem());
auto count_env =
std::make_unique<CompositeEnvWrapper>(db_options_.env, count_fs);
SaveAndRestore<Env*> save_restore_env(&db_options_.env, count_env.get());
Open();
count_fs->options_files_created.store(0, std::memory_order_relaxed);
std::vector<ColumnFamilyHandle*> handles;
ASSERT_OK(db_->CreateColumnFamilies(ColumnFamilyOptions(),
{"one", "two", "three"}, &handles));
ASSERT_EQ(3U, handles.size());
ASSERT_EQ(1, count_fs->options_files_created.load(std::memory_order_relaxed));
count_fs->options_files_created.store(0, std::memory_order_relaxed);
ASSERT_OK(db_->DropColumnFamilies(handles));
ASSERT_EQ(1, count_fs->options_files_created.load(std::memory_order_relaxed));
for (auto* handle : handles) {
ASSERT_OK(db_->DestroyColumnFamilyHandle(handle));
}
std::vector<ColumnFamilyDescriptor> loaded_cfs;
LoadLatestColumnFamilyDescriptors(&loaded_cfs);
ASSERT_EQ(1U, loaded_cfs.size());
ASSERT_EQ(kDefaultColumnFamilyName, loaded_cfs.front().name);
Close();
}
TEST_P(ColumnFamilyTest, TrackedPartialBatchFiltersOptionsSuperset) {
db_options_.track_options_file_number_in_manifest = true;
auto fault_fs =
std::make_shared<FaultInjectionTestFS>(db_options_.env->GetFileSystem());
std::unique_ptr<Env> fault_env(NewCompositeEnv(fault_fs));
SaveAndRestore<Env*> save_restore_env(&db_options_.env, fault_env.get());
Open();
std::atomic<int> manifest_writes{0};
SyncPoint::GetInstance()->SetCallBack(
"VersionSet::LogAndApply:WriteManifest", [&](void*) {
if (manifest_writes.fetch_add(1, std::memory_order_relaxed) == 1) {
fault_fs->SetThreadLocalErrorContext(
FaultInjectionIOType::kWrite, /*seed=*/0, /*one_in=*/1,
/*retryable=*/false, /*has_data_loss=*/false);
fault_fs->EnableThreadLocalErrorInjection(
FaultInjectionIOType::kWrite);
}
});
SyncPoint::GetInstance()->EnableProcessing();
std::vector<ColumnFamilyHandle*> handles;
ASSERT_NOK(db_->CreateColumnFamilies(ColumnFamilyOptions(), {"one", "two"},
&handles));
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
fault_fs->DisableThreadLocalErrorInjection(FaultInjectionIOType::kWrite);
ASSERT_EQ(1U, handles.size());
std::vector<ColumnFamilyDescriptor> loaded_cfs;
LoadLatestColumnFamilyDescriptors(&loaded_cfs);
ASSERT_EQ(2U, loaded_cfs.size());
ASSERT_EQ(kDefaultColumnFamilyName, loaded_cfs[0].name);
ASSERT_EQ("one", loaded_cfs[1].name);
ConfigOptions config_options;
config_options.env = db_options_.env;
DBOptions loaded_db_options;
ASSERT_OK(LoadLatestOptions(config_options, dbname_, &loaded_db_options,
&loaded_cfs));
ASSERT_OK(CheckOptionsCompatibility(config_options, dbname_,
loaded_db_options, loaded_cfs));
ASSERT_OK(db_->DestroyColumnFamilyHandle(handles.front()));
Close();
}
TEST_P(ColumnFamilyTest, TrackedPartialBatchDropFiltersOptionsSuperset) {
db_options_.track_options_file_number_in_manifest = true;
auto fault_fs =
std::make_shared<FaultInjectionTestFS>(db_options_.env->GetFileSystem());
std::unique_ptr<Env> fault_env(NewCompositeEnv(fault_fs));
SaveAndRestore<Env*> save_restore_env(&db_options_.env, fault_env.get());
Open();
std::vector<ColumnFamilyHandle*> handles;
ASSERT_OK(db_->CreateColumnFamilies(ColumnFamilyOptions(), {"one", "two"},
&handles));
ASSERT_EQ(2U, handles.size());
std::atomic<int> manifest_writes{0};
SyncPoint::GetInstance()->SetCallBack(
"VersionSet::LogAndApply:WriteManifest", [&](void*) {
if (manifest_writes.fetch_add(1, std::memory_order_relaxed) == 1) {
fault_fs->SetThreadLocalErrorContext(
FaultInjectionIOType::kWrite, /*seed=*/0, /*one_in=*/1,
/*retryable=*/false, /*has_data_loss=*/false);
fault_fs->EnableThreadLocalErrorInjection(
FaultInjectionIOType::kWrite);
}
});
SyncPoint::GetInstance()->EnableProcessing();
ASSERT_NOK(db_->DropColumnFamilies(handles));
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
fault_fs->DisableThreadLocalErrorInjection(FaultInjectionIOType::kWrite);
std::vector<ColumnFamilyDescriptor> loaded_cfs;
LoadLatestColumnFamilyDescriptors(&loaded_cfs);
ASSERT_EQ(2U, loaded_cfs.size());
ASSERT_EQ(kDefaultColumnFamilyName, loaded_cfs[0].name);
ASSERT_EQ("two", loaded_cfs[1].name);
for (auto* handle : handles) {
ASSERT_OK(db_->DestroyColumnFamilyHandle(handle));
}
Close();
}
TEST_P(ColumnFamilyTest, InvalidTrackedTopologyChangeCreatesNoOptionsOrphan) {
db_options_.track_options_file_number_in_manifest = true;
Open();
ColumnFamilyHandle* handle = nullptr;
ASSERT_OK(db_->CreateColumnFamily(ColumnFamilyOptions(), "one", &handle));
const size_t before_duplicate_create = CountOptionsFiles();
ColumnFamilyHandle* duplicate = nullptr;
ASSERT_TRUE(db_->CreateColumnFamily(ColumnFamilyOptions(), "one", &duplicate)
.IsInvalidArgument());
ASSERT_EQ(nullptr, duplicate);
ASSERT_EQ(before_duplicate_create, CountOptionsFiles());
ASSERT_OK(db_->DropColumnFamily(handle));
const size_t before_duplicate_drop = CountOptionsFiles();
ASSERT_TRUE(db_->DropColumnFamily(handle).IsInvalidArgument());
ASSERT_EQ(before_duplicate_drop, CountOptionsFiles());
ASSERT_OK(db_->DestroyColumnFamilyHandle(handle));
}
TEST_P(ColumnFamilyTest,
PartialMultiDropConsumesLegacyOptionsPersistenceFailure) {
auto fail_fs =
std::make_shared<FailOptionsFilesFs>(db_options_.env->GetFileSystem());
auto fail_env =
std::make_unique<CompositeEnvWrapper>(db_options_.env, fail_fs);
SaveAndRestore<Env*> save_restore_env(&db_options_.env, fail_env.get());
Open();
ColumnFamilyHandle* handle = nullptr;
ASSERT_OK(db_->CreateColumnFamily(ColumnFamilyOptions(), "one", &handle));
fail_fs->fail_options_writes.store(true, std::memory_order_relaxed);
Status s = db_->DropColumnFamilies({handle, db_->DefaultColumnFamily()});
ASSERT_TRUE(s.IsInvalidArgument());
fail_fs->fail_options_writes.store(false, std::memory_order_relaxed);
ASSERT_OK(db_->DestroyColumnFamilyHandle(handle));
Close();
AssertColumnFamilyNames({kDefaultColumnFamilyName});
}
TEST_P(ColumnFamilyTest, CreateMissingColumnFamilies) {
// Can't accidentally add CFs to an existing DB
Open();
@@ -2734,16 +2264,6 @@ TEST_P(ColumnFamilyTest, CreateMissingColumnFamilies) {
ASSERT_OK(TryOpen({"default", "one", "two", "three", "four"}));
Close();
ASSERT_EQ(my_fs->options_files_created.load(), 2);
Destroy();
db_options_.track_options_file_number_in_manifest = true;
my_fs->options_files_created.store(0, std::memory_order_relaxed);
ASSERT_OK(TryOpen({"default", "one", "two", "three", "four"}));
Close();
// A new tracked DB writes its initial snapshot, one proposed batch snapshot,
// and the normal final DB::Open snapshot, independent of the number of
// missing CFs.
ASSERT_EQ(my_fs->options_files_created.load(), 3);
}
TEST_P(ColumnFamilyTest, SanitizeCfOptions) {
@@ -3647,32 +3167,6 @@ TEST_P(ColumnFamilyTest, CreateDropAndDestroy) {
ASSERT_OK(db_->DestroyColumnFamilyHandle(cfh));
}
TEST_P(ColumnFamilyTest, DropWaitsForUnorderedMemtableWrite) {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
{{"DBImpl::WriteImpl:UnorderedWriteAfterWriteWAL",
"ColumnFamilyTest::DropWaitsForUnorderedMemtableWrite:AfterWAL"},
{"DBImpl::WaitForPendingWrites:BeforeBlock",
"DBImpl::WriteImpl:BeforeUnorderedWriteMemtable"}});
db_options_.unordered_write = true;
Open();
ColumnFamilyHandle* cfh;
ASSERT_OK(db_->CreateColumnFamily(ColumnFamilyOptions(), "yoyo", &cfh));
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
port::Thread writer(
[&] { ASSERT_OK(db_->Put(WriteOptions(), cfh, "foo", "bar")); });
TEST_SYNC_POINT(
"ColumnFamilyTest::DropWaitsForUnorderedMemtableWrite:AfterWAL");
ASSERT_OK(db_->DropColumnFamily(cfh));
writer.join();
ASSERT_OK(db_->DestroyColumnFamilyHandle(cfh));
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
}
TEST_P(ColumnFamilyTest, CreateDropAndDestroyWithoutFileDeletion) {
ColumnFamilyHandle* cfh;
Open();
@@ -3731,12 +3225,6 @@ TEST_P(ColumnFamilyTest, FlushCloseWALFiles) {
TEST_P(ColumnFamilyTest, IteratorCloseWALFile1) {
SpecialEnv env(Env::Default());
db_options_.env = &env;
// The DB keeps its Env pointer until Close(). Close it before the local Env
// is destroyed, including when an ASSERT below exits the test early.
Defer close_db_on_exit([this] {
Close();
db_options_.env = env_;
});
db_options_.max_background_flushes = 1;
// When this option is removed, the test will need re-engineering
db_options_.background_close_inactive_wals = true;
@@ -3746,38 +3234,25 @@ TEST_P(ColumnFamilyTest, IteratorCloseWALFile1) {
CreateColumnFamilies({"one"});
ASSERT_OK(Put(1, "fodor", "mirko"));
// Create an iterator holding the current super version.
std::unique_ptr<Iterator> it(db_->NewIterator(ReadOptions(), handles_[1]));
Iterator* it = db_->NewIterator(ReadOptions(), handles_[1]);
ASSERT_OK(it->status());
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({
{"DBImpl::BGWorkFlush:done",
"ColumnFamilyTest::IteratorCloseWALFile1:SetupFlushDone"},
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
Defer disable_sync_points(
[] { ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); });
// A flush will make `it` hold the last reference of its super version.
ASSERT_OK(Flush(1));
// Flush() can return before the background function releases all temporary
// references to the old super version.
TEST_SYNC_POINT("ColumnFamilyTest::IteratorCloseWALFile1:SetupFlushDone");
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({
{"ColumnFamilyTest::IteratorCloseWALFile1:ReleaseFlush",
"DBImpl::BackgroundCallFlush:start"},
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
ASSERT_OK(Put(1, "fodor", "mirko"));
ASSERT_OK(Put(0, "fodor", "mirko"));
ASSERT_OK(Put(1, "fodor", "mirko"));
// Flush jobs close previous WAL files after finishing. Keep the next flush
// from starting so the iterator destructor is responsible for closing the
// WAL. Occupying one Env worker is insufficient because the shared Env can
// have multiple HIGH-priority workers.
// Flush jobs will close previous WAL files after finishing. By
// block flush jobs from running, we trigger a condition where
// the iterator destructor should close the WAL files.
test::SleepingBackgroundTask sleeping_task;
env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task,
Env::Priority::HIGH);
// Make sure the task is sleeping. Otherwise, it might start to execute
// after sleeping_task.WaitUntilDone() and cause TSAN warning.
sleeping_task.WaitUntilSleeping();
WriteOptions wo;
wo.sync = true;
ASSERT_OK(db_->Put(wo, handles_[1], "fodor", "mirko"));
@@ -3785,15 +3260,18 @@ TEST_P(ColumnFamilyTest, IteratorCloseWALFile1) {
ASSERT_EQ(2, env.num_open_wal_file_.load());
// Deleting the iterator will clear its super version, triggering
// closing all files
it.reset();
delete it;
ASSERT_EQ(1, env.num_open_wal_file_.load());
TEST_SYNC_POINT("ColumnFamilyTest::IteratorCloseWALFile1:ReleaseFlush");
sleeping_task.WakeUp();
sleeping_task.WaitUntilDone();
WaitForFlush(1);
Reopen();
ASSERT_EQ("mirko", Get(0, "fodor"));
ASSERT_EQ("mirko", Get(1, "fodor"));
db_options_.env = env_;
Close();
}
TEST_P(ColumnFamilyTest, IteratorCloseWALFile2) {
@@ -4050,8 +3528,7 @@ TEST_P(ColumnFamilyTest, DefaultCfPathsTest) {
// Leave cf_paths for one column families to be empty.
// Files should be generated according to db_paths for that
// column family.
ColumnFamilyOptions cf_opt1;
ColumnFamilyOptions cf_opt2;
ColumnFamilyOptions cf_opt1, cf_opt2;
cf_opt1.cf_paths.emplace_back(dbname_ + "_one_1",
std::numeric_limits<uint64_t>::max());
CreateColumnFamilies({"one", "two"}, {cf_opt1, cf_opt2});
@@ -4076,8 +3553,7 @@ TEST_P(ColumnFamilyTest, DefaultCfPathsTest) {
TEST_P(ColumnFamilyTest, MultipleCFPathsTest) {
Open();
// Configure Column family specific paths.
ColumnFamilyOptions cf_opt1;
ColumnFamilyOptions cf_opt2;
ColumnFamilyOptions cf_opt1, cf_opt2;
cf_opt1.cf_paths.emplace_back(dbname_ + "_one_1",
std::numeric_limits<uint64_t>::max());
cf_opt2.cf_paths.emplace_back(dbname_ + "_two_1",
@@ -4193,9 +3669,7 @@ TEST(ColumnFamilyTest, ValidateMemtableKVChecksumOption) {
// the behavior of manual flush is that it skips retaining UDTs.
class ColumnFamilyRetainUDTTest : public ColumnFamilyTestBase {
public:
ColumnFamilyRetainUDTTest()
: ColumnFamilyTestBase(
BlockBasedTableOptions::kLatestSupportedFormatVersion) {}
ColumnFamilyRetainUDTTest() : ColumnFamilyTestBase(kLatestBbtFormatVersion) {}
void SetUp() override {
db_options_.allow_concurrent_memtable_write = false;
Loaded 100 of 909 files, more files were not shown because too many files have changed in this diff. Show more