mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 11:02:36 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e768876e5 | ||
|
|
9cbdbcfce8 | ||
|
|
a4e298049a | ||
|
|
a9bcab5e75 | ||
|
|
a24f8f8785 | ||
|
|
9d3733d9d8 | ||
|
|
8437f701ce | ||
|
|
c9fcb564f8 | ||
|
|
78eb4900c8 | ||
|
|
15951afbb5 | ||
|
|
a51c5e21ec | ||
|
|
8fa3ee8fdc | ||
|
|
d0e6a4ca27 | ||
|
|
3993edbf34 | ||
|
|
a303d02bb9 | ||
|
|
e15742470e | ||
|
|
2a2e19f51b | ||
|
|
6bc72c508b | ||
|
|
75e965d2a6 | ||
|
|
e189b2724b | ||
|
|
cba23b972b | ||
|
|
47570c7418 | ||
|
|
b07a47dd5b | ||
|
|
49a8f7d762 | ||
|
|
4500871744 | ||
|
|
b4d0865a93 | ||
|
|
5cabe9439d | ||
|
|
24282f7418 | ||
|
|
ea5db0c142 | ||
|
|
d375bbbbd0 | ||
|
|
9bfe67777a | ||
|
|
8fd33c2fac | ||
|
|
ee13e2a640 | ||
|
|
a87e2a0e43 | ||
|
|
02815ee01c | ||
|
|
563d4b14e0 | ||
|
|
7e61be9ecc | ||
|
|
a7ae6cd0a8 | ||
|
|
5f39d7b8ea | ||
|
|
fcb42ecdb2 | ||
|
|
bc11adb4ba | ||
|
|
feb6fb8377 | ||
|
|
d2d4c9b44c | ||
|
|
800b5741f2 | ||
|
|
160a3171f2 | ||
|
|
e6d1ec007f | ||
|
|
e6961ad337 | ||
|
|
7fb0d8e94a | ||
|
|
4ef117052f | ||
|
|
ce00781b64 | ||
|
|
87d442749a | ||
|
|
f216185228 | ||
|
|
f14b3508d4 | ||
|
|
92c99cb6aa | ||
|
|
42ed9b6a09 | ||
|
|
609269e0ae | ||
|
|
4750c1cce3 | ||
|
|
a12aafaf69 | ||
|
|
7e0c4d8e11 | ||
|
|
898ace2b02 | ||
|
|
b6582c84bc | ||
|
|
d27327384d | ||
|
|
0adcea5ea4 | ||
|
|
9457606301 | ||
|
|
302eaa8b1f | ||
|
|
a8a626ba8b | ||
|
|
4c38c45ac1 | ||
|
|
5d9da10d28 | ||
|
|
83747eb79f | ||
|
|
6d03b3df45 | ||
|
|
f0434b6972 | ||
|
|
0c79400bf7 | ||
|
|
935cdad34d | ||
|
|
c631d45bb6 | ||
|
|
ff044b05ae | ||
|
|
a5c1c345b2 | ||
|
|
ea31360536 | ||
|
|
e4cacf667f | ||
|
|
58d929bc85 | ||
|
|
e38f68d6ce | ||
|
|
94c7f76da0 | ||
|
|
d6a7847d37 | ||
|
|
431ce83b9b | ||
|
|
539532377d | ||
|
|
553b52b4a7 | ||
|
|
81369fc170 |
No files matched your search
@@ -1,38 +0,0 @@
|
||||
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
|
||||
@@ -2,12 +2,21 @@ name: pre-steps
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Dump ulimits for diagnostics
|
||||
- name: Dump system info 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
|
||||
@@ -15,20 +24,19 @@ 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/facebook/rocksdb/releases/edit/third-party-deps
|
||||
# https://github.com/meta/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/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"
|
||||
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"
|
||||
shell: bash
|
||||
@@ -8,7 +8,7 @@ runs:
|
||||
run: chown `whoami` . || true
|
||||
shell: bash
|
||||
- name: Set upstream
|
||||
run: git remote add upstream https://github.com/facebook/rocksdb.git
|
||||
run: git remote add upstream https://github.com/meta/rocksdb.git
|
||||
shell: bash
|
||||
- name: Fetch upstream
|
||||
run: git fetch upstream
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
// 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',
|
||||
],
|
||||
});
|
||||
};
|
||||
@@ -117,7 +117,7 @@ function assertObsoleteComment(comment, originalBody) {
|
||||
|
||||
const context = {
|
||||
repo: {
|
||||
owner: 'facebook',
|
||||
owner: 'meta',
|
||||
repo: 'rocksdb',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
# Shared analysis workflow for AI PR reviews.
|
||||
#
|
||||
# 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.
|
||||
# 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.
|
||||
|
||||
name: AI Review Analysis
|
||||
|
||||
@@ -20,7 +10,7 @@ on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
provider:
|
||||
description: Provider key, for example "claude" or "codex"
|
||||
description: Provider key, for example "claude"
|
||||
required: true
|
||||
type: string
|
||||
display_name:
|
||||
@@ -119,7 +109,7 @@ jobs:
|
||||
const FAIL_CONCLUSIONS = new Set([
|
||||
'failure', 'cancelled', 'timed_out', 'action_required'
|
||||
]);
|
||||
const AI_CHECK_TOKENS = ['claude', 'codex'];
|
||||
const AI_CHECK_TOKENS = ['claude'];
|
||||
const MIN_CHECKS = 10;
|
||||
const THRESHOLD = 0.5;
|
||||
const POLL_INTERVAL_MS = 90 * 1000;
|
||||
@@ -419,24 +409,6 @@ 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:
|
||||
@@ -541,72 +513,6 @@ 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' &&
|
||||
@@ -703,99 +609,6 @@ 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: |
|
||||
@@ -846,27 +659,6 @@ 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
|
||||
@@ -990,18 +782,6 @@ 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:
|
||||
@@ -1153,89 +933,6 @@ 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
|
||||
@@ -1328,99 +1025,6 @@ 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: |
|
||||
@@ -1474,20 +1078,3 @@ 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
|
||||
@@ -6,7 +6,7 @@ on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
provider:
|
||||
description: Provider key, for example "claude" or "codex"
|
||||
description: Provider key, for example "claude"
|
||||
required: true
|
||||
type: string
|
||||
result_artifact_name:
|
||||
@@ -71,7 +71,8 @@ jobs:
|
||||
|
||||
const post = require('./.github/scripts/post-pr-comment.js');
|
||||
const provider = process.env.PROVIDER;
|
||||
const providerTitle = provider === 'codex' ? 'Codex' : 'Claude';
|
||||
const providerTitle =
|
||||
provider.charAt(0).toUpperCase() + provider.slice(1);
|
||||
const body = fs.readFileSync(commentFile, 'utf8');
|
||||
const prNumber = parseInt(
|
||||
fs.readFileSync(prNumberFile, 'utf8').trim(),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: facebook/rocksdb/benchmark-linux
|
||||
name: meta/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 == 'facebook' }}
|
||||
if: ${{ github.repository_owner == 'meta' }}
|
||||
runs-on: ubuntu-latest # FIXME: change this back to self-hosted when ready
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
|
||||
@@ -7,11 +7,11 @@ permissions: {}
|
||||
|
||||
jobs:
|
||||
clang-tidy:
|
||||
if: github.repository_owner == 'facebook'
|
||||
if: github.repository_owner == 'meta'
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
|
||||
image: ghcr.io/meta/rocksdb_ubuntu:24.1
|
||||
options: --shm-size=8gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
|
||||
@@ -8,7 +8,7 @@ name: Claude Code Review
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["facebook/rocksdb/pr-jobs"]
|
||||
workflows: ["meta/rocksdb/pr-jobs"]
|
||||
types: [completed]
|
||||
|
||||
# The early pull_request_target path is limited to same-repo PRs by the job
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
# 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
|
||||
@@ -1,63 +0,0 @@
|
||||
# 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
|
||||
@@ -1,11 +1,11 @@
|
||||
name: facebook/rocksdb/nightly-candidate
|
||||
name: meta/rocksdb/nightly-candidate
|
||||
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 == 'facebook' }}
|
||||
if: ${{ github.repository_owner == 'meta' }}
|
||||
runs-on:
|
||||
labels: arm64large
|
||||
container:
|
||||
@@ -16,4 +16,3 @@ jobs:
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- uses: "./.github/actions/install-gflags"
|
||||
- run: make V=1 J=4 -j4 check
|
||||
- uses: "./.github/actions/post-steps"
|
||||
@@ -1,4 +1,4 @@
|
||||
name: facebook/rocksdb/nightly
|
||||
name: meta/rocksdb/nightly
|
||||
on:
|
||||
schedule:
|
||||
- cron: 0 9 * * *
|
||||
@@ -6,11 +6,11 @@ on:
|
||||
permissions: {}
|
||||
jobs:
|
||||
build-format-compatible:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
if: ${{ github.repository_owner == 'meta' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: ghcr.io/meta/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=32gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -26,13 +26,30 @@ jobs:
|
||||
mkdir /dev/shm/rocksdb
|
||||
git config --global --add safe.directory /__w/rocksdb/rocksdb
|
||||
tools/check_format_compatible.sh
|
||||
- uses: "./.github/actions/post-steps"
|
||||
- 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
|
||||
build-linux-non-shm:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
if: ${{ github.repository_owner == 'meta' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: ghcr.io/meta/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=32gb
|
||||
env:
|
||||
TEST_TMPDIR: "/tmp/rocksdb_test_tmp"
|
||||
@@ -40,13 +57,12 @@ jobs:
|
||||
- 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 == 'facebook' }}
|
||||
if: ${{ github.repository_owner == 'meta' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
|
||||
image: ghcr.io/meta/rocksdb_ubuntu:24.1
|
||||
options: --shm-size=32gb
|
||||
env:
|
||||
CC: clang-21
|
||||
@@ -77,13 +93,12 @@ 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 == 'facebook' }}
|
||||
if: ${{ github.repository_owner == 'meta' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: ghcr.io/meta/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=32gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -96,13 +111,12 @@ 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 == 'facebook' }}
|
||||
if: ${{ github.repository_owner == 'meta' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: ghcr.io/meta/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=32gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -112,17 +126,51 @@ 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 == 'facebook' }}
|
||||
if: ${{ github.repository_owner == 'meta' }}
|
||||
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 == 'facebook' }}
|
||||
if: ${{ github.repository_owner == 'meta' }}
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu-arm
|
||||
steps:
|
||||
@@ -130,9 +178,8 @@ 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 == 'facebook' }}
|
||||
if: ${{ github.repository_owner == 'meta' }}
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu-arm
|
||||
steps:
|
||||
@@ -154,26 +201,24 @@ 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 == 'facebook' }}
|
||||
if: ${{ github.repository_owner == 'meta' }}
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: ghcr.io/meta/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=8gb
|
||||
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 == 'facebook' }}
|
||||
if: ${{ github.repository_owner == 'meta' }}
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
|
||||
image: ghcr.io/meta/rocksdb_ubuntu:24.1
|
||||
options: --shm-size=8gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -182,13 +227,12 @@ 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 == 'facebook' }}
|
||||
if: ${{ github.repository_owner == 'meta' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: ghcr.io/meta/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=32gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -196,4 +240,3 @@ jobs:
|
||||
- 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"
|
||||
@@ -1,4 +1,4 @@
|
||||
name: facebook/rocksdb/pr-jobs-candidate
|
||||
name: meta/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 == 'facebook' }}
|
||||
if: ${{ github.repository_owner == 'meta' }}
|
||||
runs-on:
|
||||
labels: arm64large # GitHub hosted ARM runners do not yet exist
|
||||
steps:
|
||||
@@ -14,9 +14,8 @@ 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 == 'facebook' }}
|
||||
if: ${{ github.repository_owner == 'meta' }}
|
||||
runs-on:
|
||||
labels: arm64large # GitHub hosted ARM runners do not yet exist
|
||||
env:
|
||||
@@ -44,4 +43,3 @@ jobs:
|
||||
cd build
|
||||
cmake -DJNI=1 -DCMAKE_BUILD_TYPE=Release -DWITH_GFLAGS=1 ..
|
||||
make -j4 rocksdb rocksdbjni
|
||||
- uses: "./.github/actions/post-steps"
|
||||
+49
-115
@@ -1,9 +1,9 @@
|
||||
name: facebook/rocksdb/pr-jobs
|
||||
name: meta/rocksdb/pr-jobs
|
||||
on: [push, pull_request]
|
||||
permissions: {}
|
||||
env:
|
||||
# Set to a job name to run only that job (on any repo), or leave empty for
|
||||
# normal behavior (all jobs on facebook repo only).
|
||||
# normal behavior (all jobs on meta/rocksdb 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 == 'facebook' }} lines prevent the
|
||||
# The if: ${{ github.repository_owner == 'meta' }} 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 == 'facebook')
|
||||
if: needs.config.outputs.only_job == 'check-format-and-targets' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
|
||||
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/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
|
||||
# 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
|
||||
- name: Check format
|
||||
run: VERBOSE_CHECK=1 make check-format
|
||||
- name: Compare buckify output
|
||||
@@ -86,12 +86,12 @@ jobs:
|
||||
SANITY_CHECK=1 LONG_TEST=1 tools/check_format_compatible.sh
|
||||
# ========================= Linux With Tests ======================== #
|
||||
build-linux:
|
||||
if: needs.config.outputs.only_job == 'build-linux' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
|
||||
if: needs.config.outputs.only_job == 'build-linux' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
|
||||
needs: config
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: ghcr.io/meta/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=32gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -102,14 +102,13 @@ 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 == 'facebook')
|
||||
if: needs.config.outputs.only_job == 'build-linux-cmake-mingw' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
|
||||
needs: config
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
|
||||
image: ghcr.io/meta/rocksdb_ubuntu:24.1
|
||||
options: --shm-size=8gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -127,14 +126,13 @@ 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 == 'facebook')
|
||||
if: needs.config.outputs.only_job == 'build-linux-make-with-folly' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
|
||||
needs: config
|
||||
runs-on:
|
||||
labels: 32-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: ghcr.io/meta/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=64gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -152,14 +150,13 @@ 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 == 'facebook')
|
||||
if: needs.config.outputs.only_job == 'build-linux-make-with-folly-lite-no-test' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
|
||||
needs: config
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: ghcr.io/meta/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=32gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -172,14 +169,13 @@ 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 == 'facebook')
|
||||
if: needs.config.outputs.only_job == 'build-linux-cmake-with-folly-coroutines' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
|
||||
needs: config
|
||||
runs-on:
|
||||
labels: 32-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: ghcr.io/meta/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=64gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -197,9 +193,8 @@ 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 == 'facebook')
|
||||
if: needs.config.outputs.only_job == 'build-linux-cmake-with-benchmark-no-thread-status' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
|
||||
needs: config
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
@@ -208,7 +203,7 @@ jobs:
|
||||
matrix:
|
||||
shard: [0, 1, 2, 3]
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: ghcr.io/meta/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=32gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -222,16 +217,13 @@ 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 == 'facebook')
|
||||
if: needs.config.outputs.only_job == 'build-linux-encrypted_env-no_compression' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
|
||||
needs: config
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: ghcr.io/meta/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=32gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -243,15 +235,14 @@ 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 == 'facebook')
|
||||
if: needs.config.outputs.only_job == 'build-linux-release' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
|
||||
needs: config
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: ghcr.io/meta/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=32gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -276,14 +267,13 @@ 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 == 'facebook')
|
||||
if: needs.config.outputs.only_job == 'build-linux-clang-13-no_test_run' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
|
||||
needs: config
|
||||
runs-on:
|
||||
labels: 8-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: ghcr.io/meta/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -298,14 +288,13 @@ 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 == 'facebook')
|
||||
if: needs.config.outputs.only_job == 'build-linux-clang-21-no_test_run' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
|
||||
needs: config
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
|
||||
image: ghcr.io/meta/rocksdb_ubuntu:24.1
|
||||
options: --shm-size=32gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -365,14 +354,13 @@ 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 == 'facebook')
|
||||
if: needs.config.outputs.only_job == 'build-linux-gcc-14-no_test_run' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
|
||||
needs: config
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
|
||||
image: ghcr.io/meta/rocksdb_ubuntu:24.1
|
||||
options: --shm-size=32gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -383,12 +371,11 @@ 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 == 'facebook')
|
||||
if: needs.config.outputs.only_job == 'build-linux-unity-and-headers' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
|
||||
needs: config
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
@@ -406,9 +393,8 @@ 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 == 'facebook')
|
||||
if: needs.config.outputs.only_job == 'build-linux-mini-crashtest' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
|
||||
needs: config
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
@@ -421,7 +407,7 @@ jobs:
|
||||
- crash_test_target: blackbox_crash_test
|
||||
crash_duration: 240
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: ghcr.io/meta/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=8gb --ulimit nofile=1048576:1048576
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -442,12 +428,9 @@ 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 == 'facebook')
|
||||
if: needs.config.outputs.only_job == 'build-linux-clang21-asan-ubsan' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
|
||||
needs: config
|
||||
runs-on:
|
||||
labels: 32-core-ubuntu
|
||||
@@ -456,7 +439,7 @@ jobs:
|
||||
matrix:
|
||||
shard: [0, 1, 2]
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
|
||||
image: ghcr.io/meta/rocksdb_ubuntu:24.1
|
||||
options: --shm-size=64gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -470,11 +453,8 @@ 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 == 'facebook')
|
||||
if: needs.config.outputs.only_job == 'build-linux-clang21-mini-tsan' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
|
||||
needs: config
|
||||
runs-on:
|
||||
labels: 32-core-ubuntu
|
||||
@@ -483,7 +463,7 @@ jobs:
|
||||
matrix:
|
||||
shard: [0, 1, 2]
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
|
||||
image: ghcr.io/meta/rocksdb_ubuntu:24.1
|
||||
options: --shm-size=64gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -497,16 +477,13 @@ 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 == 'facebook')
|
||||
if: needs.config.outputs.only_job == 'build-linux-static_lib-alt_namespace-status_checked' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
|
||||
needs: config
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: ghcr.io/meta/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=32gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -517,10 +494,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 == 'facebook')
|
||||
if: needs.config.outputs.only_job == 'build-macos' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
|
||||
needs: config
|
||||
runs-on: macos-15-xlarge
|
||||
env:
|
||||
@@ -540,49 +517,10 @@ 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 == 'facebook')
|
||||
if: needs.config.outputs.only_job == 'build-windows-msvc' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
|
||||
needs: config
|
||||
runs-on: windows-8-core
|
||||
strategy:
|
||||
@@ -608,12 +546,12 @@ 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 == 'facebook')
|
||||
if: needs.config.outputs.only_job == 'build-linux-java' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
|
||||
needs: config
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: ghcr.io/meta/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=8gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -631,12 +569,12 @@ 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 == 'facebook')
|
||||
if: needs.config.outputs.only_job == 'build-linux-java-static' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
|
||||
needs: config
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: ghcr.io/meta/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=8gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -654,7 +592,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 == 'facebook')
|
||||
if: needs.config.outputs.only_job == 'build-macos-java' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
|
||||
needs: config
|
||||
runs-on: macos-15-xlarge
|
||||
env:
|
||||
@@ -680,9 +618,8 @@ 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 == 'facebook')
|
||||
if: needs.config.outputs.only_job == 'build-macos-java-static' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
|
||||
needs: config
|
||||
runs-on: macos-15-xlarge
|
||||
steps:
|
||||
@@ -706,9 +643,8 @@ 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 == 'facebook')
|
||||
if: needs.config.outputs.only_job == 'build-macos-java-static-universal' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
|
||||
needs: config
|
||||
runs-on: macos-15-xlarge
|
||||
steps:
|
||||
@@ -732,9 +668,8 @@ 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 == 'facebook')
|
||||
if: needs.config.outputs.only_job == 'build-linux-java-pmd' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
|
||||
needs: config
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
@@ -761,7 +696,7 @@ jobs:
|
||||
name: maven-site
|
||||
path: "${{ github.workspace }}/java/target/site"
|
||||
build-linux-arm:
|
||||
if: needs.config.outputs.only_job == 'build-linux-arm' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
|
||||
if: needs.config.outputs.only_job == 'build-linux-arm' || (needs.config.outputs.only_job == '' && github.repository_owner == 'meta')
|
||||
needs: config
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu-arm
|
||||
@@ -777,4 +712,3 @@ jobs:
|
||||
run: ccache -s
|
||||
if: always()
|
||||
shell: bash
|
||||
- uses: "./.github/actions/post-steps"
|
||||
@@ -1,4 +1,4 @@
|
||||
name: facebook/rocksdb/weekly
|
||||
name: meta/rocksdb/weekly
|
||||
on:
|
||||
schedule:
|
||||
- cron: 0 9 * * 0
|
||||
@@ -6,15 +6,14 @@ on:
|
||||
permissions: {}
|
||||
jobs:
|
||||
build-linux-valgrind:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
if: ${{ github.repository_owner == 'meta' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
timeout-minutes: 840
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: ghcr.io/meta/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=32gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- run: make V=1 -j20 valgrind_test
|
||||
- uses: "./.github/actions/post-steps"
|
||||
@@ -259,6 +259,7 @@ 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",
|
||||
@@ -281,6 +282,7 @@ 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",
|
||||
@@ -314,6 +316,7 @@ 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",
|
||||
@@ -321,6 +324,8 @@ 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",
|
||||
@@ -385,22 +390,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=[
|
||||
"//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",
|
||||
"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",
|
||||
], 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)
|
||||
@@ -528,6 +533,8 @@ rocks_cpp_library_wrapper(name="rocksdb_stress_lib", srcs=[
|
||||
|
||||
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)
|
||||
@@ -5088,6 +5095,12 @@ 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"],
|
||||
@@ -5384,6 +5397,12 @@ 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"],
|
||||
|
||||
@@ -16,7 +16,7 @@ This document provides guidance for generating and reviewing code in the RocksDB
|
||||
|
||||
**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!
|
||||
* 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).
|
||||
|
||||
@@ -24,7 +24,8 @@ Specifically:
|
||||
|
||||
**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.
|
||||
**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.
|
||||
|
||||
**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.
|
||||
|
||||
@@ -436,7 +437,8 @@ architectures.
|
||||
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.
|
||||
Status checks is a common failure point, even from agents. Avoid new uses
|
||||
of PermitUncheckedError(). That's the lazy solution.
|
||||
|
||||
### Monitoring make check progress
|
||||
* Use `make check-progress` to get machine-parseable JSON progress while
|
||||
|
||||
+7
-2
@@ -522,8 +522,6 @@ 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")
|
||||
@@ -1002,6 +1000,7 @@ 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
|
||||
@@ -1022,6 +1021,7 @@ 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,7 +1051,10 @@ 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
|
||||
@@ -1508,6 +1511,7 @@ 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
|
||||
@@ -1658,6 +1662,7 @@ 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
|
||||
|
||||
+88
-22
@@ -1,15 +1,80 @@
|
||||
# Rocksdb Change Log
|
||||
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
|
||||
|
||||
## 11.13.2 (10/28/2026)
|
||||
### Bug Fixes
|
||||
* Fixed a secondary instance with `open_files_async = true` getting stuck after `TryCatchUpWithPrimary()` returned `PathNotFound` for a table file that the primary deleted before the background opener reached it.
|
||||
* 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.
|
||||
## 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.
|
||||
|
||||
### 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`.
|
||||
|
||||
## 11.13.1 (10/23/2026)
|
||||
### 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
|
||||
@@ -46,6 +111,7 @@ 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.
|
||||
@@ -1090,7 +1156,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/facebook/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/meta/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)
|
||||
@@ -1135,7 +1201,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/facebook/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/meta/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.
|
||||
@@ -1299,7 +1365,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/facebook/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/meta/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)
|
||||
@@ -1419,7 +1485,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/facebook/rocksdb/pull/8336 which made InternalKeyComparator configurable as an unintended side effect.
|
||||
* 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.
|
||||
|
||||
## 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.
|
||||
@@ -1458,7 +1524,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/facebook/rocksdb/wiki/User-defined-Timestamp-(Experimental))
|
||||
* And Add C API's for Transaction, SstFileWriter, Compaction as mentioned [here](https://github.com/meta/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
|
||||
@@ -1689,7 +1755,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/facebook/rocksdb/issues/9390 for more info.
|
||||
Note: The next release will be major release 7.0. See https://github.com/meta/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.
|
||||
@@ -1838,7 +1904,7 @@ Note: The next release will be major release 7.0. See https://github.com/faceboo
|
||||
### 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/facebook/rocksdb/issues/8039).
|
||||
* Fix multiget throwing Null Pointer Exception for num of keys > 70k (https://github.com/meta/rocksdb/issues/8039).
|
||||
|
||||
## 6.25.0 (2021-09-20)
|
||||
### Bug Fixes
|
||||
@@ -2525,7 +2591,7 @@ Note: The next release will be major release 7.0. See https://github.com/faceboo
|
||||
* 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/facebook/rocksdb/issues/3469
|
||||
* Added new APIs ExportColumnFamily() and CreateColumnFamilyWithImport() to support export and import of a Column Family. https://github.com/meta/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.
|
||||
@@ -3112,7 +3178,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/facebook/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/meta/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.
|
||||
|
||||
@@ -3122,7 +3188,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/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.
|
||||
* 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.
|
||||
|
||||
## 4.6.0 (2016-03-10)
|
||||
### Public API Changes
|
||||
@@ -3419,7 +3485,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/facebook/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/meta/rocksdb/wiki/Prefix-Seek-API-Changes
|
||||
* MemTableRepFactory::CreateMemTableRep() takes info logger as an extra parameter.
|
||||
|
||||
### New Features
|
||||
@@ -3470,8 +3536,8 @@ Because of that, Options::disable_seek_compaction is now obsolete. It is still a
|
||||
|
||||
### New Features
|
||||
|
||||
* 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
|
||||
* 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
|
||||
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
|
||||
@@ -3488,8 +3554,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/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)
|
||||
* 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)
|
||||
* Lots of efforts to move malloc, memcpy and IO outside of locks
|
||||
+2
-13
@@ -153,7 +153,7 @@ most processors made since roughly 2013.
|
||||
|
||||
* Build RocksDB from source:
|
||||
cd ~
|
||||
git clone https://github.com/facebook/rocksdb.git
|
||||
git clone https://github.com/meta/rocksdb.git
|
||||
cd rocksdb
|
||||
gmake static_lib
|
||||
|
||||
@@ -174,7 +174,7 @@ most processors made since roughly 2013.
|
||||
|
||||
```bash
|
||||
cd ~
|
||||
git clone https://github.com/facebook/rocksdb.git
|
||||
git clone https://github.com/meta/rocksdb.git
|
||||
cd rocksdb
|
||||
gmake static_lib
|
||||
```
|
||||
@@ -211,14 +211,3 @@ 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,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/facebook/rocksdb/tree/main/java
|
||||
* Java - https://github.com/meta/rocksdb/tree/main/java
|
||||
* Python
|
||||
* https://github.com/rocksdict/RocksDict
|
||||
* http://python-rocksdb.readthedocs.io/en/latest/ (unmaintained)
|
||||
|
||||
@@ -355,10 +355,6 @@ 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'
|
||||
@@ -664,6 +660,7 @@ TESTS_PLATFORM_DEPENDENT := \
|
||||
dynamic_bloom_test \
|
||||
c_test \
|
||||
checkpoint_test \
|
||||
live_tuner_test \
|
||||
sorted_run_builder_test \
|
||||
crc32c_test \
|
||||
coding_test \
|
||||
@@ -1896,6 +1893,12 @@ 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)
|
||||
|
||||
@@ -2208,6 +2211,9 @@ 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)
|
||||
|
||||
@@ -2407,9 +2413,7 @@ gen-pc:
|
||||
# Jni stuff
|
||||
# ---------------------------------------------------------------------------
|
||||
JAVA_INCLUDE = -I$(JAVA_HOME)/include/ -I$(JAVA_HOME)/include/linux
|
||||
ifeq ($(PLATFORM), OS_SOLARIS)
|
||||
ARCH := $(shell isainfo -b)
|
||||
else ifeq ($(PLATFORM), OS_OPENBSD)
|
||||
ifeq ($(PLATFORM), OS_OPENBSD)
|
||||
ifneq (,$(filter amd64 ppc64 ppc64le s390x arm64 aarch64 riscv64 sparc64 loongarch64, $(MACHINE)))
|
||||
ARCH := 64
|
||||
else
|
||||
@@ -2490,12 +2494,6 @@ 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
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
## RocksDB: A Persistent Key-Value Store for Flash and RAM Storage
|
||||
|
||||
[](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)
|
||||
@@ -14,9 +12,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/facebook/rocksdb/tree/main/examples
|
||||
Start with example usage here: https://github.com/meta/rocksdb/tree/main/examples
|
||||
|
||||
See the [github wiki](https://github.com/facebook/rocksdb/wiki) for more explanation.
|
||||
See the [github wiki](https://github.com/meta/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
|
||||
|
||||
@@ -188,22 +188,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=[
|
||||
"//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",
|
||||
"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",
|
||||
],
|
||||
headers=LiteralValue("glob([\"**/*.h\"])")
|
||||
)
|
||||
@@ -299,6 +299,9 @@ def generate_buck(repo_path, deps_map):
|
||||
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"]
|
||||
|
||||
@@ -230,12 +230,6 @@ 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
|
||||
|
||||
@@ -129,12 +129,13 @@ 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
|
||||
# facebook/rocksdb.git main branch. Otherwise, we'll check format of the
|
||||
# meta/rocksdb.git main branch. Otherwise, we'll check format of the
|
||||
# uncommitted code only.
|
||||
if [ -z "$uncommitted_code" ]
|
||||
then
|
||||
# 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)"
|
||||
# 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)"
|
||||
# Fall back on 'origin' if that fails
|
||||
[ "$FORMAT_REMOTE" ] || FORMAT_REMOTE=origin
|
||||
# Use main branch from that remote
|
||||
|
||||
@@ -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/facebook/rocksdb/archive/v${ROCKSDB_VERSION}.tar.gz
|
||||
wget -qO /tmp/rocksdb-${ROCKSDB_VERSION}.tar.gz https://github.com/meta/rocksdb/archive/v${ROCKSDB_VERSION}.tar.gz
|
||||
|
||||
cd /tmp
|
||||
|
||||
|
||||
@@ -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/facebook/rocksdb_ubuntu:22.2
|
||||
# $ docker push ghcr.io/facebook/rocksdb_ubuntu:22.2
|
||||
# $ docker build -t ghcr.io/meta/rocksdb_ubuntu:22.2
|
||||
# $ docker push ghcr.io/meta/rocksdb_ubuntu:22.2
|
||||
#
|
||||
# Might need to change visibility to public through
|
||||
# https://github.com/orgs/facebook/packages/container/rocksdb_ubuntu/settings
|
||||
|
||||
@@ -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/facebook/rocksdb_ubuntu:24.1
|
||||
# $ docker push ghcr.io/facebook/rocksdb_ubuntu:24.1
|
||||
# $ docker build -t ghcr.io/meta/rocksdb_ubuntu:24.1
|
||||
# $ docker push ghcr.io/meta/rocksdb_ubuntu:24.1
|
||||
#
|
||||
# Might need to change visibility to public through
|
||||
# https://github.com/orgs/facebook/packages/container/rocksdb_ubuntu/settings
|
||||
|
||||
@@ -109,6 +109,12 @@ 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;
|
||||
@@ -172,6 +178,12 @@ 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;
|
||||
|
||||
@@ -78,6 +78,10 @@ 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);
|
||||
|
||||
@@ -117,6 +121,10 @@ 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,6 +67,11 @@ 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;
|
||||
|
||||
@@ -54,6 +54,10 @@ 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);
|
||||
|
||||
|
||||
@@ -826,6 +826,16 @@ 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;
|
||||
@@ -1919,6 +1929,16 @@ 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(
|
||||
@@ -1998,6 +2018,16 @@ 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 =
|
||||
@@ -2145,6 +2175,16 @@ 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(
|
||||
@@ -2169,6 +2209,16 @@ 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(
|
||||
@@ -2463,26 +2513,6 @@ 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;
|
||||
|
||||
@@ -255,6 +255,13 @@ 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);
|
||||
@@ -708,6 +715,14 @@ 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
|
||||
@@ -770,6 +785,14 @@ 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);
|
||||
@@ -884,6 +907,14 @@ 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
|
||||
@@ -904,6 +935,14 @@ 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
|
||||
@@ -1130,22 +1169,6 @@ 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);
|
||||
|
||||
@@ -87,12 +87,6 @@ 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);
|
||||
@@ -223,12 +217,6 @@ 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);
|
||||
@@ -321,6 +309,10 @@ 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);
|
||||
}
|
||||
|
||||
@@ -493,6 +485,18 @@ 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();
|
||||
@@ -534,6 +538,12 @@ 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(
|
||||
@@ -937,6 +947,12 @@ 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);
|
||||
@@ -1124,6 +1140,12 @@ 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);
|
||||
@@ -1399,6 +1421,7 @@ 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();
|
||||
|
||||
Vendored
+1
-1
@@ -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/facebook/rocksdb/pull/9058
|
||||
// See https://github.com/meta/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 ##
|
||||
|
||||
Vendored
+19
@@ -948,6 +948,25 @@ 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;
|
||||
|
||||
Vendored
+1
-1
@@ -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 = 0;
|
||||
e->total_charge = LRUHandle::kUnchargedStandaloneTotalCharge;
|
||||
} else {
|
||||
free(e);
|
||||
e = nullptr;
|
||||
|
||||
Vendored
+8
@@ -87,6 +87,10 @@ 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];
|
||||
|
||||
@@ -196,6 +200,10 @@ 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;
|
||||
|
||||
Vendored
+71
-58
@@ -1474,68 +1474,81 @@ TEST_P(BasicSecondaryCacheTest, CreateFailTest) {
|
||||
}
|
||||
|
||||
TEST_P(BasicSecondaryCacheTest, FullCapacityTest) {
|
||||
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());
|
||||
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());
|
||||
|
||||
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()));
|
||||
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()));
|
||||
|
||||
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()) {
|
||||
Cache::Handle* handle2;
|
||||
handle2 = cache->Lookup(k2.AsSlice(), GetHelper(),
|
||||
/*context*/ this, Cache::Priority::LOW);
|
||||
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);
|
||||
}
|
||||
// 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);
|
||||
|
||||
cache.reset();
|
||||
secondary_cache.reset();
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -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/facebook/rocksdb/pull/13195 for why we
|
||||
// See https://github.com/meta/rocksdb/pull/13195 for why we
|
||||
// want to set up our test implementation for FSAllocationPtr this
|
||||
// way.
|
||||
char* internalData = new char[req.len];
|
||||
|
||||
@@ -87,15 +87,15 @@ to scan.
|
||||
|
||||
## Reference Pull Request Summaries
|
||||
|
||||
- [PR #15216](https://github.com/facebook/rocksdb/pull/15216) is a strong
|
||||
- [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/facebook/rocksdb/pull/15218) is a concise
|
||||
- [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/facebook/rocksdb/pull/15231)
|
||||
- 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
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <optional>
|
||||
|
||||
#include "db/blob/blob_garbage_meter.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
@@ -21,10 +22,14 @@ namespace ROCKSDB_NAMESPACE {
|
||||
class BlobCountingIterator : public InternalIterator {
|
||||
public:
|
||||
BlobCountingIterator(InternalIterator* iter,
|
||||
BlobGarbageMeter* blob_garbage_meter)
|
||||
BlobGarbageMeter* blob_garbage_meter,
|
||||
bool track_current_key_inflow)
|
||||
: iter_(iter), blob_garbage_meter_(blob_garbage_meter) {
|
||||
assert(iter_);
|
||||
assert(blob_garbage_meter_);
|
||||
if (track_current_key_inflow) {
|
||||
current_key_inflow_.emplace();
|
||||
}
|
||||
|
||||
UpdateAndCountBlobIfNeeded();
|
||||
}
|
||||
@@ -127,23 +132,47 @@ 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_;
|
||||
};
|
||||
|
||||
|
||||
@@ -71,7 +71,8 @@ TEST(BlobCountingIteratorTest, CountBlobs) {
|
||||
VectorIterator input(keys, values);
|
||||
BlobGarbageMeter blob_garbage_meter;
|
||||
|
||||
BlobCountingIterator blob_counter(&input, &blob_garbage_meter);
|
||||
BlobCountingIterator blob_counter(&input, &blob_garbage_meter,
|
||||
/*track_current_key_inflow=*/false);
|
||||
|
||||
constexpr uint64_t first_expected_bytes =
|
||||
first_size +
|
||||
@@ -301,6 +302,58 @@ 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)};
|
||||
@@ -311,7 +364,8 @@ TEST(BlobCountingIteratorTest, CorruptBlobIndex) {
|
||||
VectorIterator input(keys, values);
|
||||
BlobGarbageMeter blob_garbage_meter;
|
||||
|
||||
BlobCountingIterator blob_counter(&input, &blob_garbage_meter);
|
||||
BlobCountingIterator blob_counter(&input, &blob_garbage_meter,
|
||||
/*track_current_key_inflow=*/false);
|
||||
|
||||
blob_counter.SeekToFirst();
|
||||
ASSERT_FALSE(blob_counter.Valid());
|
||||
|
||||
@@ -40,15 +40,16 @@ 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,
|
||||
BlobFileCreationReason creation_reason, std::string blob_output_path,
|
||||
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, db_id, db_session_id, job_id,
|
||||
column_family_id, column_family_name, write_hint,
|
||||
write_options, std::move(db_id), std::move(db_session_id),
|
||||
job_id, column_family_id, column_family_name, write_hint,
|
||||
io_tracer, blob_callback, creation_reason,
|
||||
output_file_paths, blob_file_additions) {}
|
||||
std::move(blob_output_path), output_file_paths,
|
||||
blob_file_additions) {}
|
||||
|
||||
BlobFileBuilder::BlobFileBuilder(
|
||||
std::function<uint64_t()> file_number_generator, FileSystem* fs,
|
||||
@@ -59,7 +60,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,
|
||||
BlobFileCreationReason creation_reason, std::string blob_output_path,
|
||||
std::vector<std::string>* output_file_paths,
|
||||
std::vector<BlobFileAddition>* blob_file_additions)
|
||||
: file_number_generator_(std::move(file_number_generator)),
|
||||
@@ -90,6 +91,7 @@ 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),
|
||||
@@ -102,16 +104,14 @@ BlobFileBuilder::BlobFileBuilder(
|
||||
assert(output_file_paths_);
|
||||
assert(output_file_paths_->empty());
|
||||
assert(blob_file_additions_);
|
||||
assert(blob_file_additions_->empty());
|
||||
// Resumable compactions restore finalized additions before creating a new
|
||||
// builder. Any newly generated blob files are appended to that state.
|
||||
}
|
||||
|
||||
BlobFileBuilder::~BlobFileBuilder() = default;
|
||||
|
||||
std::string BlobFileBuilder::GetBlobFilePath(uint64_t blob_file_number) const {
|
||||
assert(immutable_options_);
|
||||
assert(!immutable_options_->cf_paths.empty());
|
||||
return BlobFileName(immutable_options_->cf_paths.front().path,
|
||||
blob_file_number);
|
||||
return BlobFileName(blob_output_path_, blob_file_number);
|
||||
}
|
||||
|
||||
Status BlobFileBuilder::Add(const Slice& key, const Slice& value,
|
||||
@@ -194,6 +194,9 @@ 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);
|
||||
|
||||
@@ -353,7 +356,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_);
|
||||
blob_count_, blob_bytes_, /*set_bg_error_on_space_limit=*/true);
|
||||
}
|
||||
|
||||
assert(blob_file_additions_);
|
||||
@@ -400,7 +403,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_)
|
||||
blob_bytes_, /*set_bg_error_on_space_limit=*/true)
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ 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);
|
||||
|
||||
@@ -63,6 +64,7 @@ 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);
|
||||
|
||||
@@ -112,6 +114,7 @@ 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_;
|
||||
|
||||
@@ -148,7 +148,8 @@ 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, &blob_file_paths, &blob_file_additions);
|
||||
BlobFileCreationReason::kFlush, immutable_options.cf_paths.front().path,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> expected_key_value_pairs(
|
||||
number_of_blobs);
|
||||
@@ -232,7 +233,8 @@ 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, &blob_file_paths, &blob_file_additions);
|
||||
BlobFileCreationReason::kFlush, immutable_options.cf_paths.front().path,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> expected_key_value_pairs(
|
||||
number_of_blobs);
|
||||
@@ -319,7 +321,8 @@ 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, &blob_file_paths, &blob_file_additions);
|
||||
BlobFileCreationReason::kFlush, immutable_options.cf_paths.front().path,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
|
||||
for (size_t i = 0; i < number_of_blobs; ++i) {
|
||||
const std::string key = std::to_string(i);
|
||||
@@ -373,7 +376,8 @@ 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, &blob_file_paths, &blob_file_additions);
|
||||
BlobFileCreationReason::kFlush, immutable_options.cf_paths.front().path,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
|
||||
const std::string key("1");
|
||||
const std::string uncompressed_value(value_size, 'x');
|
||||
@@ -452,7 +456,8 @@ 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, &blob_file_paths, &blob_file_additions);
|
||||
BlobFileCreationReason::kFlush, immutable_options.cf_paths.front().path,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"LegacyForceBuiltinCompression:TamperWithStatus", [](void* arg) {
|
||||
@@ -532,7 +537,8 @@ 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, &blob_file_paths, &blob_file_additions);
|
||||
BlobFileCreationReason::kFlush, immutable_options.cf_paths.front().path,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
|
||||
const std::string key("1");
|
||||
const std::string value("deadbeef");
|
||||
@@ -631,7 +637,8 @@ 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, &blob_file_paths, &blob_file_additions);
|
||||
BlobFileCreationReason::kFlush, immutable_options.cf_paths.front().path,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(sync_point_, [this](void* arg) {
|
||||
Status* const s = static_cast<Status*>(arg);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "db/event_helpers.h"
|
||||
#include "file/sst_file_manager_impl.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "util/cast_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -38,14 +39,12 @@ 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) {
|
||||
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 s;
|
||||
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(sst_file_manager_);
|
||||
@@ -56,24 +55,72 @@ class BlobFileCompletionCallback {
|
||||
s = Status::SpaceLimit("Max allowed space was reached");
|
||||
TEST_SYNC_POINT(
|
||||
"BlobFileCompletionCallback::CallBack::MaxAllowedSpaceReached");
|
||||
InstrumentedMutexLock l(mutex_);
|
||||
error_handler_->SetBGError(s, BackgroundErrorReason::kFlush);
|
||||
if (set_bg_error_on_space_limit) {
|
||||
// This callback historically reports space limits as flush errors.
|
||||
SetBGError(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notify the listeners.
|
||||
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) {
|
||||
EventHelpers::LogAndNotifyBlobFileCreationFinished(
|
||||
event_logger_, listeners_, dbname_, column_family_name, file_name,
|
||||
job_id, file_number, creation_reason,
|
||||
(!report_status.ok() ? report_status : s),
|
||||
job_id, file_number, creation_reason, status,
|
||||
(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_;
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#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 {
|
||||
@@ -158,6 +159,12 @@ 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;
|
||||
@@ -171,6 +178,19 @@ 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_);
|
||||
@@ -255,7 +275,7 @@ Status BlobFilePartitionManager::OpenNewBlobFile(Partition* partition,
|
||||
|
||||
Status BlobFilePartitionManager::SealActiveBlobFile(
|
||||
const WriteOptions& write_options, Partition* partition,
|
||||
SealedFile* sealed_file) {
|
||||
SealedFile* sealed_file, bool* space_limit_reached) {
|
||||
assert(partition != nullptr);
|
||||
assert(partition->writer);
|
||||
assert(sealed_file != nullptr);
|
||||
@@ -264,10 +284,8 @@ Status BlobFilePartitionManager::SealActiveBlobFile(
|
||||
Status s =
|
||||
FinalizeBlobFile(write_options, partition->writer.get(), file_number,
|
||||
partition->blob_count, partition->total_blob_bytes,
|
||||
&sealed_file->addition);
|
||||
&sealed_file->addition, space_limit_reached);
|
||||
if (!s.ok()) {
|
||||
RemoveFilePartitionMapping(file_number);
|
||||
ResetPartitionState(partition);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -284,11 +302,15 @@ Status BlobFilePartitionManager::SealDeferredFile(
|
||||
assert(deferred->writer);
|
||||
assert(sealed_file != nullptr);
|
||||
|
||||
Status s = FinalizeBlobFile(
|
||||
write_options, deferred->writer.get(), deferred->file_number,
|
||||
deferred->blob_count, deferred->total_blob_bytes, &sealed_file->addition);
|
||||
// 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);
|
||||
if (!s.ok()) {
|
||||
RemoveFilePartitionMapping(deferred->file_number);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -301,9 +323,10 @@ 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) {
|
||||
BlobFileAddition* addition, bool* space_limit_reached) {
|
||||
assert(writer != nullptr);
|
||||
assert(addition != nullptr);
|
||||
assert(space_limit_reached != nullptr);
|
||||
|
||||
BlobLogFooter footer;
|
||||
footer.blob_count = blob_count;
|
||||
@@ -317,13 +340,13 @@ 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);
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,6 +500,15 @@ 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();
|
||||
@@ -486,11 +518,16 @@ Status BlobFilePartitionManager::WriteBlob(
|
||||
return Status::OK();
|
||||
}
|
||||
SealedFile sealed_file;
|
||||
Status s = SealActiveBlobFile(write_options, partition, &sealed_file);
|
||||
if (s.ok()) {
|
||||
current_generation_sealed_files_.push_back(std::move(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;
|
||||
}
|
||||
return s;
|
||||
current_generation_sealed_files_.push_back(std::move(sealed_file));
|
||||
return Status::OK();
|
||||
};
|
||||
|
||||
const uint64_t record_size =
|
||||
@@ -550,21 +587,16 @@ void BlobFilePartitionManager::RotateCurrentGeneration(uint64_t memtable_id) {
|
||||
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;
|
||||
}
|
||||
|
||||
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));
|
||||
batch.deferred_files.emplace_back(DetachPartitionFile(partition.get()));
|
||||
}
|
||||
|
||||
pending_generations_.emplace_back(std::move(batch));
|
||||
@@ -603,14 +635,18 @@ Status BlobFilePartitionManager::PrepareFlushAdditions(
|
||||
}
|
||||
GenerationBatch& batch = *batch_ptr;
|
||||
while (!batch.deferred_files.empty()) {
|
||||
DeferredFile deferred = std::move(batch.deferred_files.front());
|
||||
batch.deferred_files.pop_front();
|
||||
DeferredFile& deferred = batch.deferred_files.front();
|
||||
if (!deferred.seal_status.ok()) {
|
||||
return deferred.seal_status;
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -658,6 +694,12 @@ 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,
|
||||
|
||||
@@ -183,6 +183,11 @@ 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.
|
||||
@@ -192,6 +197,10 @@ 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 {
|
||||
@@ -216,17 +225,22 @@ class BlobFilePartitionManager {
|
||||
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/seal failure.
|
||||
// Stops tracking a blob file after open 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);
|
||||
Partition* partition, SealedFile* sealed_file,
|
||||
bool* space_limit_reached);
|
||||
// Finalizes one deferred file that was carried over to flush preparation.
|
||||
Status SealDeferredFile(const WriteOptions& write_options,
|
||||
DeferredFile* deferred, SealedFile* sealed_file);
|
||||
@@ -235,7 +249,8 @@ 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);
|
||||
BlobFileAddition* addition,
|
||||
bool* space_limit_reached);
|
||||
// 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,
|
||||
@@ -294,6 +309,8 @@ 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_;
|
||||
|
||||
|
||||
@@ -59,6 +59,14 @@ 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_; }
|
||||
@@ -90,6 +98,21 @@ 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_;
|
||||
}
|
||||
|
||||
@@ -106,7 +106,9 @@ class BlobIndex {
|
||||
|
||||
Status DecodeFrom(Slice slice) {
|
||||
const char* kErrorMessage = "Error while decoding blob index";
|
||||
assert(slice.size() > 0);
|
||||
if (slice.empty()) {
|
||||
return Status::Corruption(kErrorMessage, "Empty blob index");
|
||||
}
|
||||
type_ = static_cast<Type>(*slice.data());
|
||||
if (type_ >= Type::kUnknown) {
|
||||
return Status::Corruption(kErrorMessage,
|
||||
|
||||
@@ -70,6 +70,8 @@ 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:
|
||||
|
||||
@@ -41,6 +41,73 @@ 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 {
|
||||
@@ -1483,6 +1550,335 @@ 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();
|
||||
@@ -2315,6 +2711,179 @@ 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;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -37,6 +38,7 @@
|
||||
#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"
|
||||
@@ -2004,6 +2006,403 @@ TEST_F(DBBlobDirectWriteTest, RetryAfterOlderOverlappingFlushFails) {
|
||||
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()
|
||||
|
||||
@@ -57,6 +57,11 @@ 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
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
// 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
|
||||
+14
-4
@@ -116,7 +116,8 @@ 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()));
|
||||
new BlobCountingIterator(iter, blob_garbage_meter.get(),
|
||||
/*track_current_key_inflow=*/false));
|
||||
input = blob_counting_iter.get();
|
||||
}
|
||||
|
||||
@@ -161,6 +162,13 @@ 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();
|
||||
@@ -216,7 +224,8 @@ Status BuildTable(
|
||||
auto ucmp = tboptions.internal_comparator.user_comparator();
|
||||
MergeHelper merge(
|
||||
env, ucmp, ioptions.merge_operator.get(), compaction_filter.get(),
|
||||
ioptions.logger, true /* internal key corruption is not ok */,
|
||||
CompactionFilter::MergeOrder::kFilterBeforeMerge, ioptions.logger,
|
||||
true /* internal key corruption is not ok */,
|
||||
snapshots.empty() ? 0 : snapshots.back(), snapshot_checker);
|
||||
|
||||
std::unique_ptr<BlobFileBuilder> blob_file_builder(
|
||||
@@ -229,7 +238,8 @@ 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, &blob_file_paths,
|
||||
blob_callback, blob_creation_reason,
|
||||
ioptions.cf_paths.front().path, &blob_file_paths,
|
||||
blob_file_additions)
|
||||
: nullptr);
|
||||
|
||||
@@ -390,7 +400,7 @@ Status BuildTable(
|
||||
std::min(meta->fd.smallest_seqno, smallest_preferred_seqno),
|
||||
meta->fd.largest_seqno);
|
||||
relevant_mapping.SetCapacity(kMaxSeqnoTimePairsPerSST);
|
||||
relevant_mapping.Enforce(tboptions.file_creation_time);
|
||||
relevant_mapping.Enforce(meta->file_creation_time);
|
||||
}
|
||||
builder->SetSeqnoTimeTableProperties(
|
||||
relevant_mapping,
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
#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"
|
||||
|
||||
@@ -474,6 +475,23 @@ 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;
|
||||
@@ -4767,12 +4785,60 @@ 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4886,6 +4952,12 @@ 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;
|
||||
@@ -4949,6 +5021,12 @@ 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;
|
||||
@@ -5187,6 +5265,11 @@ 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;
|
||||
@@ -5652,6 +5735,16 @@ 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;
|
||||
}
|
||||
@@ -7513,6 +7606,7 @@ 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;
|
||||
}
|
||||
@@ -7524,11 +7618,21 @@ 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(
|
||||
@@ -10176,6 +10280,16 @@ 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;
|
||||
@@ -11269,6 +11383,16 @@ 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(
|
||||
@@ -11348,6 +11472,16 @@ 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 =
|
||||
@@ -11495,6 +11629,16 @@ 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(
|
||||
@@ -11519,6 +11663,16 @@ 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(
|
||||
@@ -11813,26 +11967,6 @@ 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;
|
||||
@@ -13405,6 +13539,74 @@ 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);
|
||||
|
||||
+189
-3
@@ -240,6 +240,138 @@ 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;
|
||||
@@ -324,7 +456,6 @@ static void CheckPutCF(void* ptr, uint32_t cfid, const char* k, size_t klen,
|
||||
break;
|
||||
default:
|
||||
CheckCondition(false);
|
||||
break;
|
||||
}
|
||||
(*state)++;
|
||||
}
|
||||
@@ -343,7 +474,6 @@ static void CheckDelCF(void* ptr, uint32_t cfid, const char* k, size_t klen) {
|
||||
break;
|
||||
default:
|
||||
CheckCondition(false);
|
||||
break;
|
||||
}
|
||||
(*state)++;
|
||||
}
|
||||
@@ -365,7 +495,6 @@ static void CheckMergeCF(void* ptr, uint32_t cfid, const char* k, size_t klen,
|
||||
break;
|
||||
default:
|
||||
CheckCondition(false);
|
||||
break;
|
||||
}
|
||||
(*state)++;
|
||||
}
|
||||
@@ -3630,6 +3759,42 @@ 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);
|
||||
}
|
||||
@@ -4677,6 +4842,24 @@ 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));
|
||||
@@ -5587,6 +5770,9 @@ int main(int argc, char** argv) {
|
||||
CheckNoError(err);
|
||||
}
|
||||
|
||||
StartPhase("transactiondb_batched_multi_get");
|
||||
TestTxnDBBatchedMultiGet();
|
||||
|
||||
StartPhase("transactions");
|
||||
{
|
||||
rocksdb_close(db);
|
||||
|
||||
+34
-7
@@ -475,6 +475,9 @@ 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 "
|
||||
@@ -632,6 +635,8 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
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,
|
||||
@@ -1022,13 +1027,16 @@ 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) {
|
||||
const ImmutableCFOptions& immutable_cf_options,
|
||||
bool table_builder_supported) {
|
||||
const bool auto_compactions =
|
||||
table_builder_supported && !mutable_cf_options.disable_auto_compactions;
|
||||
if (num_unflushed_memtables >= mutable_cf_options.max_write_buffer_number) {
|
||||
return {WriteStallCondition::kStopped, WriteStallCause::kMemtableLimit};
|
||||
} else if (!mutable_cf_options.disable_auto_compactions &&
|
||||
} else if (auto_compactions &&
|
||||
num_l0_files >= mutable_cf_options.level0_stop_writes_trigger) {
|
||||
return {WriteStallCondition::kStopped, WriteStallCause::kL0FileCountLimit};
|
||||
} else if (!mutable_cf_options.disable_auto_compactions &&
|
||||
} else if (auto_compactions &&
|
||||
mutable_cf_options.hard_pending_compaction_bytes_limit > 0 &&
|
||||
num_compaction_needed_bytes >=
|
||||
mutable_cf_options.hard_pending_compaction_bytes_limit) {
|
||||
@@ -1040,12 +1048,12 @@ ColumnFamilyData::GetWriteStallConditionAndCause(
|
||||
num_unflushed_memtables - 1 >=
|
||||
immutable_cf_options.min_write_buffer_number_to_merge) {
|
||||
return {WriteStallCondition::kDelayed, WriteStallCause::kMemtableLimit};
|
||||
} else if (!mutable_cf_options.disable_auto_compactions &&
|
||||
} else if (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 (!mutable_cf_options.disable_auto_compactions &&
|
||||
} else if (auto_compactions &&
|
||||
mutable_cf_options.soft_pending_compaction_bytes_limit > 0 &&
|
||||
num_compaction_needed_bytes >=
|
||||
mutable_cf_options.soft_pending_compaction_bytes_limit) {
|
||||
@@ -1067,7 +1075,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());
|
||||
ioptions(), is_table_builder_supported_);
|
||||
write_stall_condition = write_stall_condition_and_cause.first;
|
||||
auto write_stall_cause = write_stall_condition_and_cause.second;
|
||||
|
||||
@@ -1273,7 +1281,8 @@ void ColumnFamilyData::CreateNewMemtable(SequenceNumber earliest_seq) {
|
||||
}
|
||||
|
||||
bool ColumnFamilyData::NeedsCompaction() const {
|
||||
return !mutable_cf_options_.disable_auto_compactions &&
|
||||
return is_table_builder_supported_ &&
|
||||
!mutable_cf_options_.disable_auto_compactions &&
|
||||
compaction_picker_->NeedsCompaction(current_->storage_info());
|
||||
}
|
||||
|
||||
@@ -1538,6 +1547,13 @@ 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())) {
|
||||
@@ -1738,10 +1754,21 @@ 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;
|
||||
|
||||
+33
-5
@@ -368,6 +368,12 @@ 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);
|
||||
@@ -375,6 +381,19 @@ 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(); }
|
||||
|
||||
@@ -549,12 +568,15 @@ 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);
|
||||
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);
|
||||
|
||||
// Recalculate some stall conditions, which are changed only during
|
||||
// compaction, adding new memtable and/or recalculation of compaction score.
|
||||
@@ -692,6 +714,12 @@ 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_;
|
||||
|
||||
@@ -468,7 +468,6 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AssertCountLiveLogFiles(int value) {
|
||||
@@ -806,6 +805,26 @@ 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;
|
||||
|
||||
@@ -555,6 +555,10 @@ bool Compaction::InputCompressionMatchesOutput() const {
|
||||
}
|
||||
|
||||
bool Compaction::IsTrivialMove() const {
|
||||
if (skip_missing_blob_files_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Avoid a move if there is lots of overlapping grandparent data.
|
||||
// Otherwise, the move could create a parent file that will require
|
||||
// a very expensive merge later on.
|
||||
|
||||
@@ -151,6 +151,14 @@ class Compaction {
|
||||
// Returns input version of the compaction
|
||||
Version* input_version() const { return input_version_; }
|
||||
|
||||
bool skip_missing_blob_files() const { return skip_missing_blob_files_; }
|
||||
|
||||
// Set before registering or running this compaction.
|
||||
void SetSkipMissingBlobFiles(bool skip_missing_blob_files) {
|
||||
assert(is_manual_compaction_ || !skip_missing_blob_files);
|
||||
skip_missing_blob_files_ = skip_missing_blob_files;
|
||||
}
|
||||
|
||||
// Returns the ColumnFamilyData associated with the compaction.
|
||||
ColumnFamilyData* column_family_data() const { return cfd_; }
|
||||
|
||||
@@ -271,6 +279,13 @@ class Compaction {
|
||||
// Does this compaction include all sst files?
|
||||
bool is_full_compaction() const { return is_full_compaction_; }
|
||||
|
||||
bool separate_remote_compaction_pool_selected() const {
|
||||
return separate_remote_compaction_pool_selected_;
|
||||
}
|
||||
void SetSeparateRemoteCompactionPoolSelected(bool selected) {
|
||||
separate_remote_compaction_pool_selected_ = selected;
|
||||
}
|
||||
|
||||
// Was this compaction triggered manually by the client?
|
||||
bool is_manual_compaction() const { return is_manual_compaction_; }
|
||||
|
||||
@@ -531,7 +546,7 @@ class Compaction {
|
||||
|
||||
// Get the atomic file boundaries for all files in the compaction. Necessary
|
||||
// in order to avoid the scenario described in
|
||||
// https://github.com/facebook/rocksdb/pull/4432#discussion_r221072219 and
|
||||
// https://github.com/meta/rocksdb/pull/4432#discussion_r221072219 and
|
||||
// plumb down appropriate key boundaries to RangeDelAggregator during
|
||||
// compaction.
|
||||
static std::vector<CompactionInputFiles> PopulateWithAtomicBoundaries(
|
||||
@@ -609,9 +624,11 @@ class Compaction {
|
||||
const bool bottommost_level_;
|
||||
// Does this compaction include all sst files?
|
||||
const bool is_full_compaction_;
|
||||
bool separate_remote_compaction_pool_selected_ = false;
|
||||
|
||||
// Is this compaction requested by the client?
|
||||
const bool is_manual_compaction_;
|
||||
bool skip_missing_blob_files_ = false;
|
||||
|
||||
// The data with timestamp > trim_ts_ will be removed
|
||||
const std::string trim_ts_;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "db/compaction/compaction_iterator.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
@@ -12,6 +13,7 @@
|
||||
#include "db/blob/blob_fetcher.h"
|
||||
#include "db/blob/blob_file_builder.h"
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/blob/missing_blob_repair_iterator.h"
|
||||
#include "db/blob/prefetch_buffer_collection.h"
|
||||
#include "db/snapshot_checker.h"
|
||||
#include "db/wide/blob_column_resolver_util.h"
|
||||
@@ -139,7 +141,8 @@ CompactionIterator::CompactionIterator(
|
||||
const std::shared_ptr<Logger> info_log,
|
||||
const std::string* full_history_ts_low,
|
||||
std::optional<SequenceNumber> preserve_seqno_min,
|
||||
const Version* input_version, Env::IOActivity blob_read_io_activity)
|
||||
const Version* input_version, Env::IOActivity blob_read_io_activity,
|
||||
std::optional<uint64_t> blob_gc_cutoff_file_number)
|
||||
: CompactionIterator(
|
||||
input, cmp, merge_helper, last_sequence, snapshots, earliest_snapshot,
|
||||
earliest_write_conflict_snapshot, job_snapshot, snapshot_checker, env,
|
||||
@@ -149,7 +152,7 @@ CompactionIterator::CompactionIterator(
|
||||
compaction ? std::make_unique<RealCompaction>(compaction) : nullptr,
|
||||
must_count_input_entries, compaction_filter, shutting_down, info_log,
|
||||
full_history_ts_low, preserve_seqno_min, input_version,
|
||||
blob_read_io_activity) {}
|
||||
blob_read_io_activity, blob_gc_cutoff_file_number) {}
|
||||
|
||||
CompactionIterator::CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
@@ -168,7 +171,8 @@ CompactionIterator::CompactionIterator(
|
||||
const std::shared_ptr<Logger> info_log,
|
||||
const std::string* full_history_ts_low,
|
||||
std::optional<SequenceNumber> preserve_seqno_min,
|
||||
const Version* input_version, Env::IOActivity blob_read_io_activity)
|
||||
const Version* input_version, Env::IOActivity blob_read_io_activity,
|
||||
std::optional<uint64_t> blob_gc_cutoff_file_number)
|
||||
: input_(input, cmp, must_count_input_entries),
|
||||
cmp_(cmp),
|
||||
merge_helper_(merge_helper),
|
||||
@@ -183,6 +187,9 @@ CompactionIterator::CompactionIterator(
|
||||
blob_file_builder_(blob_file_builder),
|
||||
compaction_(std::move(compaction)),
|
||||
compaction_filter_(compaction_filter),
|
||||
merge_order_(merge_helper
|
||||
? merge_helper->merge_order()
|
||||
: CompactionFilter::MergeOrder::kFilterBeforeMerge),
|
||||
filter_supports_v4_(
|
||||
compaction_filter_ ? compaction_filter_->SupportsFilterV4() : false),
|
||||
shutting_down_(shutting_down),
|
||||
@@ -202,7 +209,10 @@ CompactionIterator::CompactionIterator(
|
||||
current_user_key_snapshot_(0),
|
||||
merge_out_iter_(merge_helper_),
|
||||
blob_garbage_collection_cutoff_file_number_(
|
||||
ComputeBlobGarbageCollectionCutoffFileNumber(compaction_.get())),
|
||||
blob_gc_cutoff_file_number.has_value()
|
||||
? *blob_gc_cutoff_file_number
|
||||
: ComputeBlobGarbageCollectionCutoffFileNumber(
|
||||
compaction_.get())),
|
||||
blob_fetcher_(CreateBlobFetcherIfNeeded(compaction_.get(), input_version,
|
||||
blob_read_io_activity)),
|
||||
prefetch_buffers_(
|
||||
@@ -212,8 +222,20 @@ CompactionIterator::CompactionIterator(
|
||||
level_(compaction_ == nullptr ? 0 : compaction_->level()),
|
||||
preserve_seqno_after_(preserve_seqno_min.value_or(earliest_snapshot)) {
|
||||
assert(snapshots_ != nullptr);
|
||||
if (compaction_filter_ &&
|
||||
merge_order_ == CompactionFilter::MergeOrder::kMergeBeforeFilter &&
|
||||
(snapshot_checker_ != nullptr || timestamp_size_ != 0 ||
|
||||
compaction_filter_->IsStackedBlobDbInternalCompactionFilter())) {
|
||||
status_ = Status::NotSupported(
|
||||
"Merge-before-filter does not support SnapshotChecker, timestamps, "
|
||||
"or stacked BlobDB");
|
||||
}
|
||||
assert(preserve_seqno_after_ <= earliest_snapshot_);
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"CompactionIterator::BlobGarbageCollectionCutoffFileNumber",
|
||||
&blob_garbage_collection_cutoff_file_number_);
|
||||
|
||||
if (compaction_ != nullptr) {
|
||||
level_ptrs_ = std::vector<size_t>(compaction_->number_levels(), 0);
|
||||
}
|
||||
@@ -268,18 +290,41 @@ void CompactionIterator::ResetRecordCounts() {
|
||||
}
|
||||
|
||||
void CompactionIterator::SeekToFirst() {
|
||||
SeekToFirstWithoutPreparingOutput();
|
||||
PrepareOutputIfNeeded();
|
||||
}
|
||||
|
||||
void CompactionIterator::SeekToFirstWithoutPreparingOutput() {
|
||||
if (!status_.ok()) {
|
||||
return;
|
||||
}
|
||||
NextFromInput();
|
||||
PrepareOutput();
|
||||
}
|
||||
|
||||
void CompactionIterator::Next() {
|
||||
NextImpl();
|
||||
PrepareOutputIfNeeded();
|
||||
}
|
||||
|
||||
void CompactionIterator::NextWithoutPreparingOutput() { NextImpl(); }
|
||||
|
||||
void CompactionIterator::NextImpl() {
|
||||
if (!status_.ok()) {
|
||||
return;
|
||||
}
|
||||
output_prepared_ = false;
|
||||
// If there is a merge output, return it before continuing to process the
|
||||
// input.
|
||||
if (merge_out_iter_.Valid()) {
|
||||
merge_out_iter_.Next();
|
||||
|
||||
// Check if we returned all records of the merge output.
|
||||
if (merge_out_iter_.Valid()) {
|
||||
if (merge_out_iter_.Valid() &&
|
||||
merge_order_ == CompactionFilter::MergeOrder::kMergeBeforeFilter) {
|
||||
if (!SelectMergeOutput(ValidContext::kMerge1) && status_.ok()) {
|
||||
NextFromInput();
|
||||
}
|
||||
} else if (merge_out_iter_.Valid()) {
|
||||
key_ = merge_out_iter_.key();
|
||||
value_ = merge_out_iter_.value();
|
||||
Status s = ParseInternalKey(key_, &ikey_, allow_data_in_errors_);
|
||||
@@ -339,25 +384,33 @@ void CompactionIterator::Next() {
|
||||
// Record that we've outputted a record for the current key.
|
||||
has_outputted_key_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
PrepareOutput();
|
||||
void CompactionIterator::PrepareOutputIfNeeded() {
|
||||
if (!output_prepared_) {
|
||||
PrepareOutput();
|
||||
output_prepared_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool CompactionIterator::InvokeFilterIfNeeded(bool* need_skip,
|
||||
Slice* skip_until) {
|
||||
Slice* skip_until,
|
||||
bool merge_output) {
|
||||
if (!compaction_filter_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ikey_.type != kTypeValue && ikey_.type != kTypeBlobIndex &&
|
||||
ikey_.type != kTypeWideColumnEntity) {
|
||||
ikey_.type != kTypeWideColumnEntity &&
|
||||
!(merge_output && ikey_.type == kTypeMerge)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
CompactionFilter::Decision decision =
|
||||
CompactionFilter::Decision::kUndetermined;
|
||||
CompactionFilter::ValueType value_type =
|
||||
ikey_.type == kTypeValue ? CompactionFilter::ValueType::kValue
|
||||
ikey_.type == kTypeValue ? CompactionFilter::ValueType::kValue
|
||||
: ikey_.type == kTypeMerge ? CompactionFilter::ValueType::kMergeOperand
|
||||
: ikey_.type == kTypeBlobIndex
|
||||
? CompactionFilter::ValueType::kBlobIndex
|
||||
: CompactionFilter::ValueType::kWideColumnEntity;
|
||||
@@ -590,6 +643,45 @@ bool CompactionIterator::InvokeFilterIfNeeded(bool* need_skip,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (merge_order_ == CompactionFilter::MergeOrder::kMergeBeforeFilter) {
|
||||
const bool operand = ikey_.type == kTypeMerge;
|
||||
switch (decision) {
|
||||
case CompactionFilter::Decision::kKeep:
|
||||
case CompactionFilter::Decision::kRemove:
|
||||
case CompactionFilter::Decision::kChangeValue:
|
||||
case CompactionFilter::Decision::kRemoveAndSkipUntil:
|
||||
break;
|
||||
case CompactionFilter::Decision::kPurge:
|
||||
if (!operand) {
|
||||
// Use ordinary removal for both input values and merge results, so
|
||||
// later compactions apply the same policy. SingleDelete cannot be
|
||||
// mixed with Merge or ordinary Delete on affected keys.
|
||||
decision = CompactionFilter::Decision::kRemove;
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
case CompactionFilter::Decision::kChangeWideColumnEntity:
|
||||
if (!operand) {
|
||||
break;
|
||||
}
|
||||
// Partial results and untouched operands must remain valid merge
|
||||
// operands. Neither SingleDelete nor an entity replacement represents
|
||||
// an operand; kRemove drops its effect and kChangeValue replaces it.
|
||||
[[fallthrough]];
|
||||
case CompactionFilter::Decision::kChangeBlobIndex:
|
||||
case CompactionFilter::Decision::kIOError:
|
||||
case CompactionFilter::Decision::kUndetermined:
|
||||
// kChangeBlobIndex/kIOError belong to the old stacked BlobDB protocol.
|
||||
// Integrated BlobDB changes use kChangeValue and report resolver errors
|
||||
// through Status. kUndetermined is only a FilterBlobByKey fallback,
|
||||
// never a final FilterV2/V3/V4 decision.
|
||||
status_ = Status::NotSupported(
|
||||
"Invalid decision for merge-before-filter value type");
|
||||
validity_info_.Invalidate();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (decision == CompactionFilter::Decision::kRemoveAndSkipUntil &&
|
||||
cmp_->Compare(*compaction_filter_skip_until_.rep(), ikey_.user_key) <=
|
||||
0) {
|
||||
@@ -599,6 +691,11 @@ bool CompactionIterator::InvokeFilterIfNeeded(bool* need_skip,
|
||||
}
|
||||
|
||||
if (decision == CompactionFilter::Decision::kRemove) {
|
||||
if (ikey_.type == kTypeMerge) {
|
||||
++iter_stats_.num_record_drop_user;
|
||||
validity_info_.Invalidate();
|
||||
return true;
|
||||
}
|
||||
// convert the current key to a delete; key_ is pointing into
|
||||
// current_key_ at this point, so updating current_key_ updates key()
|
||||
ikey_.type = kTypeDeletion;
|
||||
@@ -615,7 +712,7 @@ bool CompactionIterator::InvokeFilterIfNeeded(bool* need_skip,
|
||||
value_.clear();
|
||||
iter_stats_.num_record_drop_user++;
|
||||
} else if (decision == CompactionFilter::Decision::kChangeValue) {
|
||||
if (ikey_.type != kTypeValue) {
|
||||
if (ikey_.type != kTypeValue && ikey_.type != kTypeMerge) {
|
||||
ikey_.type = kTypeValue;
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, kTypeValue);
|
||||
}
|
||||
@@ -697,6 +794,221 @@ bool CompactionIterator::InvokeFilterIfNeeded(bool* need_skip,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompactionIterator::PrefilterUncombinedOutputs(uint8_t valid_context) {
|
||||
uncombined_prefiltered_ = true;
|
||||
uncombined_decisions_.clear();
|
||||
uncombined_index_ = 0;
|
||||
MergeOutputIterator it(merge_helper_);
|
||||
for (it.SeekToFirst(); it.Valid(); it.Next()) {
|
||||
key_ = it.key();
|
||||
value_ = it.value();
|
||||
status_ = ParseInternalKey(key_, &ikey_, allow_data_in_errors_);
|
||||
if (!status_.ok()) {
|
||||
validity_info_.Invalidate();
|
||||
merge_out_iter_.Invalidate();
|
||||
return false;
|
||||
}
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
|
||||
key_ = current_key_.GetInternalKey();
|
||||
ikey_.user_key = current_key_.GetUserKey();
|
||||
entity_deserialized_ = false;
|
||||
blob_value_.Reset();
|
||||
validity_info_.SetValid(valid_context);
|
||||
UncombinedDecision decision;
|
||||
if (merge_helper_->OperandEligible(ikey_.sequence)) {
|
||||
bool need_skip = false;
|
||||
Slice skip_until;
|
||||
if (!InvokeFilterIfNeeded(&need_skip, &skip_until, true)) {
|
||||
merge_out_iter_.Invalidate();
|
||||
return false;
|
||||
}
|
||||
if (need_skip) {
|
||||
// Newer operands of this batch were not output yet, so the skip drops
|
||||
// the whole batch.
|
||||
merge_out_iter_.Invalidate();
|
||||
validity_info_.Invalidate();
|
||||
pinned_iters_mgr_.ReleasePinnedData();
|
||||
SkipUntil(skip_until);
|
||||
return false;
|
||||
}
|
||||
decision.keep = Valid();
|
||||
if (decision.keep && value_.data() == compaction_filter_value_.data()) {
|
||||
decision.changed = true;
|
||||
decision.value = compaction_filter_value_;
|
||||
}
|
||||
}
|
||||
uncombined_decisions_.push_back(std::move(decision));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompactionIterator::SelectMergeOutput(uint8_t valid_context) {
|
||||
const bool prefiltered =
|
||||
compaction_filter_ != nullptr &&
|
||||
merge_helper_->outcome() == MergeHelper::Outcome::kUncombined;
|
||||
if (prefiltered && !uncombined_prefiltered_ &&
|
||||
!PrefilterUncombinedOutputs(valid_context)) {
|
||||
return false;
|
||||
}
|
||||
while (merge_out_iter_.Valid() && !IsShuttingDown() &&
|
||||
!IsPausingManualCompaction()) {
|
||||
key_ = merge_out_iter_.key();
|
||||
value_ = merge_out_iter_.value();
|
||||
status_ = ParseInternalKey(key_, &ikey_, allow_data_in_errors_);
|
||||
if (!status_.ok()) {
|
||||
validity_info_.Invalidate();
|
||||
merge_out_iter_.Invalidate();
|
||||
return false;
|
||||
}
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
|
||||
key_ = current_key_.GetInternalKey();
|
||||
ikey_.user_key = current_key_.GetUserKey();
|
||||
entity_deserialized_ = false;
|
||||
blob_value_.Reset();
|
||||
validity_info_.SetValid(valid_context);
|
||||
if (prefiltered) {
|
||||
if (uncombined_index_ < uncombined_decisions_.size()) {
|
||||
const UncombinedDecision& decision =
|
||||
uncombined_decisions_[uncombined_index_];
|
||||
if (!decision.keep) {
|
||||
validity_info_.Invalidate();
|
||||
} else if (decision.changed) {
|
||||
value_ = decision.value;
|
||||
}
|
||||
}
|
||||
++uncombined_index_;
|
||||
} else if (merge_helper_->aggregate_eligible() && compaction_filter_) {
|
||||
bool need_skip = false;
|
||||
Slice skip_until;
|
||||
if (!InvokeFilterIfNeeded(&need_skip, &skip_until, true)) {
|
||||
merge_out_iter_.Invalidate();
|
||||
return false;
|
||||
}
|
||||
if (need_skip) {
|
||||
merge_out_iter_.Invalidate();
|
||||
validity_info_.Invalidate();
|
||||
pinned_iters_mgr_.ReleasePinnedData();
|
||||
SkipUntil(skip_until);
|
||||
return false;
|
||||
}
|
||||
if (Valid() && ikey_.type == kTypeDeletion) {
|
||||
SequenceNumber prev_snapshot = 0;
|
||||
current_user_key_sequence_ = ikey_.sequence;
|
||||
current_user_key_snapshot_ =
|
||||
visible_at_tip_
|
||||
? earliest_snapshot_
|
||||
: findEarliestVisibleSnapshot(ikey_.sequence, &prev_snapshot);
|
||||
ProcessDeletion(prev_snapshot, true);
|
||||
}
|
||||
}
|
||||
if (Valid()) {
|
||||
return true;
|
||||
}
|
||||
merge_out_iter_.Next();
|
||||
}
|
||||
if (merge_until_status_.IsMergeInProgress()) {
|
||||
has_current_user_key_ = false;
|
||||
}
|
||||
pinned_iters_mgr_.ReleasePinnedData();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CompactionIterator::ProcessDeletion(SequenceNumber prev_snapshot,
|
||||
bool input_already_advanced) {
|
||||
if (compaction_ != nullptr &&
|
||||
(ikey_.type == kTypeDeletion ||
|
||||
(ikey_.type == kTypeDeletionWithTimestamp &&
|
||||
cmp_with_history_ts_low_ < 0)) &&
|
||||
!compaction_->allow_ingest_behind() &&
|
||||
DefinitelyInSnapshot(ikey_.sequence, earliest_snapshot_) &&
|
||||
compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key,
|
||||
&level_ptrs_)) {
|
||||
// TODO(noetzli): This is the only place where we use compaction_
|
||||
// (besides the constructor). We should probably get rid of this
|
||||
// dependency and find a way to do similar filtering during flushes.
|
||||
//
|
||||
// For this user key:
|
||||
// (1) there is no data in higher levels
|
||||
// (2) data in lower levels will have larger sequence numbers
|
||||
// (3) data in layers that are being compacted here and have
|
||||
// smaller sequence numbers will be dropped in the next
|
||||
// few iterations of this loop (by rule (A) above).
|
||||
// Therefore this deletion marker is obsolete and can be dropped.
|
||||
//
|
||||
// Note: Dropping this Delete will not affect TransactionDB
|
||||
// write-conflict checking since it is earlier than any snapshot.
|
||||
//
|
||||
// It seems that we can also drop deletion later than earliest snapshot
|
||||
// given that:
|
||||
// (1) The deletion is earlier than earliest_write_conflict_snapshot, and
|
||||
// (2) No value exist earlier than the deletion.
|
||||
//
|
||||
// Note also that a deletion marker of type kTypeDeletionWithTimestamp
|
||||
// will be treated as a different user key unless the timestamp is older
|
||||
// than *full_history_ts_low_.
|
||||
validity_info_.Invalidate();
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
if (!bottommost_level_) {
|
||||
++iter_stats_.num_optimized_del_drop_obsolete;
|
||||
}
|
||||
if (!input_already_advanced) {
|
||||
AdvanceInputIterWithoutOutput();
|
||||
}
|
||||
} else if ((ikey_.type == kTypeDeletion ||
|
||||
(ikey_.type == kTypeDeletionWithTimestamp &&
|
||||
cmp_with_history_ts_low_ < 0)) &&
|
||||
bottommost_level_) {
|
||||
assert(compaction_);
|
||||
assert(!compaction_->allow_ingest_behind()); // bottommost_level_ is true
|
||||
// Handle the case where we have a delete key at the bottom most level
|
||||
// We can skip outputting the key iff there are no subsequent puts for
|
||||
// this key
|
||||
assert(compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key,
|
||||
&level_ptrs_));
|
||||
validity_info_.Invalidate();
|
||||
ParsedInternalKey next_ikey;
|
||||
if (!input_already_advanced) {
|
||||
AdvanceInputIterWithoutOutput();
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
const Compaction* c =
|
||||
compaction_ ? compaction_->real_compaction() : nullptr;
|
||||
#endif
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"CompactionIterator::NextFromInput:BottommostDelete:1",
|
||||
const_cast<Compaction*>(c));
|
||||
// Skip over all versions of this key that happen to occur in the same
|
||||
// snapshot range as the delete.
|
||||
//
|
||||
// Note that a deletion marker of type kTypeDeletionWithTimestamp will be
|
||||
// considered to have a different user key unless the timestamp is older
|
||||
// than *full_history_ts_low_.
|
||||
//
|
||||
// Range tombstone start keys are skipped as they are not "real" keys.
|
||||
while (!IsPausingManualCompaction() && !IsShuttingDown() &&
|
||||
input_.Valid() &&
|
||||
(ParseInternalKey(input_.key(), &next_ikey, allow_data_in_errors_)
|
||||
.ok()) &&
|
||||
cmp_->EqualWithoutTimestamp(ikey_.user_key, next_ikey.user_key) &&
|
||||
(prev_snapshot == 0 || input_.IsDeleteRangeSentinelKey() ||
|
||||
DefinitelyNotInSnapshot(next_ikey.sequence, prev_snapshot))) {
|
||||
AdvanceInputIterWithoutOutput();
|
||||
}
|
||||
// If you find you still need to output a row with this key, we need to
|
||||
// output the delete too
|
||||
if (input_.Valid() &&
|
||||
(ParseInternalKey(input_.key(), &next_ikey, allow_data_in_errors_)
|
||||
.ok()) &&
|
||||
cmp_->EqualWithoutTimestamp(ikey_.user_key, next_ikey.user_key)) {
|
||||
validity_info_.SetValid(ValidContext::kKeepDel);
|
||||
at_next_ = true;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CompactionIterator::NextFromInput() {
|
||||
at_next_ = false;
|
||||
validity_info_.Invalidate();
|
||||
@@ -722,6 +1034,12 @@ void CompactionIterator::NextFromInput() {
|
||||
status_ = pik_status;
|
||||
return;
|
||||
}
|
||||
if (has_consumed_earlier_entries_of_current_key_ &&
|
||||
!cmp_->EqualWithoutTimestamp(ikey_.user_key, current_user_key_)) {
|
||||
// current_user_key_ remains usable when lookahead or merge handling
|
||||
// clears has_current_user_key_.
|
||||
has_consumed_earlier_entries_of_current_key_ = false;
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionIterator:ProcessKV", &ikey_);
|
||||
if (is_range_del_) {
|
||||
validity_info_.SetValid(kRangeDeletion);
|
||||
@@ -807,7 +1125,7 @@ void CompactionIterator::NextFromInput() {
|
||||
// Apply the compaction filter to the first committed version of the user
|
||||
// key.
|
||||
if (current_key_committed_ &&
|
||||
!InvokeFilterIfNeeded(&need_skip, &skip_until)) {
|
||||
!InvokeFilterIfNeeded(&need_skip, &skip_until, false)) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
@@ -829,7 +1147,7 @@ void CompactionIterator::NextFromInput() {
|
||||
// Apply the compaction filter to the first committed version of the
|
||||
// user key.
|
||||
if (current_key_committed_ &&
|
||||
!InvokeFilterIfNeeded(&need_skip, &skip_until)) {
|
||||
!InvokeFilterIfNeeded(&need_skip, &skip_until, false)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -952,14 +1270,14 @@ void CompactionIterator::NextFromInput() {
|
||||
(full_history_ts_low_ && cmp_with_history_ts_low_ < 0));
|
||||
|
||||
ParsedInternalKey next_ikey;
|
||||
AdvanceInputIter();
|
||||
AdvanceInputIterWithoutOutput();
|
||||
while (input_.Valid() && input_.IsDeleteRangeSentinelKey() &&
|
||||
ParseInternalKey(input_.key(), &next_ikey, allow_data_in_errors_)
|
||||
.ok() &&
|
||||
cmp_->EqualWithoutTimestamp(ikey_.user_key, next_ikey.user_key)) {
|
||||
// skip range tombstone start keys with the same user key
|
||||
// since they are not "real" point keys.
|
||||
AdvanceInputIter();
|
||||
AdvanceInputIterWithoutOutput();
|
||||
}
|
||||
|
||||
// Check whether the next key exists, is not corrupt, and is the same key
|
||||
@@ -976,13 +1294,27 @@ void CompactionIterator::NextFromInput() {
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"CompactionIterator::NextFromInput:SingleDelete:1",
|
||||
const_cast<Compaction*>(c));
|
||||
if (last_key_seq_zeroed_) {
|
||||
if (missing_blob_repair_iterator_ &&
|
||||
missing_blob_repair_iterator_->IsRepairDeletion()) {
|
||||
ikey_.type =
|
||||
timestamp_size_ == 0 ? kTypeDeletion : kTypeDeletionWithTimestamp;
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
|
||||
value_.clear();
|
||||
if (DeletionIsObsolete()) {
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
if (!bottommost_level_) {
|
||||
++iter_stats_.num_optimized_del_drop_obsolete;
|
||||
}
|
||||
} else {
|
||||
validity_info_.SetValid(ValidContext::kKeepDel);
|
||||
}
|
||||
} else if (last_key_seq_zeroed_) {
|
||||
// Drop SD and the next key since they are both in the last
|
||||
// snapshot (since last key has seqno zeroed).
|
||||
++iter_stats_.num_record_drop_hidden;
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
assert(bottommost_level_);
|
||||
AdvanceInputIter();
|
||||
AdvanceInputIterWithoutOutput();
|
||||
} else if (prev_snapshot == 0 ||
|
||||
DefinitelyNotInSnapshot(next_ikey.sequence, prev_snapshot)) {
|
||||
// Check whether the next key belongs to the same snapshot as the
|
||||
@@ -1054,7 +1386,7 @@ void CompactionIterator::NextFromInput() {
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
// Already called input_.Next() once. Call it a second time to
|
||||
// skip past the second key.
|
||||
AdvanceInputIter();
|
||||
AdvanceInputIterWithoutOutput();
|
||||
} else {
|
||||
// Found a matching value, but we cannot drop both keys since
|
||||
// there is an earlier snapshot and we need to leave behind a record
|
||||
@@ -1149,15 +1481,8 @@ void CompactionIterator::NextFromInput() {
|
||||
}
|
||||
|
||||
++iter_stats_.num_record_drop_hidden;
|
||||
AdvanceInputIter();
|
||||
} else if (compaction_ != nullptr &&
|
||||
(ikey_.type == kTypeDeletion ||
|
||||
(ikey_.type == kTypeDeletionWithTimestamp &&
|
||||
cmp_with_history_ts_low_ < 0)) &&
|
||||
!compaction_->allow_ingest_behind() &&
|
||||
DefinitelyInSnapshot(ikey_.sequence, earliest_snapshot_) &&
|
||||
compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key,
|
||||
&level_ptrs_)) {
|
||||
AdvanceInputIterWithoutOutput();
|
||||
} else if (DeletionIsObsolete()) {
|
||||
// TODO(noetzli): This is the only place where we use compaction_
|
||||
// (besides the constructor). We should probably get rid of this
|
||||
// dependency and find a way to do similar filtering during flushes.
|
||||
@@ -1185,53 +1510,11 @@ void CompactionIterator::NextFromInput() {
|
||||
if (!bottommost_level_) {
|
||||
++iter_stats_.num_optimized_del_drop_obsolete;
|
||||
}
|
||||
AdvanceInputIter();
|
||||
AdvanceInputIterWithoutOutput();
|
||||
} else if ((ikey_.type == kTypeDeletion ||
|
||||
(ikey_.type == kTypeDeletionWithTimestamp &&
|
||||
cmp_with_history_ts_low_ < 0)) &&
|
||||
bottommost_level_) {
|
||||
assert(compaction_);
|
||||
assert(!compaction_->allow_ingest_behind()); // bottommost_level_ is true
|
||||
// Handle the case where we have a delete key at the bottom most level
|
||||
// We can skip outputting the key iff there are no subsequent puts for
|
||||
// this key
|
||||
assert(compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key,
|
||||
&level_ptrs_));
|
||||
ParsedInternalKey next_ikey;
|
||||
AdvanceInputIter();
|
||||
#ifndef NDEBUG
|
||||
const Compaction* c =
|
||||
compaction_ ? compaction_->real_compaction() : nullptr;
|
||||
#endif
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"CompactionIterator::NextFromInput:BottommostDelete:1",
|
||||
const_cast<Compaction*>(c));
|
||||
// Skip over all versions of this key that happen to occur in the same
|
||||
// snapshot range as the delete.
|
||||
//
|
||||
// Note that a deletion marker of type kTypeDeletionWithTimestamp will be
|
||||
// considered to have a different user key unless the timestamp is older
|
||||
// than *full_history_ts_low_.
|
||||
//
|
||||
// Range tombstone start keys are skipped as they are not "real" keys.
|
||||
while (!IsPausingManualCompaction() && !IsShuttingDown() &&
|
||||
input_.Valid() &&
|
||||
(ParseInternalKey(input_.key(), &next_ikey, allow_data_in_errors_)
|
||||
.ok()) &&
|
||||
cmp_->EqualWithoutTimestamp(ikey_.user_key, next_ikey.user_key) &&
|
||||
(prev_snapshot == 0 || input_.IsDeleteRangeSentinelKey() ||
|
||||
DefinitelyNotInSnapshot(next_ikey.sequence, prev_snapshot))) {
|
||||
AdvanceInputIter();
|
||||
}
|
||||
// If you find you still need to output a row with this key, we need to
|
||||
// output the delete too
|
||||
if (input_.Valid() &&
|
||||
(ParseInternalKey(input_.key(), &next_ikey, allow_data_in_errors_)
|
||||
.ok()) &&
|
||||
cmp_->EqualWithoutTimestamp(ikey_.user_key, next_ikey.user_key)) {
|
||||
validity_info_.SetValid(ValidContext::kKeepDel);
|
||||
at_next_ = true;
|
||||
}
|
||||
ProcessDeletion(prev_snapshot, false)) {
|
||||
} else if (ikey_.type == kTypeValuePreferredSeqno &&
|
||||
DefinitelyInSnapshot(ikey_.sequence, earliest_snapshot_) &&
|
||||
(bottommost_level_ ||
|
||||
@@ -1267,7 +1550,7 @@ void CompactionIterator::NextFromInput() {
|
||||
key_, RangeDelPositioningMode::kForwardTraversal)) {
|
||||
++iter_stats_.num_record_drop_hidden;
|
||||
++iter_stats_.num_record_drop_range_del;
|
||||
AdvanceInputIter();
|
||||
AdvanceInputIterWithoutOutput();
|
||||
} else {
|
||||
InternalKey ikey_after_swap(ikey_.user_key,
|
||||
std::min(preferred_seqno, ikey_.sequence),
|
||||
@@ -1306,36 +1589,45 @@ void CompactionIterator::NextFromInput() {
|
||||
// have hit (A)
|
||||
// We encapsulate the merge related state machine in a different
|
||||
// object to minimize change to the existing flow.
|
||||
has_consumed_earlier_entries_of_current_key_ = true;
|
||||
merge_until_status_ = merge_helper_->MergeUntil(
|
||||
&input_, range_del_agg_, prev_snapshot, bottommost_level_,
|
||||
allow_data_in_errors_, blob_fetcher_.get(), full_history_ts_low_,
|
||||
prefetch_buffers_.get(), &iter_stats_);
|
||||
merge_out_iter_.SeekToFirst();
|
||||
uncombined_prefiltered_ = false;
|
||||
|
||||
if (!merge_until_status_.ok() &&
|
||||
!merge_until_status_.IsMergeInProgress()) {
|
||||
status_ = merge_until_status_;
|
||||
return;
|
||||
} else if (merge_out_iter_.Valid()) {
|
||||
// NOTE: key, value, and ikey_ refer to old entries.
|
||||
// These will be correctly set below.
|
||||
key_ = merge_out_iter_.key();
|
||||
value_ = merge_out_iter_.value();
|
||||
pik_status = ParseInternalKey(key_, &ikey_, allow_data_in_errors_);
|
||||
// MergeUntil stops when it encounters a corrupt key and does not
|
||||
// include them in the result, so we expect the keys here to valid.
|
||||
if (!pik_status.ok()) {
|
||||
ROCKS_LOG_FATAL(
|
||||
info_log_, "Invalid key %s in compaction. %s",
|
||||
allow_data_in_errors_ ? key_.ToString(true).c_str() : "hidden",
|
||||
pik_status.getState());
|
||||
assert(false);
|
||||
if (merge_order_ == CompactionFilter::MergeOrder::kMergeBeforeFilter) {
|
||||
SelectMergeOutput(ValidContext::kMerge2);
|
||||
if (!status_.ok()) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// NOTE: key, value, and ikey_ refer to old entries.
|
||||
// These will be correctly set below.
|
||||
key_ = merge_out_iter_.key();
|
||||
value_ = merge_out_iter_.value();
|
||||
pik_status = ParseInternalKey(key_, &ikey_, allow_data_in_errors_);
|
||||
// MergeUntil stops when it encounters a corrupt key and does not
|
||||
// include them in the result, so we expect the keys here to valid.
|
||||
if (!pik_status.ok()) {
|
||||
ROCKS_LOG_FATAL(
|
||||
info_log_, "Invalid key %s in compaction. %s",
|
||||
allow_data_in_errors_ ? key_.ToString(true).c_str() : "hidden",
|
||||
pik_status.getState());
|
||||
assert(false);
|
||||
}
|
||||
// Keep current_key_ in sync.
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
|
||||
key_ = current_key_.GetInternalKey();
|
||||
ikey_.user_key = current_key_.GetUserKey();
|
||||
validity_info_.SetValid(ValidContext::kMerge2);
|
||||
}
|
||||
// Keep current_key_ in sync.
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
|
||||
key_ = current_key_.GetInternalKey();
|
||||
ikey_.user_key = current_key_.GetUserKey();
|
||||
validity_info_.SetValid(ValidContext::kMerge2);
|
||||
} else {
|
||||
// all merge operands were filtered out. reset the user key, since the
|
||||
// batch consumed by the merge operator should not shadow any keys
|
||||
@@ -1364,7 +1656,7 @@ void CompactionIterator::NextFromInput() {
|
||||
if (should_delete) {
|
||||
++iter_stats_.num_record_drop_hidden;
|
||||
++iter_stats_.num_record_drop_range_del;
|
||||
AdvanceInputIter();
|
||||
AdvanceInputIterWithoutOutput();
|
||||
} else {
|
||||
validity_info_.SetValid(ValidContext::kNewUserKey);
|
||||
}
|
||||
@@ -1387,6 +1679,17 @@ void CompactionIterator::NextFromInput() {
|
||||
}
|
||||
}
|
||||
|
||||
bool CompactionIterator::DeletionIsObsolete() {
|
||||
return compaction_ &&
|
||||
(ikey_.type == kTypeDeletion ||
|
||||
(ikey_.type == kTypeDeletionWithTimestamp &&
|
||||
cmp_with_history_ts_low_ < 0)) &&
|
||||
!compaction_->allow_ingest_behind() &&
|
||||
DefinitelyInSnapshot(ikey_.sequence, earliest_snapshot_) &&
|
||||
compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key,
|
||||
&level_ptrs_);
|
||||
}
|
||||
|
||||
bool CompactionIterator::ExtractLargeValueIfNeededImpl() {
|
||||
if (!blob_file_builder_) {
|
||||
return false;
|
||||
@@ -1855,6 +2158,31 @@ void CompactionIterator::GarbageCollectEntityBlobsIfNeeded() {
|
||||
fetched_blob_values, new_blob_columns);
|
||||
}
|
||||
|
||||
Status CompactionIterator::OutputHasExternalBlobReferences(
|
||||
bool* has_external_refs) const {
|
||||
assert(has_external_refs);
|
||||
*has_external_refs = false;
|
||||
if (ikey_.type == kTypeBlobIndex) {
|
||||
BlobIndex index;
|
||||
const Status s = index.DecodeFrom(value_);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
*has_external_refs = !index.IsInlined() && !index.IsSameFile();
|
||||
return Status::OK();
|
||||
}
|
||||
if (ikey_.type == kTypeWideColumnEntity) {
|
||||
return WideColumnSerialization::ForEachBlobFileNumber(
|
||||
value_, [has_external_refs](const BlobIndex& index) {
|
||||
if (!index.IsInlined() && !index.IsSameFile()) {
|
||||
*has_external_refs = true;
|
||||
}
|
||||
return Status::OK();
|
||||
});
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void CompactionIterator::PrepareOutput() {
|
||||
if (Valid()) {
|
||||
if (LIKELY(!is_range_del_)) {
|
||||
@@ -1941,7 +2269,19 @@ void CompactionIterator::PrepareOutput() {
|
||||
if (BottommostSeqnoCanBeZeroed(
|
||||
ikey_.sequence, preserve_seqno_after_, timestamp_size_,
|
||||
full_history_ts_low_ != nullptr, cmp_with_history_ts_low_ < 0)) {
|
||||
if (!timestamp_size_) {
|
||||
bool has_external_blob_refs = false;
|
||||
if (timestamp_size_) {
|
||||
const Status s =
|
||||
OutputHasExternalBlobReferences(&has_external_blob_refs);
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
(void)status_.ok();
|
||||
validity_info_.Invalidate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// External blob records include the timestamp in their stored user key.
|
||||
if (!timestamp_size_ || has_external_blob_refs) {
|
||||
current_key_.UpdateInternalKey(0, ikey_.type);
|
||||
} else {
|
||||
// For UDT, the seqno and timestamp could only be zeroed out after the
|
||||
|
||||
@@ -5,11 +5,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cinttypes>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "db/blob/blob_index.h"
|
||||
@@ -29,6 +34,7 @@ class BlobFileBuilder;
|
||||
class BlobFileCache;
|
||||
class BlobFetcher;
|
||||
class Version;
|
||||
class MissingBlobRepairIterator;
|
||||
class PrefetchBufferCollection;
|
||||
class FilePrefetchBuffer;
|
||||
class Version;
|
||||
@@ -274,7 +280,8 @@ class CompactionIterator {
|
||||
const std::string* full_history_ts_low = nullptr,
|
||||
std::optional<SequenceNumber> preserve_seqno_min = {},
|
||||
const Version* input_version = nullptr,
|
||||
Env::IOActivity blob_read_io_activity = Env::IOActivity::kCompaction);
|
||||
Env::IOActivity blob_read_io_activity = Env::IOActivity::kCompaction,
|
||||
std::optional<uint64_t> blob_gc_cutoff_file_number = {});
|
||||
|
||||
// Constructor with custom CompactionProxy, used for tests.
|
||||
CompactionIterator(
|
||||
@@ -296,7 +303,8 @@ class CompactionIterator {
|
||||
const std::string* full_history_ts_low = nullptr,
|
||||
std::optional<SequenceNumber> preserve_seqno_min = {},
|
||||
const Version* input_version = nullptr,
|
||||
Env::IOActivity blob_read_io_activity = Env::IOActivity::kCompaction);
|
||||
Env::IOActivity blob_read_io_activity = Env::IOActivity::kCompaction,
|
||||
std::optional<uint64_t> blob_gc_cutoff_file_number = {});
|
||||
|
||||
~CompactionIterator();
|
||||
|
||||
@@ -309,16 +317,36 @@ class CompactionIterator {
|
||||
Env::IOActivity io_activity,
|
||||
bool allow_write_path_fallback);
|
||||
|
||||
// Install before the first seek; the normalizer must outlive this iterator.
|
||||
void SetMissingBlobRepairIterator(const MissingBlobRepairIterator* iterator) {
|
||||
assert(key_.empty());
|
||||
missing_blob_repair_iterator_ = iterator;
|
||||
}
|
||||
|
||||
static uint64_t ComputeBlobGarbageCollectionCutoffFileNumber(
|
||||
const CompactionProxy* compaction);
|
||||
|
||||
// Seek to the beginning of the compaction iterator output.
|
||||
//
|
||||
// REQUIRED: Call only once.
|
||||
void SeekToFirst();
|
||||
|
||||
// Positions at the first output without extracting/relocating blobs yet.
|
||||
// CompactionJob uses this when an SST checkpoint may need to be persisted
|
||||
// before the current key is added to a blob file.
|
||||
void SeekToFirstWithoutPreparingOutput();
|
||||
|
||||
// Produces the next record in the compaction.
|
||||
//
|
||||
// REQUIRED: SeekToFirst() has been called.
|
||||
void Next();
|
||||
|
||||
// Advances without extracting/relocating blobs for the new current key.
|
||||
void NextWithoutPreparingOutput();
|
||||
|
||||
// Completes output preparation once for the current key.
|
||||
void PrepareOutputIfNeeded();
|
||||
|
||||
// Getters
|
||||
const Slice& key() const { return key_; }
|
||||
const Slice& value() const { return value_; }
|
||||
@@ -348,6 +376,15 @@ class CompactionIterator {
|
||||
return at_next_ || merge_out_iter_.Valid();
|
||||
}
|
||||
|
||||
// Returns true if an earlier input entry for the current valid user key was
|
||||
// consumed without being output.
|
||||
//
|
||||
// REQUIRED: Valid() must be true
|
||||
bool HasConsumedEarlierEntriesOfCurrentKey() const {
|
||||
assert(Valid());
|
||||
return has_consumed_earlier_entries_of_current_key_;
|
||||
}
|
||||
|
||||
Status InputStatus() const { return input_.status(); }
|
||||
|
||||
bool IsDeleteRangeSentinelKey() const { return is_range_del_; }
|
||||
@@ -356,6 +393,8 @@ class CompactionIterator {
|
||||
// Processes the input stream to find the next output
|
||||
void NextFromInput();
|
||||
|
||||
void NextImpl();
|
||||
|
||||
// Do final preparations before presenting the output to the callee.
|
||||
void PrepareOutput();
|
||||
|
||||
@@ -421,7 +460,16 @@ class CompactionIterator {
|
||||
|
||||
// Invoke compaction filter if needed.
|
||||
// Return true on success, false on failures (e.g.: kIOError).
|
||||
bool InvokeFilterIfNeeded(bool* need_skip, Slice* skip_until);
|
||||
bool InvokeFilterIfNeeded(bool* need_skip, Slice* skip_until,
|
||||
bool merge_output);
|
||||
bool SelectMergeOutput(uint8_t valid_context);
|
||||
bool PrefilterUncombinedOutputs(uint8_t valid_context);
|
||||
bool ProcessDeletion(SequenceNumber prev_snapshot,
|
||||
bool input_already_advanced);
|
||||
|
||||
bool DeletionIsObsolete();
|
||||
|
||||
Status OutputHasExternalBlobReferences(bool* has_external_refs) const;
|
||||
|
||||
// Given a sequence number, return the sequence number of the
|
||||
// earliest snapshot that this sequence number is visible in.
|
||||
@@ -456,8 +504,6 @@ class CompactionIterator {
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t ComputeBlobGarbageCollectionCutoffFileNumber(
|
||||
const CompactionProxy* compaction);
|
||||
static std::unique_ptr<BlobFetcher> CreateBlobFetcherIfNeeded(
|
||||
const CompactionProxy* compaction, const Version* input_version,
|
||||
Env::IOActivity blob_read_io_activity);
|
||||
@@ -465,6 +511,7 @@ class CompactionIterator {
|
||||
CreatePrefetchBufferCollectionIfNeeded(const CompactionProxy* compaction);
|
||||
|
||||
SequenceIterWrapper input_;
|
||||
const MissingBlobRepairIterator* missing_blob_repair_iterator_ = nullptr;
|
||||
const Comparator* cmp_;
|
||||
MergeHelper* merge_helper_;
|
||||
const std::vector<SequenceNumber>* snapshots_;
|
||||
@@ -484,6 +531,7 @@ class CompactionIterator {
|
||||
BlobFileBuilder* blob_file_builder_;
|
||||
std::unique_ptr<CompactionProxy> compaction_;
|
||||
const CompactionFilter* compaction_filter_;
|
||||
const CompactionFilter::MergeOrder merge_order_;
|
||||
const bool filter_supports_v4_;
|
||||
const std::atomic<bool>* shutting_down_;
|
||||
const std::atomic<bool>& manual_compaction_canceled_;
|
||||
@@ -557,6 +605,8 @@ class CompactionIterator {
|
||||
// If false, the iterator holds a copy of the current compaction iterator
|
||||
// output (or current key in the underlying iterator during NextFromInput()).
|
||||
bool at_next_ = false;
|
||||
// Whether an earlier input entry of the current user key was not output.
|
||||
bool has_consumed_earlier_entries_of_current_key_ = false;
|
||||
|
||||
// A copy of the current internal key.
|
||||
IterKey current_key_;
|
||||
@@ -574,6 +624,17 @@ class CompactionIterator {
|
||||
bool clear_and_output_next_key_ = false;
|
||||
|
||||
MergeOutputIterator merge_out_iter_;
|
||||
// Merge-before-filter decisions for the current batch of uncombined
|
||||
// operands. They are made before any operand is output, so
|
||||
// kRemoveAndSkipUntil drops the whole batch, as in the legacy order.
|
||||
struct UncombinedDecision {
|
||||
bool keep = true;
|
||||
bool changed = false;
|
||||
std::string value;
|
||||
};
|
||||
std::vector<UncombinedDecision> uncombined_decisions_;
|
||||
size_t uncombined_index_ = 0;
|
||||
bool uncombined_prefiltered_ = false;
|
||||
Status merge_until_status_;
|
||||
// PinnedIteratorsManager used to pin input_ Iterator blocks while reading
|
||||
// merge operands and then releasing them after consuming them.
|
||||
@@ -614,6 +675,7 @@ class CompactionIterator {
|
||||
// it) and PrepareOutput() can skip redundant work. Reset for each candidate
|
||||
// input record and whenever the filter rewrites the entity value.
|
||||
bool entity_deserialized_{false};
|
||||
bool output_prepared_{false};
|
||||
// "level_ptrs" holds indices that remember which file of an associated
|
||||
// level we were last checking during the last call to compaction->
|
||||
// KeyNotExistsBeyondOutputLevel(). This allows future calls to the function
|
||||
@@ -641,7 +703,16 @@ class CompactionIterator {
|
||||
|
||||
void AdvanceInputIter() { input_.Next(); }
|
||||
|
||||
void SkipUntil(const Slice& skip_until) { input_.Seek(skip_until); }
|
||||
// Advances past an input entry that will not be emitted as output.
|
||||
void AdvanceInputIterWithoutOutput() {
|
||||
has_consumed_earlier_entries_of_current_key_ = true;
|
||||
input_.Next();
|
||||
}
|
||||
|
||||
void SkipUntil(const Slice& skip_until) {
|
||||
has_consumed_earlier_entries_of_current_key_ = true;
|
||||
input_.Seek(skip_until);
|
||||
}
|
||||
|
||||
bool IsShuttingDown() {
|
||||
// This is a best-effort facility, so memory_order_relaxed is sufficient.
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+588
-93
File diff suppressed because it is too large.
Load diff
+162
-14
@@ -14,9 +14,11 @@
|
||||
#include <limits>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "db/blob/blob_counting_iterator.h"
|
||||
#include "db/blob/blob_file_completion_callback.h"
|
||||
#include "db/column_family.h"
|
||||
#include "db/compaction/compaction_iterator.h"
|
||||
@@ -51,6 +53,7 @@ class Arena;
|
||||
class CompactionState;
|
||||
class ErrorHandler;
|
||||
class MemTable;
|
||||
class MissingBlobRepairIterator;
|
||||
class SnapshotChecker;
|
||||
class SystemClock;
|
||||
class TableCache;
|
||||
@@ -171,6 +174,20 @@ class CompactionJob {
|
||||
|
||||
virtual ~CompactionJob();
|
||||
|
||||
using RemoteCompactionExecutor = std::function<void(std::function<void()>)>;
|
||||
using LocalCompactionExecutor =
|
||||
std::function<Status(std::function<void()>, Env::Priority)>;
|
||||
|
||||
// Set before Prepare(). Remote submissions are asynchronous: returning
|
||||
// accepts the task exactly once, and throwing must leave it unsubmitted.
|
||||
// Local execution blocks until the task finishes or is canceled. Both
|
||||
// executors must outlive Run(). Service workers leave these unset.
|
||||
void SetCompactionExecutors(RemoteCompactionExecutor remote_executor,
|
||||
LocalCompactionExecutor local_executor) {
|
||||
remote_compaction_executor_ = std::move(remote_executor);
|
||||
local_compaction_executor_ = std::move(local_executor);
|
||||
}
|
||||
|
||||
// no copy/move
|
||||
CompactionJob(CompactionJob&& job) = delete;
|
||||
CompactionJob(const CompactionJob& job) = delete;
|
||||
@@ -223,6 +240,7 @@ class CompactionJob {
|
||||
|
||||
// Iterate through input and compact the kv-pairs.
|
||||
void ProcessKeyValueCompaction(SubcompactionState* sub_compact);
|
||||
void ProcessLocalKeyValueCompaction(SubcompactionState* sub_compact);
|
||||
|
||||
CompactionState* compact_;
|
||||
InternalStats::CompactionStatsFull internal_stats_;
|
||||
@@ -240,6 +258,10 @@ class CompactionJob {
|
||||
|
||||
CompactionJobStats* job_stats_;
|
||||
|
||||
// Lower bound for the minimal sequence number whose write time is
|
||||
// preserved. See Prepare().
|
||||
SequenceNumber preserve_time_min_seqno_floor_ = 0;
|
||||
|
||||
private:
|
||||
friend class CompactionJobTestBase;
|
||||
|
||||
@@ -307,6 +329,7 @@ class CompactionJob {
|
||||
|
||||
void InitializeCompactionRun();
|
||||
void RunSubcompactions();
|
||||
void RecordRemoteCompactionInputBlobFileCount();
|
||||
void UpdateTimingStats(uint64_t start_micros);
|
||||
void RemoveEmptyOutputs();
|
||||
void CleanupAbortedSubcompactions();
|
||||
@@ -326,7 +349,8 @@ class CompactionJob {
|
||||
uint64_t num_input_range_del);
|
||||
|
||||
CompactionServiceJobStatus ProcessKeyValueCompactionWithCompactionService(
|
||||
SubcompactionState* sub_compact);
|
||||
SubcompactionState* sub_compact,
|
||||
CompactionFilter::MergeOrder merge_order);
|
||||
|
||||
struct CompactionIOStatsSnapshot {
|
||||
PerfLevel prev_perf_level = PerfLevel::kEnableTime;
|
||||
@@ -370,8 +394,9 @@ class CompactionJob {
|
||||
struct SubcompactionInternalIterators {
|
||||
std::unique_ptr<InternalIterator> raw_input;
|
||||
std::unique_ptr<InternalIterator> clip;
|
||||
std::unique_ptr<InternalIterator> blob_counter;
|
||||
std::unique_ptr<BlobCountingIterator> blob_counter;
|
||||
std::unique_ptr<InternalIterator> trim_history_iter;
|
||||
std::unique_ptr<MissingBlobRepairIterator> missing_blob_repair;
|
||||
};
|
||||
|
||||
bool ShouldUseLocalCompaction(SubcompactionState* sub_compact);
|
||||
@@ -380,7 +405,8 @@ class CompactionJob {
|
||||
SubcompactionState* sub_compact,
|
||||
const CompactionFilter* configured_compaction_filter,
|
||||
const CompactionFilter*& compaction_filter,
|
||||
std::unique_ptr<CompactionFilter>& compaction_filter_from_factory);
|
||||
std::unique_ptr<CompactionFilter>& compaction_filter_from_factory,
|
||||
CompactionFilter::MergeOrder& merge_order);
|
||||
void InitializeReadOptionsAndBoundaries(
|
||||
size_t ts_sz, ReadOptions& read_options,
|
||||
SubcompactionKeyBoundaries& boundaries);
|
||||
@@ -398,11 +424,14 @@ class CompactionJob {
|
||||
MergeHelper& merge, std::unique_ptr<BlobFileBuilder>& blob_file_builder,
|
||||
const WriteOptions& write_options);
|
||||
std::pair<CompactionFileOpenFunc, CompactionFileCloseFunc> CreateFileHandlers(
|
||||
SubcompactionState* sub_compact, SubcompactionKeyBoundaries& boundaries);
|
||||
SubcompactionState* sub_compact, SubcompactionKeyBoundaries& boundaries,
|
||||
BlobFileBuilder* blob_file_builder,
|
||||
const BlobCountingIterator* blob_counter);
|
||||
Status ProcessKeyValue(SubcompactionState* sub_compact, ColumnFamilyData* cfd,
|
||||
CompactionIterator* c_iter,
|
||||
const CompactionFileOpenFunc& open_file_func,
|
||||
const CompactionFileCloseFunc& close_file_func,
|
||||
bool defer_blob_output_preparation,
|
||||
uint64_t& prev_cpu_micros);
|
||||
void UpdateSubcompactionJobStatsIncrementally(
|
||||
CompactionIterator* c_iter, CompactionJobStats* compaction_job_stats,
|
||||
@@ -431,15 +460,17 @@ class CompactionJob {
|
||||
uint64_t prev_cpu_micros,
|
||||
const CompactionIOStatsSnapshot& io_stats);
|
||||
|
||||
// update the thread status for starting a compaction.
|
||||
void ReportStartedCompaction(Compaction* compaction);
|
||||
void InitializeThreadStatus();
|
||||
|
||||
Status FinishCompactionOutputFile(
|
||||
const Status& input_status,
|
||||
const ParsedInternalKey& prev_iter_output_internal_key,
|
||||
const Slice& next_table_min_key, const Slice* comp_start_user_key,
|
||||
const Slice* comp_end_user_key, const CompactionIterator* c_iter,
|
||||
SubcompactionState* sub_compact, CompactionOutputs& outputs);
|
||||
bool post_prepare_retry, SubcompactionState* sub_compact,
|
||||
BlobFileBuilder* blob_file_builder,
|
||||
const BlobCountingIterator* blob_counter, CompactionOutputs& outputs);
|
||||
Status InstallCompactionResults(bool* compaction_released);
|
||||
Status OpenCompactionOutputFile(SubcompactionState* sub_compact,
|
||||
CompactionOutputs& outputs);
|
||||
@@ -452,6 +483,8 @@ class CompactionJob {
|
||||
void NotifyOnSubcompactionCompleted(SubcompactionState* sub_compact);
|
||||
|
||||
uint32_t job_id_;
|
||||
// Limits unsafe resume-boundary diagnostics to one per compaction job.
|
||||
mutable std::atomic<bool> logged_unsafe_resume_boundary_{false};
|
||||
|
||||
// DBImpl state
|
||||
const std::string& dbname_;
|
||||
@@ -507,6 +540,8 @@ class CompactionJob {
|
||||
// has no owning DBImpl to report to (compaction service workers, unit tests).
|
||||
// Updated without the DB mutex held.
|
||||
std::atomic<int>* num_running_remote_compactions_;
|
||||
RemoteCompactionExecutor remote_compaction_executor_;
|
||||
LocalCompactionExecutor local_compaction_executor_;
|
||||
|
||||
// Stores the sequence number to time mapping gathered from all input files
|
||||
// it also collects the smallest_seqno -> oldest_ancestor_time from the SST.
|
||||
@@ -532,16 +567,45 @@ class CompactionJob {
|
||||
uint64_t min_manifest_file_number_ = 0;
|
||||
uint64_t min_manifest_file_size_ = 0;
|
||||
|
||||
// Remote subcompactions run concurrently and validate their blob garbage
|
||||
// before moving output into the primary. This aggregate prevents each
|
||||
// subcompaction from independently claiming up to the same blob-file total.
|
||||
port::Mutex remote_blob_garbage_mutex_;
|
||||
std::unordered_map<uint64_t, BlobGarbageMeter::BlobStats>
|
||||
remote_blob_garbage_;
|
||||
|
||||
// Minimum blob-file dependency among subcompactions successfully dispatched
|
||||
// to a remote worker. Recorded once for the whole job after they finish.
|
||||
std::atomic<uint64_t> remote_compaction_min_blob_file_number_{
|
||||
std::numeric_limits<uint64_t>::max()};
|
||||
|
||||
// Writer for persisting compaction progress during compaction
|
||||
log::Writer* compaction_progress_writer_ = nullptr;
|
||||
|
||||
// Get table file name in where it's outputting to, which should also be in
|
||||
// `output_directory_`.
|
||||
virtual std::string GetTableFileName(uint64_t file_number);
|
||||
virtual const std::string& GetBlobOutputPath() const;
|
||||
virtual uint64_t NewBlobFileNumber();
|
||||
virtual uint64_t GetNextBlobFileNumber() const;
|
||||
virtual Status RestoreNextBlobFileNumber(
|
||||
const SubcompactionProgress& subcompaction_progress);
|
||||
virtual std::optional<uint64_t> GetBlobGarbageCollectionCutoffFileNumber()
|
||||
const {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// True if this job builds output files as a remote (offloaded)
|
||||
// CompactionService worker. Overridden by CompactionServiceCompactionJob.
|
||||
virtual bool IsRemoteCompaction() const { return false; }
|
||||
// Checks the actual filter order against what the job requires before any
|
||||
// input is processed. A remote worker compares it with the primary's
|
||||
// request and records the acknowledgment or decline in its result.
|
||||
virtual Status ValidateCompactionFilterMergeOrder(
|
||||
const CompactionFilter* /*compaction_filter*/,
|
||||
CompactionFilter::MergeOrder /*merge_order*/) {
|
||||
return Status::OK();
|
||||
}
|
||||
// The rate limiter priority (io_priority) is determined dynamically here.
|
||||
// The Compaction Read and Write priorities are the same for different
|
||||
// scenarios, such as write stalled.
|
||||
@@ -555,7 +619,7 @@ class CompactionJob {
|
||||
const ReadOptions& read_options,
|
||||
std::vector<std::shared_ptr<const TableProperties>>&
|
||||
output_files_table_properties,
|
||||
bool is_proximal_level = false);
|
||||
bool is_proximal_level);
|
||||
|
||||
Status ReadTablePropertiesDirectly(
|
||||
const ImmutableOptions& ioptions, const MutableCFOptions& moptions,
|
||||
@@ -572,12 +636,22 @@ class CompactionJob {
|
||||
bool ShouldUpdateSubcompactionProgress(
|
||||
const SubcompactionState* sub_compact, const CompactionIterator* c_iter,
|
||||
const ParsedInternalKey& prev_iter_output_internal_key,
|
||||
const Slice& next_table_min_internal_key, const FileMetaData* meta) const;
|
||||
const Slice& next_table_min_internal_key, const FileMetaData* meta,
|
||||
const BlobFileBuilder* blob_file_builder,
|
||||
const BlobCountingIterator* blob_counter, bool post_prepare_retry) const;
|
||||
|
||||
void UpdateSubcompactionProgress(const CompactionIterator* c_iter,
|
||||
const Slice next_table_min_key,
|
||||
SubcompactionState* sub_compact);
|
||||
|
||||
Status RestoreBlobCompactionProgress(
|
||||
SubcompactionState* sub_compact,
|
||||
const SubcompactionProgress& subcompaction_progress);
|
||||
|
||||
void UpdateBlobCompactionProgress(
|
||||
SubcompactionState* sub_compact,
|
||||
SubcompactionProgress& subcompaction_progress);
|
||||
|
||||
Status PersistSubcompactionProgress(SubcompactionState* sub_compact);
|
||||
|
||||
void UpdateSubcompactionProgressPerLevel(
|
||||
@@ -585,6 +659,12 @@ class CompactionJob {
|
||||
SubcompactionProgress& subcompaction_progress);
|
||||
};
|
||||
|
||||
// Version of the blob-aware remote compaction result produced by this binary.
|
||||
// Zero means the worker did not advertise support.
|
||||
constexpr uint64_t kCompactionServiceBlobVersion = 1;
|
||||
constexpr uint64_t kCompactionServiceBlobGCCutoffVersion = 1;
|
||||
constexpr uint64_t kCompactionServiceMergeBeforeFilterVersion = 1;
|
||||
|
||||
// CompactionServiceInput is used the pass compaction information between two
|
||||
// db instances. It contains the information needed to do a compaction. It
|
||||
// doesn't contain the LSM tree information, which is passed though MANIFEST
|
||||
@@ -592,6 +672,11 @@ class CompactionJob {
|
||||
struct CompactionServiceInput {
|
||||
std::string cf_name;
|
||||
|
||||
// Requests merge-before-filter. Old workers ignore this field and return no
|
||||
// applied-order acknowledgment, so the primary falls back to local compaction
|
||||
// before importing their output.
|
||||
bool merge_before_filter = false;
|
||||
|
||||
// Snapshot sequence numbers in strictly increasing order.
|
||||
std::vector<SequenceNumber> snapshots;
|
||||
|
||||
@@ -599,6 +684,23 @@ struct CompactionServiceInput {
|
||||
// files needed for this compaction, for both input level files and output
|
||||
// level files.
|
||||
std::vector<std::string> input_files;
|
||||
|
||||
// Blob files potentially referenced by input_files. CompactionService
|
||||
// implementations can use this list to make the files available to the
|
||||
// remote worker.
|
||||
std::vector<std::string> input_blob_files;
|
||||
|
||||
// When present, the remote worker uses this primary-computed cutoff instead
|
||||
// of deriving one from its reconstructed Version. An absent field disables
|
||||
// blob GC on the worker for compatibility with older primaries.
|
||||
bool has_blob_gc_cutoff_file_number = false;
|
||||
uint64_t blob_gc_cutoff_file_number = 0;
|
||||
|
||||
// The primary reserves this half-open file-number range before scheduling
|
||||
// the remote compaction. A worker that creates blob files must allocate them
|
||||
// consecutively from this range.
|
||||
uint64_t blob_file_number_start = kInvalidBlobFileNumber;
|
||||
uint64_t blob_file_number_limit = kInvalidBlobFileNumber;
|
||||
int output_level = 0;
|
||||
|
||||
// db_id is used to generate unique id of sst on the remote compactor
|
||||
@@ -624,6 +726,24 @@ struct CompactionServiceInput {
|
||||
uint64_t min_manifest_file_number = 0;
|
||||
uint64_t min_manifest_file_size = 0;
|
||||
|
||||
// The primary's values. The worker opens the DB as a secondary, which
|
||||
// sanitizes them to 0 as a read-only DB. They are sent here rather than
|
||||
// re-read from the OPTIONS file so that they always arrive together with
|
||||
// preserve_time_min_seqno_floor: a preserve window without that floor would
|
||||
// bring back the kBottommostFiles compaction loop.
|
||||
uint64_t preserve_internal_time_seconds = 0;
|
||||
uint64_t preclude_last_level_data_seconds = 0;
|
||||
// The primary's lower bound for the minimal sequence number whose write time
|
||||
// is preserved: the version-carried preserve boundary for a kBottommostFiles
|
||||
// compaction, 0 otherwise.
|
||||
SequenceNumber preserve_time_min_seqno_floor = 0;
|
||||
// The primary's per-key placement decisions. The worker declines the job if
|
||||
// its own Compaction differs. proximal_level is Compaction::kInvalidLevel
|
||||
// when per-key placement is off, and 0 only from an older primary.
|
||||
int proximal_level = 0;
|
||||
int proximal_output_range_type = 0;
|
||||
SequenceNumber keep_in_last_level_through_seqno = 0;
|
||||
|
||||
// serialization interface to read and write the object
|
||||
static Status Read(const std::string& data_str, CompactionServiceInput* obj);
|
||||
Status Write(std::string* output);
|
||||
@@ -634,7 +754,9 @@ struct CompactionServiceInput {
|
||||
#endif // NDEBUG
|
||||
};
|
||||
|
||||
// CompactionServiceOutputFile is the metadata for the output SST file
|
||||
// CompactionServiceOutputFile is the metadata for the output SST file. Parsing
|
||||
// a serialized result sets only the fields present in it, so every member
|
||||
// needs a default value.
|
||||
struct CompactionServiceOutputFile {
|
||||
std::string file_name;
|
||||
uint64_t file_size{};
|
||||
@@ -642,25 +764,26 @@ struct CompactionServiceOutputFile {
|
||||
SequenceNumber largest_seqno{};
|
||||
std::string smallest_internal_key;
|
||||
std::string largest_internal_key;
|
||||
uint64_t oldest_blob_file_number = kInvalidBlobFileNumber;
|
||||
uint64_t oldest_ancestor_time = kUnknownOldestAncestorTime;
|
||||
uint64_t file_creation_time = kUnknownFileCreationTime;
|
||||
uint64_t epoch_number = kUnknownEpochNumber;
|
||||
std::string file_checksum = kUnknownFileChecksum;
|
||||
std::string file_checksum_func_name = kUnknownFileChecksumFuncName;
|
||||
uint64_t paranoid_hash{};
|
||||
bool marked_for_compaction;
|
||||
bool marked_for_compaction = false;
|
||||
UniqueId64x2 unique_id{};
|
||||
TableProperties table_properties;
|
||||
bool is_proximal_level_output;
|
||||
bool is_proximal_level_output = false;
|
||||
Temperature file_temperature = Temperature::kUnknown;
|
||||
|
||||
CompactionServiceOutputFile() = default;
|
||||
CompactionServiceOutputFile(
|
||||
const std::string& name, uint64_t size, SequenceNumber smallest,
|
||||
SequenceNumber largest, std::string _smallest_internal_key,
|
||||
std::string _largest_internal_key, uint64_t _oldest_ancestor_time,
|
||||
uint64_t _file_creation_time, uint64_t _epoch_number,
|
||||
const std::string& _file_checksum,
|
||||
std::string _largest_internal_key, uint64_t _oldest_blob_file_number,
|
||||
uint64_t _oldest_ancestor_time, uint64_t _file_creation_time,
|
||||
uint64_t _epoch_number, const std::string& _file_checksum,
|
||||
const std::string& _file_checksum_func_name, uint64_t _paranoid_hash,
|
||||
bool _marked_for_compaction, UniqueId64x2 _unique_id,
|
||||
const TableProperties& _table_properties, bool _is_proximal_level_output,
|
||||
@@ -671,6 +794,7 @@ struct CompactionServiceOutputFile {
|
||||
largest_seqno(largest),
|
||||
smallest_internal_key(std::move(_smallest_internal_key)),
|
||||
largest_internal_key(std::move(_largest_internal_key)),
|
||||
oldest_blob_file_number(_oldest_blob_file_number),
|
||||
oldest_ancestor_time(_oldest_ancestor_time),
|
||||
file_creation_time(_file_creation_time),
|
||||
epoch_number(_epoch_number),
|
||||
@@ -689,7 +813,19 @@ struct CompactionServiceOutputFile {
|
||||
// permission is able to install the result to the DB.
|
||||
struct CompactionServiceResult {
|
||||
Status status;
|
||||
// Nonzero only after validating the actual worker filter/factory order.
|
||||
uint64_t merge_before_filter_version = 0;
|
||||
// The worker declined an order mismatch before processing input records.
|
||||
bool merge_order_mismatch = false;
|
||||
std::vector<CompactionServiceOutputFile> output_files;
|
||||
|
||||
// Blob metadata generated by the remote compaction. Blob file additions
|
||||
// identify files in output_path that must be installed before the output
|
||||
// SSTs. Garbage records apply to blob files already known to the primary.
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
std::vector<BlobFileGarbage> blob_file_garbages;
|
||||
uint64_t blob_compaction_version = 0;
|
||||
uint64_t blob_gc_cutoff_version = 0;
|
||||
int output_level = 0;
|
||||
|
||||
// location of the output files
|
||||
@@ -762,13 +898,25 @@ class CompactionServiceCompactionJob : private CompactionJob {
|
||||
private:
|
||||
// Get table file name in output_path
|
||||
std::string GetTableFileName(uint64_t file_number) override;
|
||||
const std::string& GetBlobOutputPath() const override;
|
||||
uint64_t NewBlobFileNumber() override;
|
||||
uint64_t GetNextBlobFileNumber() const override;
|
||||
Status RestoreNextBlobFileNumber(
|
||||
const SubcompactionProgress& subcompaction_progress) override;
|
||||
std::optional<uint64_t> GetBlobGarbageCollectionCutoffFileNumber()
|
||||
const override;
|
||||
bool IsRemoteCompaction() const override { return true; }
|
||||
Status ValidateCompactionFilterMergeOrder(
|
||||
const CompactionFilter* compaction_filter,
|
||||
CompactionFilter::MergeOrder merge_order) override;
|
||||
// Specific the compaction output path, otherwise it uses default DB path
|
||||
const std::string output_path_;
|
||||
|
||||
// Compaction job input
|
||||
const CompactionServiceInput& compaction_input_;
|
||||
|
||||
uint64_t next_blob_file_number_;
|
||||
|
||||
// Compaction job result
|
||||
CompactionServiceResult* compaction_result_;
|
||||
};
|
||||
|
||||
@@ -196,6 +196,42 @@ class MockTestFileSystem : public FileSystemWrapper {
|
||||
|
||||
enum TableTypeForTest : uint8_t { kMockTable = 0, kBlockBasedTable = 1 };
|
||||
|
||||
// Replaces the first scalar `name=value;` entry after `scope` in a serialized
|
||||
// CompactionServiceResult with `replacement`. An empty `replacement` removes
|
||||
// the entry. Returns false if there is no such entry.
|
||||
bool ReplaceResultEntry(std::string* serialized, const std::string& scope,
|
||||
const std::string& name,
|
||||
const std::string& replacement) {
|
||||
const size_t scope_pos = serialized->find(scope);
|
||||
if (scope_pos == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
// Match the whole name, so that "code" does not match "subcode".
|
||||
size_t pos = std::string::npos;
|
||||
for (const char delimiter : {'{', ';'}) {
|
||||
pos = std::min(pos, serialized->find(delimiter + name + "=", scope_pos));
|
||||
}
|
||||
if (pos == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
const size_t end = serialized->find(';', pos + 1);
|
||||
if (end == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
serialized->replace(pos + 1, end - pos, replacement);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Formats every field of `s`. A failed gtest comparison prints the `Status`
|
||||
// enums as raw characters, so tests compare these strings instead.
|
||||
std::string StatusFields(const Status& s) {
|
||||
const char* state = s.getState();
|
||||
return "code=" + std::to_string(s.code()) +
|
||||
" subcode=" + std::to_string(s.subcode()) +
|
||||
" severity=" + std::to_string(s.severity()) +
|
||||
" message=" + (state != nullptr ? state : "");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class CompactionJobTestBase : public testing::Test {
|
||||
@@ -787,6 +823,20 @@ TEST_F(CompactionJobTest, Simple) {
|
||||
RunCompaction({files}, {input_level}, {expected_results});
|
||||
}
|
||||
|
||||
TEST_F(CompactionJobTest, AtomicallyAllocatesFileNumberRange) {
|
||||
NewDB();
|
||||
|
||||
const uint64_t next = versions_->current_next_file_number();
|
||||
uint64_t first = 0;
|
||||
ASSERT_TRUE(versions_->TryAllocateFileNumberRange(3, &first));
|
||||
ASSERT_EQ(first, next);
|
||||
ASSERT_EQ(versions_->current_next_file_number(), next + 3);
|
||||
|
||||
ASSERT_FALSE(versions_->TryAllocateFileNumberRange(0, &first));
|
||||
ASSERT_FALSE(versions_->TryAllocateFileNumberRange(1, nullptr));
|
||||
ASSERT_EQ(versions_->current_next_file_number(), next + 3);
|
||||
}
|
||||
|
||||
TEST_F(CompactionJobTest, DISABLED_SimpleCorrupted) {
|
||||
NewDB();
|
||||
|
||||
@@ -1583,6 +1633,14 @@ TEST_F(CompactionJobTest, InputSerialization) {
|
||||
rnd.Uniform(kStrMaxLen - 1) +
|
||||
1)); // input file name should have at least one character
|
||||
}
|
||||
do {
|
||||
input.input_blob_files.emplace_back(
|
||||
rnd.RandomString(rnd.Uniform(kStrMaxLen - 1) + 1));
|
||||
} while (!rnd.OneIn(10));
|
||||
input.has_blob_gc_cutoff_file_number = true;
|
||||
input.blob_gc_cutoff_file_number = rnd64.Uniform(UINT64_MAX);
|
||||
input.blob_file_number_start = 123;
|
||||
input.blob_file_number_limit = 456;
|
||||
input.output_level = 4;
|
||||
input.has_begin = rnd.OneIn(2);
|
||||
if (input.has_begin) {
|
||||
@@ -1592,6 +1650,12 @@ TEST_F(CompactionJobTest, InputSerialization) {
|
||||
if (input.has_end) {
|
||||
input.end = rnd.RandomBinaryString(rnd.Uniform(kStrMaxLen));
|
||||
}
|
||||
input.preserve_internal_time_seconds = rnd64.Uniform(UINT64_MAX);
|
||||
input.preclude_last_level_data_seconds = rnd64.Uniform(UINT64_MAX);
|
||||
input.preserve_time_min_seqno_floor = rnd64.Uniform(UINT64_MAX);
|
||||
input.proximal_level = static_cast<int>(rnd.Uniform(8)) - 1;
|
||||
input.proximal_output_range_type = static_cast<int>(rnd.Uniform(4));
|
||||
input.keep_in_last_level_through_seqno = rnd64.Uniform(UINT64_MAX);
|
||||
|
||||
std::string output;
|
||||
ASSERT_OK(input.Write(&output));
|
||||
@@ -1600,6 +1664,7 @@ TEST_F(CompactionJobTest, InputSerialization) {
|
||||
CompactionServiceInput deserialized1;
|
||||
ASSERT_OK(CompactionServiceInput::Read(output, &deserialized1));
|
||||
ASSERT_TRUE(deserialized1.TEST_Equals(&input));
|
||||
ASSERT_EQ(deserialized1.input_blob_files, input.input_blob_files);
|
||||
|
||||
// Test mismatch
|
||||
deserialized1.output_level += 10;
|
||||
@@ -1616,6 +1681,54 @@ TEST_F(CompactionJobTest, InputSerialization) {
|
||||
ASSERT_OK(CompactionServiceInput::Read(output, &deserialized2));
|
||||
ASSERT_TRUE(deserialized2.TEST_Equals(&input));
|
||||
|
||||
std::string old_input = output;
|
||||
auto remove_field = [](const std::string& field, std::string* serialized) {
|
||||
const size_t pos = serialized->find(field + "=");
|
||||
ASSERT_NE(pos, std::string::npos);
|
||||
const size_t end = serialized->find(';', pos);
|
||||
ASSERT_NE(end, std::string::npos);
|
||||
serialized->erase(pos, end - pos + 1);
|
||||
};
|
||||
remove_field("input_blob_files", &old_input);
|
||||
remove_field("has_blob_gc_cutoff_file_number", &old_input);
|
||||
remove_field("blob_gc_cutoff_file_number", &old_input);
|
||||
remove_field("blob_file_number_start", &old_input);
|
||||
remove_field("blob_file_number_limit", &old_input);
|
||||
CompactionServiceInput deserialized_old_input;
|
||||
deserialized_old_input.input_blob_files.emplace_back("stale.blob");
|
||||
deserialized_old_input.has_blob_gc_cutoff_file_number = true;
|
||||
deserialized_old_input.blob_gc_cutoff_file_number = 123;
|
||||
deserialized_old_input.blob_file_number_start = 1;
|
||||
deserialized_old_input.blob_file_number_limit = 2;
|
||||
ASSERT_OK(CompactionServiceInput::Read(old_input, &deserialized_old_input));
|
||||
ASSERT_TRUE(deserialized_old_input.input_blob_files.empty());
|
||||
ASSERT_FALSE(deserialized_old_input.has_blob_gc_cutoff_file_number);
|
||||
ASSERT_EQ(deserialized_old_input.blob_gc_cutoff_file_number, 0);
|
||||
ASSERT_EQ(deserialized_old_input.blob_file_number_start,
|
||||
kInvalidBlobFileNumber);
|
||||
ASSERT_EQ(deserialized_old_input.blob_file_number_limit,
|
||||
kInvalidBlobFileNumber);
|
||||
|
||||
CompactionServiceInput invalid_range_input = input;
|
||||
invalid_range_input.blob_file_number_start = 456;
|
||||
invalid_range_input.blob_file_number_limit = 123;
|
||||
std::string invalid_range;
|
||||
ASSERT_OK(invalid_range_input.Write(&invalid_range));
|
||||
CompactionServiceInput parsed_invalid_range;
|
||||
ASSERT_TRUE(CompactionServiceInput::Read(invalid_range, &parsed_invalid_range)
|
||||
.IsInvalidArgument());
|
||||
|
||||
// Test malformed hex in an encoded string field
|
||||
const std::string cf_name_field = "cf_name=";
|
||||
std::string malformed_hex;
|
||||
ASSERT_OK(input.Write(&malformed_hex));
|
||||
const size_t cf_name_pos = malformed_hex.find(cf_name_field);
|
||||
ASSERT_NE(cf_name_pos, std::string::npos);
|
||||
malformed_hex.insert(cf_name_pos + cf_name_field.size(), "Z");
|
||||
CompactionServiceInput parsed_malformed_hex;
|
||||
ASSERT_TRUE(CompactionServiceInput::Read(malformed_hex, &parsed_malformed_hex)
|
||||
.IsInvalidArgument());
|
||||
|
||||
// Test missing field
|
||||
CompactionServiceInput deserialized3;
|
||||
deserialized3.output_level = 0;
|
||||
@@ -1704,6 +1817,7 @@ TEST_F(CompactionJobTest, ResultSerialization) {
|
||||
rnd.Uniform(kStrMaxLen)) /* smallest_internal_key */,
|
||||
rnd.RandomBinaryString(
|
||||
rnd.Uniform(kStrMaxLen)) /* largest_internal_key */,
|
||||
rnd64.Uniform(UINT64_MAX - 1) + 1 /* oldest_blob_file_number */,
|
||||
rnd64.Uniform(UINT64_MAX) /* oldest_ancestor_time */,
|
||||
rnd64.Uniform(UINT64_MAX) /* file_creation_time */,
|
||||
rnd64.Uniform(UINT64_MAX) /* epoch_number */,
|
||||
@@ -1713,6 +1827,15 @@ TEST_F(CompactionJobTest, ResultSerialization) {
|
||||
rnd.OneIn(2) /* marked_for_compaction */, id /* unique_id */, tp,
|
||||
false /* is_proximal_level_output */, Temperature::kHot);
|
||||
}
|
||||
result.blob_file_additions.emplace_back(123, 2, 4096, "checksum",
|
||||
rnd.RandomBinaryString(16));
|
||||
result.blob_file_additions.emplace_back(0, 0, 0, "", "");
|
||||
result.blob_file_additions.emplace_back();
|
||||
result.blob_file_garbages.emplace_back(123, 1, 2048);
|
||||
result.blob_file_garbages.emplace_back(0, 0, 0);
|
||||
result.blob_file_garbages.emplace_back();
|
||||
result.blob_compaction_version = kCompactionServiceBlobVersion;
|
||||
result.blob_gc_cutoff_version = kCompactionServiceBlobGCCutoffVersion;
|
||||
result.output_level = rnd.Uniform(10);
|
||||
result.output_path = rnd.RandomString(rnd.Uniform(kStrMaxLen));
|
||||
result.stats.num_output_records = rnd64.Uniform(UINT64_MAX);
|
||||
@@ -1731,6 +1854,14 @@ TEST_F(CompactionJobTest, ResultSerialization) {
|
||||
CompactionServiceResult deserialized1;
|
||||
ASSERT_OK(CompactionServiceResult::Read(output, &deserialized1));
|
||||
ASSERT_TRUE(deserialized1.TEST_Equals(&result));
|
||||
ASSERT_EQ(deserialized1.blob_file_additions.size(), 3);
|
||||
ASSERT_EQ(deserialized1.blob_file_additions[1].GetBlobFileNumber(), 0);
|
||||
ASSERT_EQ(deserialized1.blob_file_additions[2].GetBlobFileNumber(),
|
||||
kInvalidBlobFileNumber);
|
||||
ASSERT_EQ(deserialized1.blob_file_garbages.size(), 3);
|
||||
ASSERT_EQ(deserialized1.blob_file_garbages[1].GetBlobFileNumber(), 0);
|
||||
ASSERT_EQ(deserialized1.blob_file_garbages[2].GetBlobFileNumber(),
|
||||
kInvalidBlobFileNumber);
|
||||
|
||||
for (size_t i = 0; i < result.output_files.size(); i++) {
|
||||
for (const auto& prop :
|
||||
@@ -1778,11 +1909,44 @@ TEST_F(CompactionJobTest, ResultSerialization) {
|
||||
ASSERT_OK(CompactionServiceResult::Read(output, &deserialized2));
|
||||
ASSERT_TRUE(deserialized2.TEST_Equals(&result));
|
||||
|
||||
std::string old_result = output;
|
||||
size_t pos = old_result.find("blob_file_additions=");
|
||||
ASSERT_NE(pos, std::string::npos);
|
||||
size_t end = old_result.find(';', pos);
|
||||
ASSERT_NE(end, std::string::npos);
|
||||
old_result.erase(pos, end - pos + 1);
|
||||
pos = old_result.find("blob_file_garbages=");
|
||||
ASSERT_NE(pos, std::string::npos);
|
||||
end = old_result.find(';', pos);
|
||||
ASSERT_NE(end, std::string::npos);
|
||||
old_result.erase(pos, end - pos + 1);
|
||||
pos = old_result.find("blob_compaction_version=");
|
||||
ASSERT_NE(pos, std::string::npos);
|
||||
end = old_result.find(';', pos);
|
||||
ASSERT_NE(end, std::string::npos);
|
||||
old_result.erase(pos, end - pos + 1);
|
||||
pos = old_result.find("blob_gc_cutoff_version=");
|
||||
ASSERT_NE(pos, std::string::npos);
|
||||
end = old_result.find(';', pos);
|
||||
ASSERT_NE(end, std::string::npos);
|
||||
old_result.erase(pos, end - pos + 1);
|
||||
CompactionServiceResult deserialized_old_result;
|
||||
deserialized_old_result.blob_file_additions.emplace_back(321, 1, 1024, "",
|
||||
"");
|
||||
deserialized_old_result.blob_file_garbages.emplace_back(321, 1, 1024);
|
||||
ASSERT_OK(
|
||||
CompactionServiceResult::Read(old_result, &deserialized_old_result));
|
||||
ASSERT_TRUE(deserialized_old_result.blob_file_additions.empty());
|
||||
ASSERT_TRUE(deserialized_old_result.blob_file_garbages.empty());
|
||||
ASSERT_EQ(deserialized_old_result.blob_compaction_version, 0);
|
||||
ASSERT_EQ(deserialized_old_result.blob_gc_cutoff_version, 0);
|
||||
deserialized_old_result.status.PermitUncheckedError();
|
||||
|
||||
// Test missing field
|
||||
CompactionServiceResult deserialized3;
|
||||
deserialized3.bytes_read = 0;
|
||||
std::string to_remove = "bytes_read=123;";
|
||||
size_t pos = output.find(to_remove);
|
||||
pos = output.find(to_remove);
|
||||
ASSERT_TRUE(pos != std::string::npos);
|
||||
output.erase(pos, to_remove.length());
|
||||
ASSERT_OK(CompactionServiceResult::Read(output, &deserialized3));
|
||||
@@ -1811,6 +1975,102 @@ TEST_F(CompactionJobTest, ResultSerialization) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompactionJobTest, ResultSerializationMissingOrInvalidFields) {
|
||||
// Each status field differs between the written status, the destination's
|
||||
// earlier status, and `Status::OK()`, so every check below can tell which of
|
||||
// the three a parsed field came from.
|
||||
CompactionServiceResult result;
|
||||
result.status = Status(Status::kIOError, Status::kNoSpace, Status::kHardError,
|
||||
"disk full");
|
||||
CompactionServiceOutputFile file;
|
||||
file.file_name = "000123.sst";
|
||||
file.marked_for_compaction = true;
|
||||
file.is_proximal_level_output = true;
|
||||
result.output_files.push_back(file);
|
||||
std::string serialized;
|
||||
ASSERT_OK(result.Write(&serialized));
|
||||
const Status earlier(Status::kTimedOut, Status::kMutexTimeout,
|
||||
Status::kSoftError, "earlier");
|
||||
|
||||
CompactionServiceResult parsed;
|
||||
ASSERT_OK(CompactionServiceResult::Read(serialized, &parsed));
|
||||
ASSERT_EQ(StatusFields(parsed.status), StatusFields(result.status));
|
||||
ASSERT_EQ(parsed.output_files.size(), 1U);
|
||||
ASSERT_TRUE(parsed.output_files[0].marked_for_compaction);
|
||||
ASSERT_TRUE(parsed.output_files[0].is_proximal_level_output);
|
||||
|
||||
// A missing status field takes its value in `Status::OK()`, and the other
|
||||
// fields keep their written values.
|
||||
struct MissingFieldCase {
|
||||
const char* field;
|
||||
Status::Code code;
|
||||
Status::SubCode subcode;
|
||||
Status::Severity severity;
|
||||
const char* message;
|
||||
};
|
||||
const MissingFieldCase missing_field_cases[] = {
|
||||
{"code", Status::kOk, Status::kNoSpace, Status::kHardError, "disk full"},
|
||||
{"subcode", Status::kIOError, Status::kNone, Status::kHardError,
|
||||
"disk full"},
|
||||
{"severity", Status::kIOError, Status::kNoSpace, Status::kNoError,
|
||||
"disk full"},
|
||||
{"message", Status::kIOError, Status::kNoSpace, Status::kHardError, ""},
|
||||
};
|
||||
for (const MissingFieldCase& c : missing_field_cases) {
|
||||
SCOPED_TRACE(c.field);
|
||||
std::string missing_field = serialized;
|
||||
ASSERT_TRUE(ReplaceResultEntry(&missing_field, "status={", c.field, ""));
|
||||
CompactionServiceResult parsed_missing_field;
|
||||
parsed_missing_field.status = earlier;
|
||||
ASSERT_OK(
|
||||
CompactionServiceResult::Read(missing_field, &parsed_missing_field));
|
||||
const Status expected(c.code, c.subcode, c.severity, c.message);
|
||||
ASSERT_EQ(StatusFields(parsed_missing_field.status),
|
||||
StatusFields(expected));
|
||||
}
|
||||
|
||||
// A malformed or out-of-range status field fails the parse, and the
|
||||
// destination keeps its earlier status.
|
||||
struct InvalidFieldCase {
|
||||
const char* field;
|
||||
std::string entry;
|
||||
};
|
||||
const InvalidFieldCase invalid_field_cases[] = {
|
||||
{"message", "message=Z;"},
|
||||
{"code", "code=" + std::to_string(Status::kMaxCode) + ";"},
|
||||
{"subcode", "subcode=" + std::to_string(Status::kMaxSubCode) + ";"},
|
||||
{"severity", "severity=" + std::to_string(Status::kMaxSeverity) + ";"},
|
||||
};
|
||||
for (const InvalidFieldCase& c : invalid_field_cases) {
|
||||
SCOPED_TRACE(c.entry);
|
||||
std::string invalid_field = serialized;
|
||||
ASSERT_TRUE(
|
||||
ReplaceResultEntry(&invalid_field, "status={", c.field, c.entry));
|
||||
CompactionServiceResult parsed_invalid_field;
|
||||
parsed_invalid_field.status = earlier;
|
||||
const Status s =
|
||||
CompactionServiceResult::Read(invalid_field, &parsed_invalid_field);
|
||||
ASSERT_EQ(s.code(), Status::kInvalidArgument) << s.ToString();
|
||||
ASSERT_EQ(StatusFields(parsed_invalid_field.status), StatusFields(earlier));
|
||||
}
|
||||
|
||||
// A missing output-file flag reads as false.
|
||||
std::string missing_flags = serialized;
|
||||
ASSERT_TRUE(ReplaceResultEntry(&missing_flags,
|
||||
"output_files=", "marked_for_compaction", ""));
|
||||
ASSERT_TRUE(ReplaceResultEntry(
|
||||
&missing_flags, "output_files=", "is_proximal_level_output", ""));
|
||||
CompactionServiceResult parsed_missing_flags;
|
||||
ASSERT_OK(
|
||||
CompactionServiceResult::Read(missing_flags, &parsed_missing_flags));
|
||||
ASSERT_EQ(StatusFields(parsed_missing_flags.status),
|
||||
StatusFields(result.status));
|
||||
ASSERT_EQ(parsed_missing_flags.output_files.size(), 1U);
|
||||
ASSERT_EQ(parsed_missing_flags.output_files[0].file_name, "000123.sst");
|
||||
ASSERT_FALSE(parsed_missing_flags.output_files[0].marked_for_compaction);
|
||||
ASSERT_FALSE(parsed_missing_flags.output_files[0].is_proximal_level_output);
|
||||
}
|
||||
|
||||
TEST_F(CompactionJobTest, CutForMaxCompactionBytes) {
|
||||
// dynamic_file_size option should have no impact on cutting for max
|
||||
// compaction bytes.
|
||||
|
||||
@@ -235,6 +235,32 @@ uint64_t CompactionOutputs::GetCurrentKeyGrandparentOverlappedBytes(
|
||||
return overlapped_bytes;
|
||||
}
|
||||
|
||||
bool CompactionOutputs::PartitionerRequiresCut(
|
||||
const CompactionIterator& c_iter) const {
|
||||
assert(HasBuilder());
|
||||
if (!partitioner_) {
|
||||
return false;
|
||||
}
|
||||
if (partitioner_prefix_valid_) {
|
||||
// Fast path: no per-key copy or virtual call. A partition is requested at
|
||||
// the first key that no longer shares the established prefix. An empty
|
||||
// prefix means no further partitions (every key "starts with" it).
|
||||
return UNLIKELY(!c_iter.user_key().starts_with(partitioner_prefix_));
|
||||
}
|
||||
// Slow path: per-key callback (and copy elsewhere)
|
||||
return UNLIKELY(partitioner_->ShouldPartition(PartitionerRequest(
|
||||
last_key_for_partitioner_, c_iter.user_key(),
|
||||
current_output_file_size_)) == kRequired);
|
||||
}
|
||||
|
||||
bool CompactionOutputs::LargestUserKeyEquals(const Slice& user_key) {
|
||||
const InternalKey& largest = current_output().meta.largest;
|
||||
return largest.size() > 0 &&
|
||||
compaction_->column_family_data()
|
||||
->user_comparator()
|
||||
->EqualWithoutTimestamp(largest.user_key(), user_key);
|
||||
}
|
||||
|
||||
bool CompactionOutputs::ShouldStopBefore(const CompactionIterator& c_iter) {
|
||||
assert(c_iter.Valid());
|
||||
const Slice& internal_key = c_iter.key();
|
||||
@@ -270,22 +296,8 @@ bool CompactionOutputs::ShouldStopBefore(const CompactionIterator& c_iter) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (partitioner_) {
|
||||
if (partitioner_prefix_valid_) {
|
||||
// Fast path: no per-key copy or virtual call. A partition is requested at
|
||||
// the first key that no longer shares the established prefix. An empty
|
||||
// prefix means no further partitions (every key "starts with" it).
|
||||
if (UNLIKELY(!c_iter.user_key().starts_with(partitioner_prefix_))) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Slow path: per-key callback (and copy elsewhere)
|
||||
if (UNLIKELY(partitioner_->ShouldPartition(PartitionerRequest(
|
||||
last_key_for_partitioner_, c_iter.user_key(),
|
||||
current_output_file_size_)) == kRequired)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (PartitionerRequiresCut(c_iter)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// files output to Level 0 won't be split
|
||||
@@ -395,38 +407,56 @@ void CompactionOutputs::PrepareNewOutputForFirstKey(
|
||||
}
|
||||
|
||||
Status CompactionOutputs::AddToOutput(
|
||||
const CompactionIterator& c_iter,
|
||||
const CompactionFileOpenFunc& open_file_func,
|
||||
CompactionIterator& c_iter, const CompactionFileOpenFunc& open_file_func,
|
||||
const CompactionFileCloseFunc& close_file_func,
|
||||
const ParsedInternalKey& prev_iter_output_internal_key) {
|
||||
Status s;
|
||||
bool is_range_del = c_iter.IsDeleteRangeSentinelKey();
|
||||
bool cut_before;
|
||||
if (is_range_del && compaction_->bottommost_level()) {
|
||||
// We don't consider range tombstone for bottommost level since:
|
||||
// 1. there is no grandparent and hence no overlap to consider
|
||||
// 2. range tombstone may be dropped at bottommost level.
|
||||
return s;
|
||||
// At the bottommost level, a range tombstone is not by itself a reason to
|
||||
// cut: there are no grandparents to limit overlap with, and the tombstone
|
||||
// might be dropped. However, a boundary required by the SstPartitioner
|
||||
// must still be honored; otherwise AddRangeDels() would extend the
|
||||
// current file across it. No boundary can fall inside one user key, and
|
||||
// a point key there may have its seqno zeroed, so that it sorts after a
|
||||
// tombstone starting at the same user key.
|
||||
if (!HasBuilder() || LargestUserKeyEquals(c_iter.user_key()) ||
|
||||
!PartitionerRequiresCut(c_iter)) {
|
||||
return s;
|
||||
}
|
||||
cut_before = true;
|
||||
} else {
|
||||
cut_before = ShouldStopBefore(c_iter) && HasBuilder();
|
||||
}
|
||||
const Slice& key = c_iter.key();
|
||||
if (ShouldStopBefore(c_iter) && HasBuilder()) {
|
||||
if (cut_before) {
|
||||
const Slice next_table_min_key = c_iter.key();
|
||||
s = close_file_func(c_iter.InputStatus(), prev_iter_output_internal_key,
|
||||
key, &c_iter, *this);
|
||||
next_table_min_key, &c_iter,
|
||||
/*post_prepare_retry=*/false, *this);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
// reset grandparent information
|
||||
grandparent_boundary_switched_num_ = 0;
|
||||
grandparent_overlapped_bytes_ =
|
||||
GetCurrentKeyGrandparentOverlappedBytes(key);
|
||||
GetCurrentKeyGrandparentOverlappedBytes(next_table_min_key);
|
||||
if (UNLIKELY(is_range_del)) {
|
||||
// lower bound for this new output file, this is needed as the lower bound
|
||||
// does not come from the smallest point key in this case.
|
||||
range_tombstone_lower_bound_.DecodeFrom(key);
|
||||
range_tombstone_lower_bound_.DecodeFrom(next_table_min_key);
|
||||
} else {
|
||||
range_tombstone_lower_bound_.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
c_iter.PrepareOutputIfNeeded();
|
||||
if (!c_iter.status().ok() || !c_iter.Valid()) {
|
||||
s = c_iter.status();
|
||||
return s;
|
||||
}
|
||||
const Slice& key = c_iter.key();
|
||||
|
||||
// Open output file if necessary
|
||||
if (!HasBuilder()) {
|
||||
s = open_file_func(*this);
|
||||
@@ -457,7 +487,7 @@ Status CompactionOutputs::AddToOutput(
|
||||
}
|
||||
|
||||
TableBuilderAddContext add_context;
|
||||
TableBuilderAddResult add_result =
|
||||
const TableBuilderAddResult add_result =
|
||||
builder_->TryAdd(key, value, &add_context);
|
||||
|
||||
if (add_result == TableBuilderAddResult::kError) {
|
||||
@@ -479,6 +509,10 @@ Status CompactionOutputs::AddToOutput(
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
const char* const new_table_reason =
|
||||
add_context.new_table_reason == TableBuilderNewTableReason::kNone
|
||||
? "Unspecified"
|
||||
: TableBuilderNewTableReasonToString(add_context.new_table_reason);
|
||||
const Compaction* compaction = compaction_;
|
||||
if (compaction == nullptr) {
|
||||
return Status::Corruption("Compaction output is missing compaction");
|
||||
@@ -495,9 +529,8 @@ Status CompactionOutputs::AddToOutput(
|
||||
if (compaction->output_level() == 0) {
|
||||
const std::string detail =
|
||||
"cf: " + cfd->GetName() + ", file_number: " +
|
||||
std::to_string(current_output().meta.fd.GetNumber()) + ", reason: " +
|
||||
TableBuilderNewTableReasonToString(add_context.new_table_reason) +
|
||||
", key: " + key_for_diagnostics;
|
||||
std::to_string(current_output().meta.fd.GetNumber()) +
|
||||
", reason: " + new_table_reason + ", key: " + key_for_diagnostics;
|
||||
return Status::Corruption(
|
||||
"TableBuilder requested a new table while producing level 0 output",
|
||||
detail);
|
||||
@@ -505,9 +538,8 @@ Status CompactionOutputs::AddToOutput(
|
||||
if (retried_after_new_table || builder_->IsEmpty()) {
|
||||
const std::string detail =
|
||||
"cf: " + cfd->GetName() + ", file_number: " +
|
||||
std::to_string(current_output().meta.fd.GetNumber()) + ", reason: " +
|
||||
TableBuilderNewTableReasonToString(add_context.new_table_reason) +
|
||||
", key: " + key_for_diagnostics;
|
||||
std::to_string(current_output().meta.fd.GetNumber()) +
|
||||
", reason: " + new_table_reason + ", key: " + key_for_diagnostics;
|
||||
return Status::Corruption(
|
||||
"TableBuilder requested a new table without accepting any rows",
|
||||
detail);
|
||||
@@ -518,11 +550,10 @@ Status CompactionOutputs::AddToOutput(
|
||||
"[%s] Compaction output table builder requested a new table before "
|
||||
"key %s for file #%" PRIu64 ". reason: %s",
|
||||
cfd->GetName().c_str(), key_for_diagnostics.c_str(),
|
||||
current_output().meta.fd.GetNumber(),
|
||||
TableBuilderNewTableReasonToString(add_context.new_table_reason));
|
||||
current_output().meta.fd.GetNumber(), new_table_reason);
|
||||
|
||||
s = close_file_func(c_iter.InputStatus(), prev_iter_output_internal_key,
|
||||
key, &c_iter, *this);
|
||||
key, &c_iter, /*post_prepare_retry=*/true, *this);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ class CompactionOutputs;
|
||||
using CompactionFileOpenFunc = std::function<Status(CompactionOutputs&)>;
|
||||
using CompactionFileCloseFunc =
|
||||
std::function<Status(const Status&, const ParsedInternalKey&, const Slice&,
|
||||
const CompactionIterator*, CompactionOutputs&)>;
|
||||
const CompactionIterator*, bool, CompactionOutputs&)>;
|
||||
|
||||
// Files produced by subcompaction, most of the functions are used by
|
||||
// compaction_job Open/Close compaction file functions.
|
||||
@@ -245,6 +245,15 @@ class CompactionOutputs {
|
||||
// before processing the current key in compaction iterator.
|
||||
bool ShouldStopBefore(const CompactionIterator& c_iter);
|
||||
|
||||
// Returns true iff the SST partitioner requires a new output file to start
|
||||
// at the current key. Only meaningful while an output file is open: the
|
||||
// partitioner state describes the current file.
|
||||
bool PartitionerRequiresCut(const CompactionIterator& c_iter) const;
|
||||
|
||||
// Returns true iff the current output file has a largest key and its user
|
||||
// key equals `user_key`, ignoring timestamps.
|
||||
bool LargestUserKeyEquals(const Slice& user_key);
|
||||
|
||||
// Re-establishes per-file state that depends on the first key accepted by a
|
||||
// freshly opened builder. This keeps caller-requested file cuts equivalent to
|
||||
// RocksDB's built-in file-boundary decisions for partition tracking.
|
||||
@@ -278,7 +287,7 @@ class CompactionOutputs {
|
||||
|
||||
// Add current key from compaction_iterator to the output file. If needed
|
||||
// close and open new compaction output with the functions provided.
|
||||
Status AddToOutput(const CompactionIterator& c_iter,
|
||||
Status AddToOutput(CompactionIterator& c_iter,
|
||||
const CompactionFileOpenFunc& open_file_func,
|
||||
const CompactionFileCloseFunc& close_file_func,
|
||||
const ParsedInternalKey& prev_iter_output_internal_key);
|
||||
@@ -302,7 +311,8 @@ class CompactionOutputs {
|
||||
const ParsedInternalKey empty_internal_key{};
|
||||
const Slice empty_key{};
|
||||
Status s = close_file_func(status, empty_internal_key, empty_key,
|
||||
nullptr /* c_iter */, *this);
|
||||
nullptr /* c_iter */,
|
||||
/*post_prepare_retry=*/false, *this);
|
||||
if (!s.ok() && status.ok()) {
|
||||
status = s;
|
||||
}
|
||||
|
||||
@@ -764,6 +764,7 @@ Compaction* CompactionPicker::PickCompactionForCompactRange(
|
||||
compact_range_options.blob_garbage_collection_policy,
|
||||
compact_range_options.blob_garbage_collection_age_cutoff);
|
||||
|
||||
c->SetSkipMissingBlobFiles(compact_range_options.skip_missing_blob_files);
|
||||
RegisterCompaction(c);
|
||||
vstorage->ComputeCompactionScore(ioptions_, mutable_cf_options,
|
||||
full_history_ts_low);
|
||||
@@ -837,7 +838,8 @@ Compaction* CompactionPicker::PickCompactionForCompactRange(
|
||||
|
||||
// for BOTTOM LEVEL compaction only, use max_file_num_to_ignore to filter out
|
||||
// files that are created during the current compaction.
|
||||
if ((compact_range_options.bottommost_level_compaction ==
|
||||
if ((compact_range_options.skip_missing_blob_files ||
|
||||
compact_range_options.bottommost_level_compaction ==
|
||||
BottommostLevelCompaction::kForceOptimized ||
|
||||
compact_range_options.bottommost_level_compaction ==
|
||||
BottommostLevelCompaction::kIfHaveCompactionFilter) &&
|
||||
@@ -860,9 +862,6 @@ Compaction* CompactionPicker::PickCompactionForCompactRange(
|
||||
if (inputs_shrunk.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
if (inputs.size() != inputs_shrunk.size()) {
|
||||
inputs.files.swap(inputs_shrunk);
|
||||
}
|
||||
// set covering_the_whole_range to false if there is any file that need to
|
||||
// be compacted in the range of inputs[skip_input_index+1, inputs.size())
|
||||
for (size_t i = skip_input_index + 1; i < inputs.size(); ++i) {
|
||||
@@ -870,6 +869,9 @@ Compaction* CompactionPicker::PickCompactionForCompactRange(
|
||||
covering_the_whole_range = false;
|
||||
}
|
||||
}
|
||||
if (inputs.size() != inputs_shrunk.size()) {
|
||||
inputs.files.swap(inputs_shrunk);
|
||||
}
|
||||
}
|
||||
|
||||
InternalKey key_storage;
|
||||
@@ -956,6 +958,8 @@ Compaction* CompactionPicker::PickCompactionForCompactRange(
|
||||
compact_range_options.blob_garbage_collection_policy,
|
||||
compact_range_options.blob_garbage_collection_age_cutoff);
|
||||
|
||||
compaction->SetSkipMissingBlobFiles(
|
||||
compact_range_options.skip_missing_blob_files);
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionPicker::CompactRange:Return", compaction);
|
||||
RegisterCompaction(compaction);
|
||||
|
||||
|
||||
@@ -110,6 +110,13 @@ Compaction* FIFOCompactionPicker::PickTTLCompaction(
|
||||
uint64_t est_newest_key_time = newest_key_time == kUnknownNewestKeyTime
|
||||
? oldest_ancestor_time
|
||||
: newest_key_time;
|
||||
if (est_newest_key_time == kUnknownNewestKeyTime) {
|
||||
// E.g. a file written by SstFileWriter. Every key in the file was
|
||||
// written no later than the file's contents were built.
|
||||
est_newest_key_time = f->TryGetFileCreationTime();
|
||||
}
|
||||
// CheckExternalFileNotTooOld() (reject_too_old_file) mirrors this
|
||||
// estimate
|
||||
if (est_newest_key_time == kUnknownNewestKeyTime ||
|
||||
est_newest_key_time >= (current_time - mutable_cf_options.ttl)) {
|
||||
break;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
// Picking compactions for leveled compaction. See wiki page
|
||||
// https://github.com/facebook/rocksdb/wiki/Leveled-Compaction
|
||||
// https://github.com/meta/rocksdb/wiki/Leveled-Compaction
|
||||
// for description of Leveled compaction.
|
||||
class LevelCompactionPicker : public CompactionPicker {
|
||||
public:
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -106,7 +106,7 @@ Slice SubcompactionState::LargestUserKey() const {
|
||||
}
|
||||
|
||||
Status SubcompactionState::AddToOutput(
|
||||
const CompactionIterator& iter, bool use_proximal_output,
|
||||
CompactionIterator& iter, bool use_proximal_output,
|
||||
const CompactionFileOpenFunc& open_file_func,
|
||||
const CompactionFileCloseFunc& close_file_func,
|
||||
const ParsedInternalKey& prev_iter_output_internal_key) {
|
||||
@@ -117,4 +117,20 @@ Status SubcompactionState::AddToOutput(
|
||||
prev_iter_output_internal_key);
|
||||
}
|
||||
|
||||
Status SubcompactionState::UpdateBlobGarbageCheckpoint(
|
||||
const BlobGarbageMeter* next_key_inflow) {
|
||||
const BlobGarbageMeter* const meter =
|
||||
compaction_outputs_.GetBlobGarbageMeter();
|
||||
if (meter == nullptr) {
|
||||
blob_garbage_checkpoint_.reset();
|
||||
return Status::OK();
|
||||
}
|
||||
blob_garbage_checkpoint_ = std::make_unique<BlobGarbageMeter>(*meter);
|
||||
if (next_key_inflow == nullptr || next_key_inflow->flows().empty()) {
|
||||
return Status::OK();
|
||||
}
|
||||
blob_garbage_checkpoint_->ProcessOutFlow(*next_key_inflow);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -163,7 +163,8 @@ class SubcompactionState {
|
||||
sub_job_id(state.sub_job_id),
|
||||
compaction_outputs_(std::move(state.compaction_outputs_)),
|
||||
proximal_level_outputs_(std::move(state.proximal_level_outputs_)),
|
||||
range_del_agg_(std::move(state.range_del_agg_)) {
|
||||
range_del_agg_(std::move(state.range_del_agg_)),
|
||||
blob_garbage_checkpoint_(std::move(state.blob_garbage_checkpoint_)) {
|
||||
current_outputs_ = state.current_outputs_ == &state.proximal_level_outputs_
|
||||
? &proximal_level_outputs_
|
||||
: &compaction_outputs_;
|
||||
@@ -238,11 +239,16 @@ class SubcompactionState {
|
||||
}
|
||||
|
||||
// Add compaction_iterator key/value to the `Current` output group.
|
||||
Status AddToOutput(const CompactionIterator& iter, bool use_proximal_output,
|
||||
Status AddToOutput(CompactionIterator& iter, bool use_proximal_output,
|
||||
const CompactionFileOpenFunc& open_file_func,
|
||||
const CompactionFileCloseFunc& close_file_func,
|
||||
const ParsedInternalKey& prev_iter_output_internal_key);
|
||||
|
||||
Status UpdateBlobGarbageCheckpoint(const BlobGarbageMeter* next_key_inflow);
|
||||
const BlobGarbageMeter* GetBlobGarbageCheckpoint() const {
|
||||
return blob_garbage_checkpoint_.get();
|
||||
}
|
||||
|
||||
// Close all compaction output files, both output_to_proximal_level outputs
|
||||
// and normal outputs.
|
||||
Status CloseCompactionFiles(const Status& curr_status,
|
||||
@@ -271,6 +277,7 @@ class SubcompactionState {
|
||||
CompactionOutputs proximal_level_outputs_;
|
||||
CompactionOutputs* current_outputs_ = &compaction_outputs_;
|
||||
std::unique_ptr<CompactionRangeDelAggregator> range_del_agg_;
|
||||
std::unique_ptr<BlobGarbageMeter> blob_garbage_checkpoint_;
|
||||
|
||||
SubcompactionProgress subcompaction_progress_;
|
||||
};
|
||||
|
||||
@@ -843,19 +843,20 @@ TEST_F(TieredCompactionTest, LevelOutofBoundaryRangeDelete) {
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
ASSERT_EQ("0,2,10",
|
||||
FilesPerLevel()); // one file is at the proximal level
|
||||
// which only contains a range delete
|
||||
ASSERT_EQ("0,3,10",
|
||||
FilesPerLevel()); // the partitioner cuts before and after the
|
||||
// range delete, so it has a file of its own
|
||||
// at the proximal level
|
||||
std::vector<LiveFileMetaData> live_file_meta;
|
||||
db_->GetLiveFilesMetaData(&live_file_meta);
|
||||
bool found_sst_with_del = false;
|
||||
uint64_t sst_with_del_num = 0;
|
||||
for (const auto& meta : live_file_meta) {
|
||||
if (meta.num_deletions > 0) {
|
||||
// found SST with del, which has 2 entries, one for data one for range del
|
||||
// found SST with del, which only contains the range del
|
||||
ASSERT_EQ(meta.level,
|
||||
kNumLevels - 2); // output to proximal level
|
||||
ASSERT_EQ(meta.num_entries, 2);
|
||||
ASSERT_EQ(meta.num_entries, 1);
|
||||
ASSERT_EQ(meta.num_deletions, 1);
|
||||
found_sst_with_del = true;
|
||||
sst_with_del_num = meta.file_number;
|
||||
@@ -974,7 +975,9 @@ TEST_F(TieredCompactionTest, UniversalRangeDelete) {
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
ASSERT_EQ("0,0,0,0,0,2,8", FilesPerLevel());
|
||||
// the partitioner cuts before and after the range del, so it has a file of
|
||||
// its own at the proximal level
|
||||
ASSERT_EQ("0,0,0,0,0,3,8", FilesPerLevel());
|
||||
// find the SST with range del
|
||||
std::vector<LiveFileMetaData> live_file_meta;
|
||||
db_->GetLiveFilesMetaData(&live_file_meta);
|
||||
@@ -982,10 +985,10 @@ TEST_F(TieredCompactionTest, UniversalRangeDelete) {
|
||||
uint64_t sst_with_del_num = 0;
|
||||
for (const auto& meta : live_file_meta) {
|
||||
if (meta.num_deletions > 0) {
|
||||
// found SST with del, which has 2 entries, one for data one for range del
|
||||
// found SST with del, which only contains the range del
|
||||
ASSERT_EQ(meta.level,
|
||||
kNumLevels - 2); // output_to_proximal_level level
|
||||
ASSERT_EQ(meta.num_entries, 2);
|
||||
ASSERT_EQ(meta.num_entries, 1);
|
||||
ASSERT_EQ(meta.num_deletions, 1);
|
||||
found_sst_with_del = true;
|
||||
sst_with_del_num = meta.file_number;
|
||||
@@ -1313,6 +1316,9 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageLevel) {
|
||||
options.statistics->getTickerCount(COMPACTION_RANGE_DEL_DROP_OBSOLETE),
|
||||
0);
|
||||
|
||||
// Keep bottommost cleanup from erasing the sequence metadata used by the
|
||||
// following manual-compaction placement assertions.
|
||||
ASSERT_OK(dbfull()->SetOptions({{"disable_auto_compactions", "true"}}));
|
||||
db_->ReleaseSnapshot(snap);
|
||||
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
@@ -2109,7 +2115,7 @@ class PrecludeWithCompactStyleTest : public PrecludeLastLevelTestBase,
|
||||
|
||||
TEST_P(PrecludeWithCompactStyleTest, RangeTombstoneSnapshotMigrateFromLast) {
|
||||
// Reproducer for issue originally described in
|
||||
// https://github.com/facebook/rocksdb/pull/9964/files#r1024449523
|
||||
// https://github.com/meta/rocksdb/pull/9964/files#r1024449523
|
||||
const bool universal = GetParam();
|
||||
const int kNumLevels = 7;
|
||||
auto options = CurrentOptions();
|
||||
|
||||
+29
-16
@@ -14,6 +14,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "folly/Executor.h"
|
||||
#include "folly/coro/CurrentExecutor.h"
|
||||
#include "folly/coro/Nothrow.h"
|
||||
#include "folly/executors/IOExecutor.h"
|
||||
#include "folly/io/async/EventBase.h"
|
||||
@@ -41,8 +42,8 @@ folly::coro::Task<Status> CoroDB::CoGet(DB* db, const ReadOptions& options,
|
||||
|
||||
Status status;
|
||||
if (coro_db == nullptr || read_event_base == nullptr) {
|
||||
CoroutineStatsContextScope stats_scope(std::move(stats_config),
|
||||
db->GetEnv());
|
||||
CoroutineStatsContextScope stats_scope(
|
||||
WithoutAsyncReadCPUTime(std::move(stats_config)), db->GetEnv());
|
||||
status = db->Get(options, column_family, key, value, timestamp);
|
||||
} else {
|
||||
auto task = [](CoroutineStatsConfig task_stats_config, Env* task_env,
|
||||
@@ -57,8 +58,14 @@ folly::coro::Task<Status> CoroDB::CoGet(DB* db, const ReadOptions& options,
|
||||
task_value, task_timestamp));
|
||||
}(std::move(stats_config), db->GetEnv(), coro_db, options, column_family,
|
||||
key, value, timestamp);
|
||||
status = co_await folly::coro::co_nothrow(folly::coro::co_withExecutor(
|
||||
folly::Executor::getKeepAliveToken(read_event_base), std::move(task)));
|
||||
auto* current_executor = co_await folly::coro::co_current_executor;
|
||||
if (current_executor == read_event_base) {
|
||||
status = co_await folly::coro::co_nothrow(std::move(task));
|
||||
} else {
|
||||
status = co_await folly::coro::co_nothrow(folly::coro::co_withExecutor(
|
||||
folly::Executor::getKeepAliveToken(read_event_base),
|
||||
std::move(task)));
|
||||
}
|
||||
}
|
||||
co_return status;
|
||||
}
|
||||
@@ -104,8 +111,8 @@ folly::coro::Task<Status> CoroDB::CoGet(SstFileReader* reader,
|
||||
|
||||
Status status;
|
||||
if (read_event_base == nullptr) {
|
||||
CoroutineStatsContextScope stats_scope(std::move(stats_config),
|
||||
rep->options.env);
|
||||
CoroutineStatsContextScope stats_scope(
|
||||
WithoutAsyncReadCPUTime(std::move(stats_config)), rep->options.env);
|
||||
status = reader->Get(options, key, value);
|
||||
} else {
|
||||
auto task = [](CoroutineStatsConfig task_stats_config, Env* task_env,
|
||||
@@ -117,8 +124,14 @@ folly::coro::Task<Status> CoroDB::CoGet(SstFileReader* reader,
|
||||
co_return co_await folly::coro::co_nothrow(
|
||||
task_reader->rep_->GetCoroutine(task_options, task_key, task_value));
|
||||
}(std::move(stats_config), rep->options.env, reader, options, key, value);
|
||||
status = co_await folly::coro::co_nothrow(folly::coro::co_withExecutor(
|
||||
folly::Executor::getKeepAliveToken(read_event_base), std::move(task)));
|
||||
auto* current_executor = co_await folly::coro::co_current_executor;
|
||||
if (current_executor == read_event_base) {
|
||||
status = co_await folly::coro::co_nothrow(std::move(task));
|
||||
} else {
|
||||
status = co_await folly::coro::co_nothrow(folly::coro::co_withExecutor(
|
||||
folly::Executor::getKeepAliveToken(read_event_base),
|
||||
std::move(task)));
|
||||
}
|
||||
}
|
||||
co_return status;
|
||||
}
|
||||
@@ -153,8 +166,8 @@ folly::coro::Task<Status> CoroDB::CoGetEntity(DB* db,
|
||||
|
||||
Status status;
|
||||
if (coro_db == nullptr || read_event_base == nullptr) {
|
||||
CoroutineStatsContextScope stats_scope(std::move(stats_config),
|
||||
db->GetEnv());
|
||||
CoroutineStatsContextScope stats_scope(
|
||||
WithoutAsyncReadCPUTime(std::move(stats_config)), db->GetEnv());
|
||||
status = db->GetEntity(options, column_family, key, columns);
|
||||
} else {
|
||||
auto task =
|
||||
@@ -197,8 +210,8 @@ folly::coro::Task<Status> CoroDB::CoGetEntity(DB* db,
|
||||
|
||||
Status status;
|
||||
if (coro_db == nullptr || read_event_base == nullptr) {
|
||||
CoroutineStatsContextScope stats_scope(std::move(stats_config),
|
||||
db->GetEnv());
|
||||
CoroutineStatsContextScope stats_scope(
|
||||
WithoutAsyncReadCPUTime(std::move(stats_config)), db->GetEnv());
|
||||
status = db->GetEntity(options, key, result);
|
||||
} else {
|
||||
auto task =
|
||||
@@ -265,8 +278,8 @@ folly::coro::Task<std::vector<Status>> CoroDB::CoMultiGet(
|
||||
|
||||
std::vector<Status> statuses;
|
||||
if (read_event_base == nullptr) {
|
||||
CoroutineStatsContextScope stats_scope(std::move(stats_config),
|
||||
rep->options.env);
|
||||
CoroutineStatsContextScope stats_scope(
|
||||
WithoutAsyncReadCPUTime(std::move(stats_config)), rep->options.env);
|
||||
statuses = reader->MultiGet(options, keys, values);
|
||||
} else {
|
||||
auto task = [](CoroutineStatsConfig task_stats_config, Env* task_env,
|
||||
@@ -316,8 +329,8 @@ folly::coro::Task<void> CoroDB::CoMultiGet(
|
||||
CoroutineStatsConfig stats_config = CaptureAndDisableCoroutineStatsConfig();
|
||||
|
||||
if (coro_db == nullptr || read_event_base == nullptr) {
|
||||
CoroutineStatsContextScope stats_scope(std::move(stats_config),
|
||||
db->GetEnv());
|
||||
CoroutineStatsContextScope stats_scope(
|
||||
WithoutAsyncReadCPUTime(std::move(stats_config)), db->GetEnv());
|
||||
db->MultiGet(options, num_keys, column_families, keys, values, timestamps,
|
||||
statuses, sorted_input);
|
||||
} else {
|
||||
|
||||
+80
-17
@@ -1560,6 +1560,44 @@ TEST_F(DBBasicTest, CompactedDB) {
|
||||
ASSERT_FALSE((*multiget_output_metadata.newer_version_present)[0]);
|
||||
ASSERT_TRUE(metadata_statuses[1].IsNotFound());
|
||||
ASSERT_FALSE((*multiget_output_metadata.newer_version_present)[1]);
|
||||
{
|
||||
const std::string expected_aaa_value = DummyString(kFileSize / 2, 'a');
|
||||
const WideColumns expected_aaa_columns{
|
||||
{kDefaultWideColumnName, expected_aaa_value}};
|
||||
PinnableWideColumns get_entity_result;
|
||||
OutputMetadata get_entity_output_metadata = NewerVersionOutputMetadata();
|
||||
ASSERT_OK(db_->GetEntityWithMetadata(metadata_read_options, "aaa",
|
||||
&get_entity_result,
|
||||
&get_entity_output_metadata));
|
||||
ASSERT_EQ(get_entity_result.columns(), expected_aaa_columns);
|
||||
ASSERT_FALSE(*get_entity_output_metadata.newer_version_present);
|
||||
|
||||
std::array<PinnableWideColumns, 2> entity_results;
|
||||
std::array<Status, 2> entity_statuses;
|
||||
MultiGetOutputMetadata entity_output_metadata =
|
||||
NewerVersionMultiGetOutputMetadata();
|
||||
db_->MultiGetEntityWithMetadata(
|
||||
metadata_read_options, db_->DefaultColumnFamily(), metadata_keys.size(),
|
||||
metadata_keys.data(), entity_results.data(), entity_statuses.data(),
|
||||
&entity_output_metadata);
|
||||
ASSERT_OK(entity_statuses[0]);
|
||||
ASSERT_EQ(entity_results[0].columns(), expected_aaa_columns);
|
||||
ASSERT_TRUE(entity_statuses[1].IsNotFound());
|
||||
ASSERT_EQ(*entity_output_metadata.newer_version_present,
|
||||
(std::vector<uint8_t>{0, 0}));
|
||||
|
||||
std::array<PinnableAttributeGroups, 2> attribute_group_results;
|
||||
attribute_group_results[0].emplace_back(db_->DefaultColumnFamily());
|
||||
attribute_group_results[1].emplace_back(db_->DefaultColumnFamily());
|
||||
db_->MultiGetEntityWithMetadata(
|
||||
metadata_read_options, metadata_keys.size(), metadata_keys.data(),
|
||||
attribute_group_results.data(), &entity_output_metadata);
|
||||
ASSERT_OK(attribute_group_results[0][0].status());
|
||||
ASSERT_EQ(attribute_group_results[0][0].columns(), expected_aaa_columns);
|
||||
ASSERT_TRUE(attribute_group_results[1][0].status().IsNotFound());
|
||||
ASSERT_EQ(*entity_output_metadata.newer_version_present,
|
||||
(std::vector<uint8_t>{0, 0}));
|
||||
}
|
||||
db_->ReleaseSnapshot(metadata_snapshot);
|
||||
|
||||
ASSERT_TRUE(db_->GetWithMetadata(ReadOptions(), db_->DefaultColumnFamily(),
|
||||
@@ -2176,6 +2214,35 @@ TEST_F(DBBasicTest, ReadOnlyNewerVersionPresentReturnsFalse) {
|
||||
ASSERT_EQ(1, multiget_output_metadata.newer_version_present->size());
|
||||
ASSERT_FALSE((*multiget_output_metadata.newer_version_present)[0]);
|
||||
|
||||
const WideColumns expected_columns{{kDefaultWideColumnName, "value"}};
|
||||
PinnableWideColumns entity_result;
|
||||
OutputMetadata get_entity_output_metadata = NewerVersionOutputMetadata();
|
||||
ASSERT_OK(db_->GetEntityWithMetadata(read_options, handles_[1], "key",
|
||||
&entity_result,
|
||||
&get_entity_output_metadata));
|
||||
ASSERT_EQ(entity_result.columns(), expected_columns);
|
||||
ASSERT_FALSE(*get_entity_output_metadata.newer_version_present);
|
||||
|
||||
Status entity_status;
|
||||
MultiGetOutputMetadata entity_output_metadata =
|
||||
NewerVersionMultiGetOutputMetadata();
|
||||
db_->MultiGetEntityWithMetadata(read_options, handles_[1], keys.size(),
|
||||
keys.data(), &entity_result, &entity_status,
|
||||
&entity_output_metadata);
|
||||
ASSERT_OK(entity_status);
|
||||
ASSERT_EQ(entity_result.columns(), expected_columns);
|
||||
ASSERT_EQ(*entity_output_metadata.newer_version_present,
|
||||
std::vector<uint8_t>{0});
|
||||
|
||||
PinnableAttributeGroups attribute_groups;
|
||||
attribute_groups.emplace_back(handles_[1]);
|
||||
db_->MultiGetEntityWithMetadata(read_options, keys.size(), keys.data(),
|
||||
&attribute_groups, &entity_output_metadata);
|
||||
ASSERT_OK(attribute_groups[0].status());
|
||||
ASSERT_EQ(attribute_groups[0].columns(), expected_columns);
|
||||
ASSERT_EQ(*entity_output_metadata.newer_version_present,
|
||||
std::vector<uint8_t>{0});
|
||||
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
@@ -4609,12 +4676,13 @@ INSTANTIATE_TEST_CASE_P(DBMultiGetTestWithParam, DBMultiGetTestWithParam,
|
||||
#if USE_COROUTINES
|
||||
class DBMultiGetAsyncIOTest
|
||||
: public DBBasicTest,
|
||||
public ::testing::WithParamInterface<std::tuple<bool, bool>> {
|
||||
public ::testing::WithParamInterface<std::tuple<bool, bool, bool>> {
|
||||
public:
|
||||
DBMultiGetAsyncIOTest()
|
||||
: DBBasicTest(),
|
||||
optimize_multiget_for_io_(std::get<0>(GetParam())),
|
||||
use_coroutine_(std::get<1>(GetParam())),
|
||||
use_direct_io_(std::get<2>(GetParam())),
|
||||
statistics_(ROCKSDB_NAMESPACE::CreateDBStatistics()) {
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10));
|
||||
@@ -4623,6 +4691,7 @@ class DBMultiGetAsyncIOTest
|
||||
options_.statistics = statistics_;
|
||||
options_.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
options_.env = Env::Default();
|
||||
options_.use_direct_reads = use_direct_io_ && IsDirectIOSupported();
|
||||
Reopen(options_);
|
||||
int num_keys = 0;
|
||||
|
||||
@@ -4686,6 +4755,12 @@ class DBMultiGetAsyncIOTest
|
||||
EXPECT_EQ(0, num_keys);
|
||||
}
|
||||
|
||||
void SetUp() override {
|
||||
if (use_direct_io_ && !options_.use_direct_reads) {
|
||||
ROCKSDB_GTEST_SKIP("Direct IO is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
const std::shared_ptr<Statistics>& statistics() { return statistics_; }
|
||||
|
||||
protected:
|
||||
@@ -4720,6 +4795,7 @@ class DBMultiGetAsyncIOTest
|
||||
|
||||
const bool optimize_multiget_for_io_;
|
||||
const bool use_coroutine_;
|
||||
const bool use_direct_io_;
|
||||
|
||||
private:
|
||||
std::shared_ptr<Statistics> statistics_;
|
||||
@@ -5169,7 +5245,8 @@ TEST_P(DBMultiGetAsyncIOTest, MixedReadAsyncFallbackPropagatesPollError) {
|
||||
#endif // ROCKSDB_IOURING_PRESENT
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBMultiGetAsyncIOTest, DBMultiGetAsyncIOTest,
|
||||
testing::Combine(testing::Bool(), testing::Bool()));
|
||||
testing::Combine(testing::Bool(), testing::Bool(),
|
||||
testing::Bool()));
|
||||
#endif // USE_COROUTINES
|
||||
|
||||
TEST_F(DBBasicTest, MultiGetStats) {
|
||||
@@ -6216,7 +6293,7 @@ class DBBasicTestTrackWal : public DBTestBase,
|
||||
VectorLogPtr log_files;
|
||||
EXPECT_OK(dbfull()->GetSortedWalFiles(log_files));
|
||||
return static_cast<int>(log_files.size());
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
TEST_P(DBBasicTestTrackWal, DoNotTrackObsoleteWal) {
|
||||
@@ -6586,7 +6663,6 @@ TEST_P(DBBasicTestWithParallelIO, MultiGet) {
|
||||
|
||||
TEST_P(DBBasicTestWithParallelIO, MultiGetDirectIO) {
|
||||
class FakeDirectIOEnv : public EnvWrapper {
|
||||
class FakeDirectIOSequentialFile;
|
||||
class FakeDirectIORandomAccessFile;
|
||||
|
||||
public:
|
||||
@@ -6610,19 +6686,6 @@ TEST_P(DBBasicTestWithParallelIO, MultiGetDirectIO) {
|
||||
}
|
||||
|
||||
private:
|
||||
class FakeDirectIOSequentialFile : public SequentialFileWrapper {
|
||||
public:
|
||||
FakeDirectIOSequentialFile(std::unique_ptr<SequentialFile>&& file)
|
||||
: SequentialFileWrapper(file.get()), file_(std::move(file)) {}
|
||||
~FakeDirectIOSequentialFile() override {}
|
||||
|
||||
bool use_direct_io() const override { return true; }
|
||||
size_t GetRequiredBufferAlignment() const override { return 1; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<SequentialFile> file_;
|
||||
};
|
||||
|
||||
class FakeDirectIORandomAccessFile : public RandomAccessFileWrapper {
|
||||
public:
|
||||
FakeDirectIORandomAccessFile(std::unique_ptr<RandomAccessFile>&& file)
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
#include "db/db_test_util.h"
|
||||
#include "env/unique_id_gen.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/persistent_cache.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/table_properties.h"
|
||||
@@ -240,53 +239,6 @@ TEST_F(DBBlockCacheTest, TestWithoutCompressedBlockCache) {
|
||||
#ifdef SNAPPY
|
||||
|
||||
namespace {
|
||||
class PersistentCacheFromCache : public PersistentCache {
|
||||
public:
|
||||
PersistentCacheFromCache(std::shared_ptr<Cache> cache, bool read_only)
|
||||
: cache_(cache), read_only_(read_only) {}
|
||||
|
||||
Status Insert(const Slice& key, const char* data,
|
||||
const size_t size) override {
|
||||
if (read_only_) {
|
||||
return Status::NotSupported();
|
||||
}
|
||||
std::unique_ptr<char[]> copy{new char[size]};
|
||||
std::copy_n(data, size, copy.get());
|
||||
Status s = cache_.Insert(key, copy.get(), size);
|
||||
if (s.ok()) {
|
||||
copy.release();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status Lookup(const Slice& key, std::unique_ptr<char[]>* data,
|
||||
size_t* size) override {
|
||||
auto handle = cache_.Lookup(key);
|
||||
if (handle) {
|
||||
char* ptr = cache_.Value(handle);
|
||||
*size = cache_.get()->GetCharge(handle);
|
||||
data->reset(new char[*size]);
|
||||
std::copy_n(ptr, *size, data->get());
|
||||
cache_.Release(handle);
|
||||
return Status::OK();
|
||||
} else {
|
||||
return Status::NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
bool IsCompressed() override { return false; }
|
||||
|
||||
StatsType Stats() override { return StatsType(); }
|
||||
|
||||
std::string GetPrintableOptions() const override { return ""; }
|
||||
|
||||
uint64_t NewId() override { return cache_.get()->NewId(); }
|
||||
|
||||
private:
|
||||
BasicTypedSharedCacheInterface<char[], CacheEntryRole::kMisc> cache_;
|
||||
bool read_only_;
|
||||
};
|
||||
|
||||
class ReadOnlyCacheWrapper : public CacheWrapper {
|
||||
public:
|
||||
using CacheWrapper::CacheWrapper;
|
||||
|
||||
@@ -928,6 +928,43 @@ INSTANTIATE_TEST_CASE_P(
|
||||
BlockBasedTableOptions::kLatestSupportedFormatVersion)));
|
||||
#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
|
||||
TEST_F(DBBloomFilterTest, PartitionedPrefixFilterMultiGetKeysOutOfDomain) {
|
||||
for (bool decouple : {false, true}) {
|
||||
SCOPED_TRACE("decouple_partitioned_filters=" + std::to_string(decouple));
|
||||
Options options = CurrentOptions();
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(4));
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10));
|
||||
bbto.whole_key_filtering = false;
|
||||
bbto.partition_filters = true;
|
||||
bbto.decouple_partitioned_filters = decouple;
|
||||
bbto.index_type = BlockBasedTableOptions::kTwoLevelIndexSearch;
|
||||
bbto.metadata_block_size = 1;
|
||||
bbto.block_size = 1;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
std::vector<std::string> keys;
|
||||
std::vector<std::string> expected;
|
||||
for (char c = 'a'; c <= 'j'; ++c) {
|
||||
keys.emplace_back(1, c);
|
||||
expected.push_back("v_" + keys.back());
|
||||
ASSERT_OK(Put(keys.back(), expected.back()));
|
||||
}
|
||||
ASSERT_OK(Put("kkkk1", "v_kkkk1"));
|
||||
ASSERT_OK(Flush());
|
||||
keys.emplace_back("aaaa");
|
||||
expected.emplace_back("NOT_FOUND");
|
||||
keys.emplace_back("kkkk1");
|
||||
expected.emplace_back("v_kkkk1");
|
||||
|
||||
for (size_t i = 0; i < keys.size(); ++i) {
|
||||
ASSERT_EQ(expected[i], Get(keys[i]));
|
||||
}
|
||||
ASSERT_EQ(expected, MultiGet(keys));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBloomFilterTest, BloomFilterRate) {
|
||||
while (ChangeFilterOptions()) {
|
||||
Options options = CurrentOptions();
|
||||
|
||||
+44
-1
@@ -195,6 +195,49 @@ TEST_F(DBClipTest, TestClipRange) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBClipTest, SingleEmptyUserKey) {
|
||||
struct TestCase {
|
||||
const Comparator* comparator;
|
||||
const char* begin_key;
|
||||
const char* end_key;
|
||||
bool keep_empty_key;
|
||||
};
|
||||
const TestCase cases[] = {
|
||||
{BytewiseComparator(), "b", "d", false},
|
||||
{BytewiseComparator(), "", "z", true},
|
||||
{ReverseBytewiseComparator(), "d", "", false},
|
||||
};
|
||||
for (const TestCase& test_case : cases) {
|
||||
for (int level : {0, 1}) {
|
||||
SCOPED_TRACE(test_case.comparator->Name());
|
||||
SCOPED_TRACE(test_case.begin_key);
|
||||
SCOPED_TRACE(level);
|
||||
Options options = CurrentOptions();
|
||||
options.comparator = test_case.comparator;
|
||||
options.disable_auto_compactions = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(db_->ClipColumnFamily(db_->DefaultColumnFamily(),
|
||||
test_case.begin_key, test_case.end_key));
|
||||
ASSERT_EQ("NOT_FOUND", Get(""));
|
||||
ASSERT_OK(Put("", "empty-key-value"));
|
||||
ASSERT_OK(Flush());
|
||||
if (level != 0) {
|
||||
MoveFilesToLevel(level);
|
||||
}
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(level));
|
||||
|
||||
ASSERT_OK(db_->ClipColumnFamily(db_->DefaultColumnFamily(),
|
||||
test_case.begin_key, test_case.end_key));
|
||||
const std::string expected =
|
||||
test_case.keep_empty_key ? "empty-key-value" : "NOT_FOUND";
|
||||
ASSERT_EQ(expected, Get(""));
|
||||
Reopen(options);
|
||||
ASSERT_EQ(expected, Get(""));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBClipTest, KeepFileEndingAtBeginKey) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
@@ -222,4 +265,4 @@ int main(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
}
|
||||
@@ -188,8 +188,11 @@ class SyncPointAbortHelper {
|
||||
|
||||
// Clean up sync points and wait for abort completion in one call
|
||||
void CleanupAndWait() {
|
||||
CleanupSyncPoints();
|
||||
// We hold no DB mutex, and the wait releases abort_mutex_. Keeping sync
|
||||
// points enabled lets the worker observe the abort and exit, so the abort
|
||||
// thread can finish before cleanup.
|
||||
WaitForAbortCompletion();
|
||||
CleanupSyncPoints();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -8,7 +8,12 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "options/options_helper.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/utilities/transaction_db.h"
|
||||
#include "test_util/merge_filter_test_util.h"
|
||||
#include "util/defer.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -23,6 +28,17 @@ class DBTestCompactionFilter : public DBTestBase {
|
||||
public:
|
||||
DBTestCompactionFilter()
|
||||
: DBTestBase("db_compaction_filter_test", /*env_do_fsync=*/true) {}
|
||||
|
||||
Status CompactAllMergeFilterFiles() {
|
||||
std::vector<LiveFileMetaData> files;
|
||||
db_->GetLiveFilesMetaData(&files);
|
||||
std::vector<std::string> names;
|
||||
names.reserve(files.size());
|
||||
for (const auto& file : files) {
|
||||
names.push_back(file.relative_filename);
|
||||
}
|
||||
return db_->CompactFiles(CompactionOptions(), names, 1);
|
||||
}
|
||||
};
|
||||
|
||||
// Param variant of DBTestBase::ChangeCompactOptions
|
||||
@@ -1048,6 +1064,561 @@ TEST_F(DBTestCompactionFilter, DropKeyWithSingleDelete) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTestCompactionFilter, MergeBeforeFilterModeDeclarations) {
|
||||
using Order = CompactionFilter::MergeOrder;
|
||||
test::MergeFilterTrace trace;
|
||||
test::RecordingMergeFilter legacy(Order::kFilterBeforeMerge, &trace);
|
||||
test::RecordingMergeFilter post(Order::kMergeBeforeFilter, &trace);
|
||||
test::RecordingMergeFilterFactory legacy_factory(Order::kFilterBeforeMerge,
|
||||
&trace);
|
||||
test::RecordingMergeFilterFactory post_factory(Order::kMergeBeforeFilter,
|
||||
&trace);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ASSERT_EQ(Order::kFilterBeforeMerge, legacy.GetMergeOrder());
|
||||
ASSERT_EQ(Order::kMergeBeforeFilter, post.GetMergeOrder());
|
||||
ASSERT_EQ(Order::kMergeBeforeFilter, post_factory.GetMergeOrder());
|
||||
}
|
||||
Order order;
|
||||
ASSERT_OK(GetCompactionFilterMergeOrder(
|
||||
nullptr, nullptr, TableFileCreationReason::kCompaction, &order));
|
||||
ASSERT_EQ(Order::kFilterBeforeMerge, order);
|
||||
ASSERT_OK(GetCompactionFilterMergeOrder(
|
||||
&post, &post_factory, TableFileCreationReason::kCompaction, &order));
|
||||
ASSERT_EQ(Order::kMergeBeforeFilter, order);
|
||||
ASSERT_TRUE(
|
||||
GetCompactionFilterMergeOrder(
|
||||
&legacy, &post_factory, TableFileCreationReason::kCompaction, &order)
|
||||
.IsInvalidArgument());
|
||||
ASSERT_TRUE(
|
||||
GetCompactionFilterMergeOrder(
|
||||
&post, &legacy_factory, TableFileCreationReason::kCompaction, &order)
|
||||
.IsInvalidArgument());
|
||||
for (TableFileCreationReason reason :
|
||||
{TableFileCreationReason::kFlush, TableFileCreationReason::kRecovery,
|
||||
TableFileCreationReason::kMisc,
|
||||
TableFileCreationReason::kSstFileWriter}) {
|
||||
ASSERT_OK(GetCompactionFilterMergeOrder(&legacy, &legacy_factory, reason,
|
||||
&order));
|
||||
ASSERT_TRUE(
|
||||
GetCompactionFilterMergeOrder(&post, &post_factory, reason, &order)
|
||||
.IsInvalidArgument());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTestCompactionFilter, MergeBeforeFilterNullFactory) {
|
||||
test::MergeFilterTrace trace;
|
||||
auto factory = std::make_shared<test::RecordingMergeFilterFactory>(
|
||||
CompactionFilter::MergeOrder::kMergeBeforeFilter, &trace);
|
||||
factory->return_null = true;
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.compaction_filter_factory = factory;
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("a", "value"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(CompactAllMergeFilterFiles());
|
||||
ASSERT_EQ("value", Get("a"));
|
||||
ASSERT_EQ(1, factory->creations.load());
|
||||
ASSERT_TRUE(trace.Events().empty());
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBTestCompactionFilter, MergeBeforeFilterSharedCreationRuntimeGuard) {
|
||||
for (bool recovery : {false, true}) {
|
||||
test::MergeFilterTrace trace;
|
||||
auto factory = std::make_shared<test::RecordingMergeFilterFactory>(
|
||||
CompactionFilter::MergeOrder::kFilterBeforeMerge, &trace);
|
||||
factory->filter_other_reason = true;
|
||||
factory->other_reason = recovery ? TableFileCreationReason::kRecovery
|
||||
: TableFileCreationReason::kFlush;
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.compaction_filter_factory = factory;
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("a", "durable"));
|
||||
factory->instance_order = CompactionFilter::MergeOrder::kMergeBeforeFilter;
|
||||
ASSERT_OK(ValidateCompactionFilterOptions(options));
|
||||
if (recovery) {
|
||||
Close();
|
||||
ASSERT_TRUE(TryReopen(options).IsInvalidArgument());
|
||||
} else {
|
||||
ASSERT_TRUE(Flush().IsInvalidArgument());
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ("durable", Get("a"));
|
||||
Close();
|
||||
}
|
||||
ASSERT_EQ(1, factory->creations.load());
|
||||
ASSERT_TRUE(trace.Events().empty());
|
||||
factory->instance_order = CompactionFilter::MergeOrder::kFilterBeforeMerge;
|
||||
Reopen(options);
|
||||
ASSERT_EQ("durable", Get("a"));
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTestCompactionFilter, MergeBeforeFilterRepairRuntimeGuard) {
|
||||
test::MergeFilterTrace trace;
|
||||
auto factory = std::make_shared<test::RecordingMergeFilterFactory>(
|
||||
CompactionFilter::MergeOrder::kFilterBeforeMerge, &trace);
|
||||
factory->filter_other_reason = true;
|
||||
factory->other_reason = TableFileCreationReason::kRecovery;
|
||||
factory->instance_order = CompactionFilter::MergeOrder::kMergeBeforeFilter;
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("sst", "preserved"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("wal", "not-installed"));
|
||||
Close();
|
||||
options.compaction_filter_factory = factory;
|
||||
ASSERT_OK(ValidateCompactionFilterOptions(options));
|
||||
// Repair retains its best-effort policy: a failed WAL conversion is logged
|
||||
// and archived, while existing SST files are still recovered.
|
||||
ASSERT_OK(RepairDB(dbname_, options));
|
||||
ASSERT_EQ(1, factory->creations.load());
|
||||
// The filter was created but never saw a key, and the WAL data is absent.
|
||||
ASSERT_TRUE(trace.Events().empty());
|
||||
options.compaction_filter_factory.reset();
|
||||
Reopen(options);
|
||||
ASSERT_EQ("preserved", Get("sst"));
|
||||
ASSERT_EQ("NOT_FOUND", Get("wal"));
|
||||
ASSERT_EQ(1, TotalTableFiles());
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBTestCompactionFilter, MergeBeforeFilterRepeatedPurgeAndRollback) {
|
||||
test::MergeFilterTrace trace;
|
||||
test::RecordingMergeFilter post(
|
||||
CompactionFilter::MergeOrder::kMergeBeforeFilter, &trace);
|
||||
test::RecordingMergeFilter legacy(
|
||||
CompactionFilter::MergeOrder::kFilterBeforeMerge, &trace);
|
||||
auto merge = std::make_shared<test::RecordingMergeOperator>(&trace);
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.merge_operator = merge;
|
||||
options.compaction_filter = &post;
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("a", "A"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Merge("a", "B"));
|
||||
ASSERT_OK(Flush());
|
||||
trace.Clear();
|
||||
ASSERT_OK(CompactAllMergeFilterFiles());
|
||||
ASSERT_EQ((std::vector<std::string>{"full:a:AB", "value:a:AB"}),
|
||||
trace.Events());
|
||||
ASSERT_EQ("AB", Get("a"));
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
ASSERT_OK(Merge("a", "C"));
|
||||
ASSERT_OK(Flush());
|
||||
trace.Clear();
|
||||
ASSERT_OK(CompactAllMergeFilterFiles());
|
||||
ASSERT_EQ((std::vector<std::string>{"operand:a:C", "value:a:AB"}),
|
||||
trace.Events());
|
||||
ASSERT_EQ("AB", Get("a", snapshot));
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
trace.Clear();
|
||||
ASSERT_OK(CompactAllMergeFilterFiles());
|
||||
ASSERT_EQ((std::vector<std::string>{"full:a:ABC", "value:a:ABC"}),
|
||||
trace.Events());
|
||||
trace.Clear();
|
||||
post.value_decision = CompactionFilter::Decision::kPurge;
|
||||
ASSERT_OK(CompactAllMergeFilterFiles());
|
||||
ASSERT_EQ("NOT_FOUND", Get("a"));
|
||||
ASSERT_EQ((std::vector<std::string>{"value:a:ABC"}), trace.Events());
|
||||
// Startup selection changes ordering, retaining ordinary removal for history.
|
||||
Close();
|
||||
legacy.value_decision = CompactionFilter::Decision::kRemove;
|
||||
options.compaction_filter = &legacy;
|
||||
Reopen(options);
|
||||
ASSERT_EQ("NOT_FOUND", Get("a"));
|
||||
ASSERT_OK(Put("a", "new"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(CompactAllMergeFilterFiles());
|
||||
ASSERT_EQ("NOT_FOUND", Get("a"));
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBTestCompactionFilter,
|
||||
MergeBeforeFilterRemovalOutsideInputsAndExactSkip) {
|
||||
for (bool skip : {false, true}) {
|
||||
test::MergeFilterTrace trace;
|
||||
test::RecordingMergeFilter filter(
|
||||
CompactionFilter::MergeOrder::kMergeBeforeFilter, &trace);
|
||||
filter.action_key = "a";
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.compaction_filter = &filter;
|
||||
options.merge_operator =
|
||||
std::make_shared<test::RecordingMergeOperator>(&trace);
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("a", "older"));
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(options.num_levels - 1);
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
ASSERT_OK(Put("a", "base"));
|
||||
ASSERT_OK(Put("b", "middle"));
|
||||
ASSERT_OK(Put("c", "next"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Merge("a", "operand"));
|
||||
ASSERT_OK(Flush());
|
||||
filter.value_decision =
|
||||
skip ? CompactionFilter::Decision::kRemoveAndSkipUntil
|
||||
: CompactionFilter::Decision::kPurge;
|
||||
filter.skip_target = "c";
|
||||
trace.Clear();
|
||||
std::vector<LiveFileMetaData> files;
|
||||
db_->GetLiveFilesMetaData(&files);
|
||||
std::vector<std::string> inputs;
|
||||
for (const auto& file : files) {
|
||||
if (file.level == 0) {
|
||||
inputs.push_back(file.relative_filename);
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(2, inputs.size());
|
||||
ASSERT_OK(db_->CompactFiles(CompactionOptions(), inputs, 1));
|
||||
ASSERT_EQ(skip ? "older" : "NOT_FOUND", Get("a"));
|
||||
ASSERT_EQ("older", Get("a", snapshot));
|
||||
ASSERT_EQ(skip ? "NOT_FOUND" : "middle", Get("b"));
|
||||
ASSERT_EQ("next", Get("c"));
|
||||
ASSERT_EQ(
|
||||
skip ? (std::vector<std::string>{"full:a:baseoperand",
|
||||
"value:a:baseoperand", "value:c:next"})
|
||||
: (std::vector<std::string>{"full:a:baseoperand",
|
||||
"value:a:baseoperand",
|
||||
"value:b:middle", "value:c:next"}),
|
||||
trace.Events());
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
if (!skip) {
|
||||
CompactRangeOptions compact;
|
||||
compact.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(compact, nullptr, nullptr));
|
||||
ASSERT_EQ("NOT_FOUND", Get("a"));
|
||||
}
|
||||
Close();
|
||||
Reopen(options);
|
||||
ASSERT_EQ(skip ? "older" : "NOT_FOUND", Get("a"));
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTestCompactionFilter, MergeBeforeFilterWideEntityAndBlobBase) {
|
||||
for (bool blobs : {false, true}) {
|
||||
test::MergeFilterTrace trace;
|
||||
test::RecordingMergeFilter filter(
|
||||
CompactionFilter::MergeOrder::kMergeBeforeFilter, &trace);
|
||||
auto merge = std::make_shared<test::RecordingMergeOperator>(&trace);
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.compaction_filter = &filter;
|
||||
options.merge_operator = merge;
|
||||
options.enable_blob_files = blobs;
|
||||
options.min_blob_size = 0;
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(db_->PutEntity(WriteOptions(), db_->DefaultColumnFamily(), "a",
|
||||
{{"", "A"}, {"extra", "payload"}}));
|
||||
ASSERT_OK(Put("b", "base"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Merge("a", "B"));
|
||||
ASSERT_OK(Merge("b", "operand"));
|
||||
ASSERT_OK(Flush());
|
||||
trace.Clear();
|
||||
ASSERT_OK(CompactAllMergeFilterFiles());
|
||||
ASSERT_EQ(
|
||||
(std::vector<std::string>{"full:a:AB", "entity:a:AB",
|
||||
"full:b:baseoperand", "value:b:baseoperand"}),
|
||||
trace.Events());
|
||||
PinnableWideColumns columns;
|
||||
ASSERT_OK(db_->GetEntity(ReadOptions(), db_->DefaultColumnFamily(), "a",
|
||||
&columns));
|
||||
ASSERT_EQ((WideColumns{{"", "AB"}, {"extra", "payload"}}),
|
||||
columns.columns());
|
||||
ASSERT_EQ("baseoperand", Get("b"));
|
||||
filter.value_decision = CompactionFilter::Decision::kPurge;
|
||||
filter.blob_decision = CompactionFilter::Decision::kPurge;
|
||||
trace.Clear();
|
||||
ASSERT_OK(CompactAllMergeFilterFiles());
|
||||
ASSERT_EQ("NOT_FOUND", Get("a"));
|
||||
ASSERT_EQ("NOT_FOUND", Get("b"));
|
||||
if (blobs) {
|
||||
ASSERT_GT(filter.blob_key_calls.load(), 0);
|
||||
}
|
||||
Close();
|
||||
Reopen(options);
|
||||
ASSERT_EQ("NOT_FOUND", Get("a"));
|
||||
ASSERT_EQ("NOT_FOUND", Get("b"));
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTestCompactionFilter, MergeBeforeFilterBlobReadFailures) {
|
||||
class ResolvingFilter : public test::RecordingMergeFilter {
|
||||
public:
|
||||
explicit ResolvingFilter(test::MergeFilterTrace* trace)
|
||||
: RecordingMergeFilter(MergeOrder::kMergeBeforeFilter, trace) {}
|
||||
|
||||
Decision FilterV4(
|
||||
int level, const Slice& key, ValueType type, const Slice* value,
|
||||
const WideColumns* columns, std::string* new_value,
|
||||
std::vector<std::pair<std::string, std::string>>* new_columns,
|
||||
std::string* skip_until,
|
||||
WideColumnBlobResolver* resolver) const override {
|
||||
if (resolver != nullptr) {
|
||||
Slice resolved;
|
||||
Status s = resolver->ResolveColumn(0, &resolved);
|
||||
if (!s.ok()) {
|
||||
resolve_failed.store(true);
|
||||
return Decision::kKeep;
|
||||
}
|
||||
}
|
||||
return RecordingMergeFilter::FilterV4(level, key, type, value, columns,
|
||||
new_value, new_columns, skip_until,
|
||||
resolver);
|
||||
}
|
||||
|
||||
mutable std::atomic<bool> resolve_failed{false};
|
||||
};
|
||||
|
||||
for (int history : {0, 1, 2}) {
|
||||
FaultInjectionTestEnv fault_env(env_);
|
||||
test::MergeFilterTrace trace;
|
||||
ResolvingFilter filter(&trace);
|
||||
Defer cleanup([&]() {
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
fault_env.SetFilesystemActive(true);
|
||||
Close();
|
||||
});
|
||||
Options options = CurrentOptions();
|
||||
options.env = &fault_env;
|
||||
options.disable_auto_compactions = true;
|
||||
options.compaction_filter = &filter;
|
||||
options.merge_operator =
|
||||
std::make_shared<test::RecordingMergeOperator>(&trace);
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
options.blob_compaction_readahead_size = 0;
|
||||
DestroyAndReopen(options);
|
||||
if (history == 0) {
|
||||
ASSERT_OK(Put("a", "base"));
|
||||
} else {
|
||||
ASSERT_OK(db_->PutEntity(WriteOptions(), db_->DefaultColumnFamily(), "a",
|
||||
{{"", "base"}, {"extra", "payload"}}));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
if (history != 2) {
|
||||
ASSERT_OK(Merge("a", "operand"));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
trace.Clear();
|
||||
std::vector<LiveFileMetaData> before;
|
||||
db_->GetLiveFilesMetaData(&before);
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlobFileReader::GetBlob:ReadFromFile", [&](void*) {
|
||||
fault_env.SetFilesystemActive(false, Status::IOError("blob read"));
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Status s = CompactAllMergeFilterFiles();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
fault_env.SetFilesystemActive(true);
|
||||
ASSERT_TRUE(s.IsIOError()) << s.ToString();
|
||||
ASSERT_TRUE(trace.Events().empty());
|
||||
ASSERT_EQ(history == 2, filter.resolve_failed.load());
|
||||
std::vector<LiveFileMetaData> after;
|
||||
db_->GetLiveFilesMetaData(&after);
|
||||
ASSERT_EQ(before.size(), after.size());
|
||||
for (size_t i = 0; i < before.size(); ++i) {
|
||||
ASSERT_EQ(before[i].file_number, after[i].file_number);
|
||||
}
|
||||
ASSERT_EQ(history == 2 ? "base" : "baseoperand", Get("a"));
|
||||
Close();
|
||||
Reopen(options);
|
||||
ASSERT_EQ(history == 2 ? "base" : "baseoperand", Get("a"));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTestCompactionFilter, MergeBeforeFilterEarlyFactoryValidation) {
|
||||
for (TableFileCreationReason reason :
|
||||
{TableFileCreationReason::kFlush, TableFileCreationReason::kRecovery,
|
||||
TableFileCreationReason::kMisc,
|
||||
TableFileCreationReason::kSstFileWriter}) {
|
||||
test::MergeFilterTrace trace;
|
||||
auto factory = std::make_shared<test::RecordingMergeFilterFactory>(
|
||||
CompactionFilter::MergeOrder::kMergeBeforeFilter, &trace);
|
||||
factory->filter_other_reason = true;
|
||||
factory->other_reason = reason;
|
||||
test::RecordingMergeFilter direct(
|
||||
CompactionFilter::MergeOrder::kMergeBeforeFilter, &trace);
|
||||
Options invalid = CurrentOptions();
|
||||
invalid.compaction_filter_factory = factory;
|
||||
invalid.compaction_filter = &direct;
|
||||
ASSERT_TRUE(ValidateCompactionFilterOptions(invalid).IsInvalidArgument());
|
||||
ColumnFamilyHandle* handle = nullptr;
|
||||
ASSERT_TRUE(db_->CreateColumnFamily(invalid, "rejected", &handle)
|
||||
.IsInvalidArgument());
|
||||
ASSERT_EQ(nullptr, handle);
|
||||
ASSERT_EQ(0, factory->creations.load());
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
Status batch = db_->CreateColumnFamilies(
|
||||
{ColumnFamilyDescriptor("accepted", CurrentOptions()),
|
||||
ColumnFamilyDescriptor("rejected", invalid)},
|
||||
&handles);
|
||||
ASSERT_TRUE(batch.IsInvalidArgument());
|
||||
ASSERT_EQ(1, handles.size());
|
||||
ASSERT_EQ("accepted", handles[0]->GetName());
|
||||
ASSERT_OK(db_->DropColumnFamily(handles[0]));
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(handles[0]));
|
||||
Close();
|
||||
ASSERT_TRUE(TryReopen(invalid).IsInvalidArgument());
|
||||
ASSERT_EQ(0, factory->creations.load());
|
||||
ASSERT_TRUE(trace.Events().empty());
|
||||
Reopen(CurrentOptions());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTestCompactionFilter, MergeBeforeFilterUnsupportedConfigsFailEarly) {
|
||||
test::MergeFilterTrace trace;
|
||||
test::RecordingMergeFilter post(
|
||||
CompactionFilter::MergeOrder::kMergeBeforeFilter, &trace);
|
||||
test::RecordingMergeFilter legacy(
|
||||
CompactionFilter::MergeOrder::kFilterBeforeMerge, &trace);
|
||||
// User-defined timestamps.
|
||||
Options ts_options = CurrentOptions();
|
||||
ts_options.comparator = test::BytewiseComparatorWithU64TsWrapper();
|
||||
ts_options.compaction_filter = &post;
|
||||
ASSERT_TRUE(ValidateCompactionFilterOptions(ts_options).IsNotSupported());
|
||||
ColumnFamilyHandle* handle = nullptr;
|
||||
ASSERT_TRUE(
|
||||
db_->CreateColumnFamily(ts_options, "ts", &handle).IsNotSupported());
|
||||
ASSERT_EQ(nullptr, handle);
|
||||
ts_options.compaction_filter = &legacy;
|
||||
ASSERT_OK(ValidateCompactionFilterOptions(ts_options));
|
||||
// WritePrepared and WriteUnprepared transactions use a SnapshotChecker.
|
||||
const std::string path = test::PerThreadDBPath(env_, "mbf_txn");
|
||||
for (TxnDBWritePolicy policy :
|
||||
{TxnDBWritePolicy::WRITE_COMMITTED, TxnDBWritePolicy::WRITE_PREPARED,
|
||||
TxnDBWritePolicy::WRITE_UNPREPARED}) {
|
||||
SCOPED_TRACE(static_cast<int>(policy));
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.compaction_filter = &post;
|
||||
TransactionDBOptions txn_options;
|
||||
txn_options.write_policy = policy;
|
||||
ASSERT_OK(DestroyDB(path, options));
|
||||
TransactionDB* txn_db = nullptr;
|
||||
Status s = TransactionDB::Open(options, txn_options, path, &txn_db);
|
||||
if (policy == TxnDBWritePolicy::WRITE_COMMITTED) {
|
||||
ASSERT_OK(s);
|
||||
delete txn_db;
|
||||
continue;
|
||||
}
|
||||
ASSERT_TRUE(s.IsNotSupported()) << s.ToString();
|
||||
ASSERT_EQ(nullptr, txn_db);
|
||||
// Creating a merge-before-filter CF in such a DB fails too.
|
||||
options.compaction_filter = &legacy;
|
||||
ASSERT_OK(TransactionDB::Open(options, txn_options, path, &txn_db));
|
||||
Options cf_options = options;
|
||||
cf_options.compaction_filter = &post;
|
||||
ASSERT_TRUE(txn_db->CreateColumnFamily(cf_options, "post", &handle)
|
||||
.IsNotSupported());
|
||||
ASSERT_EQ(nullptr, handle);
|
||||
ASSERT_OK(txn_db->Put(WriteOptions(), "a", "value"));
|
||||
delete txn_db;
|
||||
}
|
||||
ASSERT_OK(DestroyDB(path, CurrentOptions()));
|
||||
}
|
||||
|
||||
TEST_F(DBTestCompactionFilter,
|
||||
MergeBeforeFilterSkipOnOlderOperandMatchesLegacy) {
|
||||
for (CompactionFilter::MergeOrder order :
|
||||
{CompactionFilter::MergeOrder::kFilterBeforeMerge,
|
||||
CompactionFilter::MergeOrder::kMergeBeforeFilter}) {
|
||||
SCOPED_TRACE(static_cast<int>(order));
|
||||
test::MergeFilterTrace trace;
|
||||
test::RecordingMergeFilter filter(order, &trace);
|
||||
filter.action_value = "M1";
|
||||
filter.operand_decision = CompactionFilter::Decision::kRemoveAndSkipUntil;
|
||||
filter.skip_target = "b";
|
||||
auto merge = std::make_shared<test::RecordingMergeOperator>(&trace);
|
||||
merge->partial_success = false;
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.compaction_filter = &filter;
|
||||
options.merge_operator = merge;
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("a", "base"));
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(options.num_levels - 1);
|
||||
ASSERT_OK(Merge("a", "M1"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Merge("a", "M2"));
|
||||
ASSERT_OK(Flush());
|
||||
std::vector<LiveFileMetaData> files;
|
||||
db_->GetLiveFilesMetaData(&files);
|
||||
std::vector<std::string> inputs;
|
||||
for (const auto& file : files) {
|
||||
if (file.level == 0) {
|
||||
inputs.push_back(file.relative_filename);
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(2, inputs.size());
|
||||
ASSERT_OK(db_->CompactFiles(CompactionOptions(), inputs, 1));
|
||||
// Both orders drop every operand of the batch, exposing the older base.
|
||||
ASSERT_EQ("base", Get("a"));
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTestCompactionFilter, MergeBeforeFilterRuntimeDeclarationAndErrors) {
|
||||
for (bool paranoid : {false, true}) {
|
||||
test::MergeFilterTrace trace;
|
||||
auto factory = std::make_shared<test::RecordingMergeFilterFactory>(
|
||||
CompactionFilter::MergeOrder::kMergeBeforeFilter, &trace);
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.paranoid_checks = paranoid;
|
||||
options.merge_operator =
|
||||
std::make_shared<test::RecordingMergeOperator>(&trace);
|
||||
options.compaction_filter_factory = factory;
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Merge("a", "A"));
|
||||
ASSERT_OK(Flush());
|
||||
trace.Clear();
|
||||
factory->instance_order = CompactionFilter::MergeOrder::kFilterBeforeMerge;
|
||||
CompactRangeOptions compact;
|
||||
compact.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
compact.change_level = true;
|
||||
compact.target_level = options.num_levels - 1;
|
||||
ASSERT_TRUE(
|
||||
db_->CompactRange(compact, nullptr, nullptr).IsInvalidArgument());
|
||||
ASSERT_EQ(1, factory->creations.load());
|
||||
ASSERT_TRUE(trace.Events().empty());
|
||||
ASSERT_EQ("A", Get("a"));
|
||||
Close();
|
||||
factory->instance_order = CompactionFilter::MergeOrder::kMergeBeforeFilter;
|
||||
Reopen(options);
|
||||
ASSERT_OK(db_->CompactRange(compact, nullptr, nullptr));
|
||||
factory->operand_decision = CompactionFilter::Decision::kPurge;
|
||||
// Keep the base in a lower level outside this compaction's inputs.
|
||||
ASSERT_OK(Merge("a", "B"));
|
||||
ASSERT_OK(Flush());
|
||||
std::vector<LiveFileMetaData> files;
|
||||
db_->GetLiveFilesMetaData(&files);
|
||||
ASSERT_FALSE(files.empty());
|
||||
const LiveFileMetaData* newest = &files.front();
|
||||
for (const auto& file : files) {
|
||||
if (file.file_number > newest->file_number) {
|
||||
newest = &file;
|
||||
}
|
||||
}
|
||||
Status invalid =
|
||||
db_->CompactFiles(CompactionOptions(), {newest->relative_filename}, 1);
|
||||
ASSERT_TRUE(invalid.IsNotSupported()) << invalid.ToString();
|
||||
ASSERT_EQ("AB", Get("a"));
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+614
-37
@@ -7,11 +7,15 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "compaction/compaction_picker_universal.h"
|
||||
#include "db/blob/blob_index.h"
|
||||
@@ -37,6 +41,7 @@
|
||||
#include "util/concurrent_task_limiter_impl.h"
|
||||
#include "util/fastrange.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/random.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
#include "utilities/fault_injection_fs.h"
|
||||
@@ -141,6 +146,19 @@ class DBCompactionTest : public DBTestBase {
|
||||
}
|
||||
|
||||
protected:
|
||||
std::vector<std::pair<std::string, std::string>> TableFileNamesByKey() {
|
||||
ColumnFamilyMetaData metadata;
|
||||
db_->GetColumnFamilyMetaData(&metadata);
|
||||
std::vector<std::pair<std::string, std::string>> files;
|
||||
for (const auto& level : metadata.levels) {
|
||||
for (const auto& file : level.files) {
|
||||
files.emplace_back(file.smallestkey, file.name);
|
||||
}
|
||||
}
|
||||
std::sort(files.begin(), files.end());
|
||||
return files;
|
||||
}
|
||||
|
||||
/*
|
||||
* Verifies compaction stats of cfd are valid.
|
||||
*
|
||||
@@ -591,8 +609,6 @@ class FlushedFileCollector : public EventListener {
|
||||
return result;
|
||||
}
|
||||
|
||||
void ClearFlushedFiles() { flushed_files_.clear(); }
|
||||
|
||||
private:
|
||||
std::vector<std::string> flushed_files_;
|
||||
std::mutex mutex_;
|
||||
@@ -4411,6 +4427,150 @@ static std::string ShortKey(int i) {
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, MissingBlobRepairDisablesTrivialMovesPerCall) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.num_levels = 3;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.compression = kNoCompression;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put("a", "value-a"));
|
||||
ASSERT_OK(Flush());
|
||||
const auto before = TableFileNamesByKey();
|
||||
ASSERT_EQ(before.size(), 1U);
|
||||
|
||||
std::atomic<int> trivial_moves{0};
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:TrivialMove",
|
||||
[&](void*) { ++trivial_moves; });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.skip_missing_blob_files = true;
|
||||
compact_options.bottommost_level_compaction =
|
||||
BottommostLevelCompaction::kSkip;
|
||||
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
|
||||
const auto repaired = TableFileNamesByKey();
|
||||
ASSERT_EQ(repaired.size(), 1U);
|
||||
ASSERT_NE(before[0].second, repaired[0].second);
|
||||
ASSERT_EQ(trivial_moves.load(), 0);
|
||||
|
||||
ASSERT_OK(Put("z", "value-z"));
|
||||
ASSERT_OK(Flush());
|
||||
const auto before_default = TableFileNamesByKey();
|
||||
ASSERT_EQ(before_default.size(), 2U);
|
||||
const Slice key("z");
|
||||
compact_options.skip_missing_blob_files = false;
|
||||
ASSERT_OK(db_->CompactRange(compact_options, &key, &key));
|
||||
ASSERT_EQ(TableFileNamesByKey(), before_default);
|
||||
ASSERT_EQ(trivial_moves.load(), 1);
|
||||
ASSERT_EQ(Get("a"), "value-a");
|
||||
ASSERT_EQ(Get("z"), "value-z");
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, MissingBlobRepairRewritesSelectedBottommostFiles) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.num_levels = 3;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.compression = kNoCompression;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
for (const char* key : {"a", "m", "z"}) {
|
||||
ASSERT_OK(Put(key, key));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 2;
|
||||
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
|
||||
ASSERT_EQ(NumTableFilesAtLevel(2), 3);
|
||||
const auto before = TableFileNamesByKey();
|
||||
ASSERT_EQ(before.size(), 3U);
|
||||
|
||||
compact_options = CompactRangeOptions();
|
||||
compact_options.skip_missing_blob_files = true;
|
||||
compact_options.bottommost_level_compaction =
|
||||
BottommostLevelCompaction::kSkip;
|
||||
const Slice key("m");
|
||||
ASSERT_OK(db_->CompactRange(compact_options, &key, &key));
|
||||
const auto repaired = TableFileNamesByKey();
|
||||
ASSERT_EQ(repaired.size(), 3U);
|
||||
ASSERT_EQ(before[0], repaired[0]);
|
||||
ASSERT_NE(before[1].second, repaired[1].second);
|
||||
ASSERT_EQ(before[2], repaired[2]);
|
||||
|
||||
compact_options.skip_missing_blob_files = false;
|
||||
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
|
||||
ASSERT_EQ(TableFileNamesByKey(), repaired);
|
||||
for (const char* retained : {"a", "m", "z"}) {
|
||||
ASSERT_EQ(Get(retained), retained);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, MissingBlobRepairKeepsBottommostProgress) {
|
||||
class KeepFilter : public CompactionFilter {
|
||||
public:
|
||||
const char* Name() const override { return "KeepFilter"; }
|
||||
bool Filter(int, const Slice&, const Slice&, std::string*,
|
||||
bool*) const override {
|
||||
return false;
|
||||
}
|
||||
} keep_filter;
|
||||
|
||||
for (bool repair : {false, true}) {
|
||||
SCOPED_TRACE(repair);
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.num_levels = 3;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.compression = kNoCompression;
|
||||
const auto collector = std::make_shared<CompactionStatsCollector>();
|
||||
options.listeners.push_back(collector);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
for (const char* key : {"a", "z"}) {
|
||||
ASSERT_OK(Put(key, key));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 2;
|
||||
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
|
||||
ASSERT_EQ(NumTableFilesAtLevel(2), 2);
|
||||
const auto before = TableFileNamesByKey();
|
||||
ASSERT_EQ(before.size(), 2U);
|
||||
|
||||
options.compaction_filter = &keep_filter;
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("m", "m"));
|
||||
ASSERT_OK(Flush());
|
||||
const int compactions_before =
|
||||
collector->NumberOfCompactions(CompactionReason::kManualCompaction);
|
||||
|
||||
compact_options = CompactRangeOptions();
|
||||
compact_options.skip_missing_blob_files = repair;
|
||||
compact_options.bottommost_level_compaction =
|
||||
repair ? BottommostLevelCompaction::kSkip
|
||||
: BottommostLevelCompaction::kForceOptimized;
|
||||
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
|
||||
const auto after = TableFileNamesByKey();
|
||||
ASSERT_EQ(after.size(), 3U);
|
||||
ASSERT_NE(before[0].second, after[0].second);
|
||||
ASSERT_NE(before[1].second, after[2].second);
|
||||
ASSERT_EQ(
|
||||
collector->NumberOfCompactions(CompactionReason::kManualCompaction) -
|
||||
compactions_before,
|
||||
4);
|
||||
for (const char* retained : {"a", "m", "z"}) {
|
||||
ASSERT_EQ(Get(retained), retained);
|
||||
}
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DBCompactionTestWithParam, ForceBottommostLevelCompaction) {
|
||||
int32_t trivial_move = 0;
|
||||
int32_t non_trivial_move = 0;
|
||||
@@ -4961,17 +5121,31 @@ TEST_F(DBCompactionTest, DeleteFilesInRangeConflictWithCompaction) {
|
||||
{{"VersionSet::LogAndApply:WriteManifestStart",
|
||||
"BackgroundCallCompaction:0"},
|
||||
{"DBImpl::BackgroundCompaction:Finish",
|
||||
"VersionSet::LogAndApply:WriteManifestDone"}});
|
||||
"VersionSet::LogAndApply:WriteManifestDone"},
|
||||
{"DBImpl::WaitForCompact:StartWaiting",
|
||||
"DBCompactionTest::DeleteFilesInRangeConflictWithCompaction:"
|
||||
"WaitStarted"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// release snapshot which mark bottommost file for compaction
|
||||
// Release no longer schedules work directly. WaitForCompact refreshes the
|
||||
// oldest snapshot and schedules the compaction that this test races against
|
||||
// DeleteFilesInRange().
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
Status wait_status;
|
||||
port::Thread wait_thread(
|
||||
[&]() { wait_status = dbfull()->TEST_WaitForCompact(); });
|
||||
TEST_SYNC_POINT(
|
||||
"DBCompactionTest::DeleteFilesInRangeConflictWithCompaction:"
|
||||
"WaitStarted");
|
||||
std::string begin_string = Key(0);
|
||||
std::string end_string = Key(kMaxKey + 1);
|
||||
Slice begin(begin_string);
|
||||
Slice end(end_string);
|
||||
ASSERT_OK(
|
||||
DeleteFilesInRange(db_.get(), db_->DefaultColumnFamily(), &begin, &end));
|
||||
Status delete_status =
|
||||
DeleteFilesInRange(db_.get(), db_->DefaultColumnFamily(), &begin, &end);
|
||||
wait_thread.join();
|
||||
ASSERT_OK(delete_status);
|
||||
ASSERT_OK(wait_status);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
@@ -5016,10 +5190,9 @@ TEST_F(DBCompactionTest, CompactBottomLevelFilesWithDeletions) {
|
||||
std::vector<LiveFileMetaData> pre_release_metadata;
|
||||
std::vector<LiveFileMetaData> post_release_metadata;
|
||||
db_->GetLiveFilesMetaData(&pre_release_metadata);
|
||||
// just need to bump seqnum so ReleaseSnapshot knows the newest key in the SST
|
||||
// files does not need to be preserved in case of a future snapshot.
|
||||
// Bump the published sequence so a maintenance refresh after the release can
|
||||
// advance beyond the newest key in the SST files.
|
||||
ASSERT_OK(Put(Key(0), "val"));
|
||||
ASSERT_NE(kMaxSequenceNumber, dbfull()->bottommost_files_mark_threshold_);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"LevelCompactionPicker::PickCompaction:Return", [&](void* arg) {
|
||||
Compaction* compaction = static_cast<Compaction*>(arg);
|
||||
@@ -5027,11 +5200,9 @@ TEST_F(DBCompactionTest, CompactBottomLevelFilesWithDeletions) {
|
||||
CompactionReason::kBottommostFiles);
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
// release snapshot and wait for compactions to finish. Single-file
|
||||
// compactions should be triggered, which reduce the size of each bottom-level
|
||||
// file without changing file count.
|
||||
// Release does not schedule work. WaitForCompact refreshes the oldest
|
||||
// snapshot before waiting and triggers the single-file compactions.
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
ASSERT_EQ(kMaxSequenceNumber, dbfull()->bottommost_files_mark_threshold_);
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
db_->GetLiveFilesMetaData(&post_release_metadata);
|
||||
ASSERT_EQ(pre_release_metadata.size(), post_release_metadata.size());
|
||||
@@ -5048,6 +5219,113 @@ TEST_F(DBCompactionTest, CompactBottomLevelFilesWithDeletions) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, SnapshotReleaseDuringSuperVersionInstall) {
|
||||
// A snapshot release can race with SuperVersion installation. The installer
|
||||
// must observe the exact oldest-snapshot sequence after the release and
|
||||
// schedule newly eligible bottommost work.
|
||||
Options options = CurrentOptions();
|
||||
options.compression = kNoCompression;
|
||||
options.level0_file_num_compaction_trigger = 100;
|
||||
CreateAndReopenWithCF({"one"}, options);
|
||||
|
||||
constexpr int kNumKeys = 100;
|
||||
for (int i = 0; i < kNumKeys; ++i) {
|
||||
ASSERT_OK(Put(1, Key(i), std::string(100, 'v')));
|
||||
}
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
for (int i = 0; i < kNumKeys; i += 2) {
|
||||
ASSERT_OK(Delete(1, Key(i)));
|
||||
}
|
||||
// Advance the published sequence beyond the largest sequence in CF "one".
|
||||
ASSERT_OK(Put("advance", "value"));
|
||||
bool released_during_install = false;
|
||||
std::atomic_int bottommost_compactions{0};
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::InstallSuperVersionAndScheduleWork:"
|
||||
"BeforeSnapshotRefresh",
|
||||
[&](void*) {
|
||||
if (snapshot != nullptr) {
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
snapshot = nullptr;
|
||||
released_during_install = true;
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"LevelCompactionPicker::PickCompaction:Return", [&](void* arg) {
|
||||
auto* compaction = static_cast<Compaction*>(arg);
|
||||
if (compaction->compaction_reason() ==
|
||||
CompactionReason::kBottommostFiles) {
|
||||
++bottommost_compactions;
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(Flush(1));
|
||||
ASSERT_TRUE(released_during_install);
|
||||
ASSERT_EQ(snapshot, nullptr);
|
||||
// The exact scan must observe the release and schedule newly eligible work.
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_GT(bottommost_compactions.load(), 0);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest,
|
||||
StandaloneRangeTombstoneSnapshotAdvanceRequeuesScheduling) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.num_levels = 2;
|
||||
options.level0_file_num_compaction_trigger = 100;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put("old", "value"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("new", "value"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
DBImpl* impl = dbfull();
|
||||
InstrumentedMutexLock lock(&impl->mutex_);
|
||||
ColumnFamilyData* cfd =
|
||||
impl->GetVersionSet()->GetColumnFamilySet()->GetDefault();
|
||||
VersionStorageInfo* vstorage = cfd->current()->storage_info();
|
||||
ASSERT_EQ(vstorage->LevelFiles(0).size(), 2);
|
||||
|
||||
// Only the scheduling metadata matters here. Simulate an L0 standalone
|
||||
// range tombstone that a prior Universal picker can defer.
|
||||
FileMetaData* file = vstorage->LevelFiles(0).front();
|
||||
file->marked_for_compaction = true;
|
||||
file->num_entries = 1;
|
||||
file->num_range_deletions = 1;
|
||||
ASSERT_TRUE(file->FileIsStandAloneRangeTombstone());
|
||||
vstorage->ComputeFilesMarkedForCompaction(
|
||||
vstorage->MaxOutputLevel(cfd->AllowIngestBehind()));
|
||||
const SequenceNumber threshold =
|
||||
vstorage->standalone_range_tombstone_files_mark_threshold();
|
||||
ASSERT_EQ(threshold, file->fd.smallest_seqno);
|
||||
ASSERT_TRUE(impl->compaction_queue_.empty());
|
||||
const SequenceNumber oldest_snapshot =
|
||||
std::max(threshold, vstorage->oldest_snapshot_seqnum() + 1);
|
||||
|
||||
auto consume_queued_attempt = [&]() {
|
||||
ASSERT_EQ(impl->compaction_queue_.size(), 1);
|
||||
ColumnFamilyData* queued_cfd = impl->PopFirstFromCompactionQueue();
|
||||
ASSERT_EQ(queued_cfd, cfd);
|
||||
ASSERT_FALSE(queued_cfd->UnrefAndTryDelete());
|
||||
};
|
||||
|
||||
ASSERT_TRUE(
|
||||
impl->ReconcileOldestSnapshotForColumnFamily(cfd, oldest_snapshot));
|
||||
consume_queued_attempt();
|
||||
|
||||
// The prior attempt was skipped and consumed its queue entry. A later
|
||||
// snapshot advance must enqueue another attempt even though the threshold
|
||||
// was already crossed.
|
||||
ASSERT_TRUE(
|
||||
impl->ReconcileOldestSnapshotForColumnFamily(cfd, oldest_snapshot + 1));
|
||||
consume_queued_attempt();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, DelayCompactBottomLevelFilesWithDeletions) {
|
||||
// bottom-level files may contain deletions due to snapshots protecting the
|
||||
// deleted keys. Once the snapshot is released and the files are old enough,
|
||||
@@ -5084,13 +5362,11 @@ TEST_F(DBCompactionTest, DelayCompactBottomLevelFilesWithDeletions) {
|
||||
compaction_count++;
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
// just need to bump seqnum so ReleaseSnapshot knows the newest key in the SST
|
||||
// files does not need to be preserved in case of a future snapshot.
|
||||
// Bump the published sequence so a later maintenance refresh can advance.
|
||||
ASSERT_OK(Put(Key(0), "val"));
|
||||
ASSERT_NE(kMaxSequenceNumber, dbfull()->bottommost_files_mark_threshold_);
|
||||
// release snapshot will not trigger compaction.
|
||||
// Release and the first maintenance refresh do not trigger compaction while
|
||||
// the bottommost file is still too young.
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
ASSERT_EQ(kMaxSequenceNumber, dbfull()->bottommost_files_mark_threshold_);
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(0, compaction_count);
|
||||
// Now the file is old enough for compaction.
|
||||
@@ -5875,6 +6151,8 @@ TEST_F(DBCompactionTest, PeriodicCompactionPhaseTriggerTime) {
|
||||
const uint64_t kSeed = 0x9e3779b97f4a7c15ULL;
|
||||
const uint64_t target = FastRange64(kSeed, kN);
|
||||
ASSERT_LT(target, kN);
|
||||
// Preferred-phase target for kSeed with a given periodic_compaction_seconds.
|
||||
const auto target_for = [&](uint64_t n) { return FastRange64(kSeed, n); };
|
||||
|
||||
// recovery_percent == 0 => classic behavior: trigger at file_mod + N.
|
||||
{
|
||||
@@ -5947,25 +6225,212 @@ TEST_F(DBCompactionTest, PeriodicCompactionPhaseTriggerTime) {
|
||||
ASSERT_LT(trig, 200 + grid); // within N/4 of the anchor
|
||||
}
|
||||
|
||||
// Past due against the recovery target but not yet at the deadline => spread
|
||||
// across [anchor, deadline] using the phase; never later than the deadline.
|
||||
// Natural deadline shortly after the anchor (0 < r = natural - anchor):
|
||||
// the pull toward the phase is scaled down to offset * min(q, k * r / n)
|
||||
// with k = 1/2, so the file triggers after the anchor and no earlier than
|
||||
// with the steady-state pull. Once r >= q * n / k, the steady-state pull
|
||||
// (offset * q) applies unchanged.
|
||||
{
|
||||
const uint64_t n = 100;
|
||||
const uint64_t small_target = FastRange64(kSeed, n);
|
||||
const uint64_t fm = small_target + 50 + 3 * n; // phase offset == 50
|
||||
const uint64_t fm = 3 * n + (target_for(n) + 50) % n;
|
||||
ASSERT_EQ((fm % n + n - target_for(n)) % n, uint64_t{50}); // offset
|
||||
const uint64_t natural = fm + n;
|
||||
const uint64_t anchor =
|
||||
natural - 25; // recovery target (natural-50) < anchor
|
||||
Params p;
|
||||
p.seed_hash = kSeed;
|
||||
p.recovery_percent = 100;
|
||||
p.anchor_time = anchor;
|
||||
ASSERT_EQ((fm % n + n - small_target) % n, uint64_t{50});
|
||||
const uint64_t expected = anchor + FastRange64(kSeed, natural - anchor);
|
||||
const uint64_t trig = PeriodicCompactionPhaser::TriggerTime(fm, n, p);
|
||||
ASSERT_EQ(trig, expected);
|
||||
ASSERT_GE(trig, anchor);
|
||||
ASSERT_LT(trig, natural);
|
||||
struct Case {
|
||||
int rp;
|
||||
uint64_t r;
|
||||
uint64_t pull;
|
||||
};
|
||||
for (const Case& c : {
|
||||
Case{100, 25, 6}, // 50 * 0.5 * 25/100 = 6.25
|
||||
Case{100, 150, 37}, // 50 * 0.5 * 150/100 = 37.5 (steady: 50)
|
||||
Case{100, 200, 50}, // r == q * n / k: steady-state 50 * 1.0
|
||||
Case{67, 100, 25}, // 50 * 0.5 * 100/100 = 25 (steady: 33)
|
||||
Case{67, 134, 33}, // r == q * n / k: steady-state 50 * 0.67
|
||||
Case{50, 1, 0}, // 50 * 0.5 * 1/100 = 0.25
|
||||
Case{50, 99, 24}, // 50 * 0.5 * 99/100 = 24.75
|
||||
Case{50, 100, 25}, // r == q * n / k: steady-state 50 * 0.5
|
||||
Case{33, 25, 6}, // 50 * 0.5 * 25/100 = 6.25
|
||||
Case{33, 60, 15}, // 50 * 0.5 * 60/100 = 15 (steady: 16)
|
||||
Case{33, 66, 16}, // r == q * n / k: steady-state 50 * 0.33
|
||||
Case{33, 70, 16}, // steady-state
|
||||
Case{10, 19, 4}, // 50 * 0.5 * 19/100 = 4.75
|
||||
Case{10, 20, 5}, // r == q * n / k: steady-state 50 * 0.1
|
||||
}) {
|
||||
Params p;
|
||||
p.seed_hash = kSeed;
|
||||
p.recovery_percent = c.rp;
|
||||
p.anchor_time = natural - c.r;
|
||||
const uint64_t trig = PeriodicCompactionPhaser::TriggerTime(fm, n, p);
|
||||
ASSERT_EQ(trig, natural - c.pull) << "rp=" << c.rp << " r=" << c.r;
|
||||
ASSERT_GT(trig, p.anchor_time);
|
||||
}
|
||||
}
|
||||
|
||||
// Long periodic_compaction_seconds. Up to kMaxPhasedPeriod the pull is
|
||||
// computed exactly, without overflow, including the largest intermediate
|
||||
// product (inputs chosen so the exact result is easy to derive by hand).
|
||||
// Beyond it, phasing is not applied (pull 0: the classic deadline).
|
||||
{
|
||||
const uint64_t kMaxPhased = PeriodicCompactionPhaser::kMaxPhasedPeriod;
|
||||
struct Case {
|
||||
uint64_t n;
|
||||
uint64_t offset;
|
||||
uint64_t r;
|
||||
int rp;
|
||||
uint64_t pull;
|
||||
};
|
||||
for (const Case& c : {
|
||||
// 30 days: 1296000 * 0.5 * 1/4
|
||||
Case{30 * 86400, 15 * 86400, 30 * 86400 / 4, 33, 162000},
|
||||
// Largest product: (n - 1) * (2n - 1) / (2n), rounded down
|
||||
Case{kMaxPhased, kMaxPhased - 1, 2 * kMaxPhased - 1, 100,
|
||||
kMaxPhased - 2},
|
||||
// Steady state at kMaxPhased: (n - 1) * 0.33, rounded down
|
||||
Case{kMaxPhased, kMaxPhased - 1, kMaxPhased - 1, 33,
|
||||
(kMaxPhased - 1) * 33 / 100},
|
||||
// Beyond kMaxPhasedPeriod: classic deadline
|
||||
Case{kMaxPhased + 1, kMaxPhased / 2, kMaxPhased / 4, 33, 0},
|
||||
Case{uint64_t{1} << 40, uint64_t{1} << 39, uint64_t{1} << 38, 100,
|
||||
0},
|
||||
Case{std::numeric_limits<uint64_t>::max() / 8, 12345, 678, 50, 0},
|
||||
}) {
|
||||
const uint64_t fm = 3 * c.n + (target_for(c.n) + c.offset) % c.n;
|
||||
ASSERT_EQ((fm % c.n + c.n - target_for(c.n)) % c.n, c.offset);
|
||||
Params p;
|
||||
p.seed_hash = kSeed;
|
||||
p.recovery_percent = c.rp;
|
||||
p.anchor_time = fm + c.n - c.r;
|
||||
ASSERT_EQ(PeriodicCompactionPhaser::TriggerTime(fm, c.n, p),
|
||||
fm + c.n - c.pull)
|
||||
<< "n=" << c.n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, PeriodicCompactionPhaseTriggerTimeRandomized) {
|
||||
// Randomized properties of PeriodicCompactionPhaser::TriggerTime, in
|
||||
// particular around the anchor (when phasing took effect).
|
||||
using Params = PeriodicCompactionPhaseParams;
|
||||
const uint32_t seed = static_cast<uint32_t>(
|
||||
std::chrono::system_clock::now().time_since_epoch().count());
|
||||
SCOPED_TRACE("seed=" + std::to_string(seed));
|
||||
Random64 rnd(seed);
|
||||
|
||||
// Bounds sweep over (n, recovery_percent, phase, file time, anchor),
|
||||
// including n at and beyond kMaxPhasedPeriod.
|
||||
{
|
||||
const uint64_t kMaxPhased = PeriodicCompactionPhaser::kMaxPhasedPeriod;
|
||||
const uint64_t kNs[] = {1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
7,
|
||||
100,
|
||||
86400,
|
||||
30 * 86400,
|
||||
kMaxPhased,
|
||||
kMaxPhased + 1,
|
||||
uint64_t{1} << 40,
|
||||
std::numeric_limits<uint64_t>::max() / 16};
|
||||
const int kRps[] = {1, 10, 25, 33, 50, 67, 99, 100};
|
||||
for (int i = 0; i < 100000; ++i) {
|
||||
const uint64_t n = kNs[rnd.Uniform(std::size(kNs))];
|
||||
const int rp = kRps[rnd.Uniform(std::size(kRps))];
|
||||
Params p;
|
||||
p.seed_hash = rnd.Next();
|
||||
p.recovery_percent = rp;
|
||||
p.anchor_time = rnd.Uniform(6 * n);
|
||||
const uint64_t fm = rnd.Uniform(4 * n);
|
||||
const uint64_t natural = fm + n;
|
||||
const uint64_t trig = PeriodicCompactionPhaser::TriggerTime(fm, n, p);
|
||||
if (n > kMaxPhased) {
|
||||
ASSERT_EQ(trig, natural) << "n=" << n << " rp=" << rp;
|
||||
continue;
|
||||
}
|
||||
const uint64_t offset = (fm % n + n - FastRange64(p.seed_hash, n)) % n;
|
||||
const uint64_t steady_trigger = natural - offset * rp / 100;
|
||||
if (natural > p.anchor_time) {
|
||||
// Never before the anchor, never after the natural deadline, never
|
||||
// earlier than with the steady-state pull.
|
||||
ASSERT_GT(trig, p.anchor_time) << "n=" << n << " rp=" << rp;
|
||||
ASSERT_LE(trig, natural) << "n=" << n << " rp=" << rp;
|
||||
ASSERT_GE(trig, steady_trigger) << "n=" << n << " rp=" << rp;
|
||||
if (fm >= p.anchor_time + (rp <= 50 ? 0 : n)) {
|
||||
// Written at or after the anchor (at least a period after it for
|
||||
// rp > 50): exactly the steady-state pull.
|
||||
ASSERT_EQ(trig, steady_trigger) << "n=" << n << " rp=" << rp;
|
||||
}
|
||||
} else {
|
||||
// Already past due at the anchor: within a quarter period after it.
|
||||
ASSERT_GE(trig, p.anchor_time) << "n=" << n << " rp=" << rp;
|
||||
ASSERT_LT(trig - p.anchor_time, std::max(n / 4, uint64_t{1}))
|
||||
<< "n=" << n << " rp=" << rp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bounded trigger density after the anchor. Many files (random phases)
|
||||
// share one anchor and have file ages spread uniformly over a period at the
|
||||
// anchor, so their natural deadlines are uniform over the period after it.
|
||||
// Without phasing, each 1% of that period would get 1% of the triggers. The
|
||||
// scaled pull should give about 1.39x throughout (k = 1/2) for any
|
||||
// recovery_percent, so no 1% bin may exceed 1.6x. (Choosing each trigger
|
||||
// uniformly between the anchor and the natural deadline gave ~3.8x in the
|
||||
// first 1%.)
|
||||
{
|
||||
const uint64_t n = 1000000;
|
||||
const uint64_t anchor = 10 * n + 12345;
|
||||
const int kFiles = 200000;
|
||||
for (int rp : {10, 33, 50, 67, 100}) {
|
||||
std::vector<int> bins(100);
|
||||
for (int i = 0; i < kFiles; ++i) {
|
||||
Params p;
|
||||
p.seed_hash = rnd.Next();
|
||||
p.recovery_percent = rp;
|
||||
p.anchor_time = anchor;
|
||||
// Natural deadline in (anchor, anchor + n]
|
||||
const uint64_t fm = anchor - n + 1 + rnd.Uniform(n);
|
||||
const uint64_t trig = PeriodicCompactionPhaser::TriggerTime(fm, n, p);
|
||||
ASSERT_GT(trig, anchor);
|
||||
ASSERT_LE(trig, fm + n);
|
||||
bins[(trig - anchor - 1) * 100 / n]++;
|
||||
}
|
||||
for (size_t b = 0; b < bins.size(); ++b) {
|
||||
ASSERT_LE(bins[b], kFiles / 100 * 16 / 10) << "rp=" << rp << " b=" << b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A herd: many DBs (random phases) share one natural deadline 0.62 * n after
|
||||
// the anchor, as with a lockstep fleet when phasing is enabled. Triggers
|
||||
// must stay spread over most of [natural - q * n, natural] (about 0.31 * n
|
||||
// here), with no clump: no bin of width n/1000 may hold more than 1% of the
|
||||
// herd (versus ~0.32% expected).
|
||||
{
|
||||
const uint64_t n = 1000000;
|
||||
const uint64_t anchor = 10 * n;
|
||||
const uint64_t natural = anchor + n * 62 / 100;
|
||||
const uint64_t fm = natural - n;
|
||||
const int kHerd = 100000;
|
||||
for (int rp : {33, 50, 100}) {
|
||||
std::vector<int> bins(1000);
|
||||
uint64_t earliest = natural;
|
||||
for (int i = 0; i < kHerd; ++i) {
|
||||
Params p;
|
||||
p.seed_hash = rnd.Next();
|
||||
p.recovery_percent = rp;
|
||||
p.anchor_time = anchor;
|
||||
const uint64_t trig = PeriodicCompactionPhaser::TriggerTime(fm, n, p);
|
||||
ASSERT_GT(trig, anchor);
|
||||
ASSERT_LE(trig, natural);
|
||||
earliest = std::min(earliest, trig);
|
||||
bins[(trig - anchor) * 1000 / n]++;
|
||||
}
|
||||
ASSERT_GE(natural - earliest, n * 30 / 100) << "rp=" << rp;
|
||||
for (size_t b = 0; b < bins.size(); ++b) {
|
||||
ASSERT_LE(bins[b], kHerd / 100) << "rp=" << rp << " b=" << b;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6620,6 +7085,119 @@ TEST_F(DBCompactionTest, CompactRangeShutdownWhileDelayed) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactRangeAtomicFlushOtherColumnFamilyDropped) {
|
||||
Options options = CurrentOptions();
|
||||
options.atomic_flush = true;
|
||||
options.level0_stop_writes_trigger = 2;
|
||||
options.level0_slowdown_writes_trigger = 2;
|
||||
options.level0_file_num_compaction_trigger = -1;
|
||||
for (bool drop_target : {false, true}) {
|
||||
SCOPED_TRACE(drop_target);
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"target", "obsolete"}, options);
|
||||
test::SleepingBackgroundTask sleeping_task;
|
||||
env_->SetBackgroundThreads(1, Env::LOW);
|
||||
env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task,
|
||||
Env::LOW);
|
||||
sleeping_task.WaitUntilSleeping();
|
||||
ASSERT_OK(Put(2, "old", "value"));
|
||||
ASSERT_OK(Flush(2));
|
||||
ASSERT_OK(Put(2, "pending", "value"));
|
||||
ASSERT_OK(Put(1, "key", "value"));
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::WaitUntilFlushWouldNotStallWrites:StallWait",
|
||||
"DBCompactionTest::AtomicFlushOtherColumnFamilyDropped:Drop"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Status status;
|
||||
port::Thread compact([&] {
|
||||
status = db_->CompactRange(CompactRangeOptions(), handles_[1], nullptr,
|
||||
nullptr);
|
||||
});
|
||||
TEST_SYNC_POINT(
|
||||
"DBCompactionTest::AtomicFlushOtherColumnFamilyDropped:Drop");
|
||||
if (drop_target) {
|
||||
ASSERT_OK(db_->DropColumnFamily(handles_[1]));
|
||||
}
|
||||
ASSERT_OK(db_->DropColumnFamily(handles_[2]));
|
||||
sleeping_task.WakeUp();
|
||||
compact.join();
|
||||
if (drop_target) {
|
||||
ASSERT_EQ(Status::kColumnFamilyDropped, status.code());
|
||||
} else {
|
||||
ASSERT_OK(status);
|
||||
ASSERT_EQ("value", Get(1, "key"));
|
||||
ASSERT_GT(TotalTableFiles(1), 0);
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0, 1));
|
||||
}
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactRangeAtomicFlushSelectedColumnFamilyDropped) {
|
||||
// The target has nothing to flush, so the atomic flush selects only the
|
||||
// other column family, which is dropped while the flush is pending.
|
||||
Options options = CurrentOptions();
|
||||
options.atomic_flush = true;
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"target", "obsolete"}, options);
|
||||
ASSERT_OK(Put(2, "key", "value"));
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::AtomicFlushMemTables:BeforeWaitForBgFlush",
|
||||
"DBCompactionTest::AtomicFlushSelectedColumnFamilyDropped:Drop"},
|
||||
{"DBCompactionTest::AtomicFlushSelectedColumnFamilyDropped:Dropped",
|
||||
"DBImpl::BackgroundCallFlush:start"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
CompactRangeOptions cro;
|
||||
cro.allow_write_stall = true;
|
||||
Status status;
|
||||
port::Thread compact(
|
||||
[&] { status = db_->CompactRange(cro, handles_[1], nullptr, nullptr); });
|
||||
TEST_SYNC_POINT(
|
||||
"DBCompactionTest::AtomicFlushSelectedColumnFamilyDropped:Drop");
|
||||
ASSERT_OK(db_->DropColumnFamily(handles_[2]));
|
||||
TEST_SYNC_POINT(
|
||||
"DBCompactionTest::AtomicFlushSelectedColumnFamilyDropped:Dropped");
|
||||
compact.join();
|
||||
ASSERT_OK(status);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, WaitForCompactAtomicFlushOtherColumnFamilyDropped) {
|
||||
// The flush waits for a write stall on another column family that is
|
||||
// dropped during the wait. The live column family must still be flushed.
|
||||
Options options = CurrentOptions();
|
||||
options.atomic_flush = true;
|
||||
options.level0_stop_writes_trigger = 2;
|
||||
options.level0_slowdown_writes_trigger = 2;
|
||||
options.level0_file_num_compaction_trigger = -1;
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"live", "obsolete"}, options);
|
||||
ASSERT_OK(Put(2, "old", "value"));
|
||||
ASSERT_OK(Flush(2));
|
||||
ASSERT_OK(Put(2, "pending", "value"));
|
||||
ASSERT_OK(Put(1, "key", "value"));
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::WaitUntilFlushWouldNotStallWrites:StallWait",
|
||||
"DBCompactionTest::WaitForCompactOtherColumnFamilyDropped:Drop"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Status status;
|
||||
port::Thread wait([&] {
|
||||
WaitForCompactOptions wait_for_compact_options;
|
||||
wait_for_compact_options.flush = true;
|
||||
status = db_->WaitForCompact(wait_for_compact_options);
|
||||
});
|
||||
TEST_SYNC_POINT(
|
||||
"DBCompactionTest::WaitForCompactOtherColumnFamilyDropped:Drop");
|
||||
ASSERT_OK(db_->DropColumnFamily(handles_[2]));
|
||||
wait.join();
|
||||
ASSERT_OK(status);
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0, 1));
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactRangeSkipFlushAfterDelay) {
|
||||
// Verify that, when `CompactRangeOptions::allow_write_stall == false`,
|
||||
// CompactRange skips its flush if the delay is long enough that the memtables
|
||||
@@ -7408,8 +7986,7 @@ TEST_F(DBCompactionTest,
|
||||
// The NewRandomAccessFile:O_DIRECT sync point in env/fs_posix.cc fires once
|
||||
// per fresh open with the O_DIRECT flag, so this proves the kernel path, not
|
||||
// just the FileOptions. Only runs on platforms that take the O_DIRECT path.
|
||||
#if !defined(OS_MACOSX) && !defined(OS_OPENBSD) && !defined(OS_SOLARIS) && \
|
||||
!defined(OS_WIN)
|
||||
#if !defined(OS_MACOSX) && !defined(OS_OPENBSD) && !defined(OS_WIN)
|
||||
TEST_F(DBCompactionTest, UseDirectIoForCompactionReadsEndToEnd) {
|
||||
if (!IsDirectIOSupported()) {
|
||||
ROCKSDB_GTEST_BYPASS("Direct IO not supported");
|
||||
@@ -12474,7 +13051,7 @@ TEST_F(DBCompactionTest, VerifyOutputRecordCountPlainTable) {
|
||||
|
||||
TEST_F(DBCompactionTest, ErrorWhenReadFileHead) {
|
||||
// This is to test a bug that is fixed in
|
||||
// https://github.com/facebook/rocksdb/pull/11782.
|
||||
// https://github.com/meta/rocksdb/pull/11782.
|
||||
//
|
||||
// Ingest error when reading from a file with offset = 0,
|
||||
// See if compaction handles it correctly.
|
||||
|
||||
+1175
-6
File diff suppressed because it is too large.
Load diff
@@ -5,8 +5,10 @@
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/experimental.h"
|
||||
#include "rocksdb/metadata.h"
|
||||
#include "rocksdb/sst_file_writer.h"
|
||||
#include "util/defer.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -15,6 +17,463 @@ class DBEtc3Test : public DBTestBase {
|
||||
DBEtc3Test() : DBTestBase("db_etc3_test", /*env_do_fsync=*/true) {}
|
||||
};
|
||||
|
||||
namespace {
|
||||
// The configuration in which RocksDB 11.10 through 11.13 could write a common
|
||||
// prefix extending past a user key: format_version=8 (with the default
|
||||
// optimize_key_common_prefix) and ReverseBytewiseComparator, which orders a
|
||||
// user key after longer user keys that extend it.
|
||||
Options PreviouslyBuggyCommonPrefixOptions(
|
||||
Options options, BlockBasedTableOptions table_options) {
|
||||
options.comparator = ReverseBytewiseComparator();
|
||||
options.disable_auto_compactions = true;
|
||||
table_options.format_version = 8;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
return options;
|
||||
}
|
||||
|
||||
// Makes BlockBuilder compute the common prefix as RocksDB 11.10 through 11.13
|
||||
// did, which could extend it past a user key.
|
||||
void SimulateOldCommonPrefixWriter() {
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlockBuilder::AddWithLastKeyImpl:CommonPrefixUserKeySize",
|
||||
[](void* arg) {
|
||||
*static_cast<size_t*>(arg) = std::numeric_limits<size_t>::max();
|
||||
});
|
||||
}
|
||||
|
||||
void StopSimulatingOldCommonPrefixWriter() {
|
||||
SyncPoint::GetInstance()->ClearCallBack(
|
||||
"BlockBuilder::AddWithLastKeyImpl:CommonPrefixUserKeySize");
|
||||
}
|
||||
|
||||
// Data and index blocks found on load to have a last user key shorter than
|
||||
// their common prefix.
|
||||
struct ShortLastUserKeyBlockCounts {
|
||||
std::atomic<int> data{0};
|
||||
std::atomic<int> index{0};
|
||||
};
|
||||
|
||||
void CountShortLastUserKeyBlocks(ShortLastUserKeyBlockCounts* counts) {
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"Block::InitializeDataBlockCommonPrefixCheck:LastUserKeyShorter",
|
||||
[counts](void*) { counts->data.fetch_add(1); });
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"Block::InitializeIndexBlockCommonPrefixCheck:LastUserKeyShorter",
|
||||
[counts](void*) { counts->index.fetch_add(1); });
|
||||
}
|
||||
|
||||
const std::string kPutLonger(
|
||||
"ab\x01"
|
||||
"c",
|
||||
4);
|
||||
} // namespace
|
||||
|
||||
TEST_F(DBEtc3Test, CommonKeyPrefixWithShorterKeyAfterLongerKey) {
|
||||
DestroyAndReopen(PreviouslyBuggyCommonPrefixOptions(
|
||||
CurrentOptions(), BlockBasedTableOptions()));
|
||||
ShortLastUserKeyBlockCounts counts;
|
||||
CountShortLastUserKeyBlocks(&counts);
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(Put("xy", "old"));
|
||||
ASSERT_OK(Flush());
|
||||
const std::string del_long(
|
||||
"xy\x00"
|
||||
"c",
|
||||
4);
|
||||
ASSERT_OK(Put(del_long, "long"));
|
||||
ASSERT_OK(Delete("xy"));
|
||||
ASSERT_OK(Flush());
|
||||
EXPECT_EQ("long", Get(del_long));
|
||||
EXPECT_EQ("NOT_FOUND", Get("xy"));
|
||||
|
||||
ASSERT_OK(Put(kPutLonger, "long"));
|
||||
ASSERT_OK(Put("ab", "short"));
|
||||
ASSERT_OK(Flush());
|
||||
EXPECT_EQ("long", Get(kPutLonger));
|
||||
EXPECT_EQ("short", Get("ab"));
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
|
||||
iter->Seek("ab");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
EXPECT_EQ("ab", iter->key().ToString());
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
EXPECT_EQ(0, counts.data.load());
|
||||
EXPECT_EQ(0, counts.index.load());
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(DBEtc3Test, CommonKeyPrefixReadsBlocksWithShorterKey) {
|
||||
Options options = CurrentOptions();
|
||||
options.compression = kNoCompression;
|
||||
options.max_subcompactions = 2;
|
||||
options.target_file_size_base = 1024;
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_size = 1000;
|
||||
table_options.block_size_deviation = 0;
|
||||
DestroyAndReopen(PreviouslyBuggyCommonPrefixOptions(options, table_options));
|
||||
ShortLastUserKeyBlockCounts counts;
|
||||
CountShortLastUserKeyBlocks(&counts);
|
||||
SimulateOldCommonPrefixWriter();
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Data block [kPutLonger, "ab"] with common prefix "ab\x01".
|
||||
const std::string big(2000, 'v');
|
||||
ASSERT_OK(Put("c", big));
|
||||
ASSERT_OK(Put(kPutLonger, "x"));
|
||||
ASSERT_OK(Put("ab", big));
|
||||
ASSERT_OK(Put("aa", big));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("b", "y"));
|
||||
ASSERT_OK(Flush());
|
||||
StopSimulatingOldCommonPrefixWriter();
|
||||
|
||||
auto verify = [&]() {
|
||||
EXPECT_EQ(big, Get("ab"));
|
||||
EXPECT_EQ("x", Get(kPutLonger));
|
||||
EXPECT_EQ(std::vector<std::string>({big, "x"}),
|
||||
MultiGet({"ab", kPutLonger}));
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
|
||||
// "ab\x01" equals the common prefix, so it can be compared by suffix.
|
||||
for (const Slice& target :
|
||||
{Slice("ab"), Slice("ab\x00", 3), Slice("ab\x01", 3)}) {
|
||||
iter->Seek(target);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
EXPECT_EQ("ab", iter->key().ToString());
|
||||
}
|
||||
iter->SeekForPrev("ab");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
EXPECT_EQ("ab", iter->key().ToString());
|
||||
iter->SeekForPrev(Slice("ab\x01", 3));
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
EXPECT_EQ(kPutLonger, iter->key().ToString());
|
||||
ASSERT_OK(iter->status());
|
||||
};
|
||||
verify();
|
||||
EXPECT_GT(counts.data.load(), 0);
|
||||
|
||||
// With subcompactions, a compaction starts by seeking to a key.
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
verify();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(DBEtc3Test, CommonKeyPrefixIteratesBlocksWithShorterKey) {
|
||||
// Iteration seeks internally after skipping many versions of a key
|
||||
// (max_sequential_skip_in_iterations) and when reversing.
|
||||
Options options = CurrentOptions();
|
||||
options.compression = kNoCompression;
|
||||
options.statistics = CreateDBStatistics();
|
||||
DestroyAndReopen(
|
||||
PreviouslyBuggyCommonPrefixOptions(options, BlockBasedTableOptions()));
|
||||
ShortLastUserKeyBlockCounts counts;
|
||||
CountShortLastUserKeyBlocks(&counts);
|
||||
SimulateOldCommonPrefixWriter();
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(Put("c", "vc"));
|
||||
ASSERT_OK(Put("aa", "va"));
|
||||
ASSERT_OK(Flush());
|
||||
// Data block [kPutLonger, "ab"] with common prefix "ab\x01".
|
||||
ASSERT_OK(Put(kPutLonger, "vl"));
|
||||
ASSERT_OK(Put("ab", "v0"));
|
||||
ASSERT_OK(Flush());
|
||||
StopSimulatingOldCommonPrefixWriter();
|
||||
const Snapshot* snap = db_->GetSnapshot();
|
||||
for (int i = 1; i <= 10; ++i) {
|
||||
ASSERT_OK(Put("ab", "v" + std::to_string(i)));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
auto scan = [&](const Snapshot* s, bool forward) {
|
||||
ReadOptions ro;
|
||||
ro.snapshot = s;
|
||||
std::unique_ptr<Iterator> it(db_->NewIterator(ro));
|
||||
std::string out;
|
||||
for (forward ? it->SeekToFirst() : it->SeekToLast(); it->Valid();
|
||||
forward ? it->Next() : it->Prev()) {
|
||||
out +=
|
||||
it->key().ToString(/*hex=*/true) + "=" + it->value().ToString() + " ";
|
||||
}
|
||||
EXPECT_OK(it->status());
|
||||
return out;
|
||||
};
|
||||
// Hex keys: "c", kPutLonger, "ab", "aa".
|
||||
EXPECT_EQ("63=vc 61620163=vl 6162=v10 6161=va ", scan(nullptr, true));
|
||||
EXPECT_EQ("63=vc 61620163=vl 6162=v0 6161=va ", scan(snap, true));
|
||||
EXPECT_EQ("6161=va 6162=v10 61620163=vl 63=vc ", scan(nullptr, false));
|
||||
EXPECT_EQ("6161=va 6162=v0 61620163=vl 63=vc ", scan(snap, false));
|
||||
EXPECT_GT(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 0);
|
||||
EXPECT_GT(counts.data.load(), 0);
|
||||
db_->ReleaseSnapshot(snap);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(DBEtc3Test, CommonKeyPrefixIndexBlockWithShorterKey) {
|
||||
// One key per data block. Two versions of "ab" in adjacent data blocks make
|
||||
// the index keys internal keys, so the old writer can store a common prefix
|
||||
// in the index block (with more than one key per restart interval) that
|
||||
// extends past a user key.
|
||||
Options options = CurrentOptions();
|
||||
options.compression = kNoCompression;
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_size = 1;
|
||||
table_options.index_block_restart_interval = 16;
|
||||
DestroyAndReopen(PreviouslyBuggyCommonPrefixOptions(options, table_options));
|
||||
ShortLastUserKeyBlockCounts counts;
|
||||
CountShortLastUserKeyBlocks(&counts);
|
||||
SimulateOldCommonPrefixWriter();
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(Put(kPutLonger, "x"));
|
||||
ASSERT_OK(Put("ab", "v1"));
|
||||
const Snapshot* snap = db_->GetSnapshot();
|
||||
ASSERT_OK(Put("ab", "v2"));
|
||||
ASSERT_OK(Flush());
|
||||
StopSimulatingOldCommonPrefixWriter();
|
||||
|
||||
auto verify = [&]() {
|
||||
EXPECT_EQ("v2", Get("ab"));
|
||||
EXPECT_EQ("v1", Get("ab", snap));
|
||||
EXPECT_EQ("x", Get(kPutLonger));
|
||||
EXPECT_EQ(std::vector<std::string>({"v2", "x"}),
|
||||
MultiGet({"ab", kPutLonger}));
|
||||
for (const Snapshot* s : {static_cast<const Snapshot*>(nullptr), snap}) {
|
||||
ReadOptions ro;
|
||||
ro.snapshot = s;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(ro));
|
||||
for (const Slice& target : {Slice("ab"), Slice("ab\x01", 3)}) {
|
||||
iter->Seek(target);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
EXPECT_EQ("ab", iter->key().ToString());
|
||||
EXPECT_EQ(s ? "v1" : "v2", iter->value().ToString());
|
||||
}
|
||||
iter->SeekForPrev(Slice("ab\x01", 3));
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
EXPECT_EQ(kPutLonger, iter->key().ToString());
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
};
|
||||
verify();
|
||||
EXPECT_GT(counts.index.load(), 0);
|
||||
EXPECT_EQ(0, counts.data.load());
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
verify();
|
||||
db_->ReleaseSnapshot(snap);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(DBEtc3Test, CommonKeyPrefixRandomizedReverse) {
|
||||
// Random keys, often prefixes of each other, from both the old and the
|
||||
// current writer, checked against a model.
|
||||
struct RevLess {
|
||||
bool operator()(const std::string& a, const std::string& b) const {
|
||||
return Slice(a).compare(Slice(b)) > 0;
|
||||
}
|
||||
};
|
||||
const uint32_t base_seed = static_cast<uint32_t>(env_->NowMicros());
|
||||
ShortLastUserKeyBlockCounts counts;
|
||||
for (int round = 0; round < 60; ++round) {
|
||||
const uint32_t seed = base_seed + round;
|
||||
SCOPED_TRACE("seed=" + std::to_string(seed));
|
||||
Random rnd(seed);
|
||||
const bool old_writer = rnd.OneIn(2);
|
||||
Options options = CurrentOptions();
|
||||
options.compression = kNoCompression;
|
||||
options.max_subcompactions = 1 + rnd.Uniform(3);
|
||||
const uint8_t kProtectionBytes[] = {0, 1, 2, 4, 8};
|
||||
options.block_protection_bytes_per_key = kProtectionBytes[rnd.Uniform(5)];
|
||||
BlockBasedTableOptions table_options;
|
||||
// Small blocks make it more likely that the old writer's prefix extends
|
||||
// past a user key: all keys of the block must extend that user key.
|
||||
table_options.block_size = 64 + rnd.Uniform(rnd.OneIn(2) ? 64 : 512);
|
||||
table_options.block_size_deviation = 0;
|
||||
const int kRestartIntervals[] = {1, 2, 4, 16};
|
||||
table_options.block_restart_interval = kRestartIntervals[rnd.Uniform(4)];
|
||||
table_options.index_block_restart_interval =
|
||||
kRestartIntervals[rnd.Uniform(4)];
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10));
|
||||
// Small index partitions make it more likely that the old writer's prefix
|
||||
// extends past a user key in index blocks.
|
||||
switch (rnd.Uniform(4)) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
table_options.index_type =
|
||||
BlockBasedTableOptions::kBinarySearchWithFirstKey;
|
||||
break;
|
||||
default:
|
||||
table_options.index_type = BlockBasedTableOptions::kTwoLevelIndexSearch;
|
||||
table_options.metadata_block_size = 64 + rnd.Uniform(64);
|
||||
table_options.partition_filters = true;
|
||||
table_options.decouple_partitioned_filters = rnd.OneIn(2);
|
||||
break;
|
||||
}
|
||||
table_options.cache_index_and_filter_blocks = rnd.OneIn(2);
|
||||
// Warming the cache while building a table does not check blocks (only
|
||||
// blocks from the old writer need it), so only with the current writer.
|
||||
if (!old_writer && rnd.OneIn(2)) {
|
||||
table_options.prepopulate_block_cache =
|
||||
rnd.OneIn(2)
|
||||
? BlockBasedTableOptions::PrepopulateBlockCache::kFlushOnly
|
||||
: BlockBasedTableOptions::PrepopulateBlockCache::
|
||||
kFlushAndCompaction;
|
||||
}
|
||||
table_options.separate_key_value_in_data_block = rnd.OneIn(3);
|
||||
if (rnd.OneIn(3)) {
|
||||
table_options.data_block_index_type =
|
||||
BlockBasedTableOptions::kDataBlockBinaryAndHash;
|
||||
}
|
||||
const Options db_options =
|
||||
PreviouslyBuggyCommonPrefixOptions(options, table_options);
|
||||
DestroyAndReopen(db_options);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
CountShortLastUserKeyBlocks(&counts);
|
||||
if (old_writer) {
|
||||
SimulateOldCommonPrefixWriter();
|
||||
}
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
const int flagged_before = counts.data.load() + counts.index.load();
|
||||
|
||||
auto rand_key = [&](bool allow_no_prefix) {
|
||||
static const char kAlphabet[] = {'\x00', '\x01', '\x02', 'a', 'b'};
|
||||
std::string k;
|
||||
if (!allow_no_prefix || !rnd.OneIn(4)) {
|
||||
k = "pp";
|
||||
}
|
||||
const int len = static_cast<int>(rnd.Uniform(6));
|
||||
for (int i = 0; i < len; ++i) {
|
||||
k.push_back(kAlphabet[rnd.Uniform(5)]);
|
||||
}
|
||||
return k;
|
||||
};
|
||||
std::map<std::string, std::string, RevLess> model;
|
||||
std::vector<std::string> keys;
|
||||
// Snapshots keep older versions, which can make index keys internal keys.
|
||||
std::vector<const Snapshot*> snapshots;
|
||||
Defer release_snapshots([&]() {
|
||||
for (const Snapshot* s : snapshots) {
|
||||
db_->ReleaseSnapshot(s);
|
||||
}
|
||||
});
|
||||
const int num_files = 1 + static_cast<int>(rnd.Uniform(3));
|
||||
for (int f = 0; f < num_files; ++f) {
|
||||
const int n = 20 + static_cast<int>(rnd.Uniform(300));
|
||||
for (int i = 0; i < n; ++i) {
|
||||
std::string k = rand_key(/*allow_no_prefix=*/false);
|
||||
keys.push_back(k);
|
||||
if (rnd.OneIn(5)) {
|
||||
ASSERT_OK(Delete(k));
|
||||
model.erase(k);
|
||||
} else {
|
||||
std::string v = "v" + std::to_string(rnd.Next());
|
||||
ASSERT_OK(Put(k, v));
|
||||
model[k] = v;
|
||||
}
|
||||
if (rnd.OneIn(20)) {
|
||||
snapshots.push_back(db_->GetSnapshot());
|
||||
}
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
// Sometimes also ingest a file whose keys overlap the DB, so it is
|
||||
// assigned a global sequence number (applied to block keys when seeking).
|
||||
if (rnd.OneIn(2)) {
|
||||
std::map<std::string, std::string, RevLess> file_kvs;
|
||||
const int n = 10 + static_cast<int>(rnd.Uniform(100));
|
||||
for (int i = 0; i < n; ++i) {
|
||||
file_kvs[rand_key(/*allow_no_prefix=*/false)] =
|
||||
"i" + std::to_string(rnd.Next());
|
||||
}
|
||||
const std::string file = dbname_ + "/ingest.sst";
|
||||
SstFileWriter writer(EnvOptions(), db_options);
|
||||
ASSERT_OK(writer.Open(file));
|
||||
for (const auto& kv : file_kvs) {
|
||||
ASSERT_OK(writer.Put(kv.first, kv.second));
|
||||
keys.push_back(kv.first);
|
||||
model[kv.first] = kv.second;
|
||||
}
|
||||
ASSERT_OK(writer.Finish());
|
||||
IngestExternalFileOptions ifo;
|
||||
ifo.allow_global_seqno = true;
|
||||
ASSERT_OK(db_->IngestExternalFile({file}, ifo));
|
||||
}
|
||||
|
||||
auto verify = [&]() {
|
||||
for (const auto& k : keys) {
|
||||
auto it = model.find(k);
|
||||
const std::string expected =
|
||||
it == model.end() ? "NOT_FOUND" : it->second;
|
||||
ASSERT_EQ(expected, Get(k)) << "key=" << Slice(k).ToString(true);
|
||||
}
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
|
||||
auto mit = model.begin();
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next(), ++mit) {
|
||||
ASSERT_TRUE(mit != model.end());
|
||||
ASSERT_EQ(Slice(mit->first).ToString(true), iter->key().ToString(true));
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(mit == model.end());
|
||||
auto rit = model.rbegin();
|
||||
for (iter->SeekToLast(); iter->Valid(); iter->Prev(), ++rit) {
|
||||
ASSERT_TRUE(rit != model.rend());
|
||||
ASSERT_EQ(Slice(rit->first).ToString(true), iter->key().ToString(true));
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(rit == model.rend());
|
||||
for (int p = 0; p < 400; ++p) {
|
||||
const std::string target = rand_key(/*allow_no_prefix=*/true);
|
||||
SCOPED_TRACE("target=" + Slice(target).ToString(true));
|
||||
iter->Seek(target);
|
||||
auto lb = model.lower_bound(target);
|
||||
if (lb == model.end()) {
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
} else {
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(Slice(lb->first).ToString(true),
|
||||
iter->key().ToString(true));
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
iter->SeekForPrev(target);
|
||||
auto ub = model.upper_bound(target);
|
||||
if (ub == model.begin()) {
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
} else {
|
||||
--ub;
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(Slice(ub->first).ToString(true),
|
||||
iter->key().ToString(true));
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
};
|
||||
verify();
|
||||
if (HasFatalFailure()) {
|
||||
return;
|
||||
}
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
verify();
|
||||
if (HasFatalFailure()) {
|
||||
return;
|
||||
}
|
||||
if (!old_writer) {
|
||||
// Blocks from the current writer never need the check to flag them.
|
||||
ASSERT_EQ(flagged_before, counts.data.load() + counts.index.load());
|
||||
}
|
||||
}
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
// Some blocks from the old writer needed the full-key search.
|
||||
EXPECT_GT(counts.data.load() + counts.index.load(), 0);
|
||||
}
|
||||
|
||||
TEST_F(DBEtc3Test, ManifestRollOver) {
|
||||
do {
|
||||
Options options;
|
||||
@@ -371,6 +830,301 @@ TEST_F(DBEtc3Test, AutoTuneManifestSize) {
|
||||
}
|
||||
}
|
||||
|
||||
// Tests for the DB ownership metadata recorded for SST files
|
||||
// (FileMetaData::db_ownership_start_time and data_file_provenance)
|
||||
class DBOwnershipTest : public DBEtc3Test {
|
||||
protected:
|
||||
uint64_t Now() {
|
||||
int64_t now = 0;
|
||||
EXPECT_OK(env_->GetCurrentTime(&now));
|
||||
return static_cast<uint64_t>(now);
|
||||
}
|
||||
|
||||
// The MANIFEST record and level of the file in the default column family
|
||||
// whose smallest user key is `key`
|
||||
std::pair<FileMetaData, int> FileStartingWith(const std::string& key) {
|
||||
std::vector<std::vector<FileMetaData>> metadata;
|
||||
dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(), &metadata);
|
||||
for (size_t level = 0; level < metadata.size(); ++level) {
|
||||
for (const auto& f : metadata[level]) {
|
||||
if (f.smallest.user_key() == key) {
|
||||
return {f, static_cast<int>(level)};
|
||||
}
|
||||
}
|
||||
}
|
||||
ADD_FAILURE() << "No file starting with " << key;
|
||||
return {FileMetaData(), -1};
|
||||
}
|
||||
|
||||
static DataFileProvenance Decode(const FileMetaData& f) {
|
||||
return DataFileProvenance::DecodeFrom(f.data_file_provenance);
|
||||
}
|
||||
|
||||
static DataFileProvenance OriginOnly(DataFileProvenance::Origin origin) {
|
||||
DataFileProvenance info;
|
||||
info.origin = origin;
|
||||
return info;
|
||||
}
|
||||
|
||||
// Expects `after` to be the same file as `before`, with the same contents
|
||||
// and DB ownership, plus DataFileProvenance flag bits `added_flags`
|
||||
static void ExpectSameFile(const FileMetaData& before,
|
||||
const FileMetaData& after, uint32_t added_flags) {
|
||||
EXPECT_EQ(after.fd.GetNumber(), before.fd.GetNumber());
|
||||
EXPECT_EQ(after.file_creation_time, before.file_creation_time);
|
||||
EXPECT_EQ(after.db_ownership_start_time, before.db_ownership_start_time);
|
||||
EXPECT_EQ(after.data_file_provenance,
|
||||
before.data_file_provenance | added_flags);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(DBOwnershipTest, FlushAndCompactionOutputs) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
env_->SetMockSleep();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
const uint64_t start = Now();
|
||||
ASSERT_OK(Put("a", "1"));
|
||||
ASSERT_OK(Flush());
|
||||
const FileMetaData flushed = FileStartingWith("a").first;
|
||||
EXPECT_GE(flushed.db_ownership_start_time, start);
|
||||
EXPECT_EQ(flushed.db_ownership_start_time, flushed.file_creation_time);
|
||||
EXPECT_EQ(Decode(flushed), OriginOnly(DataFileProvenance::Origin::kFlush));
|
||||
|
||||
// Reported in public metadata
|
||||
std::vector<LiveFileMetaData> live_files;
|
||||
db_->GetLiveFilesMetaData(&live_files);
|
||||
ASSERT_EQ(live_files.size(), 1U);
|
||||
EXPECT_EQ(live_files[0].db_ownership_start_time,
|
||||
flushed.db_ownership_start_time);
|
||||
EXPECT_EQ(live_files[0].data_file_provenance, flushed.data_file_provenance);
|
||||
ColumnFamilyMetaData cf_meta;
|
||||
db_->GetColumnFamilyMetaData(&cf_meta);
|
||||
ASSERT_EQ(cf_meta.levels[0].files.size(), 1U);
|
||||
EXPECT_EQ(cf_meta.levels[0].files[0].db_ownership_start_time,
|
||||
flushed.db_ownership_start_time);
|
||||
EXPECT_EQ(cf_meta.levels[0].files[0].data_file_provenance,
|
||||
flushed.data_file_provenance);
|
||||
|
||||
// Flushed during WAL recovery
|
||||
ASSERT_OK(Put("b", "1"));
|
||||
Reopen(options);
|
||||
const FileMetaData recovered = FileStartingWith("b").first;
|
||||
EXPECT_GE(recovered.db_ownership_start_time, start);
|
||||
EXPECT_EQ(recovered.db_ownership_start_time, recovered.file_creation_time);
|
||||
EXPECT_EQ(Decode(recovered), OriginOnly(DataFileProvenance::Origin::kFlush));
|
||||
|
||||
// Compactions that take a long time to finish after creating their output
|
||||
ASSERT_OK(Put("a", "2"));
|
||||
ASSERT_OK(Flush());
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:AfterRun",
|
||||
[&](void*) { env_->MockSleepForSeconds(1000); });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
// The DB has held the output since creating it
|
||||
const FileMetaData compacted = FileStartingWith("a").first;
|
||||
EXPECT_EQ(compacted.db_ownership_start_time, compacted.file_creation_time);
|
||||
EXPECT_LE(compacted.db_ownership_start_time + 1000, Now());
|
||||
EXPECT_EQ(Decode(compacted),
|
||||
OriginOnly(DataFileProvenance::Origin::kCompaction));
|
||||
}
|
||||
|
||||
TEST_F(DBOwnershipTest, MovesPreserveOwnership) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
env_->SetMockSleep();
|
||||
CompactRangeOptions force_compact;
|
||||
force_compact.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
{
|
||||
SCOPED_TRACE("PromoteL0");
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("a", "1"));
|
||||
ASSERT_OK(Flush());
|
||||
const FileMetaData before = FileStartingWith("a").first;
|
||||
env_->MockSleepForSeconds(1000);
|
||||
ASSERT_OK(
|
||||
experimental::PromoteL0(db_.get(), db_->DefaultColumnFamily(), 2));
|
||||
const auto& [after, level] = FileStartingWith("a");
|
||||
EXPECT_EQ(level, 2);
|
||||
ExpectSameFile(before, after, kProvenanceMovedBetweenLevels);
|
||||
}
|
||||
{
|
||||
SCOPED_TRACE("Trivial move");
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("a", "1"));
|
||||
ASSERT_OK(Flush());
|
||||
const FileMetaData before = FileStartingWith("a").first;
|
||||
env_->MockSleepForSeconds(1000);
|
||||
std::atomic<int> trivial_moves{0};
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:TrivialMove",
|
||||
[&](void*) { ++trivial_moves; });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
EXPECT_GT(trivial_moves.load(), 0);
|
||||
const auto& [after, level] = FileStartingWith("a");
|
||||
EXPECT_GT(level, 0);
|
||||
ExpectSameFile(before, after, kProvenanceMovedBetweenLevels);
|
||||
}
|
||||
{
|
||||
SCOPED_TRACE("CompactRange with change_level, and reopen");
|
||||
DestroyAndReopen(options);
|
||||
// A compaction output that has not been moved
|
||||
ASSERT_OK(Put("a", "1"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("a", "2"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(db_->CompactRange(force_compact, nullptr, nullptr));
|
||||
const auto& [before, before_level] = FileStartingWith("a");
|
||||
ASSERT_EQ(Decode(before),
|
||||
OriginOnly(DataFileProvenance::Origin::kCompaction));
|
||||
ASSERT_GT(before_level, 1);
|
||||
env_->MockSleepForSeconds(1000);
|
||||
CompactRangeOptions refit;
|
||||
refit.change_level = true;
|
||||
refit.target_level = 1;
|
||||
ASSERT_OK(db_->CompactRange(refit, nullptr, nullptr));
|
||||
const auto& [after, level] = FileStartingWith("a");
|
||||
EXPECT_EQ(level, 1);
|
||||
ExpectSameFile(before, after, kProvenanceMovedBetweenLevels);
|
||||
|
||||
// Preserved by replaying the MANIFEST and then by the new MANIFEST written
|
||||
// on open
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
Reopen(options);
|
||||
ExpectSameFile(after, FileStartingWith("a").first, 0);
|
||||
}
|
||||
}
|
||||
{
|
||||
SCOPED_TRACE("Level migration on open");
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("a", "1"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("a", "2"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(db_->CompactRange(force_compact, nullptr, nullptr));
|
||||
const auto& [before, before_level] = FileStartingWith("a");
|
||||
ASSERT_EQ(Decode(before),
|
||||
OriginOnly(DataFileProvenance::Origin::kCompaction));
|
||||
ASSERT_LT(before_level, options.num_levels - 1);
|
||||
env_->MockSleepForSeconds(1000);
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
// Required for the migration
|
||||
options.disable_auto_compactions = false;
|
||||
Reopen(options);
|
||||
const auto& [after, level] = FileStartingWith("a");
|
||||
EXPECT_EQ(level, options.num_levels - 1);
|
||||
ExpectSameFile(before, after, kProvenanceMovedBetweenLevels);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBOwnershipTest, FIFOTemperatureChangeCopy) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleFIFO;
|
||||
options.num_levels = 1;
|
||||
options.max_open_files = -1;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
CompactionOptionsFIFO fifo_options;
|
||||
fifo_options.file_temperature_age_thresholds = {{Temperature::kCold, 1000}};
|
||||
fifo_options.max_table_files_size = 100000000;
|
||||
fifo_options.allow_trivial_copy_when_change_temperature = true;
|
||||
options.compaction_options_fifo = fifo_options;
|
||||
env_->SetMockSleep();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put("a", "1"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("b", "1"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
const FileMetaData before = FileStartingWith("a").first;
|
||||
ASSERT_NE(before.temperature, Temperature::kCold);
|
||||
|
||||
env_->MockSleepForSeconds(2000);
|
||||
ASSERT_OK(Put("c", "1"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
const FileMetaData after = FileStartingWith("a").first;
|
||||
ASSERT_EQ(after.temperature, Temperature::kCold);
|
||||
EXPECT_NE(after.fd.GetNumber(), before.fd.GetNumber());
|
||||
// Same contents...
|
||||
EXPECT_EQ(after.file_creation_time, before.file_creation_time);
|
||||
// ...in a new file for the DB to hold
|
||||
EXPECT_GE(after.db_ownership_start_time,
|
||||
before.db_ownership_start_time + 2000);
|
||||
EXPECT_EQ(after.data_file_provenance,
|
||||
before.data_file_provenance | kProvenanceTemperatureChangedByCopy);
|
||||
}
|
||||
|
||||
TEST_F(DBOwnershipTest, TemperatureUpdatedFromFileSystem) {
|
||||
auto test_fs = std::make_shared<FileTemperatureTestFS>(env_->GetFileSystem());
|
||||
std::unique_ptr<Env> env(new CompositeEnvWrapper(env_, test_fs));
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.env = env.get();
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("a", "1"));
|
||||
ASSERT_OK(Flush());
|
||||
const FileMetaData before = FileStartingWith("a").first;
|
||||
ASSERT_NE(before.temperature, Temperature::kCold);
|
||||
|
||||
test_fs->OverrideSstFileTemperature(before.fd.GetNumber(),
|
||||
Temperature::kCold);
|
||||
Close();
|
||||
experimental::UpdateManifestForFilesStateOptions update_opts;
|
||||
update_opts.update_temperatures = true;
|
||||
ASSERT_OK(experimental::UpdateManifestForFilesState(
|
||||
options, dbname_,
|
||||
{ColumnFamilyDescriptor(kDefaultColumnFamilyName, options)},
|
||||
update_opts));
|
||||
Reopen(options);
|
||||
const FileMetaData after = FileStartingWith("a").first;
|
||||
EXPECT_EQ(after.temperature, Temperature::kCold);
|
||||
ExpectSameFile(before, after, kProvenanceTemperatureUpdatedFromFileSystem);
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBOwnershipTest, Repair) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("a", "1"));
|
||||
ASSERT_OK(Flush());
|
||||
const FileMetaData flushed = FileStartingWith("a").first;
|
||||
// Left in the WAL, for repair to write to a table
|
||||
ASSERT_OK(Put("b", "1"));
|
||||
Close();
|
||||
ASSERT_OK(RepairDB(dbname_, options));
|
||||
Reopen(options);
|
||||
|
||||
// Ownership of tables from before the repair was recorded in the lost
|
||||
// MANIFEST
|
||||
const FileMetaData repaired = FileStartingWith("a").first;
|
||||
// Restored from the table property, recorded when the flush finished
|
||||
EXPECT_GE(repaired.file_creation_time, flushed.file_creation_time);
|
||||
EXPECT_EQ(repaired.db_ownership_start_time, kUnknownDbOwnershipStartTime);
|
||||
DataFileProvenance expected;
|
||||
expected.recorded_by_repair = true;
|
||||
EXPECT_EQ(Decode(repaired), expected);
|
||||
|
||||
const FileMetaData from_wal = FileStartingWith("b").first;
|
||||
EXPECT_GT(from_wal.file_creation_time, 0U);
|
||||
EXPECT_EQ(from_wal.db_ownership_start_time, from_wal.file_creation_time);
|
||||
expected.origin = DataFileProvenance::Origin::kFlush;
|
||||
EXPECT_EQ(Decode(from_wal), expected);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+1
-1
@@ -3898,7 +3898,7 @@ TEST_F(DBFlushTest, DBStuckAfterAtomicFlushError) {
|
||||
// Resume starts and waits for background job to finish, i.e., Flush1
|
||||
// Fill memtable again, trigger Flush2 for mem0
|
||||
// Flush2 will get error status, and not rollback mem0, see code in
|
||||
// https://github.com/facebook/rocksdb/blob/b927ba5936216861c2c35ab68f50ba4a78e65747/db/db_impl/db_impl_compaction_flush.cc#L725
|
||||
// https://github.com/meta/rocksdb/blob/b927ba5936216861c2c35ab68f50ba4a78e65747/db/db_impl/db_impl_compaction_flush.cc#L725
|
||||
//
|
||||
// DB is stuck since mem0 can never be picked now
|
||||
//
|
||||
|
||||
@@ -407,6 +407,72 @@ TEST_F(DBFollowerTest, RetryCatchupManifestRollover) {
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBFollowerTest, CatchUpKeepsViewWhenNewManifestListsDeletedFiles) {
|
||||
Options opts = CurrentOptions();
|
||||
opts.disable_auto_compactions = true;
|
||||
opts.max_manifest_file_size = 1;
|
||||
opts.max_manifest_space_amp_pct = 0;
|
||||
Reopen(opts);
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency({
|
||||
{"DBImplFollower::TryCatchupWithLeader:Begin1", "Leader::Start"},
|
||||
{"Leader::Done", "DBImplFollower::TryCatchupWithLeader:Begin2"},
|
||||
{"DBImplFollower::TryCatchupWithLeader:End",
|
||||
"Follower::WaitForCatchup:1"},
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
ASSERT_OK(OpenAsFollower());
|
||||
ASSERT_EQ(FollowerGet("foo"), "v1");
|
||||
|
||||
std::atomic<bool> compacted{false};
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionEditHandlerPointInTime::MaybeCreateVersionBeforeApplyEdit:Begin1",
|
||||
[&](void*) {
|
||||
if (compacted.exchange(true)) {
|
||||
return;
|
||||
}
|
||||
std::vector<LiveFileMetaData> tables;
|
||||
db_->GetLiveFilesMetaData(&tables);
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.bottommost_level_compaction =
|
||||
BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForPurge());
|
||||
for (const auto& table : tables) {
|
||||
const Status table_status =
|
||||
env_->FileExists(MakeTableFileName(dbname_, table.file_number));
|
||||
ASSERT_TRUE(table_status.IsNotFound()) << table_status.ToString();
|
||||
}
|
||||
});
|
||||
|
||||
TEST_SYNC_POINT("Leader::Start");
|
||||
// The second flush rolls the MANIFEST again, so that the newest one lists
|
||||
// the file of the first flush in its snapshot.
|
||||
ASSERT_OK(Put("bar", "v1"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("baz", "v1"));
|
||||
ASSERT_OK(Flush());
|
||||
TEST_SYNC_POINT("Leader::Done");
|
||||
|
||||
TEST_SYNC_POINT("Follower::WaitForCatchup:1");
|
||||
ASSERT_TRUE(compacted.load());
|
||||
ASSERT_EQ(FollowerGet("foo"), "v1");
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency({
|
||||
{"DBImplFollower::TryCatchupWithLeader:End",
|
||||
"Follower::WaitForCatchup:2"},
|
||||
});
|
||||
TEST_SYNC_POINT("Follower::WaitForCatchup:2");
|
||||
ASSERT_EQ(FollowerGet("foo"), "v1");
|
||||
ASSERT_EQ(FollowerGet("bar"), "v1");
|
||||
ASSERT_EQ(FollowerGet("baz"), "v1");
|
||||
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
// This test creates 4 L0 files and compacts them. The follower, during catchup,
|
||||
// successfully instantiates 4 Versions corresponding to the 4 files (but
|
||||
// doesn't install them yet), followed by deleting those 4 and adding a new
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
#include "db/db_impl/compacted_db_impl.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "db/blob/blob_fetcher.h"
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/db_impl/db_impl_metadata.h"
|
||||
@@ -51,6 +53,18 @@ Status CompactedDBImpl::GetWithMetadata(const ReadOptions& options,
|
||||
return Get(options, column_family, key, value, timestamp);
|
||||
}
|
||||
|
||||
Status CompactedDBImpl::GetEntityWithMetadata(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice& key,
|
||||
PinnableWideColumns* columns,
|
||||
OutputMetadata* output_metadata) {
|
||||
Status s = InitGetEntityOutputMetadata(output_metadata);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
return GetEntity(options, column_family, key, columns);
|
||||
}
|
||||
|
||||
void CompactedDBImpl::MultiGetWithMetadata(
|
||||
const ReadOptions& options, size_t num_keys,
|
||||
ColumnFamilyHandle* const* column_families, const Slice* keys,
|
||||
@@ -75,6 +89,38 @@ void CompactedDBImpl::MultiGetWithMetadata(
|
||||
sorted_input);
|
||||
}
|
||||
|
||||
void CompactedDBImpl::MultiGetEntityWithMetadata(
|
||||
const ReadOptions& options, size_t num_keys,
|
||||
ColumnFamilyHandle* const* column_families, const Slice* keys,
|
||||
PinnableWideColumns* results, Status* statuses,
|
||||
MultiGetOutputMetadata* output_metadata, bool sorted_input) {
|
||||
const Status s = InitMultiGetEntityOutputMetadata(num_keys, output_metadata);
|
||||
if (!s.ok()) {
|
||||
std::fill_n(statuses, num_keys, s);
|
||||
return;
|
||||
}
|
||||
MultiGetEntityWithoutNewerVersionTracking(this, options, num_keys,
|
||||
column_families, keys, results,
|
||||
statuses, sorted_input);
|
||||
}
|
||||
|
||||
void CompactedDBImpl::MultiGetEntityWithMetadata(
|
||||
const ReadOptions& options, size_t num_keys, const Slice* keys,
|
||||
PinnableAttributeGroups* results, MultiGetOutputMetadata* output_metadata) {
|
||||
const size_t total_count = CountAttributeGroups(num_keys, results);
|
||||
const Status s =
|
||||
InitMultiGetEntityOutputMetadata(total_count, output_metadata);
|
||||
if (!results) {
|
||||
s.PermitUncheckedError();
|
||||
return;
|
||||
}
|
||||
if (!s.ok()) {
|
||||
SetAttributeGroupsStatus(num_keys, results, s);
|
||||
return;
|
||||
}
|
||||
MultiGetEntity(options, num_keys, keys, results);
|
||||
}
|
||||
|
||||
Status CompactedDBImpl::Init(const Options& options) {
|
||||
SuperVersionContext sv_context(/* create_superversion */ true);
|
||||
mutex_.Lock();
|
||||
|
||||
@@ -39,6 +39,12 @@ class CompactedDBImpl : public DBImpl {
|
||||
PinnableSlice* value,
|
||||
OutputMetadata* output_metadata) override;
|
||||
|
||||
using DB::GetEntityWithMetadata;
|
||||
Status GetEntityWithMetadata(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableWideColumns* columns,
|
||||
OutputMetadata* output_metadata) override;
|
||||
|
||||
using DB::MultiGet;
|
||||
// Note that CompactedDBImpl::MultiGet is not the optimized version of
|
||||
// MultiGet to use.
|
||||
@@ -58,6 +64,19 @@ class CompactedDBImpl : public DBImpl {
|
||||
MultiGetOutputMetadata* output_metadata,
|
||||
const bool sorted_input) override;
|
||||
|
||||
using DB::MultiGetEntityWithMetadata;
|
||||
void MultiGetEntityWithMetadata(const ReadOptions& options, size_t num_keys,
|
||||
ColumnFamilyHandle* const* column_families,
|
||||
const Slice* keys,
|
||||
PinnableWideColumns* results,
|
||||
Status* statuses,
|
||||
MultiGetOutputMetadata* output_metadata,
|
||||
bool sorted_input) override;
|
||||
void MultiGetEntityWithMetadata(
|
||||
const ReadOptions& options, size_t num_keys, const Slice* keys,
|
||||
PinnableAttributeGroups* results,
|
||||
MultiGetOutputMetadata* output_metadata) override;
|
||||
|
||||
using DBImpl::Put;
|
||||
Status Put(const WriteOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/, const Slice& /*key*/,
|
||||
|
||||
+1073
-261
File diff suppressed because it is too large.
Load diff
+230
-22
@@ -8,6 +8,7 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
@@ -61,6 +62,7 @@
|
||||
#include "rocksdb/attribute_groups.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/live_tune.h"
|
||||
#include "rocksdb/memtablerep.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/trace_reader_writer.h"
|
||||
@@ -73,9 +75,11 @@
|
||||
#include "util/autovector.h"
|
||||
#include "util/coro_utils.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/hash_containers.h"
|
||||
#include "util/repeatable_thread.h"
|
||||
#include "util/stop_watch.h"
|
||||
#include "util/thread_local.h"
|
||||
#include "util/threadpool_imp.h"
|
||||
|
||||
#if USE_COROUTINES
|
||||
#include "rocksdb/coro_db.h"
|
||||
@@ -89,6 +93,7 @@ class Cleanable;
|
||||
class FileIngestionHandleImpl;
|
||||
class SameFileBlobReader;
|
||||
class InMemoryStatsHistoryIterator;
|
||||
class LiveTuner;
|
||||
class MemTable;
|
||||
class PersistentStatsHistoryIterator;
|
||||
class TableCache;
|
||||
@@ -320,6 +325,12 @@ class DBImpl : public DB
|
||||
const Slice& key,
|
||||
PinnableAttributeGroups* result);
|
||||
|
||||
using DB::GetEntityWithMetadata;
|
||||
Status GetEntityWithMetadata(const ReadOptions& _read_options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableWideColumns* columns,
|
||||
OutputMetadata* output_metadata) override;
|
||||
|
||||
Status GetEntityLazy(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
LazyWideColumns* result) override;
|
||||
@@ -384,6 +395,20 @@ class DBImpl : public DB
|
||||
const Slice* keys,
|
||||
PinnableAttributeGroups* results) override;
|
||||
|
||||
using DB::MultiGetEntityWithMetadata;
|
||||
void MultiGetEntityWithMetadata(const ReadOptions& _read_options,
|
||||
size_t num_keys,
|
||||
ColumnFamilyHandle* const* column_families,
|
||||
const Slice* keys,
|
||||
PinnableWideColumns* results,
|
||||
Status* statuses,
|
||||
MultiGetOutputMetadata* output_metadata,
|
||||
bool sorted_input = false) override;
|
||||
void MultiGetEntityWithMetadata(
|
||||
const ReadOptions& _read_options, size_t num_keys, const Slice* keys,
|
||||
PinnableAttributeGroups* results,
|
||||
MultiGetOutputMetadata* output_metadata) override;
|
||||
|
||||
void MultiGetEntityLazy(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, size_t num_keys,
|
||||
const Slice* keys, LazyWideColumnsBatch* result,
|
||||
@@ -541,6 +566,12 @@ class DBImpl : public DB
|
||||
Status SetDBOptions(
|
||||
const std::unordered_map<std::string, std::string>& options_map) override;
|
||||
|
||||
// See rocksdb/live_tune.h.
|
||||
Status StartLiveTuneCycle(const LiveTuneOptions& options);
|
||||
Status GetLiveTuneStatus(LiveTuneStatus* status);
|
||||
Status GetLiveTuneAppliedOptions(std::map<std::string, std::string>* options);
|
||||
Status StopLiveTuneCycle();
|
||||
|
||||
using DB::NumberLevels;
|
||||
int NumberLevels(ColumnFamilyHandle* column_family) override;
|
||||
using DB::Level0StopWriteTrigger;
|
||||
@@ -765,6 +796,17 @@ class DBImpl : public DB
|
||||
using DB::EndTrace;
|
||||
Status EndTrace() override;
|
||||
|
||||
// Like StartTrace(), but returns Busy rather than replacing a trace that is
|
||||
// already running. `*trace_id` identifies the started trace for
|
||||
// EndTraceIfCurrent().
|
||||
Status StartTraceIfNone(const TraceOptions& options,
|
||||
std::unique_ptr<TraceWriter>&& trace_writer,
|
||||
uint64_t* trace_id);
|
||||
// Ends the trace `trace_id` from StartTraceIfNone() if it is still the one
|
||||
// running. Incomplete if a later StartTrace() replaced it or EndTrace()
|
||||
// ended it.
|
||||
Status EndTraceIfCurrent(uint64_t trace_id);
|
||||
|
||||
using DB::NewDefaultReplayer;
|
||||
Status NewDefaultReplayer(const std::vector<ColumnFamilyHandle*>& handles,
|
||||
std::unique_ptr<TraceReader>&& reader,
|
||||
@@ -986,10 +1028,12 @@ class DBImpl : public DB
|
||||
bool* is_blob_index);
|
||||
|
||||
Status TraceIteratorSeek(const uint32_t& cf_id, const Slice& key,
|
||||
const Slice& lower_bound, const Slice upper_bound);
|
||||
const Slice& lower_bound, const Slice upper_bound,
|
||||
const TracedReadOptions& read_options);
|
||||
Status TraceIteratorSeekForPrev(const uint32_t& cf_id, const Slice& key,
|
||||
const Slice& lower_bound,
|
||||
const Slice upper_bound);
|
||||
const Slice upper_bound,
|
||||
const TracedReadOptions& read_options);
|
||||
|
||||
// Similar to GetSnapshot(), but also lets the db know that this snapshot
|
||||
// will be used for transaction write-conflict checking. The DB can then
|
||||
@@ -1083,6 +1127,12 @@ class DBImpl : public DB
|
||||
|
||||
uint64_t MinLogNumberToRecycle();
|
||||
|
||||
// Whether the WAL is still in logs_, i.e. its writer may still be open. Such
|
||||
// a WAL must not be recycled yet: closing the old writer truncates the file
|
||||
// to its old size.
|
||||
// REQUIRES: wal_write_mutex_ not held
|
||||
bool IsWalInLogs(uint64_t wal_number);
|
||||
|
||||
// Returns the lower bound file number for SSTs that won't be deleted, even if
|
||||
// they're obsolete. This lower bound is used internally to prevent newly
|
||||
// created flush/compaction output files from being deleted before they're
|
||||
@@ -1115,17 +1165,25 @@ class DBImpl : public DB
|
||||
// Schedule a background job to actually delete obsolete files.
|
||||
void SchedulePurge();
|
||||
|
||||
const SnapshotList& snapshots() const { return snapshots_; }
|
||||
// Thread-safe; does not require any DB lock.
|
||||
const ShardedSnapshotList& snapshots() const { return snapshots_; }
|
||||
|
||||
// load list of snapshots to `snap_vector` that is no newer than `max_seq`
|
||||
// in ascending order.
|
||||
// Loads snapshots no newer than `max_seq` into `snap_vector` in ascending
|
||||
// order. Returns an exact oldest live snapshot sequence, or the last
|
||||
// published sequence if there are no snapshots, from a separate all-shard
|
||||
// scan. `snapshot_release_count` receives the release count observed by that
|
||||
// exact scan when non-null.
|
||||
// `oldest_write_conflict_snapshot` is filled with the oldest snapshot
|
||||
// which satisfies SnapshotImpl.is_write_conflict_boundary_ = true.
|
||||
void LoadSnapshots(std::vector<SequenceNumber>* snap_vector,
|
||||
SequenceNumber* oldest_write_conflict_snapshot,
|
||||
const SequenceNumber& max_seq) const {
|
||||
InstrumentedMutexLock l(mutex());
|
||||
// Thread-safe; callers that use the return values for DB-wide maintenance
|
||||
// must hold mutex_.
|
||||
SequenceNumber LoadSnapshots(std::vector<SequenceNumber>* snap_vector,
|
||||
SequenceNumber* oldest_write_conflict_snapshot,
|
||||
const SequenceNumber& max_seq,
|
||||
uint64_t* snapshot_release_count) const {
|
||||
snapshots().GetAll(snap_vector, oldest_write_conflict_snapshot, max_seq);
|
||||
return snapshots().OldestOr([this]() { return GetLastPublishedSequence(); },
|
||||
snapshot_release_count);
|
||||
}
|
||||
|
||||
const ImmutableDBOptions& immutable_db_options() const {
|
||||
@@ -1319,6 +1377,12 @@ class DBImpl : public DB
|
||||
void SetRecoverableStatePreReleaseCallback(PreReleaseCallback* callback);
|
||||
|
||||
InstrumentedMutex* mutex() const { return &mutex_; }
|
||||
const std::atomic<uint64_t>* GetMutexHoldLogThreshold() const {
|
||||
return &mutex_hold_log_threshold_us_;
|
||||
}
|
||||
const std::shared_ptr<SlowMutexLoggingContext>& GetSlowMutexLogging() const {
|
||||
return slow_mutex_logging_;
|
||||
}
|
||||
|
||||
// Initialize a brand new DB. The DB directory is expected to be empty before
|
||||
// calling it. Push new manifest file name into `new_filenames`.
|
||||
@@ -1673,8 +1737,12 @@ class DBImpl : public DB
|
||||
FileSystemPtr fs_;
|
||||
MutableDBOptions mutable_db_options_;
|
||||
Statistics* stats_;
|
||||
std::atomic<uint64_t> mutex_hold_log_threshold_us_;
|
||||
std::shared_ptr<SlowMutexLoggingContext> slow_mutex_logging_;
|
||||
RecoveredTransactionMap recovered_transactions_;
|
||||
std::unique_ptr<Tracer> tracer_;
|
||||
// Incremented by every trace started; guarded by trace_mutex_.
|
||||
uint64_t trace_id_ = 0;
|
||||
InstrumentedMutex trace_mutex_;
|
||||
BlockCacheTracer block_cache_tracer_;
|
||||
|
||||
@@ -2235,9 +2303,12 @@ class DBImpl : public DB
|
||||
friend class CompactedDBImpl;
|
||||
friend class DBImplFollower;
|
||||
#ifndef NDEBUG
|
||||
friend class DedicatedCompactionPoolTest;
|
||||
friend class DBTest_ConcurrentFlushWAL_Test;
|
||||
friend class DBTest_MixedSlowdownOptionsStop_Test;
|
||||
friend class DBCompactionTest_CompactBottomLevelFilesWithDeletions_Test;
|
||||
friend class
|
||||
DBCompactionTest_StandaloneRangeTombstoneSnapshotAdvanceRequeuesScheduling_Test;
|
||||
friend class DBCompactionTest_CompactionDuringShutdown_Test;
|
||||
friend class DBCompactionTest_DelayCompactBottomLevelFilesWithDeletions_Test;
|
||||
friend class DBCompactionTest_DisableCompactBottomLevelFiles_Test;
|
||||
@@ -2500,6 +2571,7 @@ class DBImpl : public DB
|
||||
// background compaction takes ownership of `prepicked_compaction`.
|
||||
PrepickedCompaction* prepicked_compaction;
|
||||
Env::Priority compaction_pri_;
|
||||
bool separate_remote_pool = false;
|
||||
};
|
||||
|
||||
static bool IsRecoveryFlush(FlushReason flush_reason) {
|
||||
@@ -2828,6 +2900,12 @@ class DBImpl : public DB
|
||||
// fence that is about to switch memtables.
|
||||
void MaybeSyncLastSequenceWithAllocatedForRecovery(FlushReason flush_reason);
|
||||
|
||||
// Like the memtable-switch fence, but does not wait for a write queue whose
|
||||
// leader is in a write stall: that leader has not allocated sequence numbers
|
||||
// yet, and it can be waiting for this recovery.
|
||||
// REQUIRES: mutex locked, two_write_queues
|
||||
void SyncLastSequenceWithAllocatedAtRecoveryStart();
|
||||
|
||||
Status RetryFlushesForErrorRecovery(FlushReason flush_reason, bool wait);
|
||||
|
||||
// Wait until flushing this column family won't stall writes
|
||||
@@ -3031,6 +3109,48 @@ class DBImpl : public DB
|
||||
bool track);
|
||||
|
||||
void MaybeScheduleFlushOrCompaction();
|
||||
bool UseSeparateRemoteCompactionPool() const {
|
||||
mutex_.AssertHeld();
|
||||
return immutable_db_options_.compaction_service != nullptr &&
|
||||
mutable_db_options_.use_separate_remote_compaction_pool;
|
||||
}
|
||||
int LocalCompactionsScheduled() const {
|
||||
mutex_.AssertHeld();
|
||||
return bg_compaction_scheduled_ + bg_bottom_compaction_scheduled_;
|
||||
}
|
||||
int CompactionsScheduled() const {
|
||||
mutex_.AssertHeld();
|
||||
return LocalCompactionsScheduled() + remote_coordinators_scheduled_;
|
||||
}
|
||||
Status ScheduleCompaction(CompactionArg* arg, void* tag);
|
||||
class RemoteCompactionCompletionGuard;
|
||||
void ScheduleRemoteCompaction(std::function<void()> work);
|
||||
void ScheduleRemoteCompactionLocked(std::function<void()> work);
|
||||
int RemoteCompactionPoolBaseline() const;
|
||||
void ShrinkRemoteCompactionPool();
|
||||
void ReleaseRemoteCompactionPool();
|
||||
void ConfigureCompactionExecutors(CompactionJob* job, ColumnFamilyData* cfd,
|
||||
const std::atomic<bool>& canceled,
|
||||
bool is_manual, bool separate_remote_pool);
|
||||
Status LocalCompactionStatus(ColumnFamilyData* cfd,
|
||||
const std::atomic<bool>& canceled);
|
||||
Status RunLocalCompaction(std::function<void()> work, Env::Priority priority,
|
||||
ColumnFamilyData* cfd,
|
||||
const std::atomic<bool>& canceled, bool is_manual);
|
||||
IOStatus CopyTrivialCompactionFiles(Compaction* c, LogBuffer* log_buffer,
|
||||
std::vector<FileMetaData>* out_files,
|
||||
int job_id, bool db_mutex_held);
|
||||
struct LocalCompactionTask {
|
||||
DBImpl* db;
|
||||
ColumnFamilyData* cfd;
|
||||
const std::atomic<bool>* canceled;
|
||||
std::function<void()> work;
|
||||
Env::Priority priority;
|
||||
Status status;
|
||||
bool done = false;
|
||||
};
|
||||
static void BGWorkLocalCompaction(void* arg);
|
||||
static void UnscheduleLocalCompaction(void* arg);
|
||||
|
||||
BackgroundJobPressure CaptureBackgroundJobPressure() const;
|
||||
void NotifyOnBackgroundJobPressureChanged();
|
||||
@@ -3098,7 +3218,8 @@ class DBImpl : public DB
|
||||
static void UnscheduleCompactionCallback(void* arg);
|
||||
static void UnscheduleFlushCallback(void* arg);
|
||||
void BackgroundCallCompaction(PrepickedCompaction* prepicked_compaction,
|
||||
Env::Priority thread_pri);
|
||||
Env::Priority thread_pri,
|
||||
bool separate_remote_pool);
|
||||
void BackgroundCallFlush(Env::Priority thread_pri);
|
||||
void BackgroundCallPurge();
|
||||
// Recursively removes all children of the DB session temporary directory.
|
||||
@@ -3107,7 +3228,9 @@ class DBImpl : public DB
|
||||
Status BackgroundCompaction(bool* madeProgress, JobContext* job_context,
|
||||
LogBuffer* log_buffer,
|
||||
PrepickedCompaction* prepicked_compaction,
|
||||
Env::Priority thread_pri);
|
||||
Env::Priority thread_pri,
|
||||
bool separate_remote_pool,
|
||||
int* deferred_compactions);
|
||||
Status BackgroundFlush(bool* madeProgress, JobContext* job_context,
|
||||
LogBuffer* log_buffer, FlushReason* reason,
|
||||
bool* flush_rescheduled_to_retain_udt,
|
||||
@@ -3198,8 +3321,23 @@ class DBImpl : public DB
|
||||
// WALs with log number up to up_to are not synced successfully.
|
||||
void MarkLogsNotSynced(uint64_t up_to);
|
||||
|
||||
enum class SnapshotLockMode {
|
||||
kTryWithoutDBMutex,
|
||||
kDBMutexHeld,
|
||||
kAcquireDBMutex,
|
||||
};
|
||||
SnapshotImpl* GetSnapshotImpl(bool is_write_conflict_boundary,
|
||||
bool lock = true);
|
||||
SnapshotLockMode lock_mode);
|
||||
|
||||
// Updates compaction state after the oldest snapshot advances and also
|
||||
// reconciles `installed_cfd`, when non-null, if its Version has not processed
|
||||
// the current oldest snapshot yet. Requires mutex_.
|
||||
void UpdateOldestSnapshotAndScheduleWork(SequenceNumber oldest_snapshot,
|
||||
uint64_t snapshot_release_count,
|
||||
ColumnFamilyData* installed_cfd);
|
||||
bool ReconcileOldestSnapshotForColumnFamily(ColumnFamilyData* cfd,
|
||||
SequenceNumber oldest_snapshot);
|
||||
void UpdateSnapshotCompactionThresholds();
|
||||
|
||||
// If snapshot_seq != kMaxSequenceNumber, then this function can only be
|
||||
// called from the write thread that publishes sequence numbers to readers.
|
||||
@@ -3235,6 +3373,7 @@ class DBImpl : public DB
|
||||
// See also InstallSuperVersionForConfigChange().
|
||||
void InstallSuperVersionAndScheduleWork(
|
||||
ColumnFamilyData* cfd, SuperVersionContext* sv_context,
|
||||
bool defer_snapshot_refresh,
|
||||
std::optional<std::shared_ptr<SeqnoToTimeMapping>>
|
||||
new_seqno_to_time_mapping = {});
|
||||
|
||||
@@ -3268,10 +3407,8 @@ class DBImpl : public DB
|
||||
// Reserve the next 'num' file numbers for to-be-ingested external SST files,
|
||||
// and return the current file_number in 'next_file_number'.
|
||||
// Write a version edit to the MANIFEST.
|
||||
Status ReserveFileNumbersBeforeIngestion(
|
||||
ColumnFamilyData* cfd, uint64_t num,
|
||||
std::unique_ptr<std::list<uint64_t>::iterator>& pending_output_elem,
|
||||
uint64_t* next_file_number);
|
||||
Status ReserveFileNumbersBeforeIngestion(ColumnFamilyData* cfd, uint64_t num,
|
||||
uint64_t* next_file_number);
|
||||
|
||||
bool ShouldPurge(uint64_t file_number) const;
|
||||
void MarkAsGrabbedForPurge(uint64_t file_number);
|
||||
@@ -3464,6 +3601,24 @@ class DBImpl : public DB
|
||||
Status IncreaseFullHistoryTsLowImpl(ColumnFamilyData* cfd,
|
||||
std::string ts_low);
|
||||
|
||||
// Passes GetMergeOperands collection options to every lookup layer through
|
||||
// merge_context. The caller's output array is not touched here.
|
||||
static void PrepareGetMergeOperands(const GetImplOptions& get_impl_options,
|
||||
MergeContext* merge_context);
|
||||
|
||||
// Publishes a successful GetMergeOperands lookup into the first
|
||||
// *number_of_operands output slots; later slots, and the whole array on an
|
||||
// Incomplete result, are left as the caller passed them. The output either
|
||||
// owns a copy of every operand or shares one cleanup bundle that keeps all
|
||||
// backing resources alive until the last PinnableSlice is reset. Returns
|
||||
// Incomplete when the result exceeds the caller's capacity and a plain OK
|
||||
// otherwise, so a caller must not let that OK replace a subcode it set.
|
||||
Status FinalizeGetMergeOperands(const GetImplOptions& get_impl_options,
|
||||
SuperVersion* super_version,
|
||||
MergeContext* merge_context,
|
||||
PinnedIteratorsManager* pinned_iters_manager,
|
||||
size_t* bytes_read);
|
||||
|
||||
bool ShouldReferenceSuperVersion(const MergeContext& merge_context);
|
||||
// Resolves a plain value whose current payload is an encoded direct-write
|
||||
// blob index, either through `value` directly or through the default-column
|
||||
@@ -3543,9 +3698,38 @@ class DBImpl : public DB
|
||||
// Always acquired *before* DB mutex when this one is applicable.
|
||||
InstrumentedMutex options_mutex_;
|
||||
|
||||
// True when the OPTIONS file can lag the options or column families in
|
||||
// effect: the last OPTIONS file write failed, SetOptions() changed options
|
||||
// and then failed before it wrote the file, or a column family batch with
|
||||
// track_options_file_number_in_manifest stopped part way. Then SetOptions()
|
||||
// and SetDBOptions() write the file even for a request that changes no
|
||||
// option. A successful WriteOptionsFile() clears it, and so does a tracked
|
||||
// column family change that commits a current snapshot. Protected by
|
||||
// options_mutex_.
|
||||
bool options_file_stale_ = false;
|
||||
|
||||
// True when the seqno-to-time worker can lag the time options of the column
|
||||
// families: the last RegisterRecordSeqnoTimeWorker() failed, or SetOptions()
|
||||
// changed options and then failed before it updated the worker. Then
|
||||
// SetOptions() updates the worker even for a request that changes no option.
|
||||
// A successful RegisterRecordSeqnoTimeWorker() clears it. Protected by
|
||||
// options_mutex_.
|
||||
bool seqno_time_worker_stale_ = false;
|
||||
|
||||
// Guards reads and writes to in-memory stats_history_.
|
||||
InstrumentedMutex stats_history_mutex_;
|
||||
|
||||
// Guards live_tuner_ (the running or last live tune cycle) and
|
||||
// live_tune_closed_. Never held while stopping a cycle (which joins its
|
||||
// thread, and the cycle calls into the DB) nor while holding the DB mutex.
|
||||
std::mutex live_tune_mutex_;
|
||||
// The tuner holds a ColumnFamilyHandle, whose destructor locks the DB mutex,
|
||||
// so it must be destroyed while the DB is still open: CloseHelper() takes
|
||||
// it out and destroys it first. Never hand out this pointer, which would
|
||||
// let the tuner outlive the DB.
|
||||
std::shared_ptr<LiveTuner> live_tuner_;
|
||||
bool live_tune_closed_ = false;
|
||||
|
||||
// In addition to mutex_, wal_write_mutex_ protects writes to logs_ and
|
||||
// cur_wal_number_. With two_write_queues it also protects alive_wal_files_,
|
||||
// and wal_empty_. Refer to the definition of each variable below for more
|
||||
@@ -3736,7 +3920,8 @@ class DBImpl : public DB
|
||||
// threads. Protected by wal_write_mutex_.
|
||||
autovector<log::Writer*> wals_to_free_;
|
||||
|
||||
bool is_snapshot_supported_ = true;
|
||||
// Written with mutex_ held. Read without mutex_ when creating a snapshot.
|
||||
std::atomic<bool> is_snapshot_supported_{true};
|
||||
|
||||
std::map<uint64_t, std::map<std::string, uint64_t>> stats_history_;
|
||||
|
||||
@@ -3766,7 +3951,13 @@ class DBImpl : public DB
|
||||
|
||||
TrimHistoryScheduler trim_history_scheduler_;
|
||||
|
||||
SnapshotList snapshots_;
|
||||
// Shard locks inside snapshots_ are leaf locks: they may be acquired while
|
||||
// holding mutex_, but mutex_ must never be acquired while holding one.
|
||||
// GetSnapshotImpl() reads a snapshot's sequence number inside the shard lock
|
||||
// that adds it. Flush and compaction combine that ordering with their
|
||||
// DB-mutex-protected input boundary and, when required, a managed snapshot or
|
||||
// published-sequence pin in InitSnapshotContext().
|
||||
ShardedSnapshotList snapshots_;
|
||||
|
||||
TimestampedSnapshotList timestamped_snapshots_;
|
||||
|
||||
@@ -3781,6 +3972,11 @@ class DBImpl : public DB
|
||||
// State is protected with db mutex.
|
||||
std::list<uint64_t> pending_outputs_;
|
||||
|
||||
// First file number -> reserved file count for external file ingestions.
|
||||
// Full-scan purge snapshots protect these SSTs until commit or cleanup.
|
||||
// State is protected with db mutex.
|
||||
UnorderedMap<uint64_t, uint64_t> pending_ingestion_outputs_;
|
||||
|
||||
// Similar to pending_outputs_, FindObsoleteFiles()/PurgeObsoleteFiles() never
|
||||
// deletes any OPTIONS file that has number bigger than any of the file number
|
||||
// in min_options_file_numbers_.
|
||||
@@ -3848,6 +4044,11 @@ class DBImpl : public DB
|
||||
// count how many background compactions are running or have been scheduled
|
||||
int bg_compaction_scheduled_ = 0;
|
||||
|
||||
std::shared_ptr<RemoteCompactionThreadPool> remote_compaction_pool_;
|
||||
int remote_compaction_pool_jobs_ = 0;
|
||||
int remote_coordinators_scheduled_ = 0;
|
||||
std::array<int, 2> local_compactions_running_by_priority_{};
|
||||
|
||||
// stores the number of compactions are currently running
|
||||
int num_running_compactions_ = 0;
|
||||
|
||||
@@ -3972,15 +4173,22 @@ class DBImpl : public DB
|
||||
// Guard against multiple concurrent refitting
|
||||
bool refitting_level_ = false;
|
||||
|
||||
// The min threshold to triggere bottommost compaction for removing
|
||||
// garbages, among all column families.
|
||||
// The minimum threshold for triggering bottommost compaction to remove
|
||||
// garbage across all column families. Protected by mutex_.
|
||||
SequenceNumber bottommost_files_mark_threshold_ = kMaxSequenceNumber;
|
||||
|
||||
// The min threshold to trigger compactions for standalone range deletion
|
||||
// files that are marked for compaction.
|
||||
// The minimum threshold for triggering compactions of standalone range
|
||||
// deletion files across all column families. Protected by mutex_.
|
||||
SequenceNumber standalone_range_deletion_files_mark_threshold_ =
|
||||
kMaxSequenceNumber;
|
||||
|
||||
// Largest exact oldest-snapshot sequence processed. Protected by mutex_.
|
||||
SequenceNumber last_processed_oldest_snapshot_ = 0;
|
||||
|
||||
// Sum of the per-shard snapshot release counts at the last exact refresh.
|
||||
// Protected by mutex_.
|
||||
uint64_t last_processed_snapshot_release_count_ = 0;
|
||||
|
||||
LogsWithPrepTracker logs_with_prep_tracker_;
|
||||
|
||||
// Callback for compaction to check if a key is visible to a snapshot.
|
||||
|
||||
Loaded 100 of 460 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user