mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-07 11:32:34 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef3dd12e9a | ||
|
|
dec67990f0 | ||
|
|
9b4c5422f0 | ||
|
|
7292adcc9a | ||
|
|
6b15f1643e | ||
|
|
a38cd3a439 | ||
|
|
45203a867f | ||
|
|
0561153b73 | ||
|
|
1c490a4a4d | ||
|
|
60f090647d | ||
|
|
5b77e34985 | ||
|
|
3ea1ec56df | ||
|
|
f16157cfc5 | ||
|
|
c83a84e98c | ||
|
|
c0512c4c34 | ||
|
|
5f1132b884 | ||
|
|
c2b86f3ec0 | ||
|
|
8585299552 | ||
|
|
866e8e9f06 | ||
|
|
19b6f85266 | ||
|
|
4cd0450f31 | ||
|
|
b58cbcf13c | ||
|
|
4d180247ae | ||
|
|
ded3cb72a5 | ||
|
|
aeebcbded4 | ||
|
|
11516577ab | ||
|
|
9d358e63d6 | ||
|
|
31675ca6d0 | ||
|
|
ff29163e47 | ||
|
|
176d7c7ea1 | ||
|
|
b4e068e1ed | ||
|
|
eed2f96676 | ||
|
|
5fa16200aa | ||
|
|
7510773057 | ||
|
|
5eed79a0e1 | ||
|
|
3d381e1bd4 | ||
|
|
f8d3141e5c | ||
|
|
4052fccde9 | ||
|
|
ac30c4d738 | ||
|
|
dbc2db5228 | ||
|
|
07cbacadb4 | ||
|
|
662cdbc9ec | ||
|
|
ffd4ac31af | ||
|
|
0675830967 | ||
|
|
14a82121d4 |
No files matched your search
@@ -12,6 +12,7 @@ jobs:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
|
||||
options: --shm-size=8gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
with:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: facebook/rocksdb/nightly
|
||||
name: facebook/rocksdb/nightly-candidate
|
||||
on: workflow_dispatch
|
||||
permissions: {}
|
||||
jobs:
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
labels: arm64large
|
||||
container:
|
||||
image: ubuntu-2004:202111-02
|
||||
options: --shm-size=16gb
|
||||
options: --shm-size=8gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
options: --shm-size=32gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
with:
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
options: --shm-size=32gb
|
||||
env:
|
||||
TEST_TMPDIR: "/tmp/rocksdb_test_tmp"
|
||||
steps:
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
|
||||
options: --shm-size=16gb
|
||||
options: --shm-size=32gb
|
||||
env:
|
||||
CC: clang-21
|
||||
CXX: clang++-21
|
||||
@@ -84,7 +84,7 @@ jobs:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
options: --shm-size=32gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
@@ -103,7 +103,7 @@ jobs:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
options: --shm-size=32gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
@@ -161,7 +161,7 @@ jobs:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
options: --shm-size=8gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
@@ -174,7 +174,7 @@ jobs:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
|
||||
options: --shm-size=16gb
|
||||
options: --shm-size=8gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
@@ -189,7 +189,7 @@ jobs:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
options: --shm-size=32gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
options: --shm-size=32gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
@@ -110,7 +110,7 @@ jobs:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
|
||||
options: --shm-size=16gb
|
||||
options: --shm-size=8gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
@@ -135,7 +135,7 @@ jobs:
|
||||
labels: 32-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
options: --shm-size=64gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
@@ -160,7 +160,7 @@ jobs:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
options: --shm-size=32gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
@@ -180,7 +180,7 @@ jobs:
|
||||
labels: 32-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
options: --shm-size=64gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
@@ -209,7 +209,7 @@ jobs:
|
||||
shard: [0, 1, 2, 3]
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
options: --shm-size=32gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
@@ -232,7 +232,7 @@ jobs:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
options: --shm-size=32gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
@@ -252,7 +252,7 @@ jobs:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
options: --shm-size=32gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/setup-ccache"
|
||||
@@ -306,7 +306,7 @@ jobs:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
|
||||
options: --shm-size=16gb
|
||||
options: --shm-size=32gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
@@ -373,7 +373,7 @@ jobs:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
|
||||
options: --shm-size=16gb
|
||||
options: --shm-size=32gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
@@ -394,7 +394,7 @@ jobs:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: gcc:latest
|
||||
options: --shm-size=16gb
|
||||
options: --shm-size=8gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- run: apt-get update -y && apt-get install -y libgflags-dev
|
||||
@@ -422,7 +422,7 @@ jobs:
|
||||
crash_duration: 240
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb --ulimit nofile=1048576:1048576
|
||||
options: --shm-size=8gb --ulimit nofile=1048576:1048576
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
@@ -457,7 +457,7 @@ jobs:
|
||||
shard: [0, 1, 2]
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
|
||||
options: --shm-size=16gb
|
||||
options: --shm-size=64gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
@@ -484,7 +484,7 @@ jobs:
|
||||
shard: [0, 1, 2]
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
|
||||
options: --shm-size=16gb
|
||||
options: --shm-size=64gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
@@ -507,7 +507,7 @@ jobs:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
options: --shm-size=32gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
@@ -614,7 +614,7 @@ jobs:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
options: --shm-size=8gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
@@ -637,7 +637,7 @@ jobs:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
options: --shm-size=8gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
@@ -740,7 +740,7 @@ jobs:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: evolvedbinary/rocksjava:alpine3_x64-be
|
||||
options: --shm-size=16gb
|
||||
options: --shm-size=8gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/install-maven"
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
timeout-minutes: 840
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
options: --shm-size=32gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
|
||||
@@ -241,6 +241,7 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"table/plain/plain_table_index.cc",
|
||||
"table/plain/plain_table_key_coding.cc",
|
||||
"table/plain/plain_table_reader.cc",
|
||||
"table/range_del_block.cc",
|
||||
"table/sst_file_dumper.cc",
|
||||
"table/sst_file_reader.cc",
|
||||
"table/sst_file_writer.cc",
|
||||
@@ -415,13 +416,27 @@ cpp_library_wrapper(name="rocksdb_test_lib", srcs=[
|
||||
"table/mock_table.cc",
|
||||
"test_util/mock_time_env.cc",
|
||||
"test_util/secondary_cache_test_util.cc",
|
||||
"test_util/simple_external_table_factory.cc",
|
||||
"test_util/testharness.cc",
|
||||
"test_util/testutil.cc",
|
||||
"tools/block_cache_analyzer/block_cache_trace_analyzer.cc",
|
||||
"tools/trace_analyzer_tool.cc",
|
||||
"utilities/agg_merge/test_agg_merge.cc",
|
||||
"utilities/cassandra/test_utils.cc",
|
||||
], deps=[":rocksdb_lib"], headers=[], link_whole=False, extra_test_libs=True)
|
||||
], deps=[":rocksdb_lib"], headers=[
|
||||
"db/db_test_util.h",
|
||||
"db/db_with_timestamp_test_util.h",
|
||||
"table/mock_table.h",
|
||||
"test_util/mock_time_env.h",
|
||||
"test_util/secondary_cache_test_util.h",
|
||||
"test_util/simple_external_table_factory.h",
|
||||
"test_util/testharness.h",
|
||||
"test_util/testutil.h",
|
||||
"tools/block_cache_analyzer/block_cache_trace_analyzer.h",
|
||||
"tools/trace_analyzer_tool.h",
|
||||
"utilities/agg_merge/test_agg_merge.h",
|
||||
"utilities/cassandra/test_utils.h",
|
||||
], link_whole=False, extra_test_libs=True)
|
||||
|
||||
cpp_library_wrapper(name="rocksdb_with_faiss_test_lib", srcs=[
|
||||
"db/db_test_util.cc",
|
||||
@@ -429,13 +444,27 @@ cpp_library_wrapper(name="rocksdb_with_faiss_test_lib", srcs=[
|
||||
"table/mock_table.cc",
|
||||
"test_util/mock_time_env.cc",
|
||||
"test_util/secondary_cache_test_util.cc",
|
||||
"test_util/simple_external_table_factory.cc",
|
||||
"test_util/testharness.cc",
|
||||
"test_util/testutil.cc",
|
||||
"tools/block_cache_analyzer/block_cache_trace_analyzer.cc",
|
||||
"tools/trace_analyzer_tool.cc",
|
||||
"utilities/agg_merge/test_agg_merge.cc",
|
||||
"utilities/cassandra/test_utils.cc",
|
||||
], deps=[":rocksdb_with_faiss_lib"], headers=[], link_whole=False, extra_test_libs=True)
|
||||
], deps=[":rocksdb_with_faiss_lib"], headers=[
|
||||
"db/db_test_util.h",
|
||||
"db/db_with_timestamp_test_util.h",
|
||||
"table/mock_table.h",
|
||||
"test_util/mock_time_env.h",
|
||||
"test_util/secondary_cache_test_util.h",
|
||||
"test_util/simple_external_table_factory.h",
|
||||
"test_util/testharness.h",
|
||||
"test_util/testutil.h",
|
||||
"tools/block_cache_analyzer/block_cache_trace_analyzer.h",
|
||||
"tools/trace_analyzer_tool.h",
|
||||
"utilities/agg_merge/test_agg_merge.h",
|
||||
"utilities/cassandra/test_utils.h",
|
||||
], link_whole=False, extra_test_libs=True)
|
||||
|
||||
cpp_library_wrapper(name="rocksdb_tools_lib", srcs=[
|
||||
"test_util/testutil.cc",
|
||||
@@ -444,7 +473,12 @@ cpp_library_wrapper(name="rocksdb_tools_lib", srcs=[
|
||||
"tools/simulated_hybrid_file_system.cc",
|
||||
"tools/tool_hooks.cc",
|
||||
"tools/trace_analyzer_tool.cc",
|
||||
], deps=[":rocksdb_lib"], headers=[], link_whole=False, extra_test_libs=False)
|
||||
], deps=[":rocksdb_lib"], headers=[
|
||||
"test_util/testutil.h",
|
||||
"tools/block_cache_analyzer/block_cache_trace_analyzer.h",
|
||||
"tools/simulated_hybrid_file_system.h",
|
||||
"tools/trace_analyzer_tool.h",
|
||||
], link_whole=False, extra_test_libs=False)
|
||||
|
||||
cpp_library_wrapper(name="rocksdb_cache_bench_tools_lib", srcs=["cache/cache_bench_tool.cc"], deps=[":rocksdb_lib"], headers=[], link_whole=False, extra_test_libs=False)
|
||||
|
||||
@@ -468,10 +502,28 @@ rocks_cpp_library_wrapper(name="rocksdb_stress_lib", srcs=[
|
||||
"db_stress_tool/expected_value.cc",
|
||||
"db_stress_tool/multi_ops_txns_stress.cc",
|
||||
"db_stress_tool/no_batched_ops_stress.cc",
|
||||
"test_util/simple_external_table_factory.cc",
|
||||
"test_util/testutil.cc",
|
||||
"tools/block_cache_analyzer/block_cache_trace_analyzer.cc",
|
||||
"tools/trace_analyzer_tool.cc",
|
||||
], headers=[])
|
||||
], headers=[
|
||||
"db_stress_tool/db_stress_common.h",
|
||||
"db_stress_tool/db_stress_compaction_service.h",
|
||||
"db_stress_tool/db_stress_compression_manager.h",
|
||||
"db_stress_tool/db_stress_driver.h",
|
||||
"db_stress_tool/db_stress_filters.h",
|
||||
"db_stress_tool/db_stress_listener.h",
|
||||
"db_stress_tool/db_stress_shared_state.h",
|
||||
"db_stress_tool/db_stress_test_base.h",
|
||||
"db_stress_tool/db_stress_wide_merge_operator.h",
|
||||
"db_stress_tool/expected_state.h",
|
||||
"db_stress_tool/expected_value.h",
|
||||
"db_stress_tool/multi_ops_txns_stress.h",
|
||||
"test_util/simple_external_table_factory.h",
|
||||
"test_util/testutil.h",
|
||||
"tools/block_cache_analyzer/block_cache_trace_analyzer.h",
|
||||
"tools/trace_analyzer_tool.h",
|
||||
])
|
||||
|
||||
|
||||
cpp_binary_wrapper(name="ldb", srcs=["tools/ldb.cc"], deps=[":rocksdb_tools_lib"], extra_preprocessor_flags=[], extra_bench_libs=False)
|
||||
@@ -4761,6 +4813,7 @@ cpp_unittest_wrapper(name="compression_test",
|
||||
|
||||
cpp_unittest_wrapper(name="configurable_test",
|
||||
srcs=["options/configurable_test.cc"],
|
||||
source_headers=["options/configurable_test.h"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
extra_compiler_flags=[])
|
||||
|
||||
@@ -5465,6 +5518,7 @@ cpp_unittest_wrapper(name="periodic_task_scheduler_test",
|
||||
|
||||
cpp_unittest_wrapper(name="persistent_cache_test",
|
||||
srcs=["utilities/persistent_cache/persistent_cache_test.cc"],
|
||||
source_headers=["utilities/persistent_cache/persistent_cache_test.h"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
extra_compiler_flags=[])
|
||||
|
||||
@@ -5483,6 +5537,7 @@ cpp_unittest_wrapper(name="point_lock_manager_stress_test",
|
||||
|
||||
cpp_unittest_wrapper(name="point_lock_manager_test",
|
||||
srcs=["utilities/transactions/lock/point/point_lock_manager_test.cc"],
|
||||
source_headers=["utilities/transactions/lock/point/point_lock_manager_test.h"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
extra_compiler_flags=[])
|
||||
|
||||
@@ -5693,6 +5748,7 @@ cpp_unittest_wrapper(name="trace_analyzer_test",
|
||||
|
||||
cpp_unittest_wrapper(name="transaction_test",
|
||||
srcs=["utilities/transactions/transaction_test.cc"],
|
||||
source_headers=["utilities/transactions/transaction_test.h"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
@@ -244,6 +244,11 @@ from an implementation detail instead of an explicit option.
|
||||
|
||||
## Important tips
|
||||
|
||||
### Pull request and diff summaries
|
||||
* Before writing or updating an external RocksDB pull request summary or its
|
||||
corresponding internal Phabricator diff summary, read and follow
|
||||
`claude_md/pull_request_summary_guideline.md`.
|
||||
|
||||
### Build system
|
||||
* There are 3 build system. Make for git clones, BUCK (meta internal) for hg
|
||||
clones, and CMake for some special cases.
|
||||
@@ -310,6 +315,7 @@ build (and where noted, run tests) across:
|
||||
| Axis | Must support |
|
||||
|------|--------------|
|
||||
| OS | Linux (x86_64 + ARM), macOS, Windows |
|
||||
| Architecture | Little-endian and big-endian targets, including Linux on s390x |
|
||||
| Compiler | GCC, Clang (libstdc++ **and** libc++), AppleClang, **MSVC (VS2022)**, MinGW (Linux cross-compile, build-only, no gflags) |
|
||||
| Build system | Make, CMake, and BUCK (internal) -- keep all in sync (see "Build system" above) |
|
||||
| Config | release (`-fno-rtti`), `ASSERT_STATUS_CHECKED`, ASAN/UBSAN/TSAN, folly, unity build, JNI/Java |
|
||||
@@ -324,6 +330,12 @@ abstractions; otherwise guard with `#ifdef OS_WIN` (POSIX `<unistd.h>` ->
|
||||
Windows `<process.h>`). Because libc++ is also tested, include what you use
|
||||
rather than relying on libstdc++ transitive includes.
|
||||
|
||||
Do not use `static_assert` to reject a supported architecture for an optional
|
||||
feature. Check an existing `port::` capability such as `port::kLittleEndian`
|
||||
and disable only the unsupported feature so the rest of RocksDB still builds.
|
||||
Use a portable representation when the feature itself must work across
|
||||
architectures.
|
||||
|
||||
### Unit Test
|
||||
* After all of the unit tests are added, review them and try to extract common
|
||||
reusable utility functions to reduce code duplication due to copy past between
|
||||
|
||||
@@ -972,6 +972,7 @@ set(SOURCES
|
||||
table/plain/plain_table_index.cc
|
||||
table/plain/plain_table_key_coding.cc
|
||||
table/plain/plain_table_reader.cc
|
||||
table/range_del_block.cc
|
||||
table/sst_file_dumper.cc
|
||||
table/sst_file_reader.cc
|
||||
table/sst_file_writer.cc
|
||||
@@ -1688,6 +1689,7 @@ if(WITH_TESTS)
|
||||
db/db_with_timestamp_test_util.cc
|
||||
monitoring/thread_status_updater_debug.cc
|
||||
table/mock_table.cc
|
||||
test_util/simple_external_table_factory.cc
|
||||
utilities/agg_merge/test_agg_merge.cc
|
||||
utilities/cassandra/test_utils.cc
|
||||
)
|
||||
|
||||
+66
@@ -1,6 +1,72 @@
|
||||
# Rocksdb Change Log
|
||||
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
|
||||
|
||||
## 11.12.0 (10/07/2026)
|
||||
### New Features
|
||||
* Added `FullExternalTableFactory` and related full-mode external table APIs that preserve RocksDB internal keys, enabling external tables to support nonzero sequence numbers, point and single deletions, merge operands, blob indexes, wide-column entities, snapshots, live DB writes, and multi-level compactions.
|
||||
|
||||
### Public API Changes
|
||||
* Added experimental coroutine capability interfaces for basic and full `ExternalTableReader` implementations. Coroutine-capable external readers can initiate file reads through `FSRandomAccessFile::SubmitReadAsync()`.
|
||||
* `ExternalTableFactory::NewTableReader()` now receives an already-opened `FSRandomAccessFile` and its file size instead of requiring implementations to reopen `file_path`. The supplied file preserves RocksDB's direct-I/O alignment handling, rate limiting, read statistics and listener notifications, prefetching, and synchronous, asynchronous, and batched reads.
|
||||
* Added `PutRangeDeletionBlock()`, `GetRangeDeletionBlock()`, and `IsDeleteRangeSupported()` to the external table interfaces so formats can opt in to range deletions using RocksDB's raw range-deletion metadata block.
|
||||
* Opaque `PreparedFileInfo` metadata can now be serialized to a checksummed representation with `SerializePreparedFileInfo()` and restored with `DeserializePreparedFileInfo()` for reuse across processes.
|
||||
* Removed the deprecated `IngestExternalFileOptions::write_global_seqno` option and its C, Java, and `ldb` APIs, `TableProperties::external_sst_file_global_seqno_offset`, and the `file_offset` output from `ExternalTableReader::GetPropertiesBlock()`. Existing external SST files whose global sequence number was rewritten without updating the properties-block checksum remain readable.
|
||||
* `TablePropertiesNames` has no apparent use in the public API so has been deprecated there.
|
||||
* `TableProperties::creation_time` is renamed to `oldest_ancestor_time`, which better describes its meaning, along with `TablePropertiesNames::kCreationTime` to `kOldestAncestorTime`. The misspelled `SstFileMetaData::oldest_ancester_time` is renamed to `oldest_ancestor_time`. The old names remain available as deprecated aliases and will be removed in a future release.
|
||||
* Added `WriteBufferFlushPolicy` and `WriteBufferManager` policy selection for flushing the oldest or largest column family, including configurable batched flushing of the largest column families across DBs sharing a manager, with C and Java bindings.
|
||||
|
||||
### Behavior Changes
|
||||
* When `ExternalTableBuilderBase::PutPropertiesBlock()` returns `NotSupported`, RocksDB now overrides `num_entries`, `raw_key_size`, and `raw_value_size` from `ExternalTableBuilderBase::GetTableProperties()` with the logical totals tracked by the external-table adapter. Format-specific properties such as `data_size` remain unchanged.
|
||||
* The `FSWritableFile` passed to `ExternalTableFactory::NewTableBuilder()` now reports logical file size including RocksDB-buffered data and exposes the underlying file's direct-I/O mode and required buffer alignment.
|
||||
* `Iterator::Refresh()` now returns `NotSupported` on iterators from a secondary DB's `DB::NewIterators()` only, because those iterators are pinned to one cross-column-family read view. Iterators from `DB::NewIterator()` are unaffected; to advance a batch, call `DB::NewIterators()` again after `TryCatchUpWithPrimary()`.
|
||||
* `SstFileReader` now reports standard SST read statistics and file I/O listener notifications, and honors the configured read rate limiter.
|
||||
|
||||
### Bug Fixes
|
||||
* Fixed full recovery from retryable background errors to atomically advance WAL replay boundaries for all column families, preventing partial recovery of multi-column-family write batches after a failed recovery flush.
|
||||
* Fixed RocksDB builds on big-endian architectures such as s390x by disabling the optional persistent fault-injection log, whose binary format is little-endian-only.
|
||||
* Fixed `DB::ClipColumnFamily()` deleting a non-L0 SST file whose largest key equals `begin_key`, which lost keys at the start of the kept range.
|
||||
* Fixed swapped `oldest_key_time` and `newest_key_time` values in the `table_file_creation` event log entry.
|
||||
* Fixed `ExternalTableReader::VerifyChecksum()` results not being honored by the external table adapter.
|
||||
* Fixed a bug in manifest handling that could lead to a hang or memory corruption in the case of LSM sanity checks being fatally violated (exceptional; see `force_consistency_checks`).
|
||||
* Fixed `MultiGet()` with `ReadOptions::read_tier == kBlockCacheTier` reading data blocks from disk when `allow_mmap_reads` is false. Keys whose data block is not in the block cache now return `Status::Incomplete()`, the same as `Get()`.
|
||||
* Fixed trace replay (`DB::NewDefaultReplayer()`, db_bench `replay`) failing every read record (Get, MultiGet, iterator seek) on the default column family with `Corruption: Invalid Column Family ID` when the default column family handle was not passed, which is always the case in db_bench with `--num_column_families=1`.
|
||||
* Fixed multi-threaded `Replayer::Replay()` ignoring errors from executing trace records and returning OK. It now stops and returns the error, including `Status::NotSupported()` from executing a record, as single-threaded replay does.
|
||||
* Fixed secondary DB `NewIterators()` returning iterators from different database states or blocking for `TryCatchUpWithPrimary()` by serving the last completed catch-up view.
|
||||
|
||||
### Performance Improvements
|
||||
* Single Get coroutine reads using non-mmap, non-direct I/O on a `kFSBuffer`-capable `FileSystem` now use `FSRandomAccessFile::SubmitReadAsync()` instead of falling back to synchronous `Read()`. When rate-limited, these reads acquire their full token budget before submission to preserve a single filesystem-owned buffer, which can make physical I/O burstier than reads using caller-provided buffers.
|
||||
|
||||
## 11.11.0 (09/21/2026)
|
||||
### New Features
|
||||
* External tables now support DB-assigned global sequence numbers when `IngestExternalFileOptions::allow_global_seqno` is enabled, allowing overlapping external tables to be ingested across multiple levels when compactions are disabled. This includes the deprecated `write_global_seqno` path when the reader supplies the properties block's file offset.
|
||||
* Added checksum handoff support for SST file writes when `BlockBasedTableOptions::checksum` is `kCRC32c`, allowing block and trailer CRC32C checksums to be passed from the table builder to the file writer for buffered-data validation and filesystem in-flight write protection.
|
||||
* Enhanced `sst_dump --command=recompress` to be more powerful for benchmarking, including through automation with a new `--json` option for machine-readable output.
|
||||
|
||||
### Public API Changes
|
||||
* Changed the experimental async read statistics API by removing `DB::AsyncCallback::EnableStats()` and changing `OnComplete(const PerfContext*, const IOStatsContext*)` to `OnComplete()`. Callers must configure TLS statistics before each async or coroutine read. `DB::GetAsync()` and `DB::MultiGetAsync()` callbacks must copy per-operation statistics from TLS during `OnComplete()`; coroutine users must copy them from inside a `CoroStackableDB` hook before suspending again. Each operation leaves TLS statistics collection disabled.
|
||||
* Added `Compressor::MaybeOverrideCompressionOptions()` (EXPERIMENTAL), an omnibus hook letting a custom compressor adjust the subset of `CompressionOptions` that the SST builder applies rather than the compressor itself: `parallel_threads`, `max_compressed_bytes_per_kb`, `auto_skip`, and `auto_skip_min_sample_every` (and potentially more in the future). This deprecates `Compressor::GetRecommendedParallelThreads()`, which it subsumes; the default implementation still honors an overridden `GetRecommendedParallelThreads()` for compatibility.
|
||||
* Added experimental `CoroDB::CoGet()` and `CoroDB::CoMultiGet()` overloads for point reads from an `SstFileReader`.
|
||||
* Added experimental `DB::GetWithMetadata()` and `DB::MultiGetWithMetadata()` APIs with opt-in output metadata for key timestamps and explicit-snapshot newer-version detection.
|
||||
* Added a `DB::NewIterators()` overload that accepts parallel vectors of `ReadOptions` and column family handles, allowing each returned iterator to use independent read settings while sharing a consistent database state. The vectors must have the same size, all snapshots must be null or identical, and all `tailing` settings must match.
|
||||
* Added `ReadOptions::custom_context`, an opaque, non-owning pointer passed through table reads so external table implementations can consume application-defined read options.
|
||||
|
||||
### Behavior Changes
|
||||
* `ExternalTableReader::MultiGet()` is now used by RocksDB's `DB::MultiGet()` path. Previously, the external table adapter used `TableReader`'s fallback, which called `ExternalTableReader::Get()` separately for each key.
|
||||
* `DB::OpenAndCompact()` now requires `output_directory` to be a simple name for a new subdirectory when the new `use_session_tmp_dir_for_remote_compaction` DB option is enabled. The option places that subdirectory under the canonical `<dbname>/session_tmp/` root, and `DB::Open()` deletes stale contents under the root after recovery. The disabled default preserves the existing `<dbname>/<output_directory>` layout without automatic cleanup. Previously, remote-compaction output directories were created directly under `<dbname>`, which made it challenging to safely identify and delete obsolete files.
|
||||
* Several block-based SST table properties now better support compression benchmarking and optimization: `uncompressed_data_size` is now persisted in the file (previously it was computed only at write time and read back as 0); a new `lsm_info_at_creation` property records the LSM level and "bottommost" status at the file's creation, distinguishing "not applicable" (e.g. SstFileWriter files) from "unknown" (older files); and the `compression_options` property now records the full compression manager configuration instead of only its name.
|
||||
|
||||
### Bug Fixes
|
||||
* Fixed a usually-minor performance bug where `Cache::ApplyToAllEntries()` used single-entry batches instead of the intended 256-entry default for `ApplyToAllEntriesOptions::average_entries_per_lock`. All of the Cache implementations (LRU and HCC) benefit from the fix, but the disfavored LRU cache was especially impacted by repeated mutex acquisition and release.
|
||||
* Fixed stats-disabled coroutine and asynchronous reads inheriting enabled TLS statistics state when interleaved with stats-enabled reads on the same executor. Each read now uses its captured per-call configuration, while enabled request counters remain isolated across suspensions.
|
||||
* Fixed a bug where a remote compaction (`CompactionService` / `DB::OpenAndCompact`) requested with `OpenAndCompactOptions::allow_resumption=true` but for which resumption was internally disabled (incompatible with `paranoid_file_checks` or output-iteration verification) could fail a retried attempt, because the output directory was not cleaned before starting fresh and output files left by a previously interrupted attempt collided with the reused file numbers.
|
||||
* Fixed a bug where a remote compaction (`CompactionService` / `DB::OpenAndCompact`) could fail when the primary rotated its MANIFEST while the worker was opening the primary's directory read-only. The open reads `CURRENT`, and the primary publishes a new MANIFEST by renaming a freshly written `CURRENT` over the old one; on file systems without POSIX read-after-unlink semantics (remote/object stores) the replaced object is then reported as gone (`PathNotFound`/`NotFound`, or `TryAgain` for the MANIFEST equivalent) instead of remaining readable. `DB::OpenAndCompact()` now retries the secondary open a bounded, configurable number of times for those transient statuses, so a concurrent rotation no longer fails the compaction (which, for a `CompactionService` that does not fall back to a local compaction, became a background error on the primary).
|
||||
* Fixed secondary DB `NewIterators()` returning iterators from different database states when `TryCatchUpWithPrimary()` runs concurrently.
|
||||
* Fixed a bug where a `SeekToFirst()` on an iterator previously used for a `Seek()` could end the scan early and silently omit keys with `ReadOptions::async_io`, or otherwise run with readahead effectively disabled. Affects scans using `ReadOptions::prefix_same_as_start` together with `ReadOptions::auto_readahead_size` (enabled by default); memory-mapped reads are unaffected.
|
||||
|
||||
### Performance Improvements
|
||||
* `DB::MultiGetEntityLazy()` now reads all keys of a batch under a single shared per-column-family SuperVersion pin, and a single `LazyWideColumnsBatch::MultiResolve()` coalesces the resulting blob reads across keys: one read per blob file (and one per SST for embedded blobs), for whole-column and byte-range reads alike, instead of reading each key's blobs separately.
|
||||
|
||||
|
||||
## 11.10.0 (08/28/2026)
|
||||
### New Features
|
||||
* Added `BlockBasedTableOptions::optimize_key_common_prefix` as part of new `format_version=8`. When enabled, each data block and index block stores the common user-key prefix of its keys once instead of at every restart point, reducing block size and block-cache footprint for prefix-heavy data and speeding up Seek for bytewise-ordered keys.
|
||||
|
||||
@@ -591,6 +591,7 @@ ifneq ($(filter check-headers, $(MAKECMDGOALS)),)
|
||||
DEV_HEADER_DIRS := $(sort include/ $(dir $(ALL_SOURCES)))
|
||||
FOLLY_DEPENDENT_HEADERS := \
|
||||
include/rocksdb/coro_db.h \
|
||||
include/rocksdb/coro_external_table.h \
|
||||
include/rocksdb/utilities/coro_stackable_db.h
|
||||
# Some headers like in port/ are platform-specific
|
||||
DEV_HEADERS_TO_CHECK := $(filter-out $(FOLLY_DEPENDENT_HEADERS), $(shell $(FIND) $(DEV_HEADER_DIRS) -type f -name '*.h' | grep -E -v 'port/|plugin/|range_tree/|secondary_index/'))
|
||||
|
||||
@@ -35,6 +35,49 @@ from util import ColorString
|
||||
# tests to export as libraries for inclusion in other projects
|
||||
_EXPORTED_TEST_LIBS = ["env_basic_test"]
|
||||
|
||||
_TEST_LIB_HEADERS = [
|
||||
"db/db_test_util.h",
|
||||
"db/db_with_timestamp_test_util.h",
|
||||
"table/mock_table.h",
|
||||
"test_util/mock_time_env.h",
|
||||
"test_util/secondary_cache_test_util.h",
|
||||
"test_util/simple_external_table_factory.h",
|
||||
"test_util/testharness.h",
|
||||
"test_util/testutil.h",
|
||||
"tools/block_cache_analyzer/block_cache_trace_analyzer.h",
|
||||
"tools/trace_analyzer_tool.h",
|
||||
"utilities/agg_merge/test_agg_merge.h",
|
||||
"utilities/cassandra/test_utils.h",
|
||||
]
|
||||
|
||||
_TEST_HEADERS = {
|
||||
"configurable_test": ["options/configurable_test.h"],
|
||||
"persistent_cache_test": ["utilities/persistent_cache/persistent_cache_test.h"],
|
||||
"point_lock_manager_test": [
|
||||
"utilities/transactions/lock/point/point_lock_manager_test.h",
|
||||
],
|
||||
"transaction_test": ["utilities/transactions/transaction_test.h"],
|
||||
}
|
||||
|
||||
_STRESS_LIB_HEADERS = [
|
||||
"db_stress_tool/db_stress_common.h",
|
||||
"db_stress_tool/db_stress_compaction_service.h",
|
||||
"db_stress_tool/db_stress_compression_manager.h",
|
||||
"db_stress_tool/db_stress_driver.h",
|
||||
"db_stress_tool/db_stress_filters.h",
|
||||
"db_stress_tool/db_stress_listener.h",
|
||||
"db_stress_tool/db_stress_shared_state.h",
|
||||
"db_stress_tool/db_stress_test_base.h",
|
||||
"db_stress_tool/db_stress_wide_merge_operator.h",
|
||||
"db_stress_tool/expected_state.h",
|
||||
"db_stress_tool/expected_value.h",
|
||||
"db_stress_tool/multi_ops_txns_stress.h",
|
||||
"test_util/simple_external_table_factory.h",
|
||||
"test_util/testutil.h",
|
||||
"tools/block_cache_analyzer/block_cache_trace_analyzer.h",
|
||||
"tools/trace_analyzer_tool.h"
|
||||
]
|
||||
|
||||
# Parse src.mk files as a Dictionary of
|
||||
# VAR_NAME => list of files
|
||||
def parse_src_mk(repo_path):
|
||||
@@ -183,27 +226,34 @@ def generate_buck(repo_path, deps_map):
|
||||
":rocksdb_lib",
|
||||
],
|
||||
)
|
||||
# rocksdb_test_lib
|
||||
BUCK.add_library(
|
||||
"rocksdb_test_lib",
|
||||
test_lib_sources = (
|
||||
src_mk.get("MOCK_LIB_SOURCES", [])
|
||||
+ src_mk.get("TEST_LIB_SOURCES", [])
|
||||
+ src_mk.get("EXP_LIB_SOURCES", [])
|
||||
+ src_mk.get("ANALYZER_LIB_SOURCES", []),
|
||||
+ src_mk.get("ANALYZER_LIB_SOURCES", [])
|
||||
)
|
||||
# Older release source lists may not compile every test helper yet.
|
||||
test_lib_headers = [
|
||||
header for header in _TEST_LIB_HEADERS
|
||||
if header[:-2] + ".cc" in test_lib_sources
|
||||
]
|
||||
# rocksdb_test_lib
|
||||
BUCK.add_library(
|
||||
"rocksdb_test_lib",
|
||||
test_lib_sources,
|
||||
[":rocksdb_lib"],
|
||||
extra_test_libs=True,
|
||||
headers=test_lib_headers,
|
||||
)
|
||||
# rocksdb_with_faiss_test_lib
|
||||
BUCK.add_library(
|
||||
"rocksdb_with_faiss_test_lib",
|
||||
src_mk.get("MOCK_LIB_SOURCES", [])
|
||||
+ src_mk.get("TEST_LIB_SOURCES", [])
|
||||
+ src_mk.get("EXP_LIB_SOURCES", [])
|
||||
+ src_mk.get("ANALYZER_LIB_SOURCES", []),
|
||||
test_lib_sources,
|
||||
deps=[
|
||||
":rocksdb_with_faiss_lib",
|
||||
],
|
||||
extra_test_libs=True,
|
||||
headers=test_lib_headers,
|
||||
)
|
||||
# rocksdb_tools_lib
|
||||
BUCK.add_library(
|
||||
@@ -212,6 +262,12 @@ def generate_buck(repo_path, deps_map):
|
||||
+ src_mk.get("ANALYZER_LIB_SOURCES", [])
|
||||
+ ["test_util/testutil.cc"],
|
||||
[":rocksdb_lib"],
|
||||
headers=[
|
||||
"test_util/testutil.h",
|
||||
"tools/block_cache_analyzer/block_cache_trace_analyzer.h",
|
||||
"tools/simulated_hybrid_file_system.h",
|
||||
"tools/trace_analyzer_tool.h",
|
||||
],
|
||||
)
|
||||
# rocksdb_cache_bench_tools_lib
|
||||
BUCK.add_library(
|
||||
@@ -225,12 +281,19 @@ def generate_buck(repo_path, deps_map):
|
||||
src_mk.get("POINT_LOCK_BENCH_LIB_SOURCES", []),
|
||||
[":rocksdb_lib"],
|
||||
)
|
||||
stress_sources = (
|
||||
src_mk.get("ANALYZER_LIB_SOURCES", [])
|
||||
+ src_mk.get("STRESS_LIB_SOURCES", [])
|
||||
+ ["test_util/testutil.cc"]
|
||||
)
|
||||
# rocksdb_stress_lib
|
||||
BUCK.add_rocksdb_library(
|
||||
"rocksdb_stress_lib",
|
||||
src_mk.get("ANALYZER_LIB_SOURCES", [])
|
||||
+ src_mk.get("STRESS_LIB_SOURCES", [])
|
||||
+ ["test_util/testutil.cc"],
|
||||
stress_sources,
|
||||
headers=[
|
||||
header for header in _STRESS_LIB_HEADERS
|
||||
if header[:-2] + ".cc" in stress_sources
|
||||
],
|
||||
)
|
||||
# ldb binary
|
||||
BUCK.add_binary(
|
||||
@@ -353,6 +416,7 @@ def generate_buck(repo_path, deps_map):
|
||||
test_src,
|
||||
deps=json.dumps(deps["extra_deps"] + [":" + test_library]),
|
||||
extra_compiler_flags=json.dumps(deps["extra_compiler_flags"]),
|
||||
source_headers=_TEST_HEADERS.get(test, []),
|
||||
)
|
||||
else:
|
||||
if with_faiss:
|
||||
@@ -361,6 +425,7 @@ def generate_buck(repo_path, deps_map):
|
||||
test_src,
|
||||
deps=json.dumps(deps["extra_deps"] + [":rocksdb_with_faiss_test_lib"]),
|
||||
extra_compiler_flags=json.dumps(deps["extra_compiler_flags"]),
|
||||
source_headers=_TEST_HEADERS.get(test, []),
|
||||
)
|
||||
else:
|
||||
BUCK.register_test(
|
||||
@@ -368,6 +433,7 @@ def generate_buck(repo_path, deps_map):
|
||||
test_src,
|
||||
deps=json.dumps(deps["extra_deps"] + [":rocksdb_test_lib"]),
|
||||
extra_compiler_flags=json.dumps(deps["extra_compiler_flags"]),
|
||||
source_headers=_TEST_HEADERS.get(test, []),
|
||||
)
|
||||
BUCK.export_file("tools/db_crashtest.py")
|
||||
BUCK.export_file("tools/fault_injection_log_parser.py")
|
||||
|
||||
@@ -162,12 +162,17 @@ add_c_test_wrapper()
|
||||
).encode("utf-8")
|
||||
)
|
||||
|
||||
def register_test(self, test_name, src, deps, extra_compiler_flags):
|
||||
def register_test(self, test_name, src, deps, extra_compiler_flags, source_headers):
|
||||
with open(self.path, "ab") as targets_file:
|
||||
targets_file.write(
|
||||
targets_cfg.unittests_template.format(
|
||||
test_name=test_name,
|
||||
test_cc=str(src),
|
||||
source_headers=(
|
||||
f" source_headers=[{pretty_list(source_headers, 16)}],\n"
|
||||
if source_headers
|
||||
else ""
|
||||
),
|
||||
deps=deps,
|
||||
extra_compiler_flags=extra_compiler_flags,
|
||||
).encode("utf-8")
|
||||
|
||||
@@ -30,7 +30,7 @@ cpp_binary_wrapper(name="{name}", srcs=[{srcs}], deps=[{deps}], extra_preprocess
|
||||
unittests_template = """
|
||||
cpp_unittest_wrapper(name="{test_name}",
|
||||
srcs=["{test_cc}"],
|
||||
deps={deps},
|
||||
{source_headers} deps={deps},
|
||||
extra_compiler_flags={extra_compiler_flags})
|
||||
|
||||
"""
|
||||
|
||||
@@ -142,6 +142,11 @@ uint64_t rocksdb_table_properties_column_family_id(
|
||||
return props->rep.column_family_id;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_table_properties_oldest_ancestor_time(
|
||||
const rocksdb_table_properties_t* props) {
|
||||
return props->rep.oldest_ancestor_time;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_table_properties_creation_time(
|
||||
const rocksdb_table_properties_t* props) {
|
||||
return props->rep.creation_time;
|
||||
@@ -172,11 +177,6 @@ uint64_t rocksdb_table_properties_fast_compression_estimated_data_size(
|
||||
return props->rep.fast_compression_estimated_data_size;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_table_properties_external_sst_file_global_seqno_offset(
|
||||
const rocksdb_table_properties_t* props) {
|
||||
return props->rep.external_sst_file_global_seqno_offset;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_table_properties_tail_start_offset(
|
||||
const rocksdb_table_properties_t* props) {
|
||||
return props->rep.tail_start_offset;
|
||||
|
||||
@@ -102,6 +102,10 @@ rocksdb_table_properties_fixed_key_len(const rocksdb_table_properties_t* props);
|
||||
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_table_properties_column_family_id(
|
||||
const rocksdb_table_properties_t* props);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API uint64_t
|
||||
rocksdb_table_properties_oldest_ancestor_time(
|
||||
const rocksdb_table_properties_t* props);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API uint64_t
|
||||
rocksdb_table_properties_creation_time(const rocksdb_table_properties_t* props);
|
||||
|
||||
@@ -122,10 +126,6 @@ extern ROCKSDB_LIBRARY_API uint64_t
|
||||
rocksdb_table_properties_fast_compression_estimated_data_size(
|
||||
const rocksdb_table_properties_t* props);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API uint64_t
|
||||
rocksdb_table_properties_external_sst_file_global_seqno_offset(
|
||||
const rocksdb_table_properties_t* props);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_table_properties_tail_start_offset(
|
||||
const rocksdb_table_properties_t* props);
|
||||
|
||||
|
||||
@@ -2057,16 +2057,6 @@ unsigned char rocksdb_ingestexternalfileoptions_get_ingest_behind(
|
||||
return opt->rep.ingest_behind;
|
||||
}
|
||||
|
||||
void rocksdb_ingestexternalfileoptions_set_write_global_seqno(
|
||||
rocksdb_ingestexternalfileoptions_t* opt, unsigned char v) {
|
||||
opt->rep.write_global_seqno = v;
|
||||
}
|
||||
|
||||
unsigned char rocksdb_ingestexternalfileoptions_get_write_global_seqno(
|
||||
rocksdb_ingestexternalfileoptions_t* opt) {
|
||||
return opt->rep.write_global_seqno;
|
||||
}
|
||||
|
||||
void rocksdb_ingestexternalfileoptions_set_verify_checksums_before_ingest(
|
||||
rocksdb_ingestexternalfileoptions_t* opt, unsigned char v) {
|
||||
opt->rep.verify_checksums_before_ingest = v;
|
||||
|
||||
@@ -816,14 +816,6 @@ extern ROCKSDB_LIBRARY_API unsigned char
|
||||
rocksdb_ingestexternalfileoptions_get_ingest_behind(
|
||||
rocksdb_ingestexternalfileoptions_t* opt);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void
|
||||
rocksdb_ingestexternalfileoptions_set_write_global_seqno(
|
||||
rocksdb_ingestexternalfileoptions_t* opt, unsigned char v);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API unsigned char
|
||||
rocksdb_ingestexternalfileoptions_get_write_global_seqno(
|
||||
rocksdb_ingestexternalfileoptions_t* opt);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void
|
||||
rocksdb_ingestexternalfileoptions_set_verify_checksums_before_ingest(
|
||||
rocksdb_ingestexternalfileoptions_t* opt, unsigned char v);
|
||||
|
||||
@@ -556,12 +556,6 @@ static void rocksdb_roundtrip_test_rocksdb_ingestexternalfileoptions(void) {
|
||||
rocksdb_ingestexternalfileoptions_set_verify_file_checksum(obj, 0);
|
||||
CheckCondition(
|
||||
rocksdb_ingestexternalfileoptions_get_verify_file_checksum(obj) == 0);
|
||||
rocksdb_ingestexternalfileoptions_set_write_global_seqno(obj, 1);
|
||||
CheckCondition(
|
||||
rocksdb_ingestexternalfileoptions_get_write_global_seqno(obj) == 1);
|
||||
rocksdb_ingestexternalfileoptions_set_write_global_seqno(obj, 0);
|
||||
CheckCondition(
|
||||
rocksdb_ingestexternalfileoptions_get_write_global_seqno(obj) == 0);
|
||||
rocksdb_ingestexternalfileoptions_destroy(obj);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,98 @@ A behavior change is not automatically a defect. Report a finding when the
|
||||
intent, documentation, downstream-impact analysis, or mitigation has a concrete
|
||||
gap or ambiguity that could lead to an unintended compatibility regression.
|
||||
|
||||
## DB Mutex Review Perspective
|
||||
|
||||
For every non-trivial change, determine whether the changed code, or any
|
||||
transitive callee it adds or changes, can run while `DBImpl::mutex_` is held.
|
||||
State the result explicitly, including when the DB mutex is not affected.
|
||||
|
||||
`DBImpl::mutex_` is the DB-wide metadata and coordination mutex. It protects
|
||||
cross-object invariants involving `ColumnFamilyData`, memtable and
|
||||
`SuperVersion` publication, `VersionSet`, snapshots, flush/compaction queues,
|
||||
background-job state, and file lifetime. It is shared by all column families
|
||||
and can be a hot lock. It is not the filesystem `db_lock_`, and it should not
|
||||
replace data-path synchronization already provided by `WriteThread`,
|
||||
`wal_write_mutex_`, atomics, referenced `SuperVersion`s, or component-local
|
||||
synchronization.
|
||||
|
||||
When a DB-mutex critical section is affected:
|
||||
|
||||
### Locking and correctness
|
||||
|
||||
- Trace the full call path and mark the exact lock and unlock boundaries. Do
|
||||
not inspect only the lines that directly acquire the mutex.
|
||||
- State the locking contract. Helpers that require the lock should document it
|
||||
and normally use `mutex_.AssertHeld()`. Check whether each caller expects the
|
||||
lock to be held on return.
|
||||
- Treat calls that can release and re-acquire the mutex as concurrency
|
||||
boundaries. Examples include `VersionSet::LogAndApply()`, condition-variable
|
||||
waits, write-thread coordination, and notification helpers. Revalidate state
|
||||
inspected before such a call.
|
||||
- Keep atomic metadata transitions under the mutex. Examples include moving a
|
||||
memtable between mutable and immutable state, reserving compaction inputs,
|
||||
installing a `Version` or `SuperVersion`, updating scheduling counters and
|
||||
queues, and changing file-lifetime protection.
|
||||
- For expensive work, prefer a prepare/run/install structure: reserve and pin
|
||||
the required state while locked, do CPU or I/O work while unlocked, then
|
||||
re-lock, validate, and publish. Before unlocking, copy mutable options and
|
||||
acquire every required object reference or lifetime reservation.
|
||||
- Check object lifetime across every unlocked region. Do not use a raw
|
||||
`ColumnFamilyData*`, `Version*`, memtable, file metadata pointer, or DB-owned
|
||||
container element after unlocking unless an explicit reference,
|
||||
`SuperVersion`, job object, or pending-output reservation keeps it alive.
|
||||
- Preserve the snapshot/input-selection boundary. Flush and compaction must not
|
||||
miss a snapshot whose visible key versions they may remove. Missing a newly
|
||||
created snapshot is a correctness bug; retaining a recently released
|
||||
snapshot is conservative but safe.
|
||||
- Never invoke untrusted or re-entrant code while holding the mutex. The mutex
|
||||
is non-reentrant, so a callback into RocksDB can self-deadlock.
|
||||
- Check lock ordering. `options_mutex_` is acquired before the DB mutex. When
|
||||
both the DB mutex and `wal_write_mutex_` are needed, the DB mutex is acquired
|
||||
first. Establish and document the order for other locks.
|
||||
- Keep cleanup outside the critical section when possible. File deletion,
|
||||
`JobContext::Clean()`, expensive `SuperVersion` destruction, logging, and
|
||||
listener callbacks should normally run after protected state is detached or
|
||||
snapshotted.
|
||||
- Do not add the DB mutex to steady-state point reads or the normal write data
|
||||
phase without strong justification. Reads normally use a referenced
|
||||
`SuperVersion`; writes use `WriteThread` and component-specific
|
||||
synchronization.
|
||||
|
||||
### Critical-section performance
|
||||
|
||||
- Compare the CPU and I/O performed under the mutex before and after the
|
||||
change. Include call frequency, loop bounds, input-size scaling, hidden work
|
||||
in callees, and worst-case cost.
|
||||
- Account for filesystem or network I/O, waits, callbacks, logging and
|
||||
formatting, allocation, container growth, copies, sorting, scans, reference
|
||||
release and destructors, cache misses, and repeated metadata lookups.
|
||||
- Do not allow a significant regression in mutex-held work without clear
|
||||
justification, hold-time or contention measurements, and representative
|
||||
benchmarks.
|
||||
- Treat unavoidable work under the mutex as hot-path code and keep it highly
|
||||
optimized. Precompute, allocate, copy, and perform I/O outside the lock;
|
||||
remove repeated scans and lookups; cache or batch work where practical; and
|
||||
keep locked operations O(1) or tightly bounded when possible.
|
||||
- Treat new blocking I/O, waits, callbacks into user or plugin code, unbounded
|
||||
loops, or work that scales with DB size, files, column families, snapshots,
|
||||
or pending jobs as a blocker unless it is unavoidable and supported by strong
|
||||
measurements and explicit design justification.
|
||||
|
||||
### Validation and evidence
|
||||
|
||||
- Test unlock windows with sync points rather than sleeps. Cover relevant races
|
||||
with memtable switches, snapshot create/release, column-family drop,
|
||||
flush/compaction install, shutdown, and background errors. Run TSAN where
|
||||
appropriate.
|
||||
- Distinguish wait time from hold time. `PerfContext::db_mutex_lock_nanos` and
|
||||
`DB_MUTEX_WAIT_MICROS` primarily expose acquisition wait; use contention
|
||||
profiles or scoped instrumentation to find long critical sections.
|
||||
- Report affected critical sections, before/after cost, workload assumptions,
|
||||
optimization choices, and remaining risk. If exact measurement is not
|
||||
practical, provide a conservative static bound and do not claim no regression
|
||||
without evidence.
|
||||
|
||||
IMPORTANT — Where the review goes: Your FINAL RESPONSE TEXT is the review.
|
||||
The PR comment is generated verbatim from your final response, so the
|
||||
complete, polished review MUST appear there in full. Do NOT end your turn with
|
||||
@@ -307,6 +399,9 @@ in isolation may be a critical bug when you consider the full call chain.
|
||||
- **Callee side effects**: Does the change call functions that mutate shared
|
||||
state (counters, seqnos, metadata)? Are the mutations correct for the new
|
||||
calling context? (Use the callee-chain analysis from context.md)
|
||||
- Apply the DB Mutex Review Perspective above to locking contracts, unlocked
|
||||
object lifetimes, stale-state validation, lock ordering, and snapshot
|
||||
boundaries.
|
||||
|
||||
#### Agent: Cross-Component & Adversarial Reviewer
|
||||
|
||||
@@ -410,6 +505,9 @@ Write findings to `findings-caller-audit.md`.
|
||||
- Loop optimization opportunities
|
||||
- Branch prediction (LIKELY/UNLIKELY)
|
||||
- Zero-overhead design verification
|
||||
- Apply the DB Mutex Review Perspective above, with special focus on transitive
|
||||
CPU and I/O, before/after cost, critical-section hold time, and benchmark
|
||||
evidence
|
||||
|
||||
#### Agent: API & Compatibility Reviewer
|
||||
- Public API backwards compatibility
|
||||
@@ -593,6 +691,8 @@ Rules for this structure:
|
||||
- [ ] Multi-component interactions (compaction, recovery, snapshots, iterators)
|
||||
- [ ] Configuration dependencies and unexpected option combinations
|
||||
- [ ] Potential pitfalls from caller-chain analysis
|
||||
- [ ] DB-mutex reachability checked through transitive callees; affected lock
|
||||
and unlock boundaries documented
|
||||
|
||||
### Review Phase (verified by agents)
|
||||
- [ ] Database semantics preserved (snapshot isolation, key ordering)
|
||||
@@ -606,6 +706,10 @@ Rules for this structure:
|
||||
- [ ] Code follows RocksDB style conventions
|
||||
- [ ] Behavioral contracts with upstream callers preserved
|
||||
- [ ] All callers enumerated with parameter range table
|
||||
- [ ] Before/after CPU and I/O under the DB mutex compared, with call frequency,
|
||||
scaling, hidden callee work, and worst-case cost included
|
||||
- [ ] No significant DB-mutex-held work regression; unavoidable work is highly
|
||||
optimized, bounded, and supported by measurements when material
|
||||
|
||||
## Output Structure
|
||||
Your **final response text is the primary output** — it becomes the PR comment
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
# RocksDB Diff and Pull Request Summary Guidance
|
||||
|
||||
This guidance applies to both internal Phabricator diff summaries and external
|
||||
GitHub pull request summaries for changes under `fbcode/internal_repo_rocksdb/`.
|
||||
|
||||
## Goal
|
||||
|
||||
Write each diff or pull request summary for a reviewer who has not read the
|
||||
patch. The summary must explain the behavioral story and the evidence for it,
|
||||
not merely inventory the edited files or symbols.
|
||||
|
||||
Every new or updated diff or pull request summary must clearly cover:
|
||||
|
||||
1. Why the change is required.
|
||||
2. What the previous behavior was, including the failure mode or limitation.
|
||||
3. What the new behavior is.
|
||||
4. What changed in the implementation.
|
||||
5. Safety boundaries, compatibility, and unsupported or intentionally deferred
|
||||
cases.
|
||||
6. Tests performed and known limitations of the validation.
|
||||
|
||||
Use explicit headings for a broad, subtle, or high-risk change. A small,
|
||||
focused change may combine these points into a few paragraphs, but none may be
|
||||
left implicit when it affects a review decision.
|
||||
|
||||
## What Good RocksDB Summaries Make Concrete
|
||||
|
||||
- Name the exact conditions that activate a bug or behavior: relevant options,
|
||||
call ordering, storage mode, concurrency mode, file format, platform, or
|
||||
workload shape. Also state important cases that are not affected.
|
||||
- Explain the causal chain from the old behavior to its externally observable
|
||||
result. For correctness fixes, distinguish a symptom from the underlying
|
||||
invariant violation.
|
||||
- Describe the new contract before implementation mechanics. Then identify the
|
||||
essential implementation choices and why they are sufficient.
|
||||
- Call out API, ABI, serialized-format, behavioral, and performance
|
||||
compatibility where relevant. For additive persisted fields, state how older
|
||||
data is interpreted and whether older readers remain supported.
|
||||
- State safety boundaries precisely: defaults, feature gates, fallback paths,
|
||||
ownership and lifetime constraints, memory-ordering assumptions, and cases
|
||||
deliberately postponed to a follow-up.
|
||||
- Support performance claims with a baseline, workload and configuration,
|
||||
measurement method, and results. Report meaningful tradeoffs, not only the
|
||||
winning metric.
|
||||
- Make the test plan evidence-based. Name the tests and configurations, say
|
||||
what behavior each proves, and report failures, timeouts, untested platforms,
|
||||
or stale pre-rebase results honestly. For a regression test, say how it fails
|
||||
without the fix when that was verified.
|
||||
- Separate incidental cleanup or a pre-existing test fix from the primary
|
||||
change, and explain why it belongs in the diff.
|
||||
|
||||
Avoid vague claims such as "improves performance," "fixes an issue," or "tests
|
||||
pass." Avoid copying the patch into prose, exhaustive file-by-file narration,
|
||||
raw command output, and claims broader than the evidence.
|
||||
|
||||
## Suggested Structure
|
||||
|
||||
Adapt this structure to the change; do not keep empty or irrelevant sections.
|
||||
|
||||
```text
|
||||
Summary:
|
||||
|
||||
Why the change is required:
|
||||
<User or system impact, and why the current behavior is inadequate.>
|
||||
|
||||
Previous behavior:
|
||||
<Old contract or mechanism, triggering conditions, and observable result.>
|
||||
|
||||
New behavior:
|
||||
<New contract and externally visible effect.>
|
||||
|
||||
Implementation:
|
||||
<Essential design and code changes; omit routine edit-by-edit narration.>
|
||||
|
||||
Safety, compatibility, and limitations:
|
||||
<Defaults, unaffected paths, compatibility, unsupported cases, and follow-ups.>
|
||||
|
||||
Test Plan:
|
||||
<Exact tests/benchmarks, configurations, what they demonstrate, and gaps.>
|
||||
```
|
||||
|
||||
For a focused bug fix, a cohesive narrative can be clearer than six headings:
|
||||
problem and root cause, trigger and unaffected cases, fix, then quantified or
|
||||
counterfactual validation. For a multi-part change, labeled bullets such as
|
||||
`Fix`, `Add`, and `Change` can make the implementation and review surface easy
|
||||
to scan.
|
||||
|
||||
## Reference Pull Request Summaries
|
||||
|
||||
- [PR #15216](https://github.com/facebook/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
|
||||
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)
|
||||
is a strong recent multi-part example. Its `Fix`/`Add`/`Change` bullets
|
||||
separate the behaviors, its compatibility sentence explains how old files
|
||||
read back, and its test plan maps each new persisted property to concrete
|
||||
coverage while explaining an adjacent test adjustment.
|
||||
|
||||
Use these pull requests as models for reasoning, specificity, and evidence. Do
|
||||
not copy their length or headings mechanically; scale the summary to the risk
|
||||
and complexity of the change.
|
||||
@@ -25,6 +25,7 @@ CRASHTEST_PY=$(PYTHON) -u tools/db_crashtest.py --stress_cmd=$(DB_STRESS_CMD) --
|
||||
crash_test_with_multiops_wup_txn \
|
||||
crash_test_with_optimistic_txn \
|
||||
crash_test_with_tiered_storage \
|
||||
crash_test_with_full_external_table \
|
||||
liveness_crash_test \
|
||||
blackbox_crash_test blackbox_crash_test_with_atomic_flush \
|
||||
blackbox_crash_test_with_wc_txn blackbox_crash_test_with_wp_txn \
|
||||
@@ -36,6 +37,7 @@ CRASHTEST_PY=$(PYTHON) -u tools/db_crashtest.py --stress_cmd=$(DB_STRESS_CMD) --
|
||||
blackbox_crash_test_with_multiops_wup_txn \
|
||||
blackbox_crash_test_with_optimistic_txn \
|
||||
blackbox_crash_test_with_tiered_storage \
|
||||
blackbox_crash_test_with_full_external_table \
|
||||
whitebox_crash_test whitebox_crash_test_with_atomic_flush \
|
||||
whitebox_crash_test_with_wc_txn whitebox_crash_test_with_wp_txn \
|
||||
whitebox_crash_test_with_wup_txn \
|
||||
@@ -86,6 +88,8 @@ crash_test_with_tiered_storage: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_MAKE) whitebox_crash_test_with_tiered_storage
|
||||
$(CRASHTEST_MAKE) blackbox_crash_test_with_tiered_storage
|
||||
|
||||
crash_test_with_full_external_table: blackbox_crash_test_with_full_external_table
|
||||
|
||||
crash_test_with_multiops_wc_txn: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_MAKE) blackbox_crash_test_with_multiops_wc_txn
|
||||
|
||||
@@ -132,6 +136,9 @@ blackbox_crash_test_with_multiops_wup_txn: $(DB_STRESS_CMD)
|
||||
blackbox_crash_test_with_tiered_storage: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --test_tiered_storage blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
blackbox_crash_test_with_full_external_table: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --test_full_external_table blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
blackbox_crash_test_with_optimistic_txn: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --optimistic_txn blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
|
||||
+87
-23
@@ -38,12 +38,14 @@ BlobFileCache::BlobFileCache(Cache* cache,
|
||||
}
|
||||
|
||||
Status BlobFileCache::GetBlobFileReader(
|
||||
const ReadOptions& read_options, uint64_t blob_file_number,
|
||||
const ReadOptions& read_options, const BlobFileOpenInfo& blob_file,
|
||||
CacheHandleGuard<BlobFileReader>* blob_file_reader,
|
||||
bool allow_footer_skip_retry) {
|
||||
assert(blob_file_reader);
|
||||
assert(blob_file_reader->IsEmpty());
|
||||
|
||||
const uint64_t blob_file_number = blob_file.file_number;
|
||||
|
||||
// NOTE: sharing same Cache with table_cache
|
||||
const Slice key = GetSliceForKey(&blob_file_number);
|
||||
|
||||
@@ -74,18 +76,8 @@ Status BlobFileCache::GetBlobFileReader(
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
{
|
||||
assert(file_options_);
|
||||
Status s = BlobFileReader::Create(
|
||||
*immutable_options_, read_options, *file_options_, column_family_id_,
|
||||
blob_file_read_hist_, blob_file_number, io_tracer_,
|
||||
/*skip_footer_validation=*/false, &reader);
|
||||
if (!s.ok() && s.IsCorruption() && allow_footer_skip_retry) {
|
||||
reader.reset();
|
||||
s = BlobFileReader::Create(
|
||||
*immutable_options_, read_options, *file_options_, column_family_id_,
|
||||
blob_file_read_hist_, blob_file_number, io_tracer_,
|
||||
/*skip_footer_validation=*/true, &reader);
|
||||
}
|
||||
Status s = OpenBlobFileReader(read_options, blob_file,
|
||||
allow_footer_skip_retry, &reader);
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_options_->logger,
|
||||
"BlobFileCache open failed for blob file %" PRIu64
|
||||
@@ -114,7 +106,7 @@ Status BlobFileCache::GetBlobFileReader(
|
||||
}
|
||||
|
||||
Status BlobFileCache::OpenBlobFileReaderUncached(
|
||||
const ReadOptions& read_options, uint64_t blob_file_number,
|
||||
const ReadOptions& read_options, const BlobFileOpenInfo& blob_file,
|
||||
std::unique_ptr<BlobFileReader>* blob_file_reader,
|
||||
bool allow_footer_skip_retry) {
|
||||
assert(blob_file_reader);
|
||||
@@ -123,21 +115,77 @@ Status BlobFileCache::OpenBlobFileReaderUncached(
|
||||
Statistics* const statistics = immutable_options_->stats;
|
||||
RecordTick(statistics, NO_FILE_OPENS);
|
||||
|
||||
Status s = OpenBlobFileReader(read_options, blob_file,
|
||||
allow_footer_skip_retry, blob_file_reader);
|
||||
if (!s.ok()) {
|
||||
RecordTick(statistics, NO_FILE_ERRORS);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void BlobFileCache::RegisterBlobFileChecksum(
|
||||
const BlobFileOpenInfo& blob_file) {
|
||||
assert(blob_file.file_checksum.empty() ==
|
||||
blob_file.file_checksum_func_name.empty());
|
||||
if (blob_file.file_checksum.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Slice key = GetSliceForKey(&blob_file.file_number);
|
||||
MutexLock cache_lock(&mutex_.Get(key));
|
||||
{
|
||||
MutexLock checksum_lock(&blob_file_checksums_mutex_);
|
||||
BlobFileChecksum& checksum = blob_file_checksums_[blob_file.file_number];
|
||||
checksum.value = blob_file.file_checksum.ToString();
|
||||
checksum.function_name = blob_file.file_checksum_func_name.ToString();
|
||||
}
|
||||
|
||||
// Readers opened while the file was active did not carry finalized metadata
|
||||
// and may also have observed a footer-less file size.
|
||||
cache_.get()->Erase(key);
|
||||
}
|
||||
|
||||
void BlobFileCache::UnregisterBlobFileChecksum(uint64_t blob_file_number) {
|
||||
MutexLock checksum_lock(&blob_file_checksums_mutex_);
|
||||
blob_file_checksums_.erase(blob_file_number);
|
||||
}
|
||||
|
||||
Status BlobFileCache::OpenBlobFileReader(
|
||||
const ReadOptions& read_options, const BlobFileOpenInfo& blob_file,
|
||||
bool allow_footer_skip_retry,
|
||||
std::unique_ptr<BlobFileReader>* blob_file_reader) {
|
||||
assert(file_options_);
|
||||
assert(blob_file.file_checksum.empty() ==
|
||||
blob_file.file_checksum_func_name.empty());
|
||||
|
||||
FileOptions file_options = *file_options_;
|
||||
if (!blob_file.file_checksum.empty()) {
|
||||
file_options.file_checksum = blob_file.file_checksum.ToString();
|
||||
file_options.file_checksum_func_name =
|
||||
blob_file.file_checksum_func_name.ToString();
|
||||
} else {
|
||||
MutexLock checksum_lock(&blob_file_checksums_mutex_);
|
||||
const auto it = blob_file_checksums_.find(blob_file.file_number);
|
||||
if (it != blob_file_checksums_.end()) {
|
||||
file_options.file_checksum = it->second.value;
|
||||
file_options.file_checksum_func_name = it->second.function_name;
|
||||
} else {
|
||||
file_options.file_checksum.clear();
|
||||
file_options.file_checksum_func_name.clear();
|
||||
}
|
||||
}
|
||||
|
||||
Status s = BlobFileReader::Create(
|
||||
*immutable_options_, read_options, *file_options_, column_family_id_,
|
||||
blob_file_read_hist_, blob_file_number, io_tracer_,
|
||||
*immutable_options_, read_options, file_options, column_family_id_,
|
||||
blob_file_read_hist_, blob_file.file_number, io_tracer_,
|
||||
/*skip_footer_validation=*/false, blob_file_reader);
|
||||
if (!s.ok() && s.IsCorruption() && allow_footer_skip_retry) {
|
||||
blob_file_reader->reset();
|
||||
s = BlobFileReader::Create(
|
||||
*immutable_options_, read_options, *file_options_, column_family_id_,
|
||||
blob_file_read_hist_, blob_file_number, io_tracer_,
|
||||
*immutable_options_, read_options, file_options, column_family_id_,
|
||||
blob_file_read_hist_, blob_file.file_number, io_tracer_,
|
||||
/*skip_footer_validation=*/true, blob_file_reader);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
RecordTick(statistics, NO_FILE_ERRORS);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -178,7 +226,7 @@ Status BlobFileCache::InsertBlobFileReader(
|
||||
}
|
||||
|
||||
Status BlobFileCache::RefreshBlobFileReader(
|
||||
uint64_t blob_file_number,
|
||||
const BlobFileOpenInfo& blob_file,
|
||||
std::unique_ptr<BlobFileReader>* blob_file_reader,
|
||||
CacheHandleGuard<BlobFileReader>* cached_blob_file_reader) {
|
||||
assert(blob_file_reader);
|
||||
@@ -186,10 +234,26 @@ Status BlobFileCache::RefreshBlobFileReader(
|
||||
assert(cached_blob_file_reader);
|
||||
assert(cached_blob_file_reader->IsEmpty());
|
||||
|
||||
const Slice key = GetSliceForKey(&blob_file_number);
|
||||
const Slice key = GetSliceForKey(&blob_file.file_number);
|
||||
MutexLock lock(&mutex_.Get(key));
|
||||
|
||||
TypedHandle* handle = cache_.Lookup(key);
|
||||
|
||||
if (blob_file.file_checksum.empty()) {
|
||||
MutexLock checksum_lock(&blob_file_checksums_mutex_);
|
||||
if (blob_file_checksums_.find(blob_file.file_number) !=
|
||||
blob_file_checksums_.end()) {
|
||||
// This reader can have been opened just before finalization registered
|
||||
// checksum metadata. Never let such a racing refresh repopulate the
|
||||
// cache.
|
||||
if (handle) {
|
||||
*cached_blob_file_reader = cache_.Guard(handle);
|
||||
}
|
||||
blob_file_reader->reset();
|
||||
return Status::OK();
|
||||
}
|
||||
}
|
||||
|
||||
if (handle) {
|
||||
BlobFileReader* const cached_reader = cache_.Value(handle);
|
||||
assert(cached_reader != nullptr);
|
||||
|
||||
@@ -6,9 +6,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <cinttypes>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "cache/typed_cache.h"
|
||||
#include "db/blob/blob_file_reader.h"
|
||||
#include "db/blob/blob_read_request.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
#include "util/mutexlock.h"
|
||||
|
||||
@@ -32,20 +35,30 @@ class BlobFileCache {
|
||||
BlobFileCache(const BlobFileCache&) = delete;
|
||||
BlobFileCache& operator=(const BlobFileCache&) = delete;
|
||||
|
||||
// Returns a cached reader for `blob_file_number`, opening and caching it on
|
||||
// miss. If `allow_footer_skip_retry` is true, a footer-validation corruption
|
||||
// retries once without requiring a footer.
|
||||
// Returns a cached reader for `blob_file`, opening and caching it on miss.
|
||||
// Checksum metadata is forwarded to the FileSystem through FileOptions when
|
||||
// the file is opened. If `allow_footer_skip_retry` is true, a
|
||||
// footer-validation corruption retries once without requiring a footer.
|
||||
Status GetBlobFileReader(const ReadOptions& read_options,
|
||||
uint64_t blob_file_number,
|
||||
const BlobFileOpenInfo& blob_file,
|
||||
CacheHandleGuard<BlobFileReader>* blob_file_reader,
|
||||
bool allow_footer_skip_retry = false);
|
||||
|
||||
// Opens a blob file reader without inserting it into the cache.
|
||||
Status OpenBlobFileReaderUncached(
|
||||
const ReadOptions& read_options, uint64_t blob_file_number,
|
||||
const ReadOptions& read_options, const BlobFileOpenInfo& blob_file,
|
||||
std::unique_ptr<BlobFileReader>* blob_file_reader,
|
||||
bool allow_footer_skip_retry = false);
|
||||
|
||||
// Makes finalized checksum metadata available to direct-write fallback reads
|
||||
// whose pinned Version predates the blob-file addition. Any cached
|
||||
// footer-less reader is evicted as part of the same per-file transition.
|
||||
void RegisterBlobFileChecksum(const BlobFileOpenInfo& blob_file);
|
||||
|
||||
// Stops retaining checksum metadata once no direct-write fallback can refer
|
||||
// to the file. Manifest-backed callers continue to provide it directly.
|
||||
void UnregisterBlobFileChecksum(uint64_t blob_file_number);
|
||||
|
||||
// Inserts a freshly opened uncached reader unless another thread already
|
||||
// cached the same blob file.
|
||||
Status InsertBlobFileReader(
|
||||
@@ -58,7 +71,7 @@ class BlobFileCache {
|
||||
// reader instead so a racing refresh cannot reintroduce an older active-file
|
||||
// view after the blob file grows.
|
||||
Status RefreshBlobFileReader(
|
||||
uint64_t blob_file_number,
|
||||
const BlobFileOpenInfo& blob_file,
|
||||
std::unique_ptr<BlobFileReader>* blob_file_reader,
|
||||
CacheHandleGuard<BlobFileReader>* cached_blob_file_reader);
|
||||
|
||||
@@ -72,6 +85,11 @@ class BlobFileCache {
|
||||
}
|
||||
|
||||
private:
|
||||
Status OpenBlobFileReader(const ReadOptions& read_options,
|
||||
const BlobFileOpenInfo& blob_file,
|
||||
bool allow_footer_skip_retry,
|
||||
std::unique_ptr<BlobFileReader>* blob_file_reader);
|
||||
|
||||
using CacheInterface =
|
||||
BasicTypedCacheInterface<BlobFileReader, CacheEntryRole::kMisc>;
|
||||
using TypedHandle = CacheInterface::TypedHandle;
|
||||
@@ -85,6 +103,13 @@ class BlobFileCache {
|
||||
HistogramImpl* blob_file_read_hist_;
|
||||
std::shared_ptr<IOTracer> io_tracer_;
|
||||
|
||||
struct BlobFileChecksum {
|
||||
std::string value;
|
||||
std::string function_name;
|
||||
};
|
||||
std::unordered_map<uint64_t, BlobFileChecksum> blob_file_checksums_;
|
||||
mutable port::Mutex blob_file_checksums_mutex_;
|
||||
|
||||
static constexpr size_t kNumberOfMutexStripes = 1 << 7;
|
||||
};
|
||||
|
||||
|
||||
@@ -115,13 +115,13 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader) {
|
||||
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_options,
|
||||
&file_options, column_family_id,
|
||||
blob_file_read_hist, nullptr /*IOTracer*/);
|
||||
const BlobFileOpenInfo blob_file{blob_file_number, Slice(), Slice()};
|
||||
|
||||
// First try: reader should be opened and put in cache
|
||||
CacheHandleGuard<BlobFileReader> first;
|
||||
|
||||
const ReadOptions read_options;
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options, blob_file_number,
|
||||
&first));
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options, blob_file, &first));
|
||||
ASSERT_NE(first.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
|
||||
@@ -129,8 +129,8 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader) {
|
||||
// Second try: reader should be served from cache
|
||||
CacheHandleGuard<BlobFileReader> second;
|
||||
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options, blob_file_number,
|
||||
&second));
|
||||
ASSERT_OK(
|
||||
blob_file_cache.GetBlobFileReader(read_options, blob_file, &second));
|
||||
ASSERT_NE(second.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
|
||||
@@ -163,6 +163,7 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_Race) {
|
||||
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_options,
|
||||
&file_options, column_family_id,
|
||||
blob_file_read_hist, nullptr /*IOTracer*/);
|
||||
const BlobFileOpenInfo blob_file{blob_file_number, Slice(), Slice()};
|
||||
|
||||
CacheHandleGuard<BlobFileReader> first;
|
||||
CacheHandleGuard<BlobFileReader> second;
|
||||
@@ -172,16 +173,15 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_Race) {
|
||||
"BlobFileCache::GetBlobFileReader:DoubleCheck", [&](void* /* arg */) {
|
||||
// Disabling sync points to prevent infinite recursion
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options,
|
||||
blob_file_number, &second));
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options, blob_file,
|
||||
&second));
|
||||
ASSERT_NE(second.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options, blob_file_number,
|
||||
&first));
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options, blob_file, &first));
|
||||
ASSERT_NE(first.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
|
||||
@@ -245,21 +245,22 @@ TEST_F(BlobFileCacheTest, RefreshBlobFileReaderPrefersLargestObservedFileSize) {
|
||||
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_options,
|
||||
&file_options, column_family_id,
|
||||
blob_file_read_hist, nullptr /*IOTracer*/);
|
||||
const BlobFileOpenInfo blob_file{blob_file_number, Slice(), Slice()};
|
||||
|
||||
const ReadOptions read_options;
|
||||
std::unique_ptr<BlobFileReader> stale_reader;
|
||||
ASSERT_OK(blob_file_cache.OpenBlobFileReaderUncached(
|
||||
read_options, blob_file_number, &stale_reader,
|
||||
read_options, blob_file, &stale_reader,
|
||||
/*allow_footer_skip_retry=*/true));
|
||||
|
||||
std::unique_ptr<BlobFileReader> initial_cached_reader;
|
||||
ASSERT_OK(blob_file_cache.OpenBlobFileReaderUncached(
|
||||
read_options, blob_file_number, &initial_cached_reader,
|
||||
read_options, blob_file, &initial_cached_reader,
|
||||
/*allow_footer_skip_retry=*/true));
|
||||
|
||||
CacheHandleGuard<BlobFileReader> cached_reader;
|
||||
ASSERT_OK(blob_file_cache.RefreshBlobFileReader(
|
||||
blob_file_number, &initial_cached_reader, &cached_reader));
|
||||
blob_file, &initial_cached_reader, &cached_reader));
|
||||
ASSERT_NE(cached_reader.GetValue(), nullptr);
|
||||
const uint64_t initial_file_size = cached_reader.GetValue()->GetFileSize();
|
||||
|
||||
@@ -269,20 +270,20 @@ TEST_F(BlobFileCacheTest, RefreshBlobFileReaderPrefersLargestObservedFileSize) {
|
||||
|
||||
std::unique_ptr<BlobFileReader> fresh_reader;
|
||||
ASSERT_OK(blob_file_cache.OpenBlobFileReaderUncached(
|
||||
read_options, blob_file_number, &fresh_reader,
|
||||
read_options, blob_file, &fresh_reader,
|
||||
/*allow_footer_skip_retry=*/true));
|
||||
ASSERT_GT(fresh_reader->GetFileSize(), initial_file_size);
|
||||
|
||||
CacheHandleGuard<BlobFileReader> refreshed_reader;
|
||||
ASSERT_OK(blob_file_cache.RefreshBlobFileReader(
|
||||
blob_file_number, &fresh_reader, &refreshed_reader));
|
||||
ASSERT_OK(blob_file_cache.RefreshBlobFileReader(blob_file, &fresh_reader,
|
||||
&refreshed_reader));
|
||||
ASSERT_NE(refreshed_reader.GetValue(), nullptr);
|
||||
ASSERT_GT(refreshed_reader.GetValue()->GetFileSize(), initial_file_size);
|
||||
BlobFileReader* const largest_reader = refreshed_reader.GetValue();
|
||||
|
||||
CacheHandleGuard<BlobFileReader> preserved_reader;
|
||||
ASSERT_OK(blob_file_cache.RefreshBlobFileReader(
|
||||
blob_file_number, &stale_reader, &preserved_reader));
|
||||
ASSERT_OK(blob_file_cache.RefreshBlobFileReader(blob_file, &stale_reader,
|
||||
&preserved_reader));
|
||||
ASSERT_NE(preserved_reader.GetValue(), nullptr);
|
||||
ASSERT_EQ(preserved_reader.GetValue(), largest_reader);
|
||||
ASSERT_EQ(stale_reader.get(), nullptr);
|
||||
@@ -312,12 +313,13 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_IOError) {
|
||||
|
||||
// Note: there is no blob file with the below number
|
||||
constexpr uint64_t blob_file_number = 123;
|
||||
const BlobFileOpenInfo blob_file{blob_file_number, Slice(), Slice()};
|
||||
|
||||
CacheHandleGuard<BlobFileReader> reader;
|
||||
|
||||
const ReadOptions read_options;
|
||||
ASSERT_TRUE(
|
||||
blob_file_cache.GetBlobFileReader(read_options, blob_file_number, &reader)
|
||||
blob_file_cache.GetBlobFileReader(read_options, blob_file, &reader)
|
||||
.IsIOError());
|
||||
ASSERT_EQ(reader.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
@@ -352,6 +354,7 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_CacheFull) {
|
||||
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_options,
|
||||
&file_options, column_family_id,
|
||||
blob_file_read_hist, nullptr /*IOTracer*/);
|
||||
const BlobFileOpenInfo blob_file{blob_file_number, Slice(), Slice()};
|
||||
|
||||
// Insert into cache should fail since it has zero capacity and
|
||||
// strict_capacity_limit is set
|
||||
@@ -359,7 +362,7 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_CacheFull) {
|
||||
|
||||
const ReadOptions read_options;
|
||||
ASSERT_TRUE(
|
||||
blob_file_cache.GetBlobFileReader(read_options, blob_file_number, &reader)
|
||||
blob_file_cache.GetBlobFileReader(read_options, blob_file, &reader)
|
||||
.IsMemoryLimit());
|
||||
ASSERT_EQ(reader.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
|
||||
@@ -330,6 +330,11 @@ Status BlobFilePartitionManager::FinalizeBlobFile(
|
||||
*addition =
|
||||
BlobFileAddition(file_number, blob_count, total_blob_bytes,
|
||||
std::move(checksum_method), std::move(checksum_value));
|
||||
if (blob_file_cache_ != nullptr) {
|
||||
const BlobFileOpenInfo blob_file{file_number, addition->GetChecksumValue(),
|
||||
addition->GetChecksumMethod()};
|
||||
blob_file_cache_->RegisterBlobFileChecksum(blob_file);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -753,6 +758,9 @@ void BlobFilePartitionManager::UnprotectSealedBlobFileNumbers(
|
||||
// manifest-visible path. Evict it here so delayed protection does not
|
||||
// leave an obsolete blob reader behind until DB close.
|
||||
blob_file_cache_->Evict(file_number);
|
||||
if (file_to_partition_.find(file_number) == file_to_partition_.end()) {
|
||||
blob_file_cache_->UnregisterBlobFileChecksum(file_number);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -772,6 +780,10 @@ void BlobFilePartitionManager::RemoveFilePartitionMappings(
|
||||
// this file before it was sealed. Drop it now so future manifest-visible
|
||||
// reads reopen against the finalized on-disk size and footer state.
|
||||
blob_file_cache_->Evict(file_number);
|
||||
if (protected_blob_file_refs_.find(file_number) ==
|
||||
protected_blob_file_refs_.end()) {
|
||||
blob_file_cache_->UnregisterBlobFileChecksum(file_number);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -809,9 +821,9 @@ Status BlobFilePartitionManager::ResolveBlobDirectWriteIndex(
|
||||
}
|
||||
|
||||
Status s;
|
||||
const BlobFileOpenInfo blob_file{blob_idx.file_number(), Slice(), Slice()};
|
||||
CacheHandleGuard<BlobFileReader> reader;
|
||||
s = blob_file_cache->GetBlobFileReader(read_options, blob_idx.file_number(),
|
||||
&reader,
|
||||
s = blob_file_cache->GetBlobFileReader(read_options, blob_file, &reader,
|
||||
/*allow_footer_skip_retry=*/true);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -836,7 +848,7 @@ Status BlobFilePartitionManager::ResolveBlobDirectWriteIndex(
|
||||
|
||||
std::unique_ptr<BlobFileReader> fresh_reader;
|
||||
s = blob_file_cache->OpenBlobFileReaderUncached(
|
||||
read_options, blob_idx.file_number(), &fresh_reader,
|
||||
read_options, blob_file, &fresh_reader,
|
||||
/*allow_footer_skip_retry=*/true);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -850,8 +862,7 @@ Status BlobFilePartitionManager::ResolveBlobDirectWriteIndex(
|
||||
if (s.ok()) {
|
||||
blob_value->PinSelf(fresh_contents->data());
|
||||
CacheHandleGuard<BlobFileReader> ignored;
|
||||
blob_file_cache
|
||||
->RefreshBlobFileReader(blob_idx.file_number(), &fresh_reader, &ignored)
|
||||
blob_file_cache->RefreshBlobFileReader(blob_file, &fresh_reader, &ignored)
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
|
||||
|
||||
@@ -278,22 +278,24 @@ Status BlobFileReader::ReadFromFile(const RandomAccessFileReader* file_reader,
|
||||
return s;
|
||||
}
|
||||
|
||||
FSReadRequest read_req;
|
||||
read_req.offset = read_offset;
|
||||
read_req.len = read_size;
|
||||
if (file_reader->use_direct_io()) {
|
||||
constexpr char* scratch = nullptr;
|
||||
read_req.scratch = nullptr;
|
||||
AlignedBufferAllocationContext direct_io_context{direct_io_buffer};
|
||||
|
||||
s = file_reader->Read(io_options, read_offset, read_size, slice, scratch,
|
||||
&direct_io_context, &dbg);
|
||||
file_reader->Read(io_options, &read_req, &direct_io_context, &dbg);
|
||||
} else {
|
||||
buf->reset(new char[read_size]);
|
||||
|
||||
s = file_reader->Read(io_options, read_offset, read_size, slice, buf->get(),
|
||||
nullptr, &dbg);
|
||||
read_req.scratch = buf->get();
|
||||
file_reader->Read(io_options, &read_req, nullptr, &dbg);
|
||||
}
|
||||
s = std::move(read_req.status);
|
||||
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
*slice = read_req.result;
|
||||
|
||||
if (slice->size() != read_size) {
|
||||
return Status::Corruption("Incomplete blob read from " +
|
||||
|
||||
@@ -82,8 +82,7 @@ Status ReadAndVerifySimpleGen2BlobRecord(
|
||||
IODebugContext dbg;
|
||||
Status s = file->PrepareIOOptions(read_options, opts, &dbg);
|
||||
if (s.ok()) {
|
||||
s = file->Read(opts, record_offset, record_size, &result, buf, nullptr,
|
||||
&dbg);
|
||||
s = file->Read(record_offset, record_size, opts, &result, buf, &dbg);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -171,8 +170,8 @@ Status ReadSimpleGen2BlobRange(const ReadOptions& read_options,
|
||||
IODebugContext dbg;
|
||||
Status s = file->PrepareIOOptions(read_options, opts, &dbg);
|
||||
if (s.ok()) {
|
||||
s = file->Read(opts, record_offset + range_offset, range_length, &result,
|
||||
buf, nullptr, &dbg);
|
||||
s = file->Read(record_offset + range_offset, range_length, opts, &result,
|
||||
buf, &dbg);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -424,12 +423,12 @@ IOStatus WriteSimpleGen2BlobRecord(WritableFileWriter* file,
|
||||
return io_s;
|
||||
}
|
||||
if (!payload.empty()) {
|
||||
io_s = file->Append(opts, payload);
|
||||
io_s = file->Append(payload, opts);
|
||||
if (!io_s.ok()) {
|
||||
return io_s;
|
||||
}
|
||||
}
|
||||
return file->Append(opts, Slice(trailer.data(), trailer.size()));
|
||||
return file->Append(Slice(trailer.data(), trailer.size()), opts);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -29,7 +29,7 @@ Status BlobLogSequentialReader::ReadSlice(uint64_t size, Slice* slice,
|
||||
|
||||
StopWatch read_sw(clock_, statistics_, BLOB_DB_BLOB_FILE_READ_MICROS);
|
||||
// TODO: rate limit `BlobLogSequentialReader` reads (it appears unused?)
|
||||
Status s = file_->Read(IOOptions(), next_byte_, static_cast<size_t>(size),
|
||||
Status s = file_->Read(next_byte_, static_cast<size_t>(size), IOOptions(),
|
||||
slice, buf, nullptr);
|
||||
next_byte_ += size;
|
||||
if (!s.ok()) {
|
||||
|
||||
@@ -40,7 +40,7 @@ Status BlobLogWriter::Sync(const WriteOptions& write_options) {
|
||||
IOOptions opts;
|
||||
Status s = WritableFileWriter::PrepareIOOptions(write_options, opts);
|
||||
if (s.ok()) {
|
||||
s = dest_->Sync(opts, use_fsync_);
|
||||
s = use_fsync_ ? dest_->Fsync(opts) : dest_->Sync(opts);
|
||||
}
|
||||
if (s.ok()) {
|
||||
RecordTick(statistics_, BLOB_DB_BLOB_FILE_SYNCED);
|
||||
@@ -58,7 +58,7 @@ Status BlobLogWriter::WriteHeader(const WriteOptions& write_options,
|
||||
IOOptions opts;
|
||||
Status s = WritableFileWriter::PrepareIOOptions(write_options, opts);
|
||||
if (s.ok()) {
|
||||
s = dest_->Append(opts, Slice(str));
|
||||
s = dest_->Append(Slice(str), opts);
|
||||
}
|
||||
if (s.ok()) {
|
||||
block_offset_ += str.size();
|
||||
@@ -92,7 +92,7 @@ Status BlobLogWriter::AppendFooter(const WriteOptions& write_options,
|
||||
IOOptions opts;
|
||||
s = WritableFileWriter::PrepareIOOptions(write_options, opts);
|
||||
if (s.ok()) {
|
||||
s = dest_->Append(opts, Slice(str));
|
||||
s = dest_->Append(Slice(str), opts);
|
||||
}
|
||||
if (s.ok()) {
|
||||
block_offset_ += str.size();
|
||||
@@ -181,13 +181,13 @@ Status BlobLogWriter::EmitPhysicalRecord(const WriteOptions& write_options,
|
||||
IOOptions opts;
|
||||
Status s = WritableFileWriter::PrepareIOOptions(write_options, opts);
|
||||
if (s.ok()) {
|
||||
s = dest_->Append(opts, Slice(headerbuf));
|
||||
s = dest_->Append(Slice(headerbuf), opts);
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = dest_->Append(opts, key);
|
||||
s = dest_->Append(key, opts);
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = dest_->Append(opts, val);
|
||||
s = dest_->Append(val, opts);
|
||||
}
|
||||
if (do_flush_ && s.ok()) {
|
||||
s = dest_->Flush(opts);
|
||||
|
||||
@@ -14,6 +14,16 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// Non-owning metadata needed when opening a blob file for reading. The
|
||||
// checksum slices must remain valid while this object is in use. A file without
|
||||
// finalized checksum metadata, such as an active direct-write blob file, uses
|
||||
// empty slices for both checksum fields.
|
||||
struct BlobFileOpenInfo {
|
||||
uint64_t file_number = 0;
|
||||
Slice file_checksum;
|
||||
Slice file_checksum_func_name;
|
||||
};
|
||||
|
||||
// A read Blob request structure for use in BlobSource::MultiGetBlob and
|
||||
// BlobFileReader::MultiGetBlob.
|
||||
struct BlobReadRequest {
|
||||
@@ -53,7 +63,7 @@ struct BlobReadRequest {
|
||||
};
|
||||
|
||||
using BlobFileReadRequests =
|
||||
std::tuple<uint64_t /* file_number */, uint64_t /* file_size */,
|
||||
std::tuple<BlobFileOpenInfo, uint64_t /* file_size */,
|
||||
autovector<BlobReadRequest>>;
|
||||
|
||||
// A byte-range (partial) blob read request for the lazy blob-read multi
|
||||
@@ -97,7 +107,7 @@ struct BlobRangeReadRequest {
|
||||
};
|
||||
|
||||
using BlobFileRangeReadRequests =
|
||||
std::tuple<uint64_t /* file_number */, uint64_t /* file_size */,
|
||||
std::tuple<BlobFileOpenInfo, uint64_t /* file_size */,
|
||||
autovector<BlobRangeReadRequest>>;
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+29
-25
@@ -50,9 +50,10 @@ Status AppendBlobRefreshRetryFailure(const Status& stale_status,
|
||||
template <typename ReadFn>
|
||||
Status ReadBlobWithReaderRetry(BlobFileCache* blob_file_cache,
|
||||
const ReadOptions& read_options,
|
||||
uint64_t file_number, const ReadFn& read) {
|
||||
const BlobFileOpenInfo& blob_file,
|
||||
const ReadFn& read) {
|
||||
CacheHandleGuard<BlobFileReader> blob_file_reader;
|
||||
Status s = blob_file_cache->GetBlobFileReader(read_options, file_number,
|
||||
Status s = blob_file_cache->GetBlobFileReader(read_options, blob_file,
|
||||
&blob_file_reader);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -66,11 +67,11 @@ Status ReadBlobWithReaderRetry(BlobFileCache* blob_file_cache,
|
||||
|
||||
const Status stale_status = s;
|
||||
blob_file_reader.Reset();
|
||||
blob_file_cache->Evict(file_number);
|
||||
blob_file_cache->Evict(blob_file.file_number);
|
||||
|
||||
std::unique_ptr<BlobFileReader> fresh_reader;
|
||||
s = blob_file_cache->OpenBlobFileReaderUncached(
|
||||
read_options, file_number, &fresh_reader,
|
||||
read_options, blob_file, &fresh_reader,
|
||||
/*allow_footer_skip_retry=*/false);
|
||||
if (!s.ok()) {
|
||||
return AppendBlobRefreshRetryFailure(stale_status, s);
|
||||
@@ -83,7 +84,7 @@ Status ReadBlobWithReaderRetry(BlobFileCache* blob_file_cache,
|
||||
|
||||
CacheHandleGuard<BlobFileReader> ignored_reader;
|
||||
blob_file_cache
|
||||
->RefreshBlobFileReader(file_number, &fresh_reader, &ignored_reader)
|
||||
->RefreshBlobFileReader(blob_file, &fresh_reader, &ignored_reader)
|
||||
.PermitUncheckedError();
|
||||
return s;
|
||||
}
|
||||
@@ -272,9 +273,9 @@ Status BlobSource::InsertEntryIntoCache(const Slice& key, BlobContents* value,
|
||||
}
|
||||
|
||||
Status BlobSource::GetBlob(const ReadOptions& read_options,
|
||||
const Slice& user_key, uint64_t file_number,
|
||||
uint64_t offset, uint64_t file_size,
|
||||
uint64_t value_size,
|
||||
const Slice& user_key,
|
||||
const BlobFileOpenInfo& blob_file, uint64_t offset,
|
||||
uint64_t file_size, uint64_t value_size,
|
||||
CompressionType compression_type,
|
||||
FilePrefetchBuffer* prefetch_buffer,
|
||||
PinnableSlice* value, uint64_t* bytes_read) {
|
||||
@@ -282,7 +283,8 @@ Status BlobSource::GetBlob(const ReadOptions& read_options,
|
||||
|
||||
Status s;
|
||||
|
||||
const CacheKey cache_key = GetCacheKey(file_number, file_size, offset);
|
||||
const CacheKey cache_key =
|
||||
GetCacheKey(blob_file.file_number, file_size, offset);
|
||||
|
||||
CacheHandleGuard<BlobContents> blob_handle;
|
||||
|
||||
@@ -332,8 +334,7 @@ Status BlobSource::GetBlob(const ReadOptions& read_options,
|
||||
|
||||
uint64_t read_size = 0;
|
||||
s = ReadBlobWithReaderRetry(
|
||||
blob_file_cache_, read_options, file_number,
|
||||
[&](BlobFileReader* reader) {
|
||||
blob_file_cache_, read_options, blob_file, [&](BlobFileReader* reader) {
|
||||
if (compression_type != reader->GetCompressionType()) {
|
||||
return Status::Corruption(
|
||||
"Compression type mismatch when reading blob");
|
||||
@@ -377,7 +378,8 @@ Status BlobSource::GetBlob(const ReadOptions& read_options,
|
||||
}
|
||||
|
||||
Status BlobSource::GetBlobRange(const ReadOptions& read_options,
|
||||
const Slice& user_key, uint64_t file_number,
|
||||
const Slice& user_key,
|
||||
const BlobFileOpenInfo& blob_file,
|
||||
uint64_t offset, uint64_t file_size,
|
||||
uint64_t value_size,
|
||||
CompressionType compression_type,
|
||||
@@ -394,7 +396,8 @@ Status BlobSource::GetBlobRange(const ReadOptions& read_options,
|
||||
|
||||
Status s;
|
||||
|
||||
const CacheKey cache_key = GetCacheKey(file_number, file_size, offset);
|
||||
const CacheKey cache_key =
|
||||
GetCacheKey(blob_file.file_number, file_size, offset);
|
||||
|
||||
CacheHandleGuard<BlobContents> blob_handle;
|
||||
|
||||
@@ -435,8 +438,7 @@ Status BlobSource::GetBlobRange(const ReadOptions& read_options,
|
||||
// case (offset/size mismatch surfacing as Corruption), not a payload
|
||||
// checksum failure.
|
||||
s = ReadBlobWithReaderRetry(
|
||||
blob_file_cache_, read_options, file_number,
|
||||
[&](BlobFileReader* reader) {
|
||||
blob_file_cache_, read_options, blob_file, [&](BlobFileReader* reader) {
|
||||
if (compression_type != reader->GetCompressionType()) {
|
||||
return Status::Corruption(
|
||||
"Compression type mismatch when reading blob");
|
||||
@@ -827,7 +829,7 @@ void BlobSource::MultiGetBlob(const ReadOptions& read_options,
|
||||
uint64_t total_bytes_read = 0;
|
||||
uint64_t bytes_read_in_file = 0;
|
||||
|
||||
for (auto& [file_number, file_size, blob_reqs_in_file] : blob_reqs) {
|
||||
for (auto& [blob_file, file_size, blob_reqs_in_file] : blob_reqs) {
|
||||
// sort blob_reqs_in_file by file offset.
|
||||
std::sort(
|
||||
blob_reqs_in_file.begin(), blob_reqs_in_file.end(),
|
||||
@@ -835,7 +837,7 @@ void BlobSource::MultiGetBlob(const ReadOptions& read_options,
|
||||
return lhs.offset < rhs.offset;
|
||||
});
|
||||
|
||||
MultiGetBlobFromOneFile(read_options, file_number, file_size,
|
||||
MultiGetBlobFromOneFile(read_options, blob_file, file_size,
|
||||
blob_reqs_in_file, &bytes_read_in_file);
|
||||
|
||||
total_bytes_read += bytes_read_in_file;
|
||||
@@ -847,7 +849,7 @@ void BlobSource::MultiGetBlob(const ReadOptions& read_options,
|
||||
}
|
||||
|
||||
void BlobSource::MultiGetBlobFromOneFile(const ReadOptions& read_options,
|
||||
uint64_t file_number,
|
||||
const BlobFileOpenInfo& blob_file,
|
||||
uint64_t /*file_size*/,
|
||||
autovector<BlobReadRequest>& blob_reqs,
|
||||
uint64_t* bytes_read) {
|
||||
@@ -865,6 +867,7 @@ void BlobSource::MultiGetBlobFromOneFile(const ReadOptions& read_options,
|
||||
Mask cache_hit_mask = 0;
|
||||
|
||||
uint64_t total_bytes = 0;
|
||||
const uint64_t file_number = blob_file.file_number;
|
||||
const OffsetableCacheKey base_cache_key(db_id_, db_session_id_, file_number);
|
||||
|
||||
if (blob_cache_) {
|
||||
@@ -936,7 +939,7 @@ void BlobSource::MultiGetBlobFromOneFile(const ReadOptions& read_options,
|
||||
}
|
||||
|
||||
CacheHandleGuard<BlobFileReader> blob_file_reader;
|
||||
Status s = blob_file_cache_->GetBlobFileReader(read_options, file_number,
|
||||
Status s = blob_file_cache_->GetBlobFileReader(read_options, blob_file,
|
||||
&blob_file_reader);
|
||||
if (!s.ok()) {
|
||||
for (size_t i = 0; i < _blob_reqs.size(); ++i) {
|
||||
@@ -976,7 +979,7 @@ void BlobSource::MultiGetBlobFromOneFile(const ReadOptions& read_options,
|
||||
|
||||
std::unique_ptr<BlobFileReader> fresh_reader;
|
||||
s = blob_file_cache_->OpenBlobFileReaderUncached(
|
||||
read_options, file_number, &fresh_reader,
|
||||
read_options, blob_file, &fresh_reader,
|
||||
/*allow_footer_skip_retry=*/false);
|
||||
if (!s.ok()) {
|
||||
for (const auto& blob_req : _blob_reqs) {
|
||||
@@ -1037,7 +1040,7 @@ void BlobSource::MultiGetBlobFromOneFile(const ReadOptions& read_options,
|
||||
if (install_fresh_reader) {
|
||||
CacheHandleGuard<BlobFileReader> ignored_reader;
|
||||
blob_file_cache_
|
||||
->RefreshBlobFileReader(file_number, &fresh_reader, &ignored_reader)
|
||||
->RefreshBlobFileReader(blob_file, &fresh_reader, &ignored_reader)
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
@@ -1103,7 +1106,7 @@ void BlobSource::MultiGetBlobRange(
|
||||
uint64_t total_bytes_read = 0;
|
||||
uint64_t bytes_read_in_file = 0;
|
||||
|
||||
for (auto& [file_number, file_size, blob_reqs_in_file] : blob_reqs) {
|
||||
for (auto& [blob_file, file_size, blob_reqs_in_file] : blob_reqs) {
|
||||
// Sort by effective read offset so the file system layer can coalesce
|
||||
// adjacent sub-range reads within one MultiRead.
|
||||
std::sort(blob_reqs_in_file.begin(), blob_reqs_in_file.end(),
|
||||
@@ -1113,7 +1116,7 @@ void BlobSource::MultiGetBlobRange(
|
||||
rhs.offset + rhs.range_offset;
|
||||
});
|
||||
|
||||
MultiGetBlobRangeFromOneFile(read_options, file_number, file_size,
|
||||
MultiGetBlobRangeFromOneFile(read_options, blob_file, file_size,
|
||||
blob_reqs_in_file, &bytes_read_in_file);
|
||||
|
||||
total_bytes_read += bytes_read_in_file;
|
||||
@@ -1126,7 +1129,7 @@ void BlobSource::MultiGetBlobRange(
|
||||
}
|
||||
|
||||
void BlobSource::MultiGetBlobRangeFromOneFile(
|
||||
const ReadOptions& read_options, uint64_t file_number,
|
||||
const ReadOptions& read_options, const BlobFileOpenInfo& blob_file,
|
||||
uint64_t /*file_size*/, autovector<BlobRangeReadRequest>& blob_reqs,
|
||||
uint64_t* bytes_read) {
|
||||
const size_t num_blobs = blob_reqs.size();
|
||||
@@ -1140,6 +1143,7 @@ void BlobSource::MultiGetBlobRangeFromOneFile(
|
||||
Mask cache_hit_mask = 0;
|
||||
|
||||
uint64_t total_bytes = 0;
|
||||
const uint64_t file_number = blob_file.file_number;
|
||||
const OffsetableCacheKey base_cache_key(db_id_, db_session_id_, file_number);
|
||||
|
||||
// Probe the whole-value cache per request; on a hit, slice the requested
|
||||
@@ -1196,7 +1200,7 @@ void BlobSource::MultiGetBlobRangeFromOneFile(
|
||||
// is simply surfaced per request. Hence no reader-refresh retry and no
|
||||
// cache-fill (a partial value cannot represent the whole-record cache entry).
|
||||
CacheHandleGuard<BlobFileReader> blob_file_reader;
|
||||
Status s = blob_file_cache_->GetBlobFileReader(read_options, file_number,
|
||||
Status s = blob_file_cache_->GetBlobFileReader(read_options, blob_file,
|
||||
&blob_file_reader);
|
||||
if (!s.ok()) {
|
||||
for (auto& blob_req : _blob_reqs) {
|
||||
|
||||
+14
-10
@@ -80,8 +80,9 @@ class BlobSource {
|
||||
// sets "*bytes_read" to the size of on-disk (possibly compressed) blob
|
||||
// record.
|
||||
Status GetBlob(const ReadOptions& read_options, const Slice& user_key,
|
||||
uint64_t file_number, uint64_t offset, uint64_t file_size,
|
||||
uint64_t value_size, CompressionType compression_type,
|
||||
const BlobFileOpenInfo& blob_file, uint64_t offset,
|
||||
uint64_t file_size, uint64_t value_size,
|
||||
CompressionType compression_type,
|
||||
FilePrefetchBuffer* prefetch_buffer, PinnableSlice* value,
|
||||
uint64_t* bytes_read);
|
||||
|
||||
@@ -105,10 +106,11 @@ class BlobSource {
|
||||
// *bytes_read (when non-null) is the number of bytes read from the file; on a
|
||||
// hit it is 0.
|
||||
Status GetBlobRange(const ReadOptions& read_options, const Slice& user_key,
|
||||
uint64_t file_number, uint64_t offset, uint64_t file_size,
|
||||
uint64_t value_size, CompressionType compression_type,
|
||||
uint64_t range_offset, size_t range_length,
|
||||
PinnableSlice* value, uint64_t* bytes_read);
|
||||
const BlobFileOpenInfo& blob_file, uint64_t offset,
|
||||
uint64_t file_size, uint64_t value_size,
|
||||
CompressionType compression_type, uint64_t range_offset,
|
||||
size_t range_length, PinnableSlice* value,
|
||||
uint64_t* bytes_read);
|
||||
|
||||
// Reads a SimpleGen2Blob payload (see db/blob/blob_gen2_format.h) through the
|
||||
// blob value cache and BLOB_DB_* statistics. This is the counterpart to
|
||||
@@ -232,7 +234,8 @@ class BlobSource {
|
||||
// "*bytes_read" to the total size of on-disk (possibly compressed) blob
|
||||
// records.
|
||||
void MultiGetBlobFromOneFile(const ReadOptions& read_options,
|
||||
uint64_t file_number, uint64_t file_size,
|
||||
const BlobFileOpenInfo& blob_file,
|
||||
uint64_t file_size,
|
||||
autovector<BlobReadRequest>& blob_reqs,
|
||||
uint64_t* bytes_read);
|
||||
|
||||
@@ -250,14 +253,15 @@ class BlobSource {
|
||||
// Byte-range (partial) multi-read counterpart of MultiGetBlobFromOneFile for
|
||||
// a single blob file. See MultiGetBlobRange.
|
||||
void MultiGetBlobRangeFromOneFile(const ReadOptions& read_options,
|
||||
uint64_t file_number, uint64_t file_size,
|
||||
const BlobFileOpenInfo& blob_file,
|
||||
uint64_t file_size,
|
||||
autovector<BlobRangeReadRequest>& blob_reqs,
|
||||
uint64_t* bytes_read);
|
||||
|
||||
inline Status GetBlobFileReader(
|
||||
const ReadOptions& read_options, uint64_t blob_file_number,
|
||||
const ReadOptions& read_options, const BlobFileOpenInfo& blob_file,
|
||||
CacheHandleGuard<BlobFileReader>* blob_file_reader) {
|
||||
return blob_file_cache_->GetBlobFileReader(read_options, blob_file_number,
|
||||
return blob_file_cache_->GetBlobFileReader(read_options, blob_file,
|
||||
blob_file_reader);
|
||||
}
|
||||
|
||||
|
||||
+93
-75
@@ -104,6 +104,10 @@ void WriteBlobFile(const ImmutableOptions& immutable_options,
|
||||
&checksum_method, &checksum_value));
|
||||
}
|
||||
|
||||
BlobFileOpenInfo OpenInfoWithoutChecksum(uint64_t file_number) {
|
||||
return BlobFileOpenInfo{file_number, Slice(), Slice()};
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
class BlobSourceTest : public DBTestBase {
|
||||
@@ -212,10 +216,10 @@ TEST_F(BlobSourceTest, GetBlobsFromCache) {
|
||||
ASSERT_FALSE(blob_source.TEST_BlobInCache(blob_file_number, file_size,
|
||||
blob_offsets[i]));
|
||||
|
||||
ASSERT_OK(blob_source.GetBlob(read_options, keys[i], blob_file_number,
|
||||
blob_offsets[i], file_size, blob_sizes[i],
|
||||
kNoCompression, prefetch_buffer, &values[i],
|
||||
&bytes_read));
|
||||
ASSERT_OK(blob_source.GetBlob(
|
||||
read_options, keys[i], OpenInfoWithoutChecksum(blob_file_number),
|
||||
blob_offsets[i], file_size, blob_sizes[i], kNoCompression,
|
||||
prefetch_buffer, &values[i], &bytes_read));
|
||||
ASSERT_EQ(values[i], blobs[i]);
|
||||
ASSERT_TRUE(values[i].IsPinned());
|
||||
ASSERT_EQ(bytes_read,
|
||||
@@ -251,10 +255,10 @@ TEST_F(BlobSourceTest, GetBlobsFromCache) {
|
||||
ASSERT_FALSE(blob_source.TEST_BlobInCache(blob_file_number, file_size,
|
||||
blob_offsets[i]));
|
||||
|
||||
ASSERT_OK(blob_source.GetBlob(read_options, keys[i], blob_file_number,
|
||||
blob_offsets[i], file_size, blob_sizes[i],
|
||||
kNoCompression, prefetch_buffer, &values[i],
|
||||
&bytes_read));
|
||||
ASSERT_OK(blob_source.GetBlob(
|
||||
read_options, keys[i], OpenInfoWithoutChecksum(blob_file_number),
|
||||
blob_offsets[i], file_size, blob_sizes[i], kNoCompression,
|
||||
prefetch_buffer, &values[i], &bytes_read));
|
||||
ASSERT_EQ(values[i], blobs[i]);
|
||||
ASSERT_TRUE(values[i].IsPinned());
|
||||
ASSERT_EQ(bytes_read,
|
||||
@@ -299,10 +303,10 @@ TEST_F(BlobSourceTest, GetBlobsFromCache) {
|
||||
ASSERT_TRUE(blob_source.TEST_BlobInCache(blob_file_number, file_size,
|
||||
blob_offsets[i]));
|
||||
|
||||
ASSERT_OK(blob_source.GetBlob(read_options, keys[i], blob_file_number,
|
||||
blob_offsets[i], file_size, blob_sizes[i],
|
||||
kNoCompression, prefetch_buffer, &values[i],
|
||||
&bytes_read));
|
||||
ASSERT_OK(blob_source.GetBlob(
|
||||
read_options, keys[i], OpenInfoWithoutChecksum(blob_file_number),
|
||||
blob_offsets[i], file_size, blob_sizes[i], kNoCompression,
|
||||
prefetch_buffer, &values[i], &bytes_read));
|
||||
ASSERT_EQ(values[i], blobs[i]);
|
||||
ASSERT_TRUE(values[i].IsPinned());
|
||||
ASSERT_EQ(bytes_read,
|
||||
@@ -339,10 +343,10 @@ TEST_F(BlobSourceTest, GetBlobsFromCache) {
|
||||
ASSERT_TRUE(blob_source.TEST_BlobInCache(blob_file_number, file_size,
|
||||
blob_offsets[i]));
|
||||
|
||||
ASSERT_OK(blob_source.GetBlob(read_options, keys[i], blob_file_number,
|
||||
blob_offsets[i], file_size, blob_sizes[i],
|
||||
kNoCompression, prefetch_buffer, &values[i],
|
||||
&bytes_read));
|
||||
ASSERT_OK(blob_source.GetBlob(
|
||||
read_options, keys[i], OpenInfoWithoutChecksum(blob_file_number),
|
||||
blob_offsets[i], file_size, blob_sizes[i], kNoCompression,
|
||||
prefetch_buffer, &values[i], &bytes_read));
|
||||
ASSERT_EQ(values[i], blobs[i]);
|
||||
ASSERT_TRUE(values[i].IsPinned());
|
||||
ASSERT_EQ(bytes_read,
|
||||
@@ -385,7 +389,8 @@ TEST_F(BlobSourceTest, GetBlobsFromCache) {
|
||||
blob_offsets[i]));
|
||||
|
||||
ASSERT_TRUE(blob_source
|
||||
.GetBlob(read_options, keys[i], blob_file_number,
|
||||
.GetBlob(read_options, keys[i],
|
||||
OpenInfoWithoutChecksum(blob_file_number),
|
||||
blob_offsets[i], file_size, blob_sizes[i],
|
||||
kNoCompression, prefetch_buffer, &values[i],
|
||||
&bytes_read)
|
||||
@@ -427,7 +432,8 @@ TEST_F(BlobSourceTest, GetBlobsFromCache) {
|
||||
blob_offsets[i]));
|
||||
|
||||
ASSERT_TRUE(blob_source
|
||||
.GetBlob(read_options, keys[i], file_number,
|
||||
.GetBlob(read_options, keys[i],
|
||||
OpenInfoWithoutChecksum(file_number),
|
||||
blob_offsets[i], file_size, blob_sizes[i],
|
||||
kNoCompression, prefetch_buffer, &values[i],
|
||||
&bytes_read)
|
||||
@@ -522,8 +528,8 @@ TEST_F(BlobSourceTest, GetCompressedBlobs) {
|
||||
compression, blob_offsets, blob_sizes);
|
||||
|
||||
CacheHandleGuard<BlobFileReader> blob_file_reader;
|
||||
ASSERT_OK(blob_source.GetBlobFileReader(read_options, file_number,
|
||||
&blob_file_reader));
|
||||
ASSERT_OK(blob_source.GetBlobFileReader(
|
||||
read_options, OpenInfoWithoutChecksum(file_number), &blob_file_reader));
|
||||
ASSERT_NE(blob_file_reader.GetValue(), nullptr);
|
||||
|
||||
const uint64_t file_size = blob_file_reader.GetValue()->GetFileSize();
|
||||
@@ -541,10 +547,10 @@ TEST_F(BlobSourceTest, GetCompressedBlobs) {
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
ASSERT_FALSE(blob_source.TEST_BlobInCache(file_number, file_size,
|
||||
blob_offsets[i]));
|
||||
ASSERT_OK(blob_source.GetBlob(read_options, keys[i], file_number,
|
||||
blob_offsets[i], file_size, blob_sizes[i],
|
||||
compression, nullptr /*prefetch_buffer*/,
|
||||
&values[i], &bytes_read));
|
||||
ASSERT_OK(blob_source.GetBlob(
|
||||
read_options, keys[i], OpenInfoWithoutChecksum(file_number),
|
||||
blob_offsets[i], file_size, blob_sizes[i], compression,
|
||||
nullptr /*prefetch_buffer*/, &values[i], &bytes_read));
|
||||
ASSERT_EQ(values[i], blobs[i] /*uncompressed blob*/);
|
||||
ASSERT_NE(values[i].size(), blob_sizes[i] /*compressed size*/);
|
||||
ASSERT_EQ(bytes_read,
|
||||
@@ -564,10 +570,10 @@ TEST_F(BlobSourceTest, GetCompressedBlobs) {
|
||||
blob_offsets[i]));
|
||||
|
||||
// Compressed blob size is passed in GetBlob
|
||||
ASSERT_OK(blob_source.GetBlob(read_options, keys[i], file_number,
|
||||
blob_offsets[i], file_size, blob_sizes[i],
|
||||
compression, nullptr /*prefetch_buffer*/,
|
||||
&values[i], &bytes_read));
|
||||
ASSERT_OK(blob_source.GetBlob(
|
||||
read_options, keys[i], OpenInfoWithoutChecksum(file_number),
|
||||
blob_offsets[i], file_size, blob_sizes[i], compression,
|
||||
nullptr /*prefetch_buffer*/, &values[i], &bytes_read));
|
||||
ASSERT_EQ(values[i], blobs[i] /*uncompressed blob*/);
|
||||
ASSERT_NE(values[i].size(), blob_sizes[i] /*compressed size*/);
|
||||
ASSERT_EQ(bytes_read,
|
||||
@@ -674,7 +680,8 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromMultiFiles) {
|
||||
keys[j], blob_offsets[j], blob_sizes[j], kNoCompression,
|
||||
&value_buf[i * num_blobs + j], &statuses_buf[i * num_blobs + j]);
|
||||
}
|
||||
blob_reqs.emplace_back(file_number, file_size, blob_reqs_in_file[i]);
|
||||
blob_reqs.emplace_back(OpenInfoWithoutChecksum(file_number), file_size,
|
||||
blob_reqs_in_file[i]);
|
||||
}
|
||||
|
||||
get_perf_context()->Reset();
|
||||
@@ -731,7 +738,8 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromMultiFiles) {
|
||||
}
|
||||
|
||||
// Add a fake multi-get blob request.
|
||||
blob_reqs.emplace_back(fake_file_number, file_size, fake_blob_reqs_in_file);
|
||||
blob_reqs.emplace_back(OpenInfoWithoutChecksum(fake_file_number), file_size,
|
||||
fake_blob_reqs_in_file);
|
||||
|
||||
blob_source.MultiGetBlob(read_options, blob_reqs, &bytes_read);
|
||||
|
||||
@@ -865,8 +873,9 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromCache) {
|
||||
statistics->Reset().PermitUncheckedError();
|
||||
|
||||
// Get half of blobs
|
||||
blob_source.MultiGetBlobFromOneFile(read_options, blob_file_number,
|
||||
file_size, blob_reqs, &bytes_read);
|
||||
blob_source.MultiGetBlobFromOneFile(
|
||||
read_options, OpenInfoWithoutChecksum(blob_file_number), file_size,
|
||||
blob_reqs, &bytes_read);
|
||||
|
||||
uint64_t fs_read_bytes = 0;
|
||||
uint64_t ca_read_bytes = 0;
|
||||
@@ -911,10 +920,10 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromCache) {
|
||||
ASSERT_FALSE(blob_source.TEST_BlobInCache(blob_file_number, file_size,
|
||||
blob_offsets[i]));
|
||||
|
||||
ASSERT_OK(blob_source.GetBlob(read_options, keys[i], blob_file_number,
|
||||
blob_offsets[i], file_size, blob_sizes[i],
|
||||
kNoCompression, prefetch_buffer,
|
||||
&value_buf[i], &bytes_read));
|
||||
ASSERT_OK(blob_source.GetBlob(
|
||||
read_options, keys[i], OpenInfoWithoutChecksum(blob_file_number),
|
||||
blob_offsets[i], file_size, blob_sizes[i], kNoCompression,
|
||||
prefetch_buffer, &value_buf[i], &bytes_read));
|
||||
ASSERT_EQ(value_buf[i], blobs[i]);
|
||||
ASSERT_TRUE(value_buf[i].IsPinned());
|
||||
ASSERT_EQ(bytes_read,
|
||||
@@ -935,8 +944,9 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromCache) {
|
||||
kNoCompression, &value_buf[i], &statuses_buf[i]);
|
||||
}
|
||||
|
||||
blob_source.MultiGetBlobFromOneFile(read_options, blob_file_number,
|
||||
file_size, blob_reqs, &bytes_read);
|
||||
blob_source.MultiGetBlobFromOneFile(
|
||||
read_options, OpenInfoWithoutChecksum(blob_file_number), file_size,
|
||||
blob_reqs, &bytes_read);
|
||||
|
||||
uint64_t blob_bytes = 0;
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
@@ -985,8 +995,9 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromCache) {
|
||||
get_perf_context()->Reset();
|
||||
statistics->Reset().PermitUncheckedError();
|
||||
|
||||
blob_source.MultiGetBlobFromOneFile(read_options, blob_file_number,
|
||||
file_size, blob_reqs, &bytes_read);
|
||||
blob_source.MultiGetBlobFromOneFile(
|
||||
read_options, OpenInfoWithoutChecksum(blob_file_number), file_size,
|
||||
blob_reqs, &bytes_read);
|
||||
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
ASSERT_TRUE(statuses_buf[i].IsIncomplete());
|
||||
@@ -1029,8 +1040,9 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromCache) {
|
||||
get_perf_context()->Reset();
|
||||
statistics->Reset().PermitUncheckedError();
|
||||
|
||||
blob_source.MultiGetBlobFromOneFile(read_options, non_existing_file_number,
|
||||
file_size, blob_reqs, &bytes_read);
|
||||
blob_source.MultiGetBlobFromOneFile(
|
||||
read_options, OpenInfoWithoutChecksum(non_existing_file_number),
|
||||
file_size, blob_reqs, &bytes_read);
|
||||
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
ASSERT_TRUE(statuses_buf[i].IsIOError());
|
||||
@@ -1104,8 +1116,9 @@ TEST_F(BlobSourceTest, GetBlobPreservesCorruptionDetailsWhenRefreshOpenFails) {
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
ASSERT_OK(blob_source.GetBlob(
|
||||
read_options, keys[0], blob_file_number, blob_offsets[0], file_size,
|
||||
blob_sizes[0], kNoCompression, prefetch_buffer, &value, &bytes_read));
|
||||
read_options, keys[0], OpenInfoWithoutChecksum(blob_file_number),
|
||||
blob_offsets[0], file_size, blob_sizes[0], kNoCompression,
|
||||
prefetch_buffer, &value, &bytes_read));
|
||||
ASSERT_EQ(value, blobs[0]);
|
||||
|
||||
const std::string blob_file_path =
|
||||
@@ -1114,10 +1127,10 @@ TEST_F(BlobSourceTest, GetBlobPreservesCorruptionDetailsWhenRefreshOpenFails) {
|
||||
|
||||
PinnableSlice invalid_value;
|
||||
bytes_read = 0;
|
||||
Status s =
|
||||
blob_source.GetBlob(read_options, keys[0], blob_file_number, file_size,
|
||||
file_size, blob_sizes[0], kNoCompression,
|
||||
prefetch_buffer, &invalid_value, &bytes_read);
|
||||
Status s = blob_source.GetBlob(
|
||||
read_options, keys[0], OpenInfoWithoutChecksum(blob_file_number),
|
||||
file_size, file_size, blob_sizes[0], kNoCompression, prefetch_buffer,
|
||||
&invalid_value, &bytes_read);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
|
||||
@@ -1178,8 +1191,9 @@ TEST_F(BlobSourceTest,
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
ASSERT_OK(blob_source.GetBlob(
|
||||
read_options, keys[0], blob_file_number, blob_offsets[0], file_size,
|
||||
blob_sizes[0], kNoCompression, prefetch_buffer, &value, &bytes_read));
|
||||
read_options, keys[0], OpenInfoWithoutChecksum(blob_file_number),
|
||||
blob_offsets[0], file_size, blob_sizes[0], kNoCompression,
|
||||
prefetch_buffer, &value, &bytes_read));
|
||||
ASSERT_EQ(value, blobs[0]);
|
||||
|
||||
const std::string blob_file_path =
|
||||
@@ -1193,8 +1207,9 @@ TEST_F(BlobSourceTest,
|
||||
&values[0], &statuses[0]);
|
||||
|
||||
bytes_read = 0;
|
||||
blob_source.MultiGetBlobFromOneFile(read_options, blob_file_number, file_size,
|
||||
blob_reqs, &bytes_read);
|
||||
blob_source.MultiGetBlobFromOneFile(read_options,
|
||||
OpenInfoWithoutChecksum(blob_file_number),
|
||||
file_size, blob_reqs, &bytes_read);
|
||||
ASSERT_TRUE(statuses[0].IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
|
||||
@@ -1303,8 +1318,8 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
|
||||
|
||||
CacheHandleGuard<BlobFileReader> file_reader;
|
||||
ReadOptions read_options;
|
||||
ASSERT_OK(
|
||||
blob_source.GetBlobFileReader(read_options, file_number, &file_reader));
|
||||
ASSERT_OK(blob_source.GetBlobFileReader(
|
||||
read_options, OpenInfoWithoutChecksum(file_number), &file_reader));
|
||||
ASSERT_NE(file_reader.GetValue(), nullptr);
|
||||
const uint64_t file_size = file_reader.GetValue()->GetFileSize();
|
||||
ASSERT_EQ(file_reader.GetValue()->GetCompressionType(), kNoCompression);
|
||||
@@ -1322,7 +1337,8 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
|
||||
get_perf_context()->Reset();
|
||||
|
||||
// key0 should be filled to the primary cache from the blob file.
|
||||
ASSERT_OK(blob_source.GetBlob(read_options, keys[0], file_number,
|
||||
ASSERT_OK(blob_source.GetBlob(read_options, keys[0],
|
||||
OpenInfoWithoutChecksum(file_number),
|
||||
blob_offsets[0], file_size, blob_sizes[0],
|
||||
kNoCompression, nullptr /* prefetch_buffer */,
|
||||
values.data(), nullptr /* bytes_read */));
|
||||
@@ -1331,17 +1347,18 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
|
||||
|
||||
// key0 should be evicted and key0's dummy item is inserted into secondary
|
||||
// cache. key1 should be filled to the primary cache from the blob file.
|
||||
ASSERT_OK(blob_source.GetBlob(read_options, keys[1], file_number,
|
||||
blob_offsets[1], file_size, blob_sizes[1],
|
||||
kNoCompression, nullptr /* prefetch_buffer */,
|
||||
&values[1], nullptr /* bytes_read */));
|
||||
ASSERT_OK(blob_source.GetBlob(
|
||||
read_options, keys[1], OpenInfoWithoutChecksum(file_number),
|
||||
blob_offsets[1], file_size, blob_sizes[1], kNoCompression,
|
||||
nullptr /* prefetch_buffer */, &values[1], nullptr /* bytes_read */));
|
||||
|
||||
// Release cache handle
|
||||
values[1].Reset();
|
||||
|
||||
// key0 should be filled to the primary cache from the blob file. key1
|
||||
// should be evicted and key1's dummy item is inserted into secondary cache.
|
||||
ASSERT_OK(blob_source.GetBlob(read_options, keys[0], file_number,
|
||||
ASSERT_OK(blob_source.GetBlob(read_options, keys[0],
|
||||
OpenInfoWithoutChecksum(file_number),
|
||||
blob_offsets[0], file_size, blob_sizes[0],
|
||||
kNoCompression, nullptr /* prefetch_buffer */,
|
||||
values.data(), nullptr /* bytes_read */));
|
||||
@@ -1354,10 +1371,10 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
|
||||
|
||||
// key0 should be evicted and is inserted into secondary cache.
|
||||
// key1 should be filled to the primary cache from the blob file.
|
||||
ASSERT_OK(blob_source.GetBlob(read_options, keys[1], file_number,
|
||||
blob_offsets[1], file_size, blob_sizes[1],
|
||||
kNoCompression, nullptr /* prefetch_buffer */,
|
||||
&values[1], nullptr /* bytes_read */));
|
||||
ASSERT_OK(blob_source.GetBlob(
|
||||
read_options, keys[1], OpenInfoWithoutChecksum(file_number),
|
||||
blob_offsets[1], file_size, blob_sizes[1], kNoCompression,
|
||||
nullptr /* prefetch_buffer */, &values[1], nullptr /* bytes_read */));
|
||||
ASSERT_EQ(values[1], blobs[1]);
|
||||
ASSERT_TRUE(
|
||||
blob_source.TEST_BlobInCache(file_number, file_size, blob_offsets[1]));
|
||||
@@ -1422,9 +1439,10 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
|
||||
// fetch key0 from the blob file to the primary cache.
|
||||
// key1 is evicted and inserted into the secondary cache.
|
||||
ASSERT_OK(blob_source.GetBlob(
|
||||
read_options, keys[0], file_number, blob_offsets[0], file_size,
|
||||
blob_sizes[0], kNoCompression, nullptr /* prefetch_buffer */,
|
||||
values.data(), nullptr /* bytes_read */));
|
||||
read_options, keys[0], OpenInfoWithoutChecksum(file_number),
|
||||
blob_offsets[0], file_size, blob_sizes[0], kNoCompression,
|
||||
nullptr /* prefetch_buffer */, values.data(),
|
||||
nullptr /* bytes_read */));
|
||||
ASSERT_EQ(values[0], blobs[0]);
|
||||
|
||||
// Release cache handle
|
||||
@@ -1606,8 +1624,8 @@ TEST_F(BlobSourceCacheReservationTest, SimpleCacheReservation) {
|
||||
|
||||
for (size_t i = 0; i < kNumBlobs; ++i) {
|
||||
ASSERT_OK(blob_source.GetBlob(
|
||||
read_options, keys_[i], kBlobFileNumber, blob_offsets[i],
|
||||
blob_file_size_, blob_sizes[i], kNoCompression,
|
||||
read_options, keys_[i], OpenInfoWithoutChecksum(kBlobFileNumber),
|
||||
blob_offsets[i], blob_file_size_, blob_sizes[i], kNoCompression,
|
||||
nullptr /* prefetch_buffer */, &values[i], nullptr /* bytes_read */));
|
||||
ASSERT_EQ(cache_res_mgr->GetTotalReservedCacheSize(), 0);
|
||||
ASSERT_EQ(cache_res_mgr->GetTotalMemoryUsed(), 0);
|
||||
@@ -1625,8 +1643,8 @@ TEST_F(BlobSourceCacheReservationTest, SimpleCacheReservation) {
|
||||
uint64_t blob_bytes = 0;
|
||||
for (size_t i = 0; i < kNumBlobs; ++i) {
|
||||
ASSERT_OK(blob_source.GetBlob(
|
||||
read_options, keys_[i], kBlobFileNumber, blob_offsets[i],
|
||||
blob_file_size_, blob_sizes[i], kNoCompression,
|
||||
read_options, keys_[i], OpenInfoWithoutChecksum(kBlobFileNumber),
|
||||
blob_offsets[i], blob_file_size_, blob_sizes[i], kNoCompression,
|
||||
nullptr /* prefetch_buffer */, &values[i], nullptr /* bytes_read */));
|
||||
|
||||
size_t charge = 0;
|
||||
@@ -1728,8 +1746,8 @@ TEST_F(BlobSourceCacheReservationTest, IncreaseCacheReservation) {
|
||||
|
||||
for (size_t i = 0; i < kNumBlobs; ++i) {
|
||||
ASSERT_OK(blob_source.GetBlob(
|
||||
read_options, keys_[i], kBlobFileNumber, blob_offsets[i],
|
||||
blob_file_size_, blob_sizes[i], kNoCompression,
|
||||
read_options, keys_[i], OpenInfoWithoutChecksum(kBlobFileNumber),
|
||||
blob_offsets[i], blob_file_size_, blob_sizes[i], kNoCompression,
|
||||
nullptr /* prefetch_buffer */, &values[i], nullptr /* bytes_read */));
|
||||
ASSERT_EQ(cache_res_mgr->GetTotalReservedCacheSize(), 0);
|
||||
ASSERT_EQ(cache_res_mgr->GetTotalMemoryUsed(), 0);
|
||||
@@ -1744,8 +1762,8 @@ TEST_F(BlobSourceCacheReservationTest, IncreaseCacheReservation) {
|
||||
uint64_t blob_bytes = 0;
|
||||
for (size_t i = 0; i < kNumBlobs; ++i) {
|
||||
ASSERT_OK(blob_source.GetBlob(
|
||||
read_options, keys_[i], kBlobFileNumber, blob_offsets[i],
|
||||
blob_file_size_, blob_sizes[i], kNoCompression,
|
||||
read_options, keys_[i], OpenInfoWithoutChecksum(kBlobFileNumber),
|
||||
blob_offsets[i], blob_file_size_, blob_sizes[i], kNoCompression,
|
||||
nullptr /* prefetch_buffer */, &values[i], nullptr /* bytes_read */));
|
||||
|
||||
// Release cache handle
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
@@ -19,15 +20,19 @@
|
||||
#include "db/column_family.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/db_with_timestamp_test_util.h"
|
||||
#include "env/composite_env_wrapper.h"
|
||||
#include "file/filename.h"
|
||||
#include "file/random_access_file_reader.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/file_system.h"
|
||||
#include "rocksdb/trace_reader_writer.h"
|
||||
#include "rocksdb/trace_record.h"
|
||||
#include "rocksdb/utilities/replayer.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/compression.h"
|
||||
#include "util/defer.h"
|
||||
#include "util/file_checksum_helper.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -38,6 +43,49 @@ class DBBlobBasicTest : public DBTestBase {
|
||||
: DBTestBase("db_blob_basic_test", /* env_do_fsync */ false) {}
|
||||
};
|
||||
|
||||
class BlobFileChecksumCapturingFS : public FileSystemWrapper {
|
||||
public:
|
||||
explicit BlobFileChecksumCapturingFS(const std::shared_ptr<FileSystem>& base)
|
||||
: FileSystemWrapper(base) {}
|
||||
|
||||
static const char* kClassName() { return "BlobFileChecksumCapturingFS"; }
|
||||
const char* Name() const override { return kClassName(); }
|
||||
|
||||
IOStatus NewRandomAccessFile(const std::string& fname,
|
||||
const FileOptions& opts,
|
||||
std::unique_ptr<FSRandomAccessFile>* result,
|
||||
IODebugContext* dbg) override {
|
||||
if (fname.find(".blob") != std::string::npos) {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
file_checksum_ = opts.file_checksum;
|
||||
file_checksum_func_name_ = opts.file_checksum_func_name;
|
||||
++capture_count_;
|
||||
}
|
||||
return target()->NewRandomAccessFile(fname, opts, result, dbg);
|
||||
}
|
||||
|
||||
std::string GetFileChecksum() {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
return file_checksum_;
|
||||
}
|
||||
|
||||
std::string GetFileChecksumFuncName() {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
return file_checksum_func_name_;
|
||||
}
|
||||
|
||||
int GetCaptureCount() {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
return capture_count_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex mu_;
|
||||
std::string file_checksum_;
|
||||
std::string file_checksum_func_name_;
|
||||
int capture_count_ = 0;
|
||||
};
|
||||
|
||||
TEST_F(DBBlobBasicTest, GetBlob) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
@@ -65,6 +113,41 @@ TEST_F(DBBlobBasicTest, GetBlob) {
|
||||
.IsIncomplete());
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, BlobFileChecksumInFileOptions) {
|
||||
auto capturing_fs =
|
||||
std::make_shared<BlobFileChecksumCapturingFS>(env_->GetFileSystem());
|
||||
std::unique_ptr<Env> env(new CompositeEnvWrapper(env_, capturing_fs));
|
||||
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.env = env.get();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
|
||||
Defer close_db([this]() { Close(); });
|
||||
Reopen(options);
|
||||
|
||||
constexpr char key[] = "key";
|
||||
constexpr char blob_value[] = "blob_value";
|
||||
ASSERT_OK(Put(key, blob_value));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
std::vector<ColumnFamilyMetaData> column_family_metadata;
|
||||
db_->GetAllColumnFamilyMetaData(&column_family_metadata);
|
||||
ASSERT_EQ(column_family_metadata.size(), 1);
|
||||
ASSERT_EQ(column_family_metadata[0].blob_files.size(), 1);
|
||||
const BlobMetaData& blob_metadata = column_family_metadata[0].blob_files[0];
|
||||
ASSERT_FALSE(blob_metadata.checksum_value.empty());
|
||||
ASSERT_EQ(blob_metadata.checksum_method, "FileChecksumCrc32c");
|
||||
|
||||
ASSERT_EQ(Get(key), blob_value);
|
||||
|
||||
ASSERT_GT(capturing_fs->GetCaptureCount(), 0);
|
||||
ASSERT_EQ(capturing_fs->GetFileChecksum(), blob_metadata.checksum_value);
|
||||
ASSERT_EQ(capturing_fs->GetFileChecksumFuncName(),
|
||||
blob_metadata.checksum_method);
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, BlobFileWritableFileMaxBufferSize) {
|
||||
constexpr uint64_t kBlobWriterBufferSize = 128 * 1024;
|
||||
|
||||
@@ -754,6 +837,15 @@ TEST_F(DBBlobBasicTest, MultiGetBlobsFromCache) {
|
||||
ASSERT_EQ(values[2], third_value);
|
||||
}
|
||||
|
||||
// Load the SST blocks into the block cache. The inlined key does not read
|
||||
// the blob file, so the blob cache stays empty.
|
||||
{
|
||||
ReadOptions fill_block_cache_options;
|
||||
std::string value;
|
||||
ASSERT_OK(db_->Get(fill_block_cache_options, first_key, &value));
|
||||
ASSERT_EQ(value, first_value);
|
||||
}
|
||||
|
||||
// Try again with no I/O allowed. The first (inlined) value should be
|
||||
// successfully read; however, the two blob values could only be read from the
|
||||
// blob file, so for those the read should return Incomplete.
|
||||
|
||||
@@ -40,6 +40,8 @@
|
||||
#include "rocksdb/utilities/replayer.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/compression.h"
|
||||
#include "util/defer.h"
|
||||
#include "util/file_checksum_helper.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -121,6 +123,59 @@ bool IsBlobFilePath(const std::string& fname) {
|
||||
file_type == kBlobFile;
|
||||
}
|
||||
|
||||
class BlobFileChecksumCapturingFileSystem : public FileSystemWrapper {
|
||||
public:
|
||||
explicit BlobFileChecksumCapturingFileSystem(
|
||||
const std::shared_ptr<FileSystem>& fs)
|
||||
: FileSystemWrapper(fs) {}
|
||||
|
||||
static const char* kClassName() {
|
||||
return "BlobFileChecksumCapturingFileSystem";
|
||||
}
|
||||
const char* Name() const override { return kClassName(); }
|
||||
|
||||
IOStatus NewRandomAccessFile(const std::string& fname,
|
||||
const FileOptions& opts,
|
||||
std::unique_ptr<FSRandomAccessFile>* result,
|
||||
IODebugContext* dbg) override {
|
||||
if (IsBlobFilePath(fname)) {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
file_checksum_ = opts.file_checksum;
|
||||
file_checksum_func_name_ = opts.file_checksum_func_name;
|
||||
++capture_count_;
|
||||
}
|
||||
return target()->NewRandomAccessFile(fname, opts, result, dbg);
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
file_checksum_.clear();
|
||||
file_checksum_func_name_.clear();
|
||||
capture_count_ = 0;
|
||||
}
|
||||
|
||||
std::string GetFileChecksum() const {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
return file_checksum_;
|
||||
}
|
||||
|
||||
std::string GetFileChecksumFuncName() const {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
return file_checksum_func_name_;
|
||||
}
|
||||
|
||||
int GetCaptureCount() const {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
return capture_count_;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::mutex mu_;
|
||||
std::string file_checksum_;
|
||||
std::string file_checksum_func_name_;
|
||||
int capture_count_ = 0;
|
||||
};
|
||||
|
||||
// Keeps track of when an active blob file stops being writer-visible so the
|
||||
// test filesystem can model current-size visibility only while the file remains
|
||||
// active.
|
||||
@@ -1755,6 +1810,54 @@ TEST_F(
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->value().ToString(), values[target_idx]);
|
||||
}
|
||||
|
||||
TEST_F(DBBlobDirectWriteTest, DirectWriteDelayedReadUsesFinalizedFileChecksum) {
|
||||
auto capturing_fs = std::make_shared<BlobFileChecksumCapturingFileSystem>(
|
||||
env_->GetFileSystem());
|
||||
std::unique_ptr<Env> env(new CompositeEnvWrapper(env_, capturing_fs));
|
||||
|
||||
Options options = GetDirectWriteOptions();
|
||||
options.env = env.get();
|
||||
options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
|
||||
Defer close_db([this]() { Close(); });
|
||||
Reopen(options);
|
||||
|
||||
const std::string key = "delayed-read-key";
|
||||
const std::string value(96, 'v');
|
||||
ASSERT_OK(Put(key, value));
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.allow_unprepared_value = true;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
iter->Seek(key);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key(), key);
|
||||
ASSERT_TRUE(iter->value().empty());
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
std::vector<ColumnFamilyMetaData> column_family_metadata;
|
||||
db_->GetAllColumnFamilyMetaData(&column_family_metadata);
|
||||
ASSERT_EQ(column_family_metadata.size(), 1);
|
||||
ASSERT_EQ(column_family_metadata[0].blob_files.size(), 1);
|
||||
const BlobMetaData& blob_metadata = column_family_metadata[0].blob_files[0];
|
||||
ASSERT_FALSE(blob_metadata.checksum_value.empty());
|
||||
ASSERT_EQ(blob_metadata.checksum_method, "FileChecksumCrc32c");
|
||||
|
||||
capturing_fs->Reset();
|
||||
ASSERT_TRUE(iter->PrepareValue());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->value().ToString(), value);
|
||||
ASSERT_GT(capturing_fs->GetCaptureCount(), 0);
|
||||
ASSERT_EQ(capturing_fs->GetFileChecksum(), blob_metadata.checksum_value);
|
||||
ASSERT_EQ(capturing_fs->GetFileChecksumFuncName(),
|
||||
blob_metadata.checksum_method);
|
||||
|
||||
const int capture_count = capturing_fs->GetCaptureCount();
|
||||
ASSERT_EQ(Get(key), value);
|
||||
ASSERT_EQ(capturing_fs->GetCaptureCount(), capture_count);
|
||||
}
|
||||
|
||||
TEST_F(DBBlobDirectWriteTest, OrderedTraceUsesLogicalBatchForBlobDirectWrite) {
|
||||
AssertOrderedTraceStoresLogicalPut(GetDirectWriteOptions(),
|
||||
/*expect_blob_files=*/true);
|
||||
|
||||
+3
-2
@@ -396,7 +396,7 @@ Status BuildTable(
|
||||
relevant_mapping,
|
||||
ioptions.compaction_style == CompactionStyle::kCompactionStyleFIFO
|
||||
? meta->file_creation_time
|
||||
: meta->oldest_ancester_time);
|
||||
: meta->oldest_ancestor_time);
|
||||
s = builder->Finish();
|
||||
}
|
||||
if (io_status->ok()) {
|
||||
@@ -452,7 +452,8 @@ Status BuildTable(
|
||||
WritableFileWriter::PrepareIOOptions(tboptions.write_options, opts);
|
||||
if (s.ok() && io_status->ok() && !empty) {
|
||||
StopWatch sw(ioptions.clock, ioptions.stats, TABLE_SYNC_MICROS);
|
||||
*io_status = file_writer->Sync(opts, ioptions.use_fsync);
|
||||
*io_status = ioptions.use_fsync ? file_writer->Fsync(opts)
|
||||
: file_writer->Sync(opts);
|
||||
}
|
||||
TEST_SYNC_POINT("BuildTable:BeforeCloseTableFile");
|
||||
if (s.ok() && io_status->ok() && !empty) {
|
||||
|
||||
@@ -195,6 +195,7 @@ using ROCKSDB_NAMESPACE::WALRecoveryMode;
|
||||
using ROCKSDB_NAMESPACE::WritableFile;
|
||||
using ROCKSDB_NAMESPACE::WriteBatch;
|
||||
using ROCKSDB_NAMESPACE::WriteBatchWithIndex;
|
||||
using ROCKSDB_NAMESPACE::WriteBufferFlushPolicy;
|
||||
using ROCKSDB_NAMESPACE::WriteBufferManager;
|
||||
using ROCKSDB_NAMESPACE::WriteOptions;
|
||||
using ROCKSDB_NAMESPACE::WriteStallCondition;
|
||||
@@ -5197,6 +5198,11 @@ uint64_t rocksdb_table_properties_column_family_id(
|
||||
return props->rep.column_family_id;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_table_properties_oldest_ancestor_time(
|
||||
const rocksdb_table_properties_t* props) {
|
||||
return props->rep.oldest_ancestor_time;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_table_properties_creation_time(
|
||||
const rocksdb_table_properties_t* props) {
|
||||
return props->rep.creation_time;
|
||||
@@ -5227,11 +5233,6 @@ uint64_t rocksdb_table_properties_fast_compression_estimated_data_size(
|
||||
return props->rep.fast_compression_estimated_data_size;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_table_properties_external_sst_file_global_seqno_offset(
|
||||
const rocksdb_table_properties_t* props) {
|
||||
return props->rep.external_sst_file_global_seqno_offset;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_table_properties_tail_start_offset(
|
||||
const rocksdb_table_properties_t* props) {
|
||||
return props->rep.tail_start_offset;
|
||||
@@ -7761,6 +7762,35 @@ rocksdb_write_buffer_manager_t* rocksdb_write_buffer_manager_create(
|
||||
return wbm;
|
||||
}
|
||||
|
||||
static bool rocksdb_write_buffer_manager_parse_flush_policy(
|
||||
int value, WriteBufferFlushPolicy* policy) {
|
||||
switch (value) {
|
||||
case rocksdb_write_buffer_manager_flush_policy_oldest:
|
||||
*policy = WriteBufferFlushPolicy::kFlushOldest;
|
||||
return true;
|
||||
case rocksdb_write_buffer_manager_flush_policy_largest:
|
||||
*policy = WriteBufferFlushPolicy::kFlushLargest;
|
||||
return true;
|
||||
case rocksdb_write_buffer_manager_flush_policy_largest_across_dbs:
|
||||
*policy = WriteBufferFlushPolicy::kFlushLargestAcrossDBs;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
rocksdb_write_buffer_manager_t* rocksdb_write_buffer_manager_create_with_policy(
|
||||
size_t buffer_size, bool allow_stall, int flush_policy) {
|
||||
WriteBufferFlushPolicy policy;
|
||||
if (!rocksdb_write_buffer_manager_parse_flush_policy(flush_policy, &policy)) {
|
||||
return nullptr;
|
||||
}
|
||||
rocksdb_write_buffer_manager_t* wbm = new rocksdb_write_buffer_manager_t;
|
||||
wbm->rep = std::make_shared<WriteBufferManager>(
|
||||
buffer_size, std::shared_ptr<Cache>{}, allow_stall, policy);
|
||||
return wbm;
|
||||
}
|
||||
|
||||
rocksdb_write_buffer_manager_t* rocksdb_write_buffer_manager_create_with_cache(
|
||||
size_t buffer_size, const rocksdb_cache_t* cache, bool allow_stall) {
|
||||
rocksdb_write_buffer_manager_t* wbm = new rocksdb_write_buffer_manager_t;
|
||||
@@ -7769,6 +7799,20 @@ rocksdb_write_buffer_manager_t* rocksdb_write_buffer_manager_create_with_cache(
|
||||
return wbm;
|
||||
}
|
||||
|
||||
rocksdb_write_buffer_manager_t*
|
||||
rocksdb_write_buffer_manager_create_with_cache_and_policy(
|
||||
size_t buffer_size, const rocksdb_cache_t* cache, bool allow_stall,
|
||||
int flush_policy) {
|
||||
WriteBufferFlushPolicy policy;
|
||||
if (!rocksdb_write_buffer_manager_parse_flush_policy(flush_policy, &policy)) {
|
||||
return nullptr;
|
||||
}
|
||||
rocksdb_write_buffer_manager_t* wbm = new rocksdb_write_buffer_manager_t;
|
||||
wbm->rep = std::make_shared<WriteBufferManager>(buffer_size, cache->rep,
|
||||
allow_stall, policy);
|
||||
return wbm;
|
||||
}
|
||||
|
||||
void rocksdb_write_buffer_manager_destroy(rocksdb_write_buffer_manager_t* wbm) {
|
||||
delete wbm;
|
||||
}
|
||||
@@ -7809,6 +7853,19 @@ ROCKSDB_LIBRARY_API void rocksdb_write_buffer_manager_set_allow_stall(
|
||||
wbm->rep->SetAllowStall(new_allow_stall);
|
||||
}
|
||||
|
||||
int rocksdb_write_buffer_manager_flush_policy(
|
||||
rocksdb_write_buffer_manager_t* wbm) {
|
||||
return static_cast<int>(wbm->rep->flush_policy());
|
||||
}
|
||||
|
||||
void rocksdb_write_buffer_manager_set_flush_policy(
|
||||
rocksdb_write_buffer_manager_t* wbm, int flush_policy) {
|
||||
WriteBufferFlushPolicy policy;
|
||||
if (rocksdb_write_buffer_manager_parse_flush_policy(flush_policy, &policy)) {
|
||||
wbm->rep->SetFlushPolicy(policy);
|
||||
}
|
||||
}
|
||||
|
||||
rocksdb_sst_file_manager_t* rocksdb_sst_file_manager_create(
|
||||
rocksdb_env_t* env) {
|
||||
rocksdb_sst_file_manager_t* sfm = new rocksdb_sst_file_manager_t;
|
||||
@@ -11276,16 +11333,6 @@ unsigned char rocksdb_ingestexternalfileoptions_get_ingest_behind(
|
||||
return opt->rep.ingest_behind;
|
||||
}
|
||||
|
||||
void rocksdb_ingestexternalfileoptions_set_write_global_seqno(
|
||||
rocksdb_ingestexternalfileoptions_t* opt, unsigned char v) {
|
||||
opt->rep.write_global_seqno = v;
|
||||
}
|
||||
|
||||
unsigned char rocksdb_ingestexternalfileoptions_get_write_global_seqno(
|
||||
rocksdb_ingestexternalfileoptions_t* opt) {
|
||||
return opt->rep.write_global_seqno;
|
||||
}
|
||||
|
||||
void rocksdb_ingestexternalfileoptions_set_verify_checksums_before_ingest(
|
||||
rocksdb_ingestexternalfileoptions_t* opt, unsigned char v) {
|
||||
opt->rep.verify_checksums_before_ingest = v;
|
||||
|
||||
+30
@@ -6716,6 +6716,36 @@ int main(int argc, char** argv) {
|
||||
300 == rocksdb_write_buffer_manager_buffer_size(write_buffer_manager));
|
||||
|
||||
rocksdb_write_buffer_manager_destroy(write_buffer_manager);
|
||||
|
||||
write_buffer_manager = rocksdb_write_buffer_manager_create_with_policy(
|
||||
200, true,
|
||||
rocksdb_write_buffer_manager_flush_policy_largest_across_dbs);
|
||||
CheckCondition(write_buffer_manager != NULL);
|
||||
CheckCondition(
|
||||
rocksdb_write_buffer_manager_flush_policy_largest_across_dbs ==
|
||||
rocksdb_write_buffer_manager_flush_policy(write_buffer_manager));
|
||||
rocksdb_write_buffer_manager_set_flush_policy(
|
||||
write_buffer_manager,
|
||||
rocksdb_write_buffer_manager_flush_policy_largest);
|
||||
CheckCondition(
|
||||
rocksdb_write_buffer_manager_flush_policy_largest ==
|
||||
rocksdb_write_buffer_manager_flush_policy(write_buffer_manager));
|
||||
rocksdb_write_buffer_manager_set_flush_policy(write_buffer_manager, 999);
|
||||
CheckCondition(
|
||||
rocksdb_write_buffer_manager_flush_policy_largest ==
|
||||
rocksdb_write_buffer_manager_flush_policy(write_buffer_manager));
|
||||
rocksdb_write_buffer_manager_destroy(write_buffer_manager);
|
||||
|
||||
write_buffer_manager =
|
||||
rocksdb_write_buffer_manager_create_with_cache_and_policy(
|
||||
200, lru, true,
|
||||
rocksdb_write_buffer_manager_flush_policy_largest_across_dbs);
|
||||
CheckCondition(write_buffer_manager != NULL);
|
||||
rocksdb_write_buffer_manager_destroy(write_buffer_manager);
|
||||
|
||||
write_buffer_manager =
|
||||
rocksdb_write_buffer_manager_create_with_policy(200, true, 999);
|
||||
CheckCondition(write_buffer_manager == NULL);
|
||||
rocksdb_cache_destroy(lru);
|
||||
}
|
||||
|
||||
|
||||
+116
-2
@@ -33,6 +33,7 @@
|
||||
#include "db/write_controller.h"
|
||||
#include "file/sst_file_manager_impl.h"
|
||||
#include "logging/logging.h"
|
||||
#include "memtable/flush_initiator.h"
|
||||
#include "monitoring/thread_status_util.h"
|
||||
#include "options/options_helper.h"
|
||||
#include "port/port.h"
|
||||
@@ -672,7 +673,7 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
new InternalStats(ioptions_.num_levels, ioptions_.clock, this));
|
||||
table_cache_.reset(new TableCache(ioptions_, file_options, _table_cache,
|
||||
block_cache_tracer, io_tracer,
|
||||
db_session_id, fast_sst_open));
|
||||
db_session_id, fast_sst_open, name_));
|
||||
blob_file_cache_.reset(
|
||||
new BlobFileCache(_table_cache, &ioptions(), soptions(), id_,
|
||||
internal_stats_->GetBlobFileReadHist(), io_tracer));
|
||||
@@ -834,6 +835,11 @@ void ColumnFamilyData::SetDropped() {
|
||||
// can't drop default CF
|
||||
assert(id_ != 0);
|
||||
dropped_ = true;
|
||||
// A tracker can still contain accounting published while a different WBM
|
||||
// policy was active. Cleanup therefore cannot depend on the current policy.
|
||||
if (mem_ != nullptr) {
|
||||
mem_->StopWBMTracking();
|
||||
}
|
||||
write_controller_token_.reset();
|
||||
|
||||
// remove from column_family_set
|
||||
@@ -1246,7 +1252,10 @@ uint64_t ColumnFamilyData::GetLiveSstFilesSize() const {
|
||||
MemTable* ColumnFamilyData::ConstructNewMemtable(
|
||||
const MutableCFOptions& mutable_cf_options, SequenceNumber earliest_seq) {
|
||||
return new MemTable(internal_comparator_, ioptions_, mutable_cf_options,
|
||||
write_buffer_manager_, earliest_seq, id_);
|
||||
write_buffer_manager_, earliest_seq, id_,
|
||||
column_family_set_ != nullptr
|
||||
? column_family_set_->flush_initiator()
|
||||
: nullptr);
|
||||
}
|
||||
|
||||
void ColumnFamilyData::CreateNewMemtable(SequenceNumber earliest_seq) {
|
||||
@@ -1899,6 +1908,102 @@ size_t ColumnFamilySet::NumberOfColumnFamilies() const {
|
||||
return column_families_.size();
|
||||
}
|
||||
|
||||
void ColumnFamilySet::RefreshFlushableMemAccounting() {
|
||||
if (flush_initiator_ == nullptr || write_buffer_manager_ == nullptr ||
|
||||
!write_buffer_manager_->ShouldTrackFlushInitiator()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (bulk_flushable_mem_accounting_depth_ > 0) {
|
||||
bulk_flushable_mem_accounting_dirty_ = true;
|
||||
flush_initiator_->InvalidateLargestFlushableCFMem();
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep candidate readers from consuming a mixture of the previous snapshot
|
||||
// and the values published by this rebuild.
|
||||
flush_initiator_->InvalidateLargestFlushableCFMem();
|
||||
constexpr int kMaxRefreshAttempts = 3;
|
||||
for (int attempt = 0; attempt < kMaxRefreshAttempts; ++attempt) {
|
||||
const uint64_t update_seq =
|
||||
flush_initiator_->GetFlushableMemUpdateSequence();
|
||||
bool has_mutable_mem = false;
|
||||
bool has_ineligible_cf = false;
|
||||
size_t largest = 0;
|
||||
size_t waiting_immutable_mem = 0;
|
||||
for (auto cfd : *this) {
|
||||
if (cfd->IsDropped() || !cfd->initialized()) {
|
||||
continue;
|
||||
}
|
||||
const bool mutable_not_empty = !cfd->mem()->IsEmpty();
|
||||
if (mutable_not_empty) {
|
||||
cfd->mem()->RefreshWBMTracking();
|
||||
}
|
||||
const bool has_mutable =
|
||||
mutable_not_empty && cfd->mem()->IsWBMTrackingActive();
|
||||
has_mutable_mem = has_mutable_mem || has_mutable;
|
||||
if (flush_initiator_->UsesTotalMutableMem()) {
|
||||
waiting_immutable_mem +=
|
||||
cfd->imm()->WBMTrackedUnstartedMemTablesMemoryUsage();
|
||||
continue;
|
||||
}
|
||||
if (cfd->imm()->IsFlushPendingOrRunning()) {
|
||||
has_ineligible_cf = true;
|
||||
continue;
|
||||
}
|
||||
const size_t mutable_mem =
|
||||
has_mutable ? cfd->mem()->WBMTrackedMemoryUsage() : 0;
|
||||
const size_t immutable_mem =
|
||||
cfd->imm()->WBMTrackedUnflushedMemTablesMemoryUsage();
|
||||
waiting_immutable_mem += immutable_mem;
|
||||
largest = std::max(largest, mutable_mem + immutable_mem);
|
||||
}
|
||||
if (flush_initiator_->UsesTotalMutableMem()) {
|
||||
flush_initiator_->SetHasIneligibleCF(false);
|
||||
flush_initiator_->SetHasFlushableCF(has_mutable_mem ||
|
||||
waiting_immutable_mem > 0);
|
||||
if (flush_initiator_->TrySetLargestFlushableCFMem(
|
||||
0, waiting_immutable_mem, update_seq)) {
|
||||
write_buffer_manager_->NotifyFlushInitiatorChanged();
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
flush_initiator_->SetHasIneligibleCF(has_ineligible_cf);
|
||||
flush_initiator_->SetHasFlushableCF(largest > 0);
|
||||
if (flush_initiator_->TrySetLargestFlushableCFMem(
|
||||
largest, waiting_immutable_mem, update_seq)) {
|
||||
write_buffer_manager_->NotifyFlushInitiatorChanged();
|
||||
return;
|
||||
}
|
||||
}
|
||||
flush_initiator_->InvalidateLargestFlushableCFMem();
|
||||
}
|
||||
|
||||
void ColumnFamilySet::BeginBulkFlushableMemAccountingUpdate() {
|
||||
++bulk_flushable_mem_accounting_depth_;
|
||||
if (bulk_flushable_mem_accounting_depth_ == 1) {
|
||||
bulk_flushable_mem_accounting_dirty_ = true;
|
||||
if (flush_initiator_ != nullptr && write_buffer_manager_ != nullptr &&
|
||||
write_buffer_manager_->ShouldTrackFlushInitiator()) {
|
||||
flush_initiator_->InvalidateLargestFlushableCFMem();
|
||||
write_buffer_manager_->NotifyFlushInitiatorChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ColumnFamilySet::EndBulkFlushableMemAccountingUpdate() {
|
||||
assert(bulk_flushable_mem_accounting_depth_ > 0);
|
||||
--bulk_flushable_mem_accounting_depth_;
|
||||
if (bulk_flushable_mem_accounting_depth_ == 0 &&
|
||||
bulk_flushable_mem_accounting_dirty_) {
|
||||
bulk_flushable_mem_accounting_dirty_ = false;
|
||||
TEST_SYNC_POINT(
|
||||
"ColumnFamilySet::EndBulkFlushableMemAccountingUpdate:Refresh");
|
||||
RefreshFlushableMemAccounting();
|
||||
}
|
||||
}
|
||||
|
||||
// under a DB mutex AND write thread
|
||||
ColumnFamilyData* ColumnFamilySet::CreateColumnFamily(
|
||||
const std::string& name, uint32_t id, Version* dummy_versions,
|
||||
@@ -1939,6 +2044,15 @@ void ColumnFamilySet::RemoveColumnFamily(ColumnFamilyData* cfd) {
|
||||
column_families_.erase(cfd->GetName());
|
||||
running_ts_sz_.erase(cf_id);
|
||||
ts_sz_for_record_.erase(cf_id);
|
||||
if (bulk_flushable_mem_accounting_depth_ > 0) {
|
||||
bulk_flushable_mem_accounting_dirty_ = true;
|
||||
if (flush_initiator_ != nullptr) {
|
||||
flush_initiator_->InvalidateLargestFlushableCFMem();
|
||||
}
|
||||
return;
|
||||
}
|
||||
TEST_SYNC_POINT("ColumnFamilySet::RemoveColumnFamily:Refresh");
|
||||
RefreshFlushableMemAccounting();
|
||||
}
|
||||
|
||||
// under a DB mutex OR from a write thread
|
||||
|
||||
@@ -52,6 +52,7 @@ struct SuperVersionContext;
|
||||
class BlobFileCache;
|
||||
class BlobFilePartitionManager;
|
||||
class BlobSource;
|
||||
class FlushInitiator;
|
||||
|
||||
extern const double kIncSlowdownRatio;
|
||||
// This file contains a list of data structures for managing column family
|
||||
@@ -871,6 +872,19 @@ class ColumnFamilySet {
|
||||
|
||||
WriteBufferManager* write_buffer_manager() { return write_buffer_manager_; }
|
||||
|
||||
void SetFlushInitiator(FlushInitiator* flush_initiator) {
|
||||
flush_initiator_ = flush_initiator;
|
||||
}
|
||||
|
||||
FlushInitiator* flush_initiator() const { return flush_initiator_; }
|
||||
|
||||
void RefreshFlushableMemAccounting();
|
||||
|
||||
// Coalesces accounting rebuilds while a caller removes multiple column
|
||||
// families. Both methods require the DB mutex.
|
||||
void BeginBulkFlushableMemAccountingUpdate();
|
||||
void EndBulkFlushableMemAccountingUpdate();
|
||||
|
||||
WriteController* write_controller() { return write_controller_; }
|
||||
|
||||
private:
|
||||
@@ -898,6 +912,9 @@ class ColumnFamilySet {
|
||||
// column families with non-zero user-defined timestamp size.
|
||||
UnorderedMap<uint32_t, size_t> ts_sz_for_record_;
|
||||
|
||||
int bulk_flushable_mem_accounting_depth_ = 0;
|
||||
bool bulk_flushable_mem_accounting_dirty_ = false;
|
||||
|
||||
uint32_t max_column_family_;
|
||||
const FileOptions file_options_;
|
||||
|
||||
@@ -912,6 +929,7 @@ class ColumnFamilySet {
|
||||
const ImmutableDBOptions* const db_options_;
|
||||
Cache* table_cache_;
|
||||
WriteBufferManager* write_buffer_manager_;
|
||||
FlushInitiator* flush_initiator_ = nullptr;
|
||||
WriteController* write_controller_;
|
||||
BlockCacheTracer* const block_cache_tracer_;
|
||||
std::shared_ptr<IOTracer> io_tracer_;
|
||||
|
||||
@@ -774,6 +774,8 @@ TEST_P(ColumnFamilyTest, BulkAddDrop) {
|
||||
constexpr int kNumCF = 1000;
|
||||
ColumnFamilyOptions cf_options;
|
||||
WriteOptions write_options;
|
||||
db_options_.write_buffer_manager = std::make_shared<WriteBufferManager>(
|
||||
1 << 30, nullptr, false, WriteBufferFlushPolicy::kFlushLargestAcrossDBs);
|
||||
Open();
|
||||
std::vector<std::string> cf_names;
|
||||
std::vector<ColumnFamilyHandle*> cf_handles;
|
||||
@@ -784,7 +786,22 @@ TEST_P(ColumnFamilyTest, BulkAddDrop) {
|
||||
for (int i = 1; i <= kNumCF; i++) {
|
||||
ASSERT_OK(db_->Put(write_options, cf_handles[i - 1], "foo", "bar"));
|
||||
}
|
||||
std::atomic<int> per_removal_refreshes{0};
|
||||
std::atomic<int> bulk_refreshes{0};
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"ColumnFamilySet::RemoveColumnFamily:Refresh",
|
||||
[&](void*) { ++per_removal_refreshes; });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"ColumnFamilySet::EndBulkFlushableMemAccountingUpdate:Refresh",
|
||||
[&](void*) { ++bulk_refreshes; });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
Defer cleanup_sync_points([] {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
});
|
||||
ASSERT_OK(db_->DropColumnFamilies(cf_handles));
|
||||
EXPECT_EQ(0, per_removal_refreshes.load());
|
||||
EXPECT_EQ(1, bulk_refreshes.load());
|
||||
std::vector<ColumnFamilyDescriptor> cf_descriptors;
|
||||
for (auto* handle : cf_handles) {
|
||||
delete handle;
|
||||
@@ -798,7 +815,10 @@ TEST_P(ColumnFamilyTest, BulkAddDrop) {
|
||||
for (int i = 1; i <= kNumCF; i++) {
|
||||
ASSERT_OK(db_->Put(write_options, cf_handles[i - 1], "foo", "bar"));
|
||||
}
|
||||
bulk_refreshes.store(0);
|
||||
ASSERT_OK(db_->DropColumnFamilies(cf_handles));
|
||||
EXPECT_EQ(0, per_removal_refreshes.load());
|
||||
EXPECT_EQ(1, bulk_refreshes.load());
|
||||
for (auto* handle : cf_handles) {
|
||||
delete handle;
|
||||
}
|
||||
@@ -3167,6 +3187,32 @@ TEST_P(ColumnFamilyTest, CreateDropAndDestroy) {
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(cfh));
|
||||
}
|
||||
|
||||
TEST_P(ColumnFamilyTest, DropWaitsForUnorderedMemtableWrite) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::WriteImpl:UnorderedWriteAfterWriteWAL",
|
||||
"ColumnFamilyTest::DropWaitsForUnorderedMemtableWrite:AfterWAL"},
|
||||
{"DBImpl::WaitForPendingWrites:BeforeBlock",
|
||||
"DBImpl::WriteImpl:BeforeUnorderedWriteMemtable"}});
|
||||
|
||||
db_options_.unordered_write = true;
|
||||
Open();
|
||||
ColumnFamilyHandle* cfh;
|
||||
ASSERT_OK(db_->CreateColumnFamily(ColumnFamilyOptions(), "yoyo", &cfh));
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
port::Thread writer(
|
||||
[&] { ASSERT_OK(db_->Put(WriteOptions(), cfh, "foo", "bar")); });
|
||||
|
||||
TEST_SYNC_POINT(
|
||||
"ColumnFamilyTest::DropWaitsForUnorderedMemtableWrite:AfterWAL");
|
||||
ASSERT_OK(db_->DropColumnFamily(cfh));
|
||||
writer.join();
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(cfh));
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_P(ColumnFamilyTest, CreateDropAndDestroyWithoutFileDeletion) {
|
||||
ColumnFamilyHandle* cfh;
|
||||
Open();
|
||||
|
||||
@@ -986,9 +986,9 @@ uint64_t Compaction::MaxInputFileNewestKeyTime(const InternalKey* start,
|
||||
return newest_key_time;
|
||||
}
|
||||
|
||||
uint64_t Compaction::MinInputFileOldestAncesterTime(
|
||||
uint64_t Compaction::MinInputFileOldestAncestorTime(
|
||||
const InternalKey* start, const InternalKey* end) const {
|
||||
uint64_t min_oldest_ancester_time = std::numeric_limits<uint64_t>::max();
|
||||
uint64_t min_oldest_ancestor_time = std::numeric_limits<uint64_t>::max();
|
||||
const InternalKeyComparator& icmp =
|
||||
column_family_data()->internal_comparator();
|
||||
for (const auto& level_files : inputs_) {
|
||||
@@ -999,14 +999,14 @@ uint64_t Compaction::MinInputFileOldestAncesterTime(
|
||||
if (end != nullptr && icmp.Compare(file->smallest, *end) > 0) {
|
||||
continue;
|
||||
}
|
||||
uint64_t oldest_ancester_time = file->TryGetOldestAncesterTime();
|
||||
if (oldest_ancester_time != 0) {
|
||||
min_oldest_ancester_time =
|
||||
std::min(min_oldest_ancester_time, oldest_ancester_time);
|
||||
uint64_t oldest_ancestor_time = file->TryGetOldestAncestorTime();
|
||||
if (oldest_ancestor_time != 0) {
|
||||
min_oldest_ancestor_time =
|
||||
std::min(min_oldest_ancestor_time, oldest_ancestor_time);
|
||||
}
|
||||
}
|
||||
}
|
||||
return min_oldest_ancester_time;
|
||||
return min_oldest_ancestor_time;
|
||||
}
|
||||
|
||||
uint64_t Compaction::MinInputFileEpochNumber() const {
|
||||
|
||||
@@ -438,8 +438,8 @@ class Compaction {
|
||||
const InternalKey* end) const;
|
||||
|
||||
// start and end are sub compact range. Null if no boundary.
|
||||
// This is used to filter out some input files' ancester's time range.
|
||||
uint64_t MinInputFileOldestAncesterTime(const InternalKey* start,
|
||||
// This is used to filter out some input files' ancestor's time range.
|
||||
uint64_t MinInputFileOldestAncestorTime(const InternalKey* start,
|
||||
const InternalKey* end) const;
|
||||
// Return the minimum epoch number among
|
||||
// input files' associated with this compaction
|
||||
|
||||
@@ -2160,20 +2160,20 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
|
||||
if (s.ok()) {
|
||||
// With accurate smallest and largest key, we can get a slightly more
|
||||
// accurate oldest ancester time.
|
||||
// This makes oldest ancester time in manifest more accurate than in
|
||||
// accurate oldest ancestor time.
|
||||
// This makes oldest ancestor time in manifest more accurate than in
|
||||
// table properties. Not sure how to resolve it.
|
||||
if (meta->smallest.size() > 0 && meta->largest.size() > 0) {
|
||||
uint64_t refined_oldest_ancester_time;
|
||||
uint64_t refined_oldest_ancestor_time;
|
||||
Slice new_smallest = meta->smallest.user_key();
|
||||
Slice new_largest = meta->largest.user_key();
|
||||
if (!new_largest.empty() && !new_smallest.empty()) {
|
||||
refined_oldest_ancester_time =
|
||||
sub_compact->compaction->MinInputFileOldestAncesterTime(
|
||||
refined_oldest_ancestor_time =
|
||||
sub_compact->compaction->MinInputFileOldestAncestorTime(
|
||||
&(meta->smallest), &(meta->largest));
|
||||
if (refined_oldest_ancester_time !=
|
||||
if (refined_oldest_ancestor_time !=
|
||||
std::numeric_limits<uint64_t>::max()) {
|
||||
meta->oldest_ancester_time = refined_oldest_ancester_time;
|
||||
meta->oldest_ancestor_time = refined_oldest_ancestor_time;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2555,7 +2555,7 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
|
||||
return s;
|
||||
}
|
||||
|
||||
// Try to figure out the output file's oldest ancester time.
|
||||
// Try to figure out the output file's oldest ancestor time.
|
||||
int64_t temp_current_time = 0;
|
||||
auto get_time_status = db_options_.clock->GetCurrentTime(&temp_current_time);
|
||||
// Safe to proceed even if GetCurrentTime fails. So, log and proceed.
|
||||
@@ -2572,14 +2572,14 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
|
||||
if (sub_compact->end.has_value()) {
|
||||
tmp_end.SetMinPossibleForUserKey(*(sub_compact->end));
|
||||
}
|
||||
uint64_t oldest_ancester_time =
|
||||
sub_compact->compaction->MinInputFileOldestAncesterTime(
|
||||
uint64_t oldest_ancestor_time =
|
||||
sub_compact->compaction->MinInputFileOldestAncestorTime(
|
||||
sub_compact->start.has_value() ? &tmp_start : nullptr,
|
||||
sub_compact->end.has_value() ? &tmp_end : nullptr);
|
||||
if (oldest_ancester_time == std::numeric_limits<uint64_t>::max()) {
|
||||
if (oldest_ancestor_time == std::numeric_limits<uint64_t>::max()) {
|
||||
// TODO: fix DBSSTTest.GetTotalSstFilesSize and use
|
||||
// kUnknownOldestAncesterTime
|
||||
oldest_ancester_time = current_time;
|
||||
// kUnknownOldestAncestorTime
|
||||
oldest_ancestor_time = current_time;
|
||||
}
|
||||
|
||||
uint64_t newest_key_time = sub_compact->compaction->MaxInputFileNewestKeyTime(
|
||||
@@ -2592,7 +2592,7 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
|
||||
FileMetaData meta;
|
||||
meta.fd = FileDescriptor(file_number,
|
||||
sub_compact->compaction->output_path_id(), 0);
|
||||
meta.oldest_ancester_time = oldest_ancester_time;
|
||||
meta.oldest_ancestor_time = oldest_ancestor_time;
|
||||
meta.file_creation_time = current_time;
|
||||
meta.epoch_number = epoch_number;
|
||||
meta.temperature = temperature;
|
||||
@@ -3268,7 +3268,8 @@ Status CompactionJob::PersistSubcompactionProgress(
|
||||
s = WritableFileWriter::PrepareIOOptions(write_options, opts);
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = compaction_progress_writer_->file()->Sync(opts, db_options_.use_fsync);
|
||||
s = db_options_.use_fsync ? compaction_progress_writer_->file()->Fsync(opts)
|
||||
: compaction_progress_writer_->file()->Sync(opts);
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
|
||||
@@ -508,7 +508,7 @@ class CompactionJob {
|
||||
std::atomic<int>* num_running_remote_compactions_;
|
||||
|
||||
// Stores the sequence number to time mapping gathered from all input files
|
||||
// it also collects the smallest_seqno -> oldest_ancester_time from the SST.
|
||||
// it also collects the smallest_seqno -> oldest_ancestor_time from the SST.
|
||||
SeqnoToTimeMapping seqno_to_time_mapping_;
|
||||
|
||||
// Max seqno that can be zeroed out in last level, including for preserving
|
||||
@@ -641,7 +641,7 @@ struct CompactionServiceOutputFile {
|
||||
SequenceNumber largest_seqno{};
|
||||
std::string smallest_internal_key;
|
||||
std::string largest_internal_key;
|
||||
uint64_t oldest_ancester_time = kUnknownOldestAncesterTime;
|
||||
uint64_t oldest_ancestor_time = kUnknownOldestAncestorTime;
|
||||
uint64_t file_creation_time = kUnknownFileCreationTime;
|
||||
uint64_t epoch_number = kUnknownEpochNumber;
|
||||
std::string file_checksum = kUnknownFileChecksum;
|
||||
@@ -657,7 +657,7 @@ struct CompactionServiceOutputFile {
|
||||
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_ancester_time,
|
||||
std::string _largest_internal_key, 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,
|
||||
@@ -670,7 +670,7 @@ struct CompactionServiceOutputFile {
|
||||
largest_seqno(largest),
|
||||
smallest_internal_key(std::move(_smallest_internal_key)),
|
||||
largest_internal_key(std::move(_largest_internal_key)),
|
||||
oldest_ancester_time(_oldest_ancester_time),
|
||||
oldest_ancestor_time(_oldest_ancestor_time),
|
||||
file_creation_time(_file_creation_time),
|
||||
epoch_number(_epoch_number),
|
||||
file_checksum(_file_checksum),
|
||||
|
||||
@@ -387,7 +387,7 @@ class CompactionJobTestBase : public testing::Test {
|
||||
edit.AddFile(
|
||||
level, file_number, 0, file_size, smallest_key, largest_key,
|
||||
smallest_seqno, largest_seqno, false, Temperature::kUnknown,
|
||||
oldest_blob_file_number, kUnknownOldestAncesterTime,
|
||||
oldest_blob_file_number, kUnknownOldestAncestorTime,
|
||||
kUnknownFileCreationTime,
|
||||
versions_->GetColumnFamilySet()->GetDefault()->NewEpochNumber(),
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName, kNullUniqueId64x2,
|
||||
@@ -1702,7 +1702,7 @@ TEST_F(CompactionJobTest, ResultSerialization) {
|
||||
rnd.Uniform(kStrMaxLen)) /* smallest_internal_key */,
|
||||
rnd.RandomBinaryString(
|
||||
rnd.Uniform(kStrMaxLen)) /* largest_internal_key */,
|
||||
rnd64.Uniform(UINT64_MAX) /* oldest_ancester_time */,
|
||||
rnd64.Uniform(UINT64_MAX) /* oldest_ancestor_time */,
|
||||
rnd64.Uniform(UINT64_MAX) /* file_creation_time */,
|
||||
rnd64.Uniform(UINT64_MAX) /* epoch_number */,
|
||||
file_checksum /* file_checksum */,
|
||||
|
||||
@@ -32,7 +32,7 @@ Status CompactionOutputs::Finish(
|
||||
meta->fd.largest_seqno);
|
||||
relevant_mapping.SetCapacity(kMaxSeqnoTimePairsPerSST);
|
||||
builder_->SetSeqnoTimeTableProperties(relevant_mapping,
|
||||
meta->oldest_ancester_time);
|
||||
meta->oldest_ancestor_time);
|
||||
s = builder_->Finish();
|
||||
|
||||
} else {
|
||||
@@ -73,7 +73,7 @@ IOStatus CompactionOutputs::WriterSyncClose(const Status& input_status,
|
||||
WriteOptions(Env::IOActivity::kCompaction), opts);
|
||||
if (input_status.ok() && io_s.ok()) {
|
||||
StopWatch sw(clock, statistics, COMPACTION_OUTFILE_SYNC_MICROS);
|
||||
io_s = file_writer_->Sync(opts, use_fsync);
|
||||
io_s = use_fsync ? file_writer_->Fsync(opts) : file_writer_->Sync(opts);
|
||||
}
|
||||
if (input_status.ok() && io_s.ok()) {
|
||||
io_s = file_writer_->Close(opts);
|
||||
@@ -817,10 +817,10 @@ void CompactionOutputs::FillFilesToCutForTtl() {
|
||||
*(compaction_->inputs(compaction_->num_input_levels() - 1));
|
||||
for (FileMetaData* file : olevel) {
|
||||
// Worth filtering out by start and end?
|
||||
uint64_t oldest_ancester_time = file->TryGetOldestAncesterTime();
|
||||
uint64_t oldest_ancestor_time = file->TryGetOldestAncestorTime();
|
||||
// We put old files if they are not too small to prevent a flood
|
||||
// of small files.
|
||||
if (oldest_ancester_time < old_age_thres &&
|
||||
if (oldest_ancestor_time < old_age_thres &&
|
||||
file->fd.GetFileSize() >
|
||||
compaction_->mutable_cf_options().target_file_size_base / 2) {
|
||||
files_to_cut_for_ttl_.push_back(file);
|
||||
|
||||
@@ -371,7 +371,7 @@ class CompactionOutputs {
|
||||
// repetitive comparison in ShouldStopBefore()
|
||||
const InternalKey* local_output_split_key_ = nullptr;
|
||||
|
||||
// Some identified files with old oldest ancester time and the range should be
|
||||
// Some identified files with old oldest ancestor time and the range should be
|
||||
// isolated out so that the output file(s) in that range can be merged down
|
||||
// for TTL and clear the timestamps for the range.
|
||||
std::vector<FileMetaData*> files_to_cut_for_ttl_;
|
||||
|
||||
@@ -105,9 +105,10 @@ Compaction* FIFOCompactionPicker::PickTTLCompaction(
|
||||
TableReader* reader = f->fd.pinned_reader.Get();
|
||||
if (reader != nullptr && reader->GetTableProperties() != nullptr) {
|
||||
uint64_t newest_key_time = f->TryGetNewestKeyTime();
|
||||
uint64_t creation_time = reader->GetTableProperties()->creation_time;
|
||||
uint64_t oldest_ancestor_time =
|
||||
reader->GetTableProperties()->oldest_ancestor_time;
|
||||
uint64_t est_newest_key_time = newest_key_time == kUnknownNewestKeyTime
|
||||
? creation_time
|
||||
? oldest_ancestor_time
|
||||
: newest_key_time;
|
||||
if (est_newest_key_time == kUnknownNewestKeyTime ||
|
||||
est_newest_key_time >= (current_time - mutable_cf_options.ttl)) {
|
||||
@@ -165,13 +166,13 @@ Compaction* FIFOCompactionPicker::PickTTLCompaction(
|
||||
for (const auto& f : inputs[0].files) {
|
||||
assert(f);
|
||||
uint64_t newest_key_time = f->TryGetNewestKeyTime();
|
||||
uint64_t creation_time = 0;
|
||||
uint64_t oldest_ancestor_time = 0;
|
||||
TableReader* reader = f->fd.pinned_reader.Get();
|
||||
if (reader != nullptr && reader->GetTableProperties() != nullptr) {
|
||||
creation_time = reader->GetTableProperties()->creation_time;
|
||||
oldest_ancestor_time = reader->GetTableProperties()->oldest_ancestor_time;
|
||||
}
|
||||
uint64_t est_newest_key_time = newest_key_time == kUnknownNewestKeyTime
|
||||
? creation_time
|
||||
? oldest_ancestor_time
|
||||
: newest_key_time;
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[%s] FIFO compaction: picking file %" PRIu64
|
||||
|
||||
@@ -120,7 +120,7 @@ class CompactionPickerTestBase : public testing::Test {
|
||||
SequenceNumber smallest_seq = 100, SequenceNumber largest_seq = 100,
|
||||
size_t compensated_file_size = 0, bool marked_for_compact = false,
|
||||
Temperature temperature = Temperature::kUnknown,
|
||||
uint64_t oldest_ancestor_time = kUnknownOldestAncesterTime,
|
||||
uint64_t oldest_ancestor_time = kUnknownOldestAncestorTime,
|
||||
uint64_t newest_key_time = kUnknownNewestKeyTime,
|
||||
Slice ts_of_smallest = Slice(), Slice ts_of_largest = Slice(),
|
||||
uint64_t epoch_number = kUnknownEpochNumber) {
|
||||
@@ -162,15 +162,15 @@ class CompactionPickerTestBase : public testing::Test {
|
||||
FileMetaData* f = new FileMetaData(
|
||||
file_number, path_id, file_size, smallest_ikey, largest_ikey,
|
||||
smallest_seq, largest_seq, marked_for_compact, temperature,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncestorTime,
|
||||
kUnknownFileCreationTime, epoch_number, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0, 0,
|
||||
true /* user_defined_timestamps_persisted */, "" /* min timestamp */,
|
||||
"" /* max timestamp */);
|
||||
f->compensated_file_size =
|
||||
(compensated_file_size != 0) ? compensated_file_size : file_size;
|
||||
// oldest_ancester_time is only used if newest_key_time is not available
|
||||
f->oldest_ancester_time = oldest_ancestor_time;
|
||||
// oldest_ancestor_time is only used if newest_key_time is not available
|
||||
f->oldest_ancestor_time = oldest_ancestor_time;
|
||||
// Set min/max timestamps for UDT support
|
||||
if (!ts_of_smallest.empty()) {
|
||||
f->min_timestamp = ts_of_smallest.ToString();
|
||||
@@ -325,7 +325,7 @@ class CompactionPickerU64TsTest : public CompactionPickerTestBase {
|
||||
/*smallest_seq=*/10, /*largest_seq=*/40,
|
||||
/*compensated_file_size=*/1000,
|
||||
/*marked_for_compact=*/false, Temperature::kUnknown,
|
||||
kUnknownOldestAncesterTime, kUnknownNewestKeyTime, ts_small, ts_large);
|
||||
kUnknownOldestAncestorTime, kUnknownNewestKeyTime, ts_small, ts_large);
|
||||
|
||||
std::string full_history_ts_low = MakeU64Timestamp(full_history_ts_low_val);
|
||||
|
||||
@@ -350,12 +350,12 @@ class CompactionPickerU64TsTest : public CompactionPickerTestBase {
|
||||
/*smallest_seq=*/100, /*largest_seq=*/100,
|
||||
/*compensated_file_size=*/file_size,
|
||||
/*marked_for_compact=*/false, Temperature::kUnknown,
|
||||
kUnknownOldestAncesterTime, kUnknownNewestKeyTime, ts1, ts2);
|
||||
kUnknownOldestAncestorTime, kUnknownNewestKeyTime, ts1, ts2);
|
||||
Add(0, 2U, "150", "250", file_size, /*path_id=*/0,
|
||||
/*smallest_seq=*/200, /*largest_seq=*/200,
|
||||
/*compensated_file_size=*/file_size,
|
||||
/*marked_for_compact=*/false, Temperature::kUnknown,
|
||||
kUnknownOldestAncesterTime, kUnknownNewestKeyTime, ts1, ts2);
|
||||
kUnknownOldestAncestorTime, kUnknownNewestKeyTime, ts1, ts2);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1452,14 +1452,14 @@ TEST_F(CompactionPickerTest, FIFOToCold1) {
|
||||
static_cast<uint64_t>(current_time) - kColdThreshold;
|
||||
Add(0 /* level */, 4U /* file_number */, "260", "300", 1 * kFileSize, 0,
|
||||
2500, 2600, 0, true, Temperature::kUnknown,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncesterTime
|
||||
newestKeyTimeKnown ? kUnknownOldestAncestorTime
|
||||
: threshold_time - 2000 /* oldest_ancestor_time */,
|
||||
newestKeyTimeKnown ? threshold_time - 2000
|
||||
: kUnknownNewestKeyTime /* newest_key_time */);
|
||||
// Qualifies for compaction to kCold.
|
||||
Add(0, 3U, "200", "300", 4 * kFileSize, 0, 2300, 2400, 0, true,
|
||||
Temperature::kUnknown,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time - 3000,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time - 3000,
|
||||
newestKeyTimeKnown ? threshold_time - 3000
|
||||
: kUnknownNewestKeyTime /* newest_key_time */);
|
||||
UpdateVersionStorageInfo();
|
||||
@@ -1504,32 +1504,32 @@ TEST_F(CompactionPickerTest, FIFOToColdMaxCompactionSize) {
|
||||
static_cast<uint64_t>(current_time) - kColdThreshold;
|
||||
Add(0, 6U, "240", "290", 2 * kFileSize, 0, 2900, 3000, 0, true,
|
||||
Temperature::kUnknown,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncesterTime
|
||||
newestKeyTimeKnown ? kUnknownOldestAncestorTime
|
||||
: static_cast<uint64_t>(current_time) - 100,
|
||||
newestKeyTimeKnown ? static_cast<uint64_t>(current_time) - 100
|
||||
: kUnknownNewestKeyTime);
|
||||
Add(0, 5U, "240", "290", 2 * kFileSize, 0, 2700, 2800, 0, true,
|
||||
Temperature::kUnknown,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time + 100,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time + 100,
|
||||
newestKeyTimeKnown ? threshold_time + 100 : kUnknownNewestKeyTime);
|
||||
Add(0, 4U, "260", "300", 1 * kFileSize, 0, 2500, 2600, 0, true,
|
||||
Temperature::kUnknown,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time - 2000,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time - 2000,
|
||||
newestKeyTimeKnown ? threshold_time - 2000 : kUnknownNewestKeyTime);
|
||||
// The following two files qualify for compaction to kCold.
|
||||
// But only the last two should be included to respect
|
||||
// `max_compaction_bytes`.
|
||||
Add(0, 3U, "200", "300", 4 * kFileSize, 0, 2300, 2400, 0, true,
|
||||
Temperature::kUnknown,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time - 3000,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time - 3000,
|
||||
newestKeyTimeKnown ? threshold_time - 3000 : kUnknownNewestKeyTime);
|
||||
Add(0, 2U, "200", "300", 4 * kFileSize, 0, 2100, 2200, 0, true,
|
||||
Temperature::kUnknown,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time - 4000,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time - 4000,
|
||||
newestKeyTimeKnown ? threshold_time - 4000 : kUnknownNewestKeyTime);
|
||||
Add(0, 1U, "200", "300", 4 * kFileSize, 0, 2000, 2100, 0, true,
|
||||
Temperature::kUnknown,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time - 5000,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time - 5000,
|
||||
newestKeyTimeKnown ? threshold_time - 5000 : kUnknownNewestKeyTime);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
@@ -1574,30 +1574,30 @@ TEST_F(CompactionPickerTest, FIFOToColdWithExistingCold) {
|
||||
static_cast<uint64_t>(current_time) - kColdThreshold;
|
||||
Add(0, 6U, "240", "290", 2 * kFileSize, 0, 2900, 3000, 0, true,
|
||||
Temperature::kUnknown,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncesterTime
|
||||
newestKeyTimeKnown ? kUnknownOldestAncestorTime
|
||||
: static_cast<uint64_t>(current_time) - 100,
|
||||
newestKeyTimeKnown ? static_cast<uint64_t>(current_time) - 100
|
||||
: kUnknownNewestKeyTime);
|
||||
Add(0, 5U, "240", "290", 2 * kFileSize, 0, 2700, 2800, 0, true,
|
||||
Temperature::kUnknown,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time + 100,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time + 100,
|
||||
newestKeyTimeKnown ? threshold_time + 100 : kUnknownNewestKeyTime);
|
||||
Add(0, 4U, "260", "300", 1 * kFileSize, 0, 2500, 2600, 0, true,
|
||||
Temperature::kUnknown,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time - 2000,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time - 2000,
|
||||
newestKeyTimeKnown ? threshold_time - 2000 : kUnknownNewestKeyTime);
|
||||
// The following two files qualify for compaction to kCold.
|
||||
Add(0, 3U, "200", "300", 4 * kFileSize, 0, 2300, 2400, 0, true,
|
||||
Temperature::kUnknown,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time - 3000,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time - 3000,
|
||||
newestKeyTimeKnown ? threshold_time - 3000 : kUnknownNewestKeyTime);
|
||||
Add(0, 2U, "200", "300", 4 * kFileSize, 0, 2100, 2200, 0, true,
|
||||
Temperature::kUnknown,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time - 4000,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time - 4000,
|
||||
newestKeyTimeKnown ? threshold_time - 4000 : kUnknownNewestKeyTime);
|
||||
Add(0, 1U, "200", "300", 4 * kFileSize, 0, 2000, 2100, 0, true,
|
||||
Temperature::kCold,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time - 5000,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time - 5000,
|
||||
newestKeyTimeKnown ? threshold_time - 5000 : kUnknownNewestKeyTime);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
@@ -1642,30 +1642,30 @@ TEST_F(CompactionPickerTest, FIFOToColdWithHotBetweenCold) {
|
||||
static_cast<uint64_t>(current_time) - kColdThreshold;
|
||||
Add(0, 6U, "240", "290", 2 * kFileSize, 0, 2900, 3000, 0, true,
|
||||
Temperature::kUnknown,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncesterTime
|
||||
newestKeyTimeKnown ? kUnknownOldestAncestorTime
|
||||
: static_cast<uint64_t>(current_time) - 100,
|
||||
newestKeyTimeKnown ? static_cast<uint64_t>(current_time) - 100
|
||||
: kUnknownNewestKeyTime);
|
||||
Add(0, 5U, "240", "290", 2 * kFileSize, 0, 2700, 2800, 0, true,
|
||||
Temperature::kUnknown,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time + 100,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time + 100,
|
||||
newestKeyTimeKnown ? threshold_time + 100 : kUnknownNewestKeyTime);
|
||||
Add(0, 4U, "260", "300", 1 * kFileSize, 0, 2500, 2600, 0, true,
|
||||
Temperature::kUnknown,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time - 2000,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time - 2000,
|
||||
newestKeyTimeKnown ? threshold_time - 2000 : kUnknownNewestKeyTime);
|
||||
Add(0, 3U, "200", "300", 4 * kFileSize, 0, 2300, 2400, 0, true,
|
||||
Temperature::kCold,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time - 3000,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time - 3000,
|
||||
newestKeyTimeKnown ? threshold_time - 3000 : kUnknownNewestKeyTime);
|
||||
// Qualifies for compaction to kCold.
|
||||
Add(0, 2U, "200", "300", 4 * kFileSize, 0, 2100, 2200, 0, true,
|
||||
Temperature::kUnknown,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time - 4000,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time - 4000,
|
||||
newestKeyTimeKnown ? threshold_time - 4000 : kUnknownNewestKeyTime);
|
||||
Add(0, 1U, "200", "300", 4 * kFileSize, 0, 2000, 2100, 0, true,
|
||||
Temperature::kCold,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time - 5000,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time - 5000,
|
||||
newestKeyTimeKnown ? threshold_time - 5000 : kUnknownNewestKeyTime);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
@@ -1714,36 +1714,36 @@ TEST_F(CompactionPickerTest, FIFOToHotAndWarm) {
|
||||
static_cast<uint64_t>(current_time) - kWarmThreshold;
|
||||
Add(0, 6U, "240", "290", 2 * kFileSize, 0, 2900, 3000, 0, true,
|
||||
Temperature::kUnknown,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncesterTime
|
||||
newestKeyTimeKnown ? kUnknownOldestAncestorTime
|
||||
: static_cast<uint64_t>(current_time) - 100,
|
||||
newestKeyTimeKnown ? static_cast<uint64_t>(current_time) - 100
|
||||
: kUnknownNewestKeyTime);
|
||||
Add(0, 5U, "240", "290", 2 * kFileSize, 0, 2700, 2800, 0, true,
|
||||
Temperature::kUnknown,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncesterTime
|
||||
newestKeyTimeKnown ? kUnknownOldestAncestorTime
|
||||
: hot_threshold_time + 100,
|
||||
newestKeyTimeKnown ? hot_threshold_time + 100 : kUnknownNewestKeyTime);
|
||||
Add(0, 4U, "260", "300", 1 * kFileSize, 0, 2500, 2600, 0, true,
|
||||
Temperature::kUnknown,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncesterTime
|
||||
newestKeyTimeKnown ? kUnknownOldestAncestorTime
|
||||
: hot_threshold_time - 200,
|
||||
newestKeyTimeKnown ? hot_threshold_time - 200 : kUnknownNewestKeyTime);
|
||||
// Qualifies for Hot
|
||||
Add(0, 3U, "200", "300", 4 * kFileSize, 0, 2300, 2400, 0, true,
|
||||
Temperature::kUnknown,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncesterTime
|
||||
newestKeyTimeKnown ? kUnknownOldestAncestorTime
|
||||
: warm_threshold_time - 100,
|
||||
newestKeyTimeKnown ? warm_threshold_time - 100 : kUnknownNewestKeyTime);
|
||||
// Qualifies for Warm
|
||||
Add(0, 2U, "200", "300", 4 * kFileSize, 0, 2100, 2200, 0, true,
|
||||
Temperature::kUnknown,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncesterTime
|
||||
newestKeyTimeKnown ? kUnknownOldestAncestorTime
|
||||
: warm_threshold_time - 4000,
|
||||
newestKeyTimeKnown ? warm_threshold_time - 4000
|
||||
: kUnknownNewestKeyTime);
|
||||
Add(0, 1U, "200", "300", 4 * kFileSize, 0, 2000, 2100, 0, true,
|
||||
Temperature::kUnknown,
|
||||
newestKeyTimeKnown ? kUnknownOldestAncesterTime
|
||||
newestKeyTimeKnown ? kUnknownOldestAncestorTime
|
||||
: warm_threshold_time - 5000,
|
||||
newestKeyTimeKnown ? warm_threshold_time - 5000
|
||||
: kUnknownNewestKeyTime);
|
||||
@@ -2490,7 +2490,7 @@ TEST_F(CompactionPickerTest, FileTtlBoosterLargeNumLevels) {
|
||||
FileTtlBooster booster(kCurrentTime, /*ttl=*/2048,
|
||||
/*num_non_empty_levels=*/100, /*level=*/1);
|
||||
FileMetaData meta;
|
||||
meta.oldest_ancester_time = kCurrentTime - 1023;
|
||||
meta.oldest_ancestor_time = kCurrentTime - 1023;
|
||||
ASSERT_EQ(1, booster.GetBoostScore(&meta));
|
||||
}
|
||||
|
||||
@@ -2509,47 +2509,47 @@ TEST_F(CompactionPickerTest, FileTtlBooster) {
|
||||
FileTtlBooster booster(kCurrentTime, 2048, 7, 3);
|
||||
|
||||
// Not triggering if the file is younger than ttl/2
|
||||
meta.oldest_ancester_time = kCurrentTime - 1023;
|
||||
meta.oldest_ancestor_time = kCurrentTime - 1023;
|
||||
ASSERT_EQ(1, booster.GetBoostScore(&meta));
|
||||
meta.oldest_ancester_time = kCurrentTime - 1024;
|
||||
meta.oldest_ancestor_time = kCurrentTime - 1024;
|
||||
ASSERT_EQ(1, booster.GetBoostScore(&meta));
|
||||
meta.oldest_ancester_time = kCurrentTime + 10;
|
||||
meta.oldest_ancestor_time = kCurrentTime + 10;
|
||||
ASSERT_EQ(1, booster.GetBoostScore(&meta));
|
||||
|
||||
// Within one boosting step
|
||||
meta.oldest_ancester_time = kCurrentTime - (1024 + 120 + 6);
|
||||
meta.oldest_ancestor_time = kCurrentTime - (1024 + 120 + 6);
|
||||
ASSERT_EQ(1, booster.GetBoostScore(&meta));
|
||||
|
||||
// One boosting step
|
||||
meta.oldest_ancester_time = kCurrentTime - (1024 + 120 + 7);
|
||||
meta.oldest_ancestor_time = kCurrentTime - (1024 + 120 + 7);
|
||||
ASSERT_EQ(2, booster.GetBoostScore(&meta));
|
||||
meta.oldest_ancester_time = kCurrentTime - (1024 + 120 + 8);
|
||||
meta.oldest_ancestor_time = kCurrentTime - (1024 + 120 + 8);
|
||||
ASSERT_EQ(2, booster.GetBoostScore(&meta));
|
||||
|
||||
// Multiple boosting steps
|
||||
meta.oldest_ancester_time = kCurrentTime - (1024 + 120 + 30);
|
||||
meta.oldest_ancestor_time = kCurrentTime - (1024 + 120 + 30);
|
||||
ASSERT_EQ(5, booster.GetBoostScore(&meta));
|
||||
|
||||
// Very high boosting steps
|
||||
meta.oldest_ancester_time = kCurrentTime - (1024 + 120 + 700);
|
||||
meta.oldest_ancestor_time = kCurrentTime - (1024 + 120 + 700);
|
||||
ASSERT_EQ(101, booster.GetBoostScore(&meta));
|
||||
}
|
||||
{
|
||||
// Test second last level
|
||||
FileTtlBooster booster(kCurrentTime, 2048, 7, 5);
|
||||
meta.oldest_ancester_time = kCurrentTime - (1024 + 480);
|
||||
meta.oldest_ancestor_time = kCurrentTime - (1024 + 480);
|
||||
ASSERT_EQ(1, booster.GetBoostScore(&meta));
|
||||
meta.oldest_ancester_time = kCurrentTime - (1024 + 480 + 60);
|
||||
meta.oldest_ancestor_time = kCurrentTime - (1024 + 480 + 60);
|
||||
ASSERT_EQ(3, booster.GetBoostScore(&meta));
|
||||
}
|
||||
{
|
||||
// Test last level
|
||||
FileTtlBooster booster(kCurrentTime, 2048, 7, 6);
|
||||
meta.oldest_ancester_time = kCurrentTime - (1024 + 480);
|
||||
meta.oldest_ancestor_time = kCurrentTime - (1024 + 480);
|
||||
ASSERT_EQ(1, booster.GetBoostScore(&meta));
|
||||
meta.oldest_ancester_time = kCurrentTime - (1024 + 480 + 60);
|
||||
meta.oldest_ancestor_time = kCurrentTime - (1024 + 480 + 60);
|
||||
ASSERT_EQ(1, booster.GetBoostScore(&meta));
|
||||
meta.oldest_ancester_time = kCurrentTime - 3000;
|
||||
meta.oldest_ancestor_time = kCurrentTime - 3000;
|
||||
ASSERT_EQ(1, booster.GetBoostScore(&meta));
|
||||
}
|
||||
}
|
||||
@@ -3577,21 +3577,21 @@ TEST_F(CompactionPickerTest, UniversalMarkedCompactionFullOverlap) {
|
||||
|
||||
Add(0, 1U, "150", "200", kFileSize, 0, 500, 550, /*compensated_file_size*/ 0,
|
||||
/*marked_for_compact*/ false, /* temperature*/ Temperature::kUnknown,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
|
||||
/*newest_key_time*/ kUnknownNewestKeyTime,
|
||||
/*ts_of_smallest*/ Slice(), /*ts_of_largest*/ Slice(),
|
||||
/*epoch_number*/ 3);
|
||||
Add(0, 2U, "201", "250", 2 * kFileSize, 0, 401, 450,
|
||||
/*compensated_file_size*/ 0, /*marked_for_compact*/ false,
|
||||
/* temperature*/ Temperature::kUnknown,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
|
||||
/*newest_key_time*/ kUnknownNewestKeyTime,
|
||||
/*ts_of_smallest*/ Slice(), /*ts_of_largest*/ Slice(),
|
||||
/*epoch_number*/ 2);
|
||||
Add(0, 4U, "260", "300", 4 * kFileSize, 0, 260, 300,
|
||||
/*compensated_file_size*/ 0, /*marked_for_compact*/ false,
|
||||
/* temperature*/ Temperature::kUnknown,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
|
||||
/*newest_key_time*/ kUnknownNewestKeyTime,
|
||||
/*ts_of_smallest*/ Slice(), /*ts_of_largest*/ Slice(),
|
||||
/*epoch_number*/ 1);
|
||||
@@ -3619,7 +3619,7 @@ TEST_F(CompactionPickerTest, UniversalMarkedCompactionFullOverlap) {
|
||||
// Simulate a flush and mark the file for compaction
|
||||
Add(0, 7U, "150", "200", kFileSize, 0, 551, 600, 0, true,
|
||||
/* temperature*/ Temperature::kUnknown,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
|
||||
/*newest_key_time*/ kUnknownNewestKeyTime,
|
||||
/*ts_of_smallest*/ Slice(), /*ts_of_largest*/ Slice(),
|
||||
/*epoch_number*/ 4);
|
||||
@@ -3647,7 +3647,7 @@ TEST_F(CompactionPickerTest, UniversalMarkedCompactionFullOverlap2) {
|
||||
// Mark file number 4 for compaction
|
||||
Add(0, 4U, "260", "300", 4 * kFileSize, 0, 260, 300, 0, true,
|
||||
/* temperature*/ Temperature::kUnknown,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
|
||||
/*newest_key_time*/ kUnknownNewestKeyTime,
|
||||
/*ts_of_smallest*/ Slice(), /*ts_of_largest*/ Slice(),
|
||||
/*epoch_number*/ 1);
|
||||
@@ -3674,14 +3674,14 @@ TEST_F(CompactionPickerTest, UniversalMarkedCompactionFullOverlap2) {
|
||||
AddVersionStorage();
|
||||
Add(0, 1U, "150", "200", kFileSize, 0, 500, 550, /*compensated_file_size*/ 0,
|
||||
/*marked_for_compact*/ false, /* temperature*/ Temperature::kUnknown,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
|
||||
/*newest_key_time*/ kUnknownNewestKeyTime,
|
||||
/*ts_of_smallest*/ Slice(), /*ts_of_largest*/ Slice(),
|
||||
/*epoch_number*/ 3);
|
||||
Add(0, 2U, "201", "250", 2 * kFileSize, 0, 401, 450,
|
||||
/*compensated_file_size*/ 0, /*marked_for_compact*/ false,
|
||||
/* temperature*/ Temperature::kUnknown,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
|
||||
/*newest_key_time*/ kUnknownNewestKeyTime,
|
||||
/*ts_of_smallest*/ Slice(), /*ts_of_largest*/ Slice(),
|
||||
/*epoch_number*/ 2);
|
||||
@@ -3859,27 +3859,27 @@ TEST_F(CompactionPickerTest, UniversalMarkedL0Overlap2) {
|
||||
Add(0, 4U, "260", "300", 1 * kFileSize, 0, 260, 300,
|
||||
/*compensated_file_size*/ 0, /*marked_for_compact*/ false,
|
||||
/* temperature*/ Temperature::kUnknown,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
|
||||
/*newest_key_time*/ kUnknownNewestKeyTime,
|
||||
/*ts_of_smallest*/ Slice(), /*ts_of_largest*/ Slice(),
|
||||
/*epoch_number*/ 4);
|
||||
Add(0, 5U, "240", "290", 2 * kFileSize, 0, 201, 250, 0, true,
|
||||
/* temperature*/ Temperature::kUnknown,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
|
||||
/*newest_key_time*/ kUnknownNewestKeyTime,
|
||||
/*ts_of_smallest*/ Slice(), /*ts_of_largest*/ Slice(),
|
||||
/*epoch_number*/ 3);
|
||||
Add(0, 3U, "301", "350", 4 * kFileSize, 0, 101, 150,
|
||||
/*compensated_file_size*/ 0, /*marked_for_compact*/ false,
|
||||
/* temperature*/ Temperature::kUnknown,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
|
||||
/*newest_key_time*/ kUnknownNewestKeyTime,
|
||||
/*ts_of_smallest*/ Slice(), /*ts_of_largest*/ Slice(),
|
||||
/*epoch_number*/ 2);
|
||||
Add(0, 6U, "501", "750", 8 * kFileSize, 0, 50, 100,
|
||||
/*compensated_file_size*/ 0, /*marked_for_compact*/ false,
|
||||
/* temperature*/ Temperature::kUnknown,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
|
||||
/*newest_key_time*/ kUnknownNewestKeyTime,
|
||||
/*ts_of_smallest*/ Slice(), /*ts_of_largest*/ Slice(),
|
||||
/*epoch_number*/ 1);
|
||||
@@ -3906,14 +3906,14 @@ TEST_F(CompactionPickerTest, UniversalMarkedL0Overlap2) {
|
||||
Add(0, 1U, "150", "200", kFileSize, 0, 500, 550, /*compensated_file_size*/ 0,
|
||||
/*marked_for_compact*/ false,
|
||||
/* temperature*/ Temperature::kUnknown,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
|
||||
/*newest_key_time*/ kUnknownNewestKeyTime,
|
||||
/*ts_of_smallest*/ Slice(), /*ts_of_largest*/ Slice(),
|
||||
/*epoch_number*/ 6);
|
||||
Add(0, 2U, "201", "250", kFileSize, 0, 401, 450, /*compensated_file_size*/ 0,
|
||||
/*marked_for_compact*/ false,
|
||||
/* temperature*/ Temperature::kUnknown,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
|
||||
/*newest_key_time*/ kUnknownNewestKeyTime,
|
||||
/*ts_of_smallest*/ Slice(), /*ts_of_largest*/ Slice(),
|
||||
/*epoch_number*/ 5);
|
||||
@@ -4149,7 +4149,7 @@ TEST_F(CompactionPickerU64TsTest, Overlap) {
|
||||
/*file_size=*/1U, /*path_id=*/0,
|
||||
/*smallest_seq=*/100, /*largest_seq=*/100, /*compensated_file_size=*/0,
|
||||
/*marked_for_compact=*/false, /*temperature=*/Temperature::kUnknown,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
|
||||
/*newest_key_time*/ kUnknownNewestKeyTime, ts1, ts2);
|
||||
UpdateVersionStorageInfo();
|
||||
}
|
||||
@@ -4231,12 +4231,12 @@ TEST_F(CompactionPickerU64TsTest, CannotTrivialMoveUniversal) {
|
||||
Add(1, 1U, "150", "150", kFileSize, /*path_id=*/0, /*smallest_seq=*/100,
|
||||
/*largest_seq=*/100, /*compensated_file_size=*/kFileSize,
|
||||
/*marked_for_compact=*/false, Temperature::kUnknown,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
|
||||
/*newest_key_time*/ kUnknownNewestKeyTime, ts1, ts2);
|
||||
Add(2, 2U, "150", "150", kFileSize, /*path_id=*/0, /*smallest_seq=*/100,
|
||||
/*largest_seq=*/100, /*compensated_file_size=*/kFileSize,
|
||||
/*marked_for_compact=*/false, Temperature::kUnknown,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
|
||||
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
|
||||
/*newest_key_time*/ kUnknownNewestKeyTime, ts3, ts4);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
@@ -5033,12 +5033,12 @@ TEST_F(CompactionPickerTest, StandaloneRangeDeletionOnlyPicksOlderFiles) {
|
||||
// Create L0 files with overlapping ranges
|
||||
// File 1: newest regular file (epoch 5), keys [100, 200]
|
||||
Add(0, 1U, "100", "200", 1U, 0, 100, 100, 0, false, Temperature::kUnknown,
|
||||
kUnknownOldestAncesterTime, kUnknownNewestKeyTime, Slice(), Slice(), 5);
|
||||
kUnknownOldestAncestorTime, kUnknownNewestKeyTime, Slice(), Slice(), 5);
|
||||
|
||||
// File 2: standalone range deletion (epoch 4), keys [150, 250]
|
||||
// This file should be marked as having only range deletions
|
||||
Add(0, 2U, "150", "250", 1U, 0, 200, 200, 0, true, Temperature::kUnknown,
|
||||
kUnknownOldestAncesterTime, kUnknownNewestKeyTime, Slice(), Slice(), 4);
|
||||
kUnknownOldestAncestorTime, kUnknownNewestKeyTime, Slice(), Slice(), 4);
|
||||
|
||||
// Manually set file 2 as standalone range deletion
|
||||
FileMetaData* range_del_file = file_map_[2U].first;
|
||||
@@ -5169,7 +5169,7 @@ TEST_F(CompactionPickerU64TsTest,
|
||||
/*smallest_seq=*/10, /*largest_seq=*/40,
|
||||
/*compensated_file_size=*/1000,
|
||||
/*marked_for_compact=*/false, Temperature::kUnknown,
|
||||
kUnknownOldestAncesterTime, kUnknownNewestKeyTime, ts_small, ts_large);
|
||||
kUnknownOldestAncestorTime, kUnknownNewestKeyTime, ts_small, ts_large);
|
||||
|
||||
// Update version storage with empty full_history_ts_low
|
||||
UpdateVersionStorageInfo();
|
||||
@@ -5209,7 +5209,7 @@ TEST_F(CompactionPickerU64TsTest,
|
||||
/*smallest_seq=*/10, /*largest_seq=*/40,
|
||||
/*compensated_file_size=*/1000,
|
||||
/*marked_for_compact=*/false, Temperature::kUnknown,
|
||||
kUnknownOldestAncesterTime, kUnknownNewestKeyTime, ts_small, ts_large);
|
||||
kUnknownOldestAncestorTime, kUnknownNewestKeyTime, ts_small, ts_large);
|
||||
|
||||
// Simulate a file whose min/max timestamp was never recorded.
|
||||
ASSERT_EQ(1U, vstorage_->LevelFiles(5).size());
|
||||
@@ -5249,7 +5249,7 @@ TEST_F(CompactionPickerU64TsTest,
|
||||
/*smallest_seq=*/10, /*largest_seq=*/40,
|
||||
/*compensated_file_size=*/1000,
|
||||
/*marked_for_compact=*/false, Temperature::kUnknown,
|
||||
kUnknownOldestAncesterTime, kUnknownNewestKeyTime, ts_small, ts_large);
|
||||
kUnknownOldestAncestorTime, kUnknownNewestKeyTime, ts_small, ts_large);
|
||||
|
||||
// Simulate a file whose min/max timestamp was never recorded.
|
||||
ASSERT_EQ(1U, vstorage_->LevelFiles(5).size());
|
||||
@@ -5715,13 +5715,13 @@ TEST_F(CompactionPickerTest, FIFOTTLBlobEstimationSingleLevel) {
|
||||
|
||||
uint64_t recent_time = static_cast<uint64_t>(time(nullptr));
|
||||
Add(0, 1U, "100", "200", 50 * 1024, 0, 100, 100, 0, false,
|
||||
Temperature::kUnknown, kUnknownOldestAncesterTime, recent_time);
|
||||
Temperature::kUnknown, kUnknownOldestAncestorTime, recent_time);
|
||||
Add(0, 2U, "200", "300", 50 * 1024, 0, 100, 100, 0, false,
|
||||
Temperature::kUnknown, kUnknownOldestAncesterTime, recent_time);
|
||||
Temperature::kUnknown, kUnknownOldestAncestorTime, recent_time);
|
||||
Add(0, 3U, "300", "400", 50 * 1024, 0, 100, 100, 0, false,
|
||||
Temperature::kUnknown, kUnknownOldestAncesterTime, 1);
|
||||
Temperature::kUnknown, kUnknownOldestAncestorTime, 1);
|
||||
Add(0, 4U, "400", "500", 50 * 1024, 0, 100, 100, 0, false,
|
||||
Temperature::kUnknown, kUnknownOldestAncesterTime, 1);
|
||||
Temperature::kUnknown, kUnknownOldestAncestorTime, 1);
|
||||
if (blob_total > 0) {
|
||||
AddBlobFile(100, blob_total / 2);
|
||||
AddBlobFile(101, blob_total / 2);
|
||||
@@ -5794,13 +5794,13 @@ TEST_F(CompactionPickerTest, FIFOTTLBlobEstimationMultiLevel) {
|
||||
uint64_t recent_time = static_cast<uint64_t>(time(nullptr));
|
||||
// L0 files: 2 recent, 2 expired
|
||||
Add(0, 1U, "100", "200", 50 * 1024, 0, 100, 100, 0, false,
|
||||
Temperature::kUnknown, kUnknownOldestAncesterTime, recent_time);
|
||||
Temperature::kUnknown, kUnknownOldestAncestorTime, recent_time);
|
||||
Add(0, 2U, "200", "300", 50 * 1024, 0, 100, 100, 0, false,
|
||||
Temperature::kUnknown, kUnknownOldestAncesterTime, recent_time);
|
||||
Temperature::kUnknown, kUnknownOldestAncestorTime, recent_time);
|
||||
Add(0, 3U, "300", "400", 50 * 1024, 0, 100, 100, 0, false,
|
||||
Temperature::kUnknown, kUnknownOldestAncesterTime, 1);
|
||||
Temperature::kUnknown, kUnknownOldestAncestorTime, 1);
|
||||
Add(0, 4U, "400", "500", 50 * 1024, 0, 100, 100, 0, false,
|
||||
Temperature::kUnknown, kUnknownOldestAncesterTime, 1);
|
||||
Temperature::kUnknown, kUnknownOldestAncestorTime, 1);
|
||||
// L2 legacy migration file
|
||||
Add(2, 10U, "100", "600", 200 * 1024);
|
||||
// Blob files (associated with ALL levels)
|
||||
|
||||
@@ -271,7 +271,7 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
file.smallest_seqno, file.largest_seqno);
|
||||
meta.smallest.DecodeFrom(file.smallest_internal_key);
|
||||
meta.largest.DecodeFrom(file.largest_internal_key);
|
||||
meta.oldest_ancester_time = file.oldest_ancester_time;
|
||||
meta.oldest_ancestor_time = file.oldest_ancestor_time;
|
||||
meta.file_creation_time = file.file_creation_time;
|
||||
meta.epoch_number = file.epoch_number;
|
||||
meta.file_checksum = file.file_checksum;
|
||||
@@ -469,7 +469,7 @@ Status CompactionServiceCompactionJob::Run() {
|
||||
MakeTableFileName(meta.fd.GetNumber()), meta.fd.GetFileSize(),
|
||||
meta.fd.smallest_seqno, meta.fd.largest_seqno,
|
||||
meta.smallest.Encode().ToString(), meta.largest.Encode().ToString(),
|
||||
meta.oldest_ancester_time, meta.file_creation_time, meta.epoch_number,
|
||||
meta.oldest_ancestor_time, meta.file_creation_time, meta.epoch_number,
|
||||
meta.file_checksum, meta.file_checksum_func_name,
|
||||
output_file.validator.GetHash(), meta.marked_for_compaction,
|
||||
meta.unique_id, *output_file.table_properties,
|
||||
@@ -601,8 +601,9 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
{offsetof(struct CompactionServiceOutputFile, largest_internal_key),
|
||||
OptionType::kEncodedString, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
// Misspelled key kept for compatibility with other versions
|
||||
{"oldest_ancester_time",
|
||||
{offsetof(struct CompactionServiceOutputFile, oldest_ancester_time),
|
||||
{offsetof(struct CompactionServiceOutputFile, oldest_ancestor_time),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"file_creation_time",
|
||||
@@ -920,24 +921,11 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
{offsetof(struct InternalStats::CompactionStats, count),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
// The remote worker may serialize a different number of
|
||||
// CompactionReason counts than this primary expects (RocksDB versions
|
||||
// can disagree on kNumOfReasons). Deserialization tolerates the
|
||||
// mismatch: extra trailing elements are dropped, missing ones are
|
||||
// zero-filled.
|
||||
//
|
||||
// The serialized width is intentionally kept at kNumOfReasons - 1 (the
|
||||
// reduced width) so this change does NOT alter what is written -- only
|
||||
// the read path becomes tolerant. Widening the serialized width to the
|
||||
// full kNumOfReasons is a separate, later change that must not land
|
||||
// until this tolerant read has been deployed to every primary,
|
||||
// including rollback targets.
|
||||
{"counts",
|
||||
SizeTolerantDeserializeArray<
|
||||
int, static_cast<int>(CompactionReason::kNumOfReasons) - 1>(
|
||||
offsetof(struct InternalStats::CompactionStats, counts),
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone,
|
||||
{0, OptionType::kInt})},
|
||||
{"counts", SizeTolerantDeserializeArray<
|
||||
int, static_cast<int>(CompactionReason::kNumOfReasons)>(
|
||||
offsetof(struct InternalStats::CompactionStats, counts),
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone,
|
||||
{0, OptionType::kInt})},
|
||||
};
|
||||
|
||||
static std::unordered_map<std::string, OptionTypeInfo>
|
||||
|
||||
@@ -1900,7 +1900,7 @@ TEST_F(CompactionServiceTest, InvalidResultFallsBackToLocal) {
|
||||
TEST_F(CompactionServiceTest, CompatCheckCountsWidthTolerance) {
|
||||
// Number of per-CompactionReason counts this binary serializes and expects.
|
||||
constexpr size_t kExpected =
|
||||
static_cast<size_t>(CompactionReason::kNumOfReasons) - 1;
|
||||
static_cast<size_t>(CompactionReason::kNumOfReasons);
|
||||
constexpr size_t kLastIndex = kExpected - 1;
|
||||
constexpr int kFirstCount = 3;
|
||||
constexpr int kLastCount = 5;
|
||||
@@ -2452,6 +2452,8 @@ TEST_F(CompactionServiceTest, RemoteCompactionManifestFloorKillSwitchOff) {
|
||||
Options options = CurrentOptions();
|
||||
options.num_levels = 7;
|
||||
options.max_open_files = -1;
|
||||
options.open_files_async = true;
|
||||
options.skip_stats_update_on_db_open = true;
|
||||
options.level0_file_num_compaction_trigger = 100; // no auto compaction
|
||||
options.remote_compaction_manifest_floor = false; // kill switch OFF
|
||||
ReopenWithCompactionService(&options);
|
||||
@@ -2464,8 +2466,15 @@ TEST_F(CompactionServiceTest, RemoteCompactionManifestFloorKillSwitchOff) {
|
||||
ASSERT_OK(Flush()); // L0: Delete(k)
|
||||
|
||||
// Reopen so the MANIFEST is rewritten as current state before we perturb it.
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::BGWorkAsyncFileOpen:Done",
|
||||
"CompactionServiceTest::ManifestFloorKillSwitchOff:OpenDone"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "cf_1", "cf_2", "cf_3"},
|
||||
options);
|
||||
TEST_SYNC_POINT("CompactionServiceTest::ManifestFloorKillSwitchOff:OpenDone");
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
GetCompactionService()->SetCanceled(false);
|
||||
|
||||
bool floor_provided_seen = true;
|
||||
|
||||
@@ -68,11 +68,11 @@ class FileTtlBooster {
|
||||
if (!enabled_) {
|
||||
return 1;
|
||||
}
|
||||
uint64_t oldest_ancester_time = f->TryGetOldestAncesterTime();
|
||||
if (oldest_ancester_time >= current_time_) {
|
||||
uint64_t oldest_ancestor_time = f->TryGetOldestAncestorTime();
|
||||
if (oldest_ancestor_time >= current_time_) {
|
||||
return 1;
|
||||
}
|
||||
uint64_t age = current_time_ - oldest_ancester_time;
|
||||
uint64_t age = current_time_ - oldest_ancestor_time;
|
||||
if (age > boost_age_start_) {
|
||||
// Use integer just for convenience.
|
||||
// We could make all file_to_order double if we want.
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/utilities/checkpoint.h"
|
||||
#include "rocksdb/utilities/debug.h"
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
#include "table/block_based/block_based_table_reader.h"
|
||||
#include "table/block_based/block_builder.h"
|
||||
#include "table/format.h"
|
||||
@@ -5767,6 +5768,9 @@ INSTANTIATE_TEST_CASE_P(BestEffortsRecoverIncompleteVersionTest,
|
||||
TEST_F(DBBasicTest, BestEffortsRecoveryTryMultipleManifests) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.write_buffer_manager = std::make_shared<WriteBufferManager>(
|
||||
64 << 20, nullptr /* cache */, false /* allow_stall */,
|
||||
WriteBufferFlushPolicy::kFlushLargestAcrossDBs);
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("foo", "value0"));
|
||||
ASSERT_OK(Flush());
|
||||
@@ -5788,6 +5792,9 @@ TEST_F(DBBasicTest, BestEffortsRecoveryTryMultipleManifests) {
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("bar", "value"));
|
||||
options.write_buffer_manager->TEST_RefreshFlushInitiatorCandidate();
|
||||
ASSERT_TRUE(
|
||||
options.write_buffer_manager->TEST_ScheduleFlushOnLargestDB(nullptr));
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, RecoverWithNoCurrentFile) {
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/port.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/random.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -14,6 +17,62 @@ class DBClipTest : public DBTestBase {
|
||||
DBClipTest() : DBTestBase("db_clip_test", /*env_do_fsync=*/true) {}
|
||||
};
|
||||
|
||||
// ClipColumnFamily reads the CF's boundary keys under the DB mutex, then drops
|
||||
// it and issues several writes with those keys. Those keys must not point into
|
||||
// Version-owned FileMetaData, which a background job can replace and free in
|
||||
// that window. Here a full compaction runs inside the window and obsoletes the
|
||||
// Version the keys were read from.
|
||||
TEST_F(DBClipTest, BoundaryKeysSurviveConcurrentVersionChange) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.num_levels = 3;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
std::map<int32_t, std::string> values;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
for (int j = 0; j < 100; j++) {
|
||||
int k = i * 100 + j;
|
||||
values[k] = rnd.RandomString(1024);
|
||||
ASSERT_OK(Put(Key(k), values[k]));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
std::atomic<bool> fired{false};
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::ClipColumnFamily:PostBoundaryKeys", [&](void*) {
|
||||
if (fired.exchange(true)) {
|
||||
return;
|
||||
}
|
||||
// Replace and free the Version the boundary keys were read from, on
|
||||
// another thread so this does not re-enter Clip's own call stack.
|
||||
port::Thread compactor([&] {
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForPurge());
|
||||
});
|
||||
compactor.join();
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
const auto begin_key = Key(251);
|
||||
const auto end_key = Key(751);
|
||||
ASSERT_OK(
|
||||
db_->ClipColumnFamily(db_->DefaultColumnFamily(), begin_key, end_key));
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
ASSERT_TRUE(fired.load());
|
||||
|
||||
for (int i = 251; i < 751; i++) {
|
||||
ASSERT_EQ(Get(Key(i)), values[i]);
|
||||
}
|
||||
for (int i : {0, 250, 751, 999}) {
|
||||
std::string result;
|
||||
ASSERT_TRUE(db_->Get(ReadOptions(), Key(i), &result).IsNotFound());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBClipTest, TestClipRange) {
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 10 * 1024 * 1024;
|
||||
@@ -133,6 +192,28 @@ TEST_F(DBClipTest, TestClipRange) {
|
||||
ASSERT_TRUE(in_range);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBClipTest, KeepFileEndingAtBeginKey) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put("a", "va"));
|
||||
ASSERT_OK(Put("b", "vb"));
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(1);
|
||||
ASSERT_OK(Put("c", "vc"));
|
||||
ASSERT_OK(Put("d", "vd"));
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(1);
|
||||
ASSERT_EQ("0,2", FilesPerLevel(0));
|
||||
|
||||
ASSERT_OK(db_->ClipColumnFamily(db_->DefaultColumnFamily(), "b", "d"));
|
||||
ASSERT_EQ("NOT_FOUND", Get("a"));
|
||||
ASSERT_EQ("vb", Get("b"));
|
||||
ASSERT_EQ("vc", Get("c"));
|
||||
ASSERT_EQ("NOT_FOUND", Get("d"));
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+10
-10
@@ -5350,10 +5350,10 @@ TEST_F(DBCompactionTest, LevelTtlCascadingCompactions) {
|
||||
*max_open_files = 2;
|
||||
});
|
||||
// In the case where all files are opened and doing DB restart
|
||||
// forcing the oldest ancester time in manifest file to be 0 to
|
||||
// forcing the oldest ancestor time in manifest file to be 0 to
|
||||
// simulate the case of reading from an old version.
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionEdit::EncodeTo:VarintOldestAncesterTime", [&](void* arg) {
|
||||
"VersionEdit::EncodeTo:VarintOldestAncestorTime", [&](void* arg) {
|
||||
if (if_restart && if_open_all_files) {
|
||||
std::string* encoded_field = static_cast<std::string*>(arg);
|
||||
*encoded_field = "";
|
||||
@@ -5390,7 +5390,7 @@ TEST_F(DBCompactionTest, LevelTtlCascadingCompactions) {
|
||||
std::vector<std::vector<FileMetaData>> level_to_files;
|
||||
dbfull()->TEST_GetFilesMetaData(dbfull()->DefaultColumnFamily(),
|
||||
&level_to_files);
|
||||
uint64_t oldest_time = level_to_files[0][0].oldest_ancester_time;
|
||||
uint64_t oldest_time = level_to_files[0][0].oldest_ancestor_time;
|
||||
// Add 1 hour and do another flush.
|
||||
env_->MockSleepForSeconds(1 * 60 * 60);
|
||||
for (int i = 101; i <= 200; ++i) {
|
||||
@@ -5458,7 +5458,7 @@ TEST_F(DBCompactionTest, LevelTtlCascadingCompactions) {
|
||||
|
||||
dbfull()->TEST_GetFilesMetaData(dbfull()->DefaultColumnFamily(),
|
||||
&level_to_files);
|
||||
ASSERT_EQ(oldest_time, level_to_files[6][0].oldest_ancester_time);
|
||||
ASSERT_EQ(oldest_time, level_to_files[6][0].oldest_ancestor_time);
|
||||
|
||||
env_->MockSleepForSeconds(25 * 60 * 60);
|
||||
ASSERT_OK(Put(Key(2), "1"));
|
||||
@@ -5881,7 +5881,7 @@ TEST_F(DBCompactionTest, LevelPeriodicCompactionWithOldDB) {
|
||||
props->file_creation_time = 0;
|
||||
}
|
||||
if (set_creation_time_to_zero) {
|
||||
props->creation_time = 0;
|
||||
props->oldest_ancestor_time = 0;
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
@@ -12417,7 +12417,7 @@ TEST_F(DBCompactionTest, RecordNewestKeyTimeForTtlCompaction) {
|
||||
ASSERT_EQ(newest_key_time, file_metadatas[idx]
|
||||
->fd.pinned_reader.Get()
|
||||
->GetTableProperties()
|
||||
->creation_time);
|
||||
->oldest_ancestor_time);
|
||||
}
|
||||
// The delta between the first and last newest_key_times is 15s
|
||||
uint64_t last_newest_key_time = prev_newest_key_time;
|
||||
@@ -12436,14 +12436,14 @@ TEST_F(DBCompactionTest, RecordNewestKeyTimeForTtlCompaction) {
|
||||
->GetTableProperties()
|
||||
->newest_key_time,
|
||||
first_newest_key_time);
|
||||
// Contrast newest_key_time with creation_time, which records the oldest
|
||||
// ancestor time (15s older than newest_key_time)
|
||||
// Contrast newest_key_time with oldest_ancestor_time (15s older than
|
||||
// newest_key_time)
|
||||
ASSERT_EQ(file_metadatas[0]
|
||||
->fd.pinned_reader.Get()
|
||||
->GetTableProperties()
|
||||
->creation_time,
|
||||
->oldest_ancestor_time,
|
||||
last_newest_key_time);
|
||||
ASSERT_EQ(file_metadatas[0]->oldest_ancester_time, last_newest_key_time);
|
||||
ASSERT_EQ(file_metadatas[0]->oldest_ancestor_time, last_newest_key_time);
|
||||
|
||||
// Make sure TTL of 5s causes compaction
|
||||
env_->MockSleepForSeconds(6);
|
||||
|
||||
@@ -4271,6 +4271,147 @@ TEST_F(DBTest2, TraceWithLimit) {
|
||||
ASSERT_OK(DestroyDB(dbname2, options));
|
||||
}
|
||||
|
||||
namespace {
|
||||
Status ReplayTraceFile(Env* env, const std::string& trace_filename, DB* db,
|
||||
const std::vector<ColumnFamilyHandle*>& handles,
|
||||
uint32_t num_threads,
|
||||
TraceExecutionResultHandler* res_handler = nullptr) {
|
||||
std::unique_ptr<TraceReader> trace_reader;
|
||||
Status s =
|
||||
NewFileTraceReader(env, EnvOptions(), trace_filename, &trace_reader);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
std::unique_ptr<Replayer> replayer;
|
||||
s = db->NewDefaultReplayer(handles, std::move(trace_reader), &replayer);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
s = replayer->Prepare();
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
// Count the replayed records by type in `res_handler`, if given.
|
||||
std::function<void(Status, std::unique_ptr<TraceRecordResult>&&)> res_cb;
|
||||
if (res_handler != nullptr) {
|
||||
res_cb = [res_handler](const Status& exec_s,
|
||||
std::unique_ptr<TraceRecordResult>&& res) {
|
||||
exec_s.PermitUncheckedError();
|
||||
if (res != nullptr) {
|
||||
EXPECT_OK(res->Accept(res_handler));
|
||||
}
|
||||
};
|
||||
}
|
||||
return replayer->Replay(ReplayOptions(num_threads, 1.0), res_cb);
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
TEST_F(DBTest2, TraceReplayDefaultColumnFamilyWithoutHandles) {
|
||||
Options options = CurrentOptions();
|
||||
std::string trace_filename = dbname_ + "/rocksdb.trace";
|
||||
std::unique_ptr<TraceWriter> trace_writer;
|
||||
ASSERT_OK(
|
||||
NewFileTraceWriter(env_, EnvOptions(), trace_filename, &trace_writer));
|
||||
ASSERT_OK(db_->StartTrace(TraceOptions(), std::move(trace_writer)));
|
||||
ASSERT_OK(Put("a", "1"));
|
||||
ASSERT_OK(Put("b", "2"));
|
||||
ASSERT_EQ("1", Get("a"));
|
||||
std::vector<std::string> values;
|
||||
for (const Status& s :
|
||||
db_->MultiGet(ReadOptions(), {Slice("a"), Slice("b")}, &values)) {
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
|
||||
iter->Seek("a");
|
||||
iter->SeekForPrev("b");
|
||||
ASSERT_OK(iter->status());
|
||||
iter.reset();
|
||||
ASSERT_OK(db_->EndTrace());
|
||||
|
||||
std::string dbname2 = test::PerThreadDBPath(env_, "/db_replay2");
|
||||
options.create_if_missing = true;
|
||||
for (uint32_t num_threads : {1U, 2U}) {
|
||||
SCOPED_TRACE("num_threads=" + std::to_string(num_threads));
|
||||
ASSERT_OK(DestroyDB(dbname2, options));
|
||||
std::unique_ptr<DB> db2;
|
||||
ASSERT_OK(DB::Open(options, dbname2, &db2));
|
||||
// No handles: every read record on the default column family must still
|
||||
// replay.
|
||||
TraceExecutionResultHandler res_handler;
|
||||
ASSERT_OK(ReplayTraceFile(env_, trace_filename, db2.get(),
|
||||
/*handles=*/{}, num_threads, &res_handler));
|
||||
// Each record replayed and produced a result.
|
||||
ASSERT_EQ(2, res_handler.GetNumWrites());
|
||||
ASSERT_EQ(1, res_handler.GetNumGets());
|
||||
ASSERT_EQ(1, res_handler.GetNumMultiGets());
|
||||
ASSERT_EQ(2, res_handler.GetNumIterSeeks());
|
||||
std::string value;
|
||||
ASSERT_OK(db2->Get(ReadOptions(), "a", &value));
|
||||
ASSERT_EQ("1", value);
|
||||
ASSERT_OK(db2->Get(ReadOptions(), "b", &value));
|
||||
ASSERT_EQ("2", value);
|
||||
}
|
||||
ASSERT_OK(DestroyDB(dbname2, options));
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, TraceReplayReturnsExecutionError) {
|
||||
Options options = CurrentOptions();
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
std::string trace_filename = dbname_ + "/rocksdb.trace";
|
||||
std::unique_ptr<TraceWriter> trace_writer;
|
||||
ASSERT_OK(
|
||||
NewFileTraceWriter(env_, EnvOptions(), trace_filename, &trace_writer));
|
||||
ASSERT_OK(db_->StartTrace(TraceOptions(), std::move(trace_writer)));
|
||||
ASSERT_EQ("NOT_FOUND", Get(1, "a"));
|
||||
ASSERT_EQ("NOT_FOUND", Get(1, "b"));
|
||||
ASSERT_OK(db_->EndTrace());
|
||||
|
||||
// The replay DB has no "pikachu" column family, so both Get records fail.
|
||||
std::string dbname2 = test::PerThreadDBPath(env_, "/db_replay2");
|
||||
options.create_if_missing = true;
|
||||
ASSERT_OK(DestroyDB(dbname2, options));
|
||||
std::unique_ptr<DB> db2;
|
||||
ASSERT_OK(DB::Open(options, dbname2, &db2));
|
||||
for (uint32_t num_threads : {1U, 2U}) {
|
||||
SCOPED_TRACE("num_threads=" + std::to_string(num_threads));
|
||||
Status s = ReplayTraceFile(env_, trace_filename, db2.get(),
|
||||
{db2->DefaultColumnFamily()}, num_threads);
|
||||
ASSERT_TRUE(s.IsCorruption()) << s.ToString();
|
||||
}
|
||||
db2.reset();
|
||||
ASSERT_OK(DestroyDB(dbname2, options));
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, TraceReplayReturnsNotSupportedExecutionError) {
|
||||
Options options = CurrentOptions();
|
||||
std::string trace_filename = dbname_ + "/rocksdb.trace";
|
||||
std::unique_ptr<TraceWriter> trace_writer;
|
||||
ASSERT_OK(
|
||||
NewFileTraceWriter(env_, EnvOptions(), trace_filename, &trace_writer));
|
||||
ASSERT_OK(db_->StartTrace(TraceOptions(), std::move(trace_writer)));
|
||||
ASSERT_OK(Put("a", "1"));
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z"));
|
||||
ASSERT_OK(db_->EndTrace());
|
||||
|
||||
// DeleteRange is not compatible with row_cache, so replaying the DeleteRange
|
||||
// write fails with NotSupported. Unlike an unsupported trace type, this must
|
||||
// not be skipped.
|
||||
std::string dbname2 = test::PerThreadDBPath(env_, "/db_replay2");
|
||||
options.create_if_missing = true;
|
||||
options.row_cache = NewLRUCache(1 << 20);
|
||||
for (uint32_t num_threads : {1U, 2U}) {
|
||||
SCOPED_TRACE("num_threads=" + std::to_string(num_threads));
|
||||
ASSERT_OK(DestroyDB(dbname2, options));
|
||||
std::unique_ptr<DB> db2;
|
||||
ASSERT_OK(DB::Open(options, dbname2, &db2));
|
||||
Status s = ReplayTraceFile(env_, trace_filename, db2.get(),
|
||||
/*handles=*/{}, num_threads);
|
||||
ASSERT_TRUE(s.IsNotSupported()) << s.ToString();
|
||||
}
|
||||
ASSERT_OK(DestroyDB(dbname2, options));
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, TraceWithSampling) {
|
||||
Options options = CurrentOptions();
|
||||
ReadOptions ro;
|
||||
@@ -7651,6 +7792,48 @@ TEST_F(DBTest2, TableCacheMissDuringReadFromBlockCacheTier) {
|
||||
ASSERT_EQ(orig_num_file_opens, TestGetTickerCount(options, NO_FILE_OPENS));
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, MultiGetBlockCacheTierSkipsColdSstData) {
|
||||
Options options = CurrentOptions();
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
// Use buffered reads to reach the non-mmap RetrieveMultipleBlocks path.
|
||||
options.allow_mmap_reads = false;
|
||||
BlockBasedTableOptions table_options;
|
||||
// Data blocks use the block cache. Index and filter blocks stay in the table
|
||||
// reader (cache_index_and_filter_blocks is false by default), so a
|
||||
// kBlockCacheTier read gets to the data block without metadata I/O.
|
||||
table_options.block_cache = NewLRUCache(1 << 20);
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put("k1", "v1"));
|
||||
ASSERT_OK(Put("k2", "v2"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Open the table reader so that kBlockCacheTier does not fail at table
|
||||
// open. Do not add the data block to the block cache.
|
||||
ReadOptions warm_ro;
|
||||
warm_ro.fill_cache = false;
|
||||
std::string value;
|
||||
ASSERT_OK(db_->Get(warm_ro, "k1", &value));
|
||||
ASSERT_EQ(value, "v1");
|
||||
// Make sure the data block is not in the block cache.
|
||||
table_options.block_cache->EraseUnRefEntries();
|
||||
|
||||
ReadOptions no_io_ro;
|
||||
no_io_ro.read_tier = kBlockCacheTier;
|
||||
|
||||
ASSERT_TRUE(db_->Get(no_io_ro, "k1", &value).IsIncomplete());
|
||||
ASSERT_TRUE(db_->Get(no_io_ro, "k2", &value).IsIncomplete());
|
||||
|
||||
Slice keys[2] = {Slice("k1"), Slice("k2")};
|
||||
PinnableSlice values[2];
|
||||
Status statuses[2];
|
||||
db_->MultiGet(no_io_ro, db_->DefaultColumnFamily(), 2, keys, values,
|
||||
statuses);
|
||||
ASSERT_TRUE(statuses[0].IsIncomplete());
|
||||
ASSERT_TRUE(statuses[1].IsIncomplete());
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, GetFileChecksumsFromCurrentManifest_CRC32) {
|
||||
Options opts = CurrentOptions();
|
||||
opts.create_if_missing = true;
|
||||
|
||||
@@ -2982,6 +2982,34 @@ TEST_P(DBAtomicFlushTest, ManualAtomicFlush) {
|
||||
}
|
||||
}
|
||||
|
||||
// Two handles naming the same column family make the selected-cfd list longer
|
||||
// than the per-CF memtable-id list, which is keyed by CF. The wait loop must
|
||||
// not read the id list past its own length.
|
||||
TEST_P(DBAtomicFlushTest, ManualAtomicFlushDuplicateColumnFamilyHandles) {
|
||||
if (!GetParam()) {
|
||||
// Only the atomic path reaches the shared wait list; without atomic_flush
|
||||
// each handle is flushed independently.
|
||||
return;
|
||||
}
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.atomic_flush = true;
|
||||
options.write_buffer_size = (static_cast<size_t>(64) << 20);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
WriteOptions wopts;
|
||||
wopts.disableWAL = true;
|
||||
ASSERT_OK(Put("key", "value", wopts));
|
||||
|
||||
FlushOptions flush_options; // wait == true
|
||||
ASSERT_OK(db_->Flush(
|
||||
flush_options, {db_->DefaultColumnFamily(), db_->DefaultColumnFamily()}));
|
||||
|
||||
auto cfh = static_cast<ColumnFamilyHandleImpl*>(db_->DefaultColumnFamily());
|
||||
ASSERT_EQ(0, cfh->cfd()->imm()->NumNotFlushed());
|
||||
ASSERT_TRUE(cfh->cfd()->mem()->IsEmpty());
|
||||
}
|
||||
|
||||
TEST_P(DBAtomicFlushTest, PrecomputeMinLogNumberToKeepNon2PC) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
|
||||
@@ -204,6 +204,19 @@ class DBFollowerTest : public DBTestBase {
|
||||
return result;
|
||||
}
|
||||
|
||||
void VerifyFollowerBatchValue(const std::string& key,
|
||||
const std::string& expected) {
|
||||
std::vector<Iterator*> iterators;
|
||||
ASSERT_OK(follower()->NewIterators(
|
||||
ReadOptions(), {follower()->DefaultColumnFamily()}, &iterators));
|
||||
ASSERT_EQ(1U, iterators.size());
|
||||
std::unique_ptr<Iterator> iterator(iterators[0]);
|
||||
iterator->Seek(key);
|
||||
ASSERT_OK(iterator->status());
|
||||
ASSERT_TRUE(iterator->Valid());
|
||||
ASSERT_EQ(expected, iterator->value());
|
||||
}
|
||||
|
||||
DB* follower() { return follower_.get(); }
|
||||
DBFollowerTestFS* follower_fs() {
|
||||
return static_cast<DBFollowerTestFS*>(follower_env_->GetFileSystem().get());
|
||||
@@ -253,6 +266,32 @@ TEST_F(DBFollowerTest, Basic) {
|
||||
CheckDirs();
|
||||
}
|
||||
|
||||
TEST_F(DBFollowerTest, NewIteratorsRefreshesAfterCatchUp) {
|
||||
ASSERT_OK(Put("key", "v1"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency({
|
||||
{"DBImplFollower::TryCatchupWithLeader:Begin1", "Leader::Start"},
|
||||
{"Leader::Done", "DBImplFollower::TryCatchupWithLeader:Begin2"},
|
||||
{"DBImplFollower::TryCatchupWithLeader:End", "Follower::WaitForCatchup"},
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(OpenAsFollower());
|
||||
VerifyFollowerBatchValue("key", "v1");
|
||||
|
||||
TEST_SYNC_POINT("Leader::Start");
|
||||
ASSERT_OK(Put("key", "v2"));
|
||||
ASSERT_OK(Flush());
|
||||
TEST_SYNC_POINT("Leader::Done");
|
||||
TEST_SYNC_POINT("Follower::WaitForCatchup");
|
||||
|
||||
VerifyFollowerBatchValue("key", "v2");
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearTrace();
|
||||
}
|
||||
|
||||
TEST_F(DBFollowerTest, Flush) {
|
||||
SyncPoint::GetInstance()->LoadDependency({
|
||||
{"DBImplFollower::TryCatchupWithLeader:Begin1", "Leader::Start"},
|
||||
|
||||
+419
-56
@@ -70,6 +70,7 @@
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "db/write_callback.h"
|
||||
#include "env/unique_id_gen.h"
|
||||
#include "file/delete_scheduler.h"
|
||||
#include "file/file_util.h"
|
||||
#include "file/filename.h"
|
||||
#include "file/random_access_file_reader.h"
|
||||
@@ -275,6 +276,11 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
SetDbSessionId();
|
||||
assert(!db_session_id_.empty());
|
||||
|
||||
if (write_buffer_manager_ != nullptr && !read_only_) {
|
||||
wbm_flush_initiator_ = std::make_unique<WBMFlushInitiator>(
|
||||
this, immutable_db_options_.atomic_flush);
|
||||
}
|
||||
|
||||
periodic_task_functions_.emplace(PeriodicTaskType::kDumpStats,
|
||||
[this]() { this->DumpStats(); });
|
||||
periodic_task_functions_.emplace(PeriodicTaskType::kPersistStats,
|
||||
@@ -293,6 +299,8 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
table_cache_.get(), write_buffer_manager_, &write_controller_,
|
||||
&block_cache_tracer_, io_tracer_, db_id_, db_session_id_,
|
||||
options.daily_offpeak_time_utc, &error_handler_, read_only));
|
||||
versions_->GetColumnFamilySet()->SetFlushInitiator(
|
||||
wbm_flush_initiator_.get());
|
||||
column_family_memtables_.reset(
|
||||
new ColumnFamilyMemTablesImpl(versions_->GetColumnFamilySet()));
|
||||
|
||||
@@ -319,6 +327,10 @@ Status DBImpl::Resume() {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
if (read_only_) {
|
||||
return Status::NotSupported("Cannot resume a read-only DB");
|
||||
}
|
||||
|
||||
if (error_handler_.IsRecoveryInProgress()) {
|
||||
// Don't allow a mix of manual and automatic recovery
|
||||
return Status::Busy("Recovery in progress");
|
||||
@@ -330,18 +342,23 @@ Status DBImpl::Resume() {
|
||||
return s;
|
||||
}
|
||||
|
||||
// This function implements the guts of recovery from a background error. It
|
||||
// is eventually called for both manual as well as automatic recovery. It does
|
||||
// the following -
|
||||
// 1. Wait for currently scheduled background flush/compaction to exit, in
|
||||
// order to inadvertently causing an error and thinking recovery failed
|
||||
// 2. Flush memtables if there's any data for all the CFs. This may result
|
||||
// another error, which will be saved by error_handler_ and reported later
|
||||
// as the recovery status
|
||||
// 3. Find and delete any obsolete files
|
||||
// 4. Schedule compactions if needed for all the CFs. This is needed as the
|
||||
// flush in the prior step might have been a no-op for some CFs, which
|
||||
// means a new super version wouldn't have been installed
|
||||
// This function implements both manual and automatic background-error
|
||||
// recovery. It discards stale queued flush requests, waits for scheduled work
|
||||
// to exit, rebuilds and runs the flush work required by the recovery context,
|
||||
// purges obsolete files, and schedules any follow-up compactions.
|
||||
//
|
||||
// A file-scoped WAL write error needs a stronger protocol because the failed
|
||||
// record might be absent, partial, or complete, and the failed file handle must
|
||||
// never be used again:
|
||||
// 1. Keep every WAL through failed_wal_number quarantined from concurrent sync.
|
||||
// 2. Fence WAL iterators at the failed sequence range and persist its end so a
|
||||
// possibly complete record is neither exposed nor allowed to collide with a
|
||||
// later write after recovery.
|
||||
// 3. Replace the failed WAL and atomically flush all live column families.
|
||||
// 4. Verify every column family advanced beyond the failed WAL.
|
||||
// 5. Detach the quarantined handles, then clear the cutoff only if a concurrent
|
||||
// failure did not extend it. Any failure before completion leaves the hard
|
||||
// background error and quarantine in place.
|
||||
Status DBImpl::ResumeImpl(DBRecoverContext context,
|
||||
Env::IOActivity io_activity) {
|
||||
mutex_.AssertHeld();
|
||||
@@ -369,6 +386,10 @@ Status DBImpl::ResumeImpl(DBRecoverContext context,
|
||||
|
||||
TEST_SYNC_POINT("DBImpl::ResumeImpl:Start");
|
||||
|
||||
if (context.IsWALWriteErrorRecovery()) {
|
||||
ExtendWALRecoveryCutoff(context.failed_wal_number);
|
||||
}
|
||||
|
||||
// With two_write_queues=true, sequence numbers are allocated via
|
||||
// FetchAddLastAllocatedSequence() before writes complete, but only
|
||||
// published via SetLastSequence() after success. If we're recovering from
|
||||
@@ -399,6 +420,44 @@ Status DBImpl::ResumeImpl(DBRecoverContext context,
|
||||
s = Status::ShutdownInProgress();
|
||||
}
|
||||
|
||||
if (s.ok() && context.IsWALWriteErrorRecovery()) {
|
||||
const bool has_blob_direct_write =
|
||||
HasAnyBlobDirectWriteColumnFamilyWithLockHeld();
|
||||
if (immutable_db_options_.manual_wal_flush ||
|
||||
immutable_db_options_.allow_2pc || two_write_queues_ ||
|
||||
immutable_db_options_.enable_pipelined_write ||
|
||||
immutable_db_options_.unordered_write ||
|
||||
immutable_db_options_.recycle_log_file_num != 0 ||
|
||||
immutable_db_options_.WAL_ttl_seconds != 0 ||
|
||||
immutable_db_options_.WAL_size_limit_MB != 0 ||
|
||||
immutable_db_options_.track_and_verify_wals_in_manifest ||
|
||||
immutable_db_options_.track_and_verify_wals || has_blob_direct_write) {
|
||||
ROCKS_LOG_WARN(
|
||||
immutable_db_options_.info_log,
|
||||
"[WAL recovery] Unsupported configuration; DB remains write-stopped: "
|
||||
"manual_wal_flush=%d, allow_2pc=%d, two_write_queues=%d, "
|
||||
"pipelined_write=%d, unordered_write=%d, wal_recycling=%d, "
|
||||
"wal_ttl=%d, wal_size_limit=%d, track_wals_in_manifest=%d, "
|
||||
"track_wals=%d, blob_direct_write=%d",
|
||||
static_cast<int>(immutable_db_options_.manual_wal_flush),
|
||||
static_cast<int>(immutable_db_options_.allow_2pc),
|
||||
static_cast<int>(two_write_queues_),
|
||||
static_cast<int>(immutable_db_options_.enable_pipelined_write),
|
||||
static_cast<int>(immutable_db_options_.unordered_write),
|
||||
static_cast<int>(immutable_db_options_.recycle_log_file_num != 0),
|
||||
static_cast<int>(immutable_db_options_.WAL_ttl_seconds != 0),
|
||||
static_cast<int>(immutable_db_options_.WAL_size_limit_MB != 0),
|
||||
static_cast<int>(
|
||||
immutable_db_options_.track_and_verify_wals_in_manifest),
|
||||
static_cast<int>(immutable_db_options_.track_and_verify_wals),
|
||||
static_cast<int>(has_blob_direct_write));
|
||||
s = Status::NotSupported(
|
||||
"file-scoped WAL write error recovery is not supported with manual "
|
||||
"WAL flush, 2PC, two write queues, pipelined or unordered writes, "
|
||||
"WAL recycling, retention or tracking, or blob direct write");
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
Status bg_error = error_handler_.GetBGError();
|
||||
if (bg_error.severity() > Status::Severity::kHardError) {
|
||||
@@ -439,7 +498,56 @@ Status DBImpl::ResumeImpl(DBRecoverContext context,
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok() && context.IsWALWriteErrorRecovery() &&
|
||||
context.failed_wal_sequence > 0) {
|
||||
// A WAL error can be reported after the whole record reached storage but
|
||||
// before its sequence range was published. Persist the reservation before
|
||||
// admitting later writes so they cannot reuse an ambiguous sequence. This
|
||||
// guarantee belongs to file-scoped recovery, which also atomically advances
|
||||
// every column family past the failed WAL before retiring it. Unsupported
|
||||
// configurations fail the check above and remain write-stopped.
|
||||
assert(context.IsWALWriteErrorRecovery());
|
||||
assert(context.failed_wal_sequence_start != 0);
|
||||
assert(context.failed_wal_sequence_start <= context.failed_wal_sequence);
|
||||
wal_manager_.RecordIndeterminateWriteSequence(
|
||||
context.failed_wal_number, context.failed_wal_sequence_start,
|
||||
context.failed_wal_sequence);
|
||||
if (context.failed_wal_sequence > versions_->LastAllocatedSequence()) {
|
||||
versions_->SetLastAllocatedSequence(context.failed_wal_sequence);
|
||||
}
|
||||
if (context.failed_wal_sequence > versions_->LastPublishedSequence()) {
|
||||
versions_->SetLastPublishedSequence(context.failed_wal_sequence);
|
||||
}
|
||||
if (context.failed_wal_sequence > versions_->LastSequence()) {
|
||||
versions_->SetLastSequence(context.failed_wal_sequence);
|
||||
}
|
||||
VersionEdit edit;
|
||||
edit.SetLastSequence(context.failed_wal_sequence);
|
||||
auto cfh =
|
||||
static_cast_with_check<ColumnFamilyHandleImpl>(default_cf_handle_);
|
||||
assert(cfh);
|
||||
s = versions_->LogAndApply(cfh->cfd(), read_options, write_options, &edit,
|
||||
&mutex_, directories_.GetDbDir());
|
||||
if (!s.ok() && versions_->io_status().IsIOError()) {
|
||||
error_handler_.SetBGError(versions_->io_status(),
|
||||
BackgroundErrorReason::kManifestWrite);
|
||||
}
|
||||
if (s.ok()) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"[WAL recovery] Persisted sequence reservation through "
|
||||
"#%" PRIu64,
|
||||
context.failed_wal_sequence);
|
||||
} else {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"[WAL recovery] Failed to persist sequence reservation "
|
||||
"through #%" PRIu64 ": %s",
|
||||
context.failed_wal_sequence, s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
recovering_from_wal_write_error_ = context.IsWALWriteErrorRecovery();
|
||||
recover_wal_through_number_ = context.failed_wal_number;
|
||||
if (context.flush_reason == FlushReason::kErrorRecoveryRetryFlush) {
|
||||
s = RetryFlushesForErrorRecovery(FlushReason::kErrorRecoveryRetryFlush,
|
||||
true /* wait */);
|
||||
@@ -449,8 +557,26 @@ Status DBImpl::ResumeImpl(DBRecoverContext context,
|
||||
FlushOptions flush_opts;
|
||||
// We allow flush to stall write since we are trying to resume from error.
|
||||
flush_opts.allow_write_stall = true;
|
||||
// Keep the per-column-family WAL replay boundaries in one MANIFEST
|
||||
// commit. Otherwise a crash between sequential flushes can recover only
|
||||
// part of a write batch spanning multiple column families.
|
||||
flush_opts.force_atomic_flush = true;
|
||||
if (context.IsWALWriteErrorRecovery()) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"[WAL recovery] Starting atomic flush of all live "
|
||||
"column families through WAL #%" PRIu64,
|
||||
context.failed_wal_number);
|
||||
}
|
||||
s = FlushAllColumnFamilies(flush_opts, context.flush_reason);
|
||||
if (s.ok() && context.IsWALWriteErrorRecovery()) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"[WAL recovery] Atomic flush completed through WAL "
|
||||
"#%" PRIu64,
|
||||
context.failed_wal_number);
|
||||
}
|
||||
}
|
||||
recovering_from_wal_write_error_ = false;
|
||||
recover_wal_through_number_ = 0;
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"DB resume requested but failed due to Flush failure [%s]",
|
||||
@@ -458,6 +584,71 @@ Status DBImpl::ResumeImpl(DBRecoverContext context,
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok() && context.IsWALWriteErrorRecovery()) {
|
||||
for (auto* cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (!cfd->IsDropped() &&
|
||||
cfd->GetLogNumber() <= context.failed_wal_number) {
|
||||
s = Status::Corruption(
|
||||
"WAL write error recovery did not advance column family " +
|
||||
cfd->GetName() + " past WAL " +
|
||||
std::to_string(context.failed_wal_number));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok() && context.IsWALWriteErrorRecovery()) {
|
||||
// The flush and MANIFEST transition above make the abandoned WALs
|
||||
// unnecessary for recovery. Detach their writers even when physical file
|
||||
// deletion is disabled so no later SyncWAL() can touch a poisoned handle.
|
||||
mutex_.Unlock();
|
||||
{
|
||||
InstrumentedMutexLock wal_lock(&wal_write_mutex_);
|
||||
size_t retired_wal_count = 0;
|
||||
if (logs_.empty() || logs_.back().number <= context.failed_wal_number) {
|
||||
s = Status::Corruption(
|
||||
"WAL write error recovery did not install a replacement WAL "
|
||||
"after WAL " +
|
||||
std::to_string(context.failed_wal_number));
|
||||
} else {
|
||||
for (auto it = logs_.begin();
|
||||
it != logs_.end() && it->number <= context.failed_wal_number;) {
|
||||
if (it->IsSyncing()) {
|
||||
wal_sync_cv_.Wait();
|
||||
it = logs_.begin();
|
||||
continue;
|
||||
}
|
||||
wals_to_free_.push_back(it->ReleaseWriter());
|
||||
it = logs_.erase(it);
|
||||
++retired_wal_count;
|
||||
}
|
||||
uint64_t expected_cutoff = context.failed_wal_number;
|
||||
// A failed compare-exchange means a concurrent WAL failure extended the
|
||||
// cutoff. That failure also sets recovery_error_, so the background
|
||||
// error remains set and a later recovery attempt owns the larger range.
|
||||
const bool cutoff_cleared =
|
||||
wal_recovery_cutoff_.compare_exchange_strong(
|
||||
expected_cutoff, 0, std::memory_order_acq_rel,
|
||||
std::memory_order_acquire);
|
||||
if (cutoff_cleared) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"[WAL recovery] Retired %" ROCKSDB_PRIszt
|
||||
" WAL handle(s) through "
|
||||
"#%" PRIu64 " and cleared the quarantine cutoff",
|
||||
retired_wal_count, context.failed_wal_number);
|
||||
} else {
|
||||
ROCKS_LOG_WARN(
|
||||
immutable_db_options_.info_log,
|
||||
"[WAL recovery] Retired %" ROCKSDB_PRIszt
|
||||
" WAL handle(s) through "
|
||||
"#%" PRIu64 "; quarantine cutoff advanced to #%" PRIu64,
|
||||
retired_wal_count, context.failed_wal_number, expected_cutoff);
|
||||
}
|
||||
}
|
||||
}
|
||||
mutex_.Lock();
|
||||
}
|
||||
|
||||
JobContext job_context(0);
|
||||
FindObsoleteFiles(&job_context, true);
|
||||
mutex_.Unlock();
|
||||
@@ -472,6 +663,21 @@ Status DBImpl::ResumeImpl(DBRecoverContext context,
|
||||
if (s.ok()) {
|
||||
// Will notify and unblock threads waiting for error recovery to finish.
|
||||
s = error_handler_.ClearBGError();
|
||||
if (s.ok() && AsyncWALPrecreateEnabled()) {
|
||||
size_t max_write_buffer_size = 0;
|
||||
for (ColumnFamilyData* cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (!cfd->IsDropped()) {
|
||||
max_write_buffer_size =
|
||||
std::max(max_write_buffer_size,
|
||||
cfd->GetLatestMutableCFOptions().write_buffer_size);
|
||||
}
|
||||
}
|
||||
// WAL rotation during recovery cannot schedule its successor while the
|
||||
// hard background error is set. Refill the async slot only after
|
||||
// ClearBGError() has made normal background work eligible again.
|
||||
MaybeScheduleAsyncWALPrecreate(
|
||||
GetWalPreallocateBlockSize(max_write_buffer_size));
|
||||
}
|
||||
} else {
|
||||
// NOTE: this is needed to pass ASSERT_STATUS_CHECKED
|
||||
// in the DBSSTTest.DBWithMaxSpaceAllowedRandomized test.
|
||||
@@ -479,13 +685,6 @@ Status DBImpl::ResumeImpl(DBRecoverContext context,
|
||||
error_handler_.GetRecoveryError().PermitUncheckedError();
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "Successfully resumed DB");
|
||||
} else {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "Failed to resume DB [%s]",
|
||||
s.ToString().c_str());
|
||||
}
|
||||
|
||||
// Check for shutdown again before scheduling further compactions,
|
||||
// since we released and re-acquired the lock above
|
||||
if (shutdown_initiated_) {
|
||||
@@ -516,6 +715,23 @@ Status DBImpl::ResumeImpl(DBRecoverContext context,
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
|
||||
if (s.ok() && context.IsWALWriteErrorRecovery()) {
|
||||
ROCKS_LOG_INFO(
|
||||
immutable_db_options_.info_log,
|
||||
"[WAL recovery] Successfully resumed DB through WAL #%" PRIu64,
|
||||
context.failed_wal_number);
|
||||
} else if (s.ok()) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "Successfully resumed DB");
|
||||
} else if (context.IsWALWriteErrorRecovery()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"[WAL recovery] Failed to resume DB through WAL #%" PRIu64
|
||||
": %s",
|
||||
context.failed_wal_number, s.ToString().c_str());
|
||||
} else {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "Failed to resume DB [%s]",
|
||||
s.ToString().c_str());
|
||||
}
|
||||
|
||||
// Wake up any waiters - in this case, it could be the shutdown thread
|
||||
bg_cv_.SignalAll();
|
||||
|
||||
@@ -527,7 +743,9 @@ Status DBImpl::ResumeImpl(DBRecoverContext context,
|
||||
void DBImpl::WaitForBackgroundWork() {
|
||||
// Wait for background work to finish
|
||||
while (bg_bottom_compaction_scheduled_ || bg_compaction_scheduled_ ||
|
||||
bg_flush_scheduled_ || bg_pressure_callback_in_progress_) {
|
||||
bg_flush_scheduled_ || bg_wbm_flush_scheduled_ ||
|
||||
bg_pressure_callback_in_progress_ ||
|
||||
bg_async_file_open_state_ == AsyncFileOpenState::kScheduled) {
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
}
|
||||
@@ -610,6 +828,7 @@ void DBImpl::CancelAllBackgroundWork(bool wait) {
|
||||
}
|
||||
shutting_down_.store(true, std::memory_order_release);
|
||||
bg_cv_.SignalAll();
|
||||
env_->UnSchedule(&bg_async_file_open_state_, Env::Priority::HIGH);
|
||||
if (!wait) {
|
||||
return;
|
||||
}
|
||||
@@ -818,6 +1037,18 @@ Status DBImpl::MaybeWriteWalMarkersToManifestOnClose() {
|
||||
/*new_descriptor_log=*/false);
|
||||
}
|
||||
|
||||
void DBImpl::MaybeRegisterFlushInitiator() {
|
||||
// Register only fully opened read-write DBs. Do not hold mutex_: the lock
|
||||
// order is registry mutex before DB mutex.
|
||||
if (write_buffer_manager_ == nullptr || read_only_ || !opened_successfully_) {
|
||||
return;
|
||||
}
|
||||
if (wbm_flush_initiator_ == nullptr || wbm_flush_initiator_->IsRegistered()) {
|
||||
return;
|
||||
}
|
||||
write_buffer_manager_->RegisterFlushInitiator(wbm_flush_initiator_.get());
|
||||
}
|
||||
|
||||
Status DBImpl::CloseHelper() {
|
||||
// Guarantee that there is no background error recovery in progress before
|
||||
// continuing with the shutdown
|
||||
@@ -829,6 +1060,13 @@ Status DBImpl::CloseHelper() {
|
||||
}
|
||||
mutex_.Unlock();
|
||||
|
||||
// Deregister before teardown and without mutex_ to preserve registry-to-DB
|
||||
// lock ordering.
|
||||
if (write_buffer_manager_ && wbm_flush_initiator_ &&
|
||||
wbm_flush_initiator_->IsRegistered()) {
|
||||
write_buffer_manager_->DeregisterFlushInitiator(wbm_flush_initiator_.get());
|
||||
}
|
||||
|
||||
// Below check is added as recovery_error_ is not checked and it causes crash
|
||||
// in DBSSTTest.DBWithMaxSpaceAllowedWithBlobFiles when space limit is
|
||||
// reached.
|
||||
@@ -855,8 +1093,8 @@ Status DBImpl::CloseHelper() {
|
||||
|
||||
// Wait for background work to finish
|
||||
while (bg_bottom_compaction_scheduled_ || bg_compaction_scheduled_ ||
|
||||
bg_flush_scheduled_ || bg_purge_scheduled_ ||
|
||||
bg_pressure_callback_in_progress_ ||
|
||||
bg_flush_scheduled_ || bg_wbm_flush_scheduled_ ||
|
||||
bg_purge_scheduled_ || bg_pressure_callback_in_progress_ ||
|
||||
bg_async_file_open_state_ == AsyncFileOpenState::kScheduled ||
|
||||
async_wal_precreate_state_ == AsyncWALPrecreateState::kScheduled ||
|
||||
pending_purge_obsolete_files_ ||
|
||||
@@ -1045,6 +1283,10 @@ Status DBImpl::CloseHelper() {
|
||||
#endif // !NDEBUG
|
||||
table_cache_->EraseUnRefEntries();
|
||||
|
||||
// The flush initiator was deregistered before background-work teardown.
|
||||
// Detach it before ColumnFamilySet destroys its column families one by one;
|
||||
// otherwise each removal rebuilds accounting by scanning all survivors.
|
||||
versions_->GetColumnFamilySet()->SetFlushInitiator(nullptr);
|
||||
versions_.reset();
|
||||
mutex_.Unlock();
|
||||
if (db_lock_ != nullptr) {
|
||||
@@ -2081,6 +2323,14 @@ Status DBImpl::SyncWAL() {
|
||||
s = ApplyWALToManifest(read_options, write_options, &synced_wals);
|
||||
}
|
||||
|
||||
if (s.ok() && wal_recovery_cutoff_.load(std::memory_order_acquire) != 0) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
Status bg_error = error_handler_.GetBGError();
|
||||
if (!bg_error.ok()) {
|
||||
s = bg_error;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT("DBImpl::SyncWAL:BeforeMarkLogsSynced:2");
|
||||
return s;
|
||||
}
|
||||
@@ -2122,6 +2372,17 @@ IOStatus DBImpl::SyncWalImpl(bool include_current_wal,
|
||||
auto& log = *it;
|
||||
// Ensure the head of logs_ is marked as getting_synced if any is.
|
||||
log.PrepareForSync();
|
||||
if (log.number <= wal_recovery_cutoff_.load(std::memory_order_acquire)) {
|
||||
// The failed file handle is permanently unusable. Recovery will make
|
||||
// this WAL obsolete only after atomically persisting all accepted
|
||||
// memtable contents, so do not retry any operation on the handle.
|
||||
// PrepareForSync() pins the entry until MarkLogsSynced() or
|
||||
// MarkLogsNotSynced() calls FinishSync(), which prevents recovery from
|
||||
// clearing the cutoff while this call still relies on it.
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::SyncWalImpl:SkipFailedWAL",
|
||||
&log.number);
|
||||
continue;
|
||||
}
|
||||
// If last sync failed on a later WAL, this could be a fully synced
|
||||
// and closed WAL that just needs to be recorded as synced in the
|
||||
// manifest.
|
||||
@@ -2140,8 +2401,15 @@ IOStatus DBImpl::SyncWalImpl(bool include_current_wal,
|
||||
IOOptions opts;
|
||||
IOStatus io_s = WritableFileWriter::PrepareIOOptions(write_options, opts);
|
||||
std::list<log::Writer*> wals_internally_closed;
|
||||
uint64_t failed_wal_number = 0;
|
||||
bool failed_with_previous_error = false;
|
||||
if (io_s.ok()) {
|
||||
for (log::Writer* log : wals_to_sync) {
|
||||
if (log->get_log_number() <=
|
||||
wal_recovery_cutoff_.load(std::memory_order_acquire)) {
|
||||
continue;
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::SyncWalImpl:BeforeSyncWAL", log);
|
||||
if (job_context) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"[JOB %d] Syncing log #%" PRIu64, job_context->job_id,
|
||||
@@ -2152,12 +2420,17 @@ IOStatus DBImpl::SyncWalImpl(bool include_current_wal,
|
||||
}
|
||||
if (log->get_log_number() >= maybe_active_number) {
|
||||
assert(log->get_log_number() == maybe_active_number);
|
||||
io_s = log->file()->SyncWithoutFlush(opts,
|
||||
immutable_db_options_.use_fsync);
|
||||
io_s = log->file()->SyncWithoutFlush(
|
||||
opts, immutable_db_options_.use_fsync, &failed_with_previous_error);
|
||||
} else {
|
||||
io_s = log->file()->Sync(opts, immutable_db_options_.use_fsync);
|
||||
io_s = log->file()->Sync(opts, immutable_db_options_.use_fsync,
|
||||
&failed_with_previous_error);
|
||||
}
|
||||
std::pair<log::Writer*, IOStatus*> wal_and_status(log, &io_s);
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::SyncWalImpl:AfterSyncWAL",
|
||||
&wal_and_status);
|
||||
if (!io_s.ok()) {
|
||||
failed_wal_number = log->get_log_number();
|
||||
break;
|
||||
}
|
||||
// WALs can be closed when purging obsolete files, but if recycling is
|
||||
@@ -2177,6 +2450,7 @@ IOStatus DBImpl::SyncWalImpl(bool include_current_wal,
|
||||
io_s = log->file()->Close(opts);
|
||||
wals_internally_closed.push_back(log);
|
||||
if (!io_s.ok()) {
|
||||
failed_wal_number = log->get_log_number();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -2187,7 +2461,16 @@ IOStatus DBImpl::SyncWalImpl(bool include_current_wal,
|
||||
io_s.ToString().c_str());
|
||||
// In case there is a fs error we should set it globally to prevent the
|
||||
// future writes
|
||||
WALIOStatusCheck(io_s);
|
||||
// The live-WAL operation that first observed the I/O failure owns
|
||||
// classifying its original status, either through WALIOStatusCheck() or a
|
||||
// direct SetBGError() call. With paranoid checks enabled, a concurrent sync
|
||||
// must not classify the synthetic "previous error" status because it has
|
||||
// lost the original scope and could incorrectly make the error fatal. With
|
||||
// paranoid checks disabled, WALIOStatusCheck() instead preserves the
|
||||
// legacy behavior of resetting the writer so a later operation can retry.
|
||||
if (!failed_with_previous_error || !immutable_db_options_.paranoid_checks) {
|
||||
WALIOStatusCheck(io_s, failed_wal_number);
|
||||
}
|
||||
}
|
||||
if (io_s.ok() && need_wal_dir_sync) {
|
||||
io_s = directories_.GetWalDir()->FsyncWithDirOptions(
|
||||
@@ -2334,6 +2617,12 @@ void DBImpl::MarkLogsSynced(uint64_t up_to, bool synced_dir,
|
||||
auto& wal = *it;
|
||||
assert(wal.IsSyncing());
|
||||
|
||||
if (wal.number <= wal_recovery_cutoff_.load(std::memory_order_acquire)) {
|
||||
wal.FinishSync();
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (wal.number < logs_.back().number) {
|
||||
// Inactive WAL
|
||||
if (immutable_db_options_.track_and_verify_wals_in_manifest &&
|
||||
@@ -4410,12 +4699,29 @@ Status DBImpl::DropColumnFamilies(
|
||||
InstrumentedMutexLock ol(&options_mutex_);
|
||||
Status s;
|
||||
bool success_once = false;
|
||||
for (auto* handle : column_families) {
|
||||
s = DropColumnFamilyImpl(handle);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
ColumnFamilySet* const column_family_set = versions_->GetColumnFamilySet();
|
||||
const bool batch_flushable_mem_accounting =
|
||||
write_buffer_manager_ != nullptr &&
|
||||
write_buffer_manager_->ShouldTrackFlushInitiator();
|
||||
if (batch_flushable_mem_accounting) {
|
||||
InstrumentedMutexLock lock(&mutex_);
|
||||
column_family_set->BeginBulkFlushableMemAccountingUpdate();
|
||||
}
|
||||
{
|
||||
Defer finish_bulk_accounting(
|
||||
[this, column_family_set, batch_flushable_mem_accounting] {
|
||||
if (batch_flushable_mem_accounting) {
|
||||
InstrumentedMutexLock lock(&mutex_);
|
||||
column_family_set->EndBulkFlushableMemAccountingUpdate();
|
||||
}
|
||||
});
|
||||
for (auto* handle : column_families) {
|
||||
s = DropColumnFamilyImpl(handle);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
success_once = true;
|
||||
}
|
||||
success_once = true;
|
||||
}
|
||||
if (success_once) {
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
@@ -4460,6 +4766,7 @@ Status DBImpl::DropColumnFamilyImpl(ColumnFamilyHandle* column_family) {
|
||||
// we drop column family from a single write thread
|
||||
WriteThread::Writer w;
|
||||
write_thread_.EnterUnbatched(&w, &mutex_);
|
||||
WaitForPendingWrites();
|
||||
s = versions_->LogAndApply(cfd, read_options, write_options, &edit,
|
||||
&mutex_, directories_.GetDbDir());
|
||||
write_thread_.ExitUnbatched(&w);
|
||||
@@ -6135,21 +6442,36 @@ Status DestroyDB(const std::string& dbname, const Options& options,
|
||||
// log file and prevents cleanup and directory removal
|
||||
soptions.info_log.reset();
|
||||
IOOptions io_opts;
|
||||
// Ignore error in case directory does not exist
|
||||
soptions.fs
|
||||
->GetChildren(dbname, io_opts, &filenames,
|
||||
/*IODebugContext*=*/nullptr)
|
||||
.PermitUncheckedError();
|
||||
|
||||
std::set<std::string> paths_to_delete;
|
||||
FileLock* lock;
|
||||
const std::string lockname = LockFileName(dbname);
|
||||
Status result = env->LockFile(lockname, &lock);
|
||||
if (result.ok()) {
|
||||
if (sfm) {
|
||||
sfm->WaitForEmptyTrashInDirectory(dbname);
|
||||
}
|
||||
// Ignore error in case directory does not exist
|
||||
soptions.fs
|
||||
->GetChildren(dbname, io_opts, &filenames,
|
||||
/*IODebugContext*=*/nullptr)
|
||||
.PermitUncheckedError();
|
||||
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
InfoLogPrefix info_log_prefix(!soptions.db_log_dir.empty(), dbname);
|
||||
for (const auto& fname : filenames) {
|
||||
if (DeleteScheduler::IsTrashFile(fname)) {
|
||||
std::string trash_file_path = dbname;
|
||||
trash_file_path.append("/").append(fname);
|
||||
Status del = DeleteExistingTrashDBFile(&soptions, trash_file_path,
|
||||
dbname, /*force_bg=*/false,
|
||||
/*force_fg=*/false, bucket);
|
||||
if (!del.ok() && result.ok()) {
|
||||
result = del;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (ParseFileName(fname, &number, info_log_prefix.prefix, &type) &&
|
||||
type != kDBLockFile) { // Lock file will be deleted at end
|
||||
Status del;
|
||||
@@ -6189,14 +6511,26 @@ Status DestroyDB(const std::string& dbname, const Options& options,
|
||||
}
|
||||
|
||||
for (const auto& path : paths) {
|
||||
if (sfm) {
|
||||
sfm->WaitForEmptyTrashInDirectory(path);
|
||||
}
|
||||
if (soptions.fs
|
||||
->GetChildren(path, io_opts, &filenames,
|
||||
/*IODebugContext*=*/nullptr)
|
||||
.ok()) {
|
||||
for (const auto& fname : filenames) {
|
||||
if (ParseFileName(fname, &number, &type) &&
|
||||
(type == kTableFile ||
|
||||
type == kBlobFile)) { // Lock file will be deleted at end
|
||||
if (DeleteScheduler::IsTrashFile(fname)) {
|
||||
std::string trash_file_path = path;
|
||||
trash_file_path.append("/").append(fname);
|
||||
Status del = DeleteExistingTrashDBFile(&soptions, trash_file_path,
|
||||
path, /*force_bg=*/false,
|
||||
/*force_fg=*/false, bucket);
|
||||
if (!del.ok() && result.ok()) {
|
||||
result = del;
|
||||
}
|
||||
} else if (ParseFileName(fname, &number, &type) &&
|
||||
(type == kTableFile ||
|
||||
type == kBlobFile)) { // Lock file will be deleted at end
|
||||
std::string file_path = path + "/" + fname;
|
||||
Status del = DeleteUnaccountedDBFile(&soptions, file_path, dbname,
|
||||
/*force_bg=*/false,
|
||||
@@ -6215,6 +6549,9 @@ Status DestroyDB(const std::string& dbname, const Options& options,
|
||||
std::string archivedir = ArchivalDirectory(dbname);
|
||||
bool wal_dir_exists = false;
|
||||
if (!soptions.IsWalDirSameAsDBPath(dbname)) {
|
||||
if (sfm) {
|
||||
sfm->WaitForEmptyTrashInDirectory(soptions.wal_dir);
|
||||
}
|
||||
wal_dir_exists =
|
||||
soptions.fs
|
||||
->GetChildren(soptions.wal_dir, io_opts, &walDirFiles,
|
||||
@@ -6227,13 +6564,25 @@ Status DestroyDB(const std::string& dbname, const Options& options,
|
||||
// processed and removed before those otherwise we have issues
|
||||
// removing them
|
||||
std::vector<std::string> archiveFiles;
|
||||
if (sfm) {
|
||||
sfm->WaitForEmptyTrashInDirectory(archivedir);
|
||||
}
|
||||
if (soptions.fs
|
||||
->GetChildren(archivedir, io_opts, &archiveFiles,
|
||||
/*IODebugContext*=*/nullptr)
|
||||
.ok()) {
|
||||
// Delete archival files.
|
||||
for (const auto& file : archiveFiles) {
|
||||
if (ParseFileName(file, &number, &type) && type == kWalFile) {
|
||||
if (DeleteScheduler::IsTrashFile(file)) {
|
||||
std::string trash_file_path = archivedir;
|
||||
trash_file_path.append("/").append(file);
|
||||
Status del = DeleteExistingTrashDBFile(
|
||||
&soptions, trash_file_path, archivedir, /*force_bg=*/false,
|
||||
/*force_fg=*/!wal_in_db_path, bucket);
|
||||
if (!del.ok() && result.ok()) {
|
||||
result = del;
|
||||
}
|
||||
} else if (ParseFileName(file, &number, &type) && type == kWalFile) {
|
||||
Status del = DeleteUnaccountedDBFile(
|
||||
&soptions, archivedir + "/" + file, archivedir,
|
||||
/*force_bg=*/false, /*force_fg=*/!wal_in_db_path, bucket);
|
||||
@@ -6248,7 +6597,16 @@ Status DestroyDB(const std::string& dbname, const Options& options,
|
||||
// Delete log files in the WAL dir
|
||||
if (wal_dir_exists) {
|
||||
for (const auto& file : walDirFiles) {
|
||||
if (ParseFileName(file, &number, &type) && type == kWalFile) {
|
||||
if (DeleteScheduler::IsTrashFile(file)) {
|
||||
std::string trash_file_path = soptions.wal_dir;
|
||||
trash_file_path.append("/").append(file);
|
||||
Status del = DeleteExistingTrashDBFile(
|
||||
&soptions, trash_file_path, soptions.wal_dir,
|
||||
/*force_bg=*/false, /*force_fg=*/!wal_in_db_path, bucket);
|
||||
if (!del.ok() && result.ok()) {
|
||||
result = del;
|
||||
}
|
||||
} else if (ParseFileName(file, &number, &type) && type == kWalFile) {
|
||||
Status del = DeleteUnaccountedDBFile(
|
||||
&soptions, LogFileName(soptions.wal_dir, number),
|
||||
soptions.wal_dir, /*force_bg=*/false,
|
||||
@@ -6816,10 +7174,6 @@ Status DBImpl::PrepareFileIngestion(
|
||||
"] size must match external_files[" +
|
||||
std::to_string(i) + "] size");
|
||||
}
|
||||
if (args[i].options.write_global_seqno) {
|
||||
return Status::InvalidArgument(
|
||||
"write_global_seqno is not supported when file_infos is set");
|
||||
}
|
||||
}
|
||||
if (i && args[i].options.fill_cache != args[i - 1].options.fill_cache) {
|
||||
return Status::InvalidArgument(
|
||||
@@ -6866,13 +7220,6 @@ Status DBImpl::PrepareFileIngestion(
|
||||
}
|
||||
}
|
||||
|
||||
if (ingest_opts.allow_db_generated_files) {
|
||||
if (ingest_opts.write_global_seqno) {
|
||||
return Status::NotSupported(
|
||||
"write_global_seqno is deprecated and does not work with "
|
||||
"allow_db_generated_files.");
|
||||
}
|
||||
}
|
||||
if (ingest_opts.move_files && ingest_opts.link_files) {
|
||||
return Status::InvalidArgument(
|
||||
"`move_files` and `link_files` can not both be true.");
|
||||
@@ -7467,19 +7814,35 @@ Status DBImpl::ClipColumnFamily(ColumnFamilyHandle* column_family,
|
||||
std::vector<RangeOpt> ranges;
|
||||
ranges.emplace_back(OptSlice{}, begin_key);
|
||||
ranges.emplace_back(end_key, OptSlice{});
|
||||
status = DeleteFilesInRanges(column_family, ranges.data(), ranges.size());
|
||||
// begin_key is kept, so a file whose largest key is begin_key must stay.
|
||||
status = DeleteFilesInRanges(column_family, ranges.data(), ranges.size(),
|
||||
false /* include_end */);
|
||||
}
|
||||
|
||||
// DeleteRange the remaining overlapping keys
|
||||
bool empty_after_delete = false;
|
||||
if (status.ok()) {
|
||||
Slice smallest_user_key, largest_user_key;
|
||||
// GetSstFilesBoundaryKeys returns Slices into the current Version's
|
||||
// FileMetaData. Copy them out while holding the mutex: the deletes below
|
||||
// drop the mutex and each is a full write, during which a background
|
||||
// compaction can install a new Version and free that metadata.
|
||||
std::string smallest_buf;
|
||||
std::string largest_buf;
|
||||
{
|
||||
// Lock db mutex
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
cfd->current()->GetSstFilesBoundaryKeys(&smallest_user_key,
|
||||
&largest_user_key);
|
||||
Slice smallest_slice;
|
||||
Slice largest_slice;
|
||||
cfd->current()->GetSstFilesBoundaryKeys(&smallest_slice, &largest_slice);
|
||||
smallest_buf.assign(smallest_slice.data(), smallest_slice.size());
|
||||
largest_buf.assign(largest_slice.data(), largest_slice.size());
|
||||
}
|
||||
Slice smallest_user_key(smallest_buf);
|
||||
Slice largest_user_key(largest_buf);
|
||||
// Opens the window in which the current Version (and the FileMetaData the
|
||||
// boundary keys were read from) can be replaced and freed by a background
|
||||
// job before the deletes below run.
|
||||
TEST_SYNC_POINT("DBImpl::ClipColumnFamily:PostBoundaryKeys");
|
||||
// all the files has been deleted after DeleteFilesInRanges;
|
||||
if (smallest_user_key.empty() && largest_user_key.empty()) {
|
||||
empty_after_delete = true;
|
||||
|
||||
+128
-3
@@ -9,6 +9,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
@@ -51,6 +52,7 @@
|
||||
#include "db/write_controller.h"
|
||||
#include "db/write_thread.h"
|
||||
#include "logging/event_logger.h"
|
||||
#include "memtable/flush_initiator.h"
|
||||
#include "memtable/wbwi_memtable.h"
|
||||
#include "monitoring/instrumented_mutex.h"
|
||||
#include "options/db_options.h"
|
||||
@@ -1342,6 +1344,9 @@ class DBImpl : public DB
|
||||
|
||||
Status TEST_SwitchMemtable(ColumnFamilyData* cfd = nullptr);
|
||||
|
||||
size_t TEST_GetWBMFlushableMemUsage();
|
||||
bool TEST_IsWBMFlushInitiatorFlushable() const;
|
||||
|
||||
// Force current memtable contents to be flushed.
|
||||
Status TEST_FlushMemTable(bool wait = true, bool allow_write_stall = false,
|
||||
ColumnFamilyHandle* cfh = nullptr);
|
||||
@@ -1362,7 +1367,10 @@ class DBImpl : public DB
|
||||
// finishes. For example in CompactRange.
|
||||
Status TEST_AtomicFlushMemTables(
|
||||
const autovector<ColumnFamilyData*>& provided_candidate_cfds,
|
||||
const FlushOptions& flush_opts);
|
||||
const FlushOptions& flush_opts, bool non_blocking_write_thread = false);
|
||||
|
||||
void TEST_BeginWriteStall();
|
||||
void TEST_EndWriteStall();
|
||||
|
||||
// Wait for background threads to complete scheduled work.
|
||||
Status TEST_WaitForBackgroundWork();
|
||||
@@ -1555,6 +1563,21 @@ class DBImpl : public DB
|
||||
}
|
||||
}
|
||||
|
||||
// Returns false when the wait times out while the DB is still blocked.
|
||||
bool BlockFor(uint64_t timeout_micros) {
|
||||
MutexLock lock(&state_mutex_);
|
||||
const std::chrono::microseconds deadline(
|
||||
SystemClock::Default()->NowMicros() + timeout_micros);
|
||||
while (state_ == State::BLOCKED) {
|
||||
TEST_SYNC_POINT("WBMStallInterface::BlockDB");
|
||||
if (SystemClock::Default()->TimedWait(&state_cv_, deadline) &&
|
||||
state_ == State::BLOCKED) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Called from WriteBufferManager. This function changes the state_
|
||||
// to State::RUNNING indicating the stall is cleared and DB can proceed.
|
||||
void Signal() override {
|
||||
@@ -1575,6 +1598,24 @@ class DBImpl : public DB
|
||||
State state_;
|
||||
};
|
||||
|
||||
// Adapts DBImpl to the shared WriteBufferManager flush registry.
|
||||
class WBMFlushInitiator : public FlushInitiator {
|
||||
public:
|
||||
WBMFlushInitiator(DBImpl* db, bool atomic_flush)
|
||||
: FlushInitiator(atomic_flush), db_(db) {}
|
||||
|
||||
bool ScheduleFlush() override {
|
||||
return db_->ScheduleWriteBufferManagerFlush();
|
||||
}
|
||||
|
||||
bool TryRefreshMemoryAccounting() override {
|
||||
return db_->TryRefreshFlushableMemAccounting();
|
||||
}
|
||||
|
||||
private:
|
||||
DBImpl* const db_;
|
||||
};
|
||||
|
||||
static void TEST_ResetDbSessionIdGen();
|
||||
static std::string GenerateDbSessionId(Env* env);
|
||||
|
||||
@@ -1592,6 +1633,9 @@ class DBImpl : public DB
|
||||
// db_session_id_ is an identifier that gets reset
|
||||
// every time the DB is opened
|
||||
std::string db_session_id_;
|
||||
// Declared before versions_ so memtables cannot outlive their allocation
|
||||
// accounting target during destruction.
|
||||
std::unique_ptr<FlushInitiator> wbm_flush_initiator_;
|
||||
std::unique_ptr<VersionSet> versions_;
|
||||
// Flag to check whether we allocated and own the info log file
|
||||
bool own_info_log_;
|
||||
@@ -2631,7 +2675,7 @@ class DBImpl : public DB
|
||||
|
||||
// Begin stalling of writes when memory usage increases beyond a certain
|
||||
// threshold.
|
||||
void WriteBufferManagerStallWrites();
|
||||
Status WriteBufferManagerStallWrites(WriteContext* write_context);
|
||||
|
||||
Status ThrottleLowPriWritesIfNeeded(const WriteOptions& write_options,
|
||||
WriteBatch* my_batch);
|
||||
@@ -2659,6 +2703,16 @@ class DBImpl : public DB
|
||||
ReadOnlyMemTable* new_imm = nullptr,
|
||||
SequenceNumber last_seqno = 0);
|
||||
|
||||
// `refresh_wbm_accounting` can be false when a caller switches a batch of
|
||||
// memtables and refreshes the DB-wide accounting once after the batch.
|
||||
Status SwitchMemtableImpl(ColumnFamilyData* cfd, WriteContext* context,
|
||||
ReadOnlyMemTable* new_imm,
|
||||
SequenceNumber last_seqno,
|
||||
bool refresh_wbm_accounting);
|
||||
|
||||
// REQUIRES: mutex_ is held
|
||||
void UpdateWBMAccountingAfterMemtableSwitch(bool refresh_now);
|
||||
|
||||
// Select and output column families qualified for atomic flush in
|
||||
// `selected_cfds`. If `provided_candidate_cfds` is non-empty, it will be used
|
||||
// as candidate CFs to select qualified ones from. Otherwise, all column
|
||||
@@ -2683,6 +2737,18 @@ class DBImpl : public DB
|
||||
const autovector<ColumnFamilyData*>& provided_candidate_cfds = {},
|
||||
bool entered_write_thread = false);
|
||||
|
||||
enum class WriteThreadJoinMode { kBlocking, kNonBlocking };
|
||||
|
||||
Status FlushMemTableImpl(ColumnFamilyData* cfd, const FlushOptions& options,
|
||||
FlushReason flush_reason, bool entered_write_thread,
|
||||
WriteThreadJoinMode join_mode, bool* made_progress);
|
||||
|
||||
Status AtomicFlushMemTablesImpl(
|
||||
const FlushOptions& options, FlushReason flush_reason,
|
||||
const autovector<ColumnFamilyData*>& provided_candidate_cfds,
|
||||
bool entered_write_thread, WriteThreadJoinMode join_mode,
|
||||
bool* made_progress);
|
||||
|
||||
// REQUIRES: mutex locked and write queues drained up to the recovery flush
|
||||
// fence that is about to switch memtables.
|
||||
void MaybeSyncLastSequenceWithAllocatedForRecovery(FlushReason flush_reason);
|
||||
@@ -2769,6 +2835,18 @@ class DBImpl : public DB
|
||||
return static_cast<uint8_t*>(static_cast<void*>(this)) + type;
|
||||
}
|
||||
|
||||
// Checks DB-local write queues; manager-wide stall state would reject healthy
|
||||
// DBs. REQUIRES: mutex_ held.
|
||||
bool WouldBlockJoiningWriteThread() {
|
||||
mutex_.AssertHeld();
|
||||
return write_thread_.GetBegunCountOfOutstandingStall() != 0 ||
|
||||
(two_write_queues_ &&
|
||||
nonmem_write_thread_.GetBegunCountOfOutstandingStall() != 0);
|
||||
}
|
||||
|
||||
Status EnterWriteThreadForNonBlockingFlush(WriteThread::Writer* w,
|
||||
WriteThread::Writer* nonmem_w);
|
||||
|
||||
// REQUIRES: mutex locked and in write thread.
|
||||
void AssignAtomicFlushSeq(const autovector<ColumnFamilyData*>& cfds);
|
||||
|
||||
@@ -2778,6 +2856,31 @@ class DBImpl : public DB
|
||||
// REQUIRES: mutex locked and in write thread.
|
||||
Status HandleWriteBufferManagerFlush(WriteContext* write_context);
|
||||
|
||||
// Column families and memory eligible for a WriteBufferManager flush.
|
||||
struct FlushableCFs {
|
||||
ColumnFamilyData* largest = nullptr; // most reclaimable memtable memory
|
||||
ColumnFamilyData* oldest = nullptr; // smallest memtable creation seq
|
||||
size_t largest_mem = 0;
|
||||
size_t total_mem = 0;
|
||||
};
|
||||
|
||||
// Shared selection for bidding and flushing. REQUIRES: mutex_ held.
|
||||
FlushableCFs CollectFlushableCFs(bool include_waiting_immutable);
|
||||
|
||||
// Registers an opened read-write DB without holding mutex_.
|
||||
void MaybeRegisterFlushInitiator();
|
||||
|
||||
// Refreshes the published mutable-memory counters after memtables change.
|
||||
// REQUIRES: mutex_ held.
|
||||
void RefreshFlushableMemAccounting();
|
||||
|
||||
bool TryRefreshFlushableMemAccounting();
|
||||
|
||||
// Queues one WBM flush; false lets the coordinator try another DB.
|
||||
bool ScheduleWriteBufferManagerFlush();
|
||||
|
||||
void BackgroundCallWBMFlush();
|
||||
|
||||
// REQUIRES: mutex locked
|
||||
Status PreprocessWrite(const WriteOptions& write_options,
|
||||
WalContext* log_context, WriteContext* write_context);
|
||||
@@ -2816,7 +2919,13 @@ class DBImpl : public DB
|
||||
|
||||
// Used by WriteImpl to update bg_error_ when IO error happens, e.g., write
|
||||
// WAL, sync WAL fails, if paranoid check is enabled.
|
||||
void WALIOStatusCheck(const IOStatus& status);
|
||||
void WALIOStatusCheck(const IOStatus& status, uint64_t failed_wal_number = 0,
|
||||
SequenceNumber failed_wal_sequence = 0,
|
||||
SequenceNumber failed_wal_sequence_start = 0);
|
||||
|
||||
// Extends the inclusive WAL-number cutoff that must not receive more I/O.
|
||||
// This is lock-free because a WAL error can race with public SyncWAL().
|
||||
void ExtendWALRecoveryCutoff(uint64_t failed_wal_number);
|
||||
|
||||
// Used by WriteImpl to update bg_error_ in case of memtable insert error.
|
||||
void HandleMemTableInsertFailure(const Status& nonok_memtable_insert_status);
|
||||
@@ -3402,6 +3511,16 @@ class DBImpl : public DB
|
||||
// * whenever a column family is dropped.
|
||||
InstrumentedCondVar bg_cv_;
|
||||
|
||||
// Set only while ResumeImpl performs a file-scoped WAL recovery. Protected
|
||||
// by mutex_; SwitchMemtable uses it to avoid touching the failed writer.
|
||||
bool recovering_from_wal_write_error_ = false;
|
||||
uint64_t recover_wal_through_number_ = 0;
|
||||
|
||||
// Once a file-scoped WAL error is reported, all writers through this
|
||||
// inclusive cutoff are quarantined until recovery detaches them. A
|
||||
// concurrent SyncWAL() reads this without taking the DB mutex.
|
||||
std::atomic<uint64_t> wal_recovery_cutoff_{0};
|
||||
|
||||
ColumnFamilyHandleImpl* persist_stats_cf_handle_ = nullptr;
|
||||
|
||||
bool persistent_stats_cfd_exists_ = true;
|
||||
@@ -3665,6 +3784,12 @@ class DBImpl : public DB
|
||||
// number of background memtable flush jobs, submitted to the HIGH pool
|
||||
int bg_flush_scheduled_ = 0;
|
||||
|
||||
// LOW avoids consuming a flush worker while joining the target write thread.
|
||||
static constexpr Env::Priority kWBMFlushPriority = Env::Priority::LOW;
|
||||
|
||||
// Outstanding cross-DB WBM flush jobs, drained during shutdown.
|
||||
int bg_wbm_flush_scheduled_ = 0;
|
||||
|
||||
// stores the number of flushes are currently running
|
||||
int num_running_flushes_ = 0;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "db/blob/blob_file_partition_manager.h"
|
||||
@@ -1712,7 +1713,7 @@ void DBImpl::PrepareTrivialMoveEdit(Compaction& c, LogBuffer* log_buffer,
|
||||
c.output_level(), f->fd.GetNumber(), f->fd.GetPathId(),
|
||||
f->fd.GetFileSize(), f->smallest, f->largest, f->fd.smallest_seqno,
|
||||
f->fd.largest_seqno, f->marked_for_compaction, f->temperature,
|
||||
f->oldest_blob_file_number, f->oldest_ancester_time,
|
||||
f->oldest_blob_file_number, f->oldest_ancestor_time,
|
||||
f->file_creation_time, f->epoch_number, f->file_checksum,
|
||||
f->file_checksum_func_name, f->unique_id,
|
||||
f->compensated_range_deletion_size, f->tail_size,
|
||||
@@ -2033,7 +2034,7 @@ Status DBImpl::PauseBackgroundWork() {
|
||||
InstrumentedMutexLock guard_lock(&mutex_);
|
||||
bg_compaction_paused_++;
|
||||
while (bg_bottom_compaction_scheduled_ > 0 || bg_compaction_scheduled_ > 0 ||
|
||||
bg_flush_scheduled_ > 0) {
|
||||
bg_flush_scheduled_ > 0 || bg_wbm_flush_scheduled_ > 0) {
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
bg_work_paused_++;
|
||||
@@ -2289,7 +2290,7 @@ Status DBImpl::ReFitLevel(ColumnFamilyData* cfd, int level, int target_level) {
|
||||
to_level, f->fd.GetNumber(), f->fd.GetPathId(), f->fd.GetFileSize(),
|
||||
f->smallest, f->largest, f->fd.smallest_seqno, f->fd.largest_seqno,
|
||||
f->marked_for_compaction, f->temperature, f->oldest_blob_file_number,
|
||||
f->oldest_ancester_time, f->file_creation_time, f->epoch_number,
|
||||
f->oldest_ancestor_time, f->file_creation_time, f->epoch_number,
|
||||
f->file_checksum, f->file_checksum_func_name, f->unique_id,
|
||||
f->compensated_range_deletion_size, f->tail_size,
|
||||
f->user_defined_timestamps_persisted, f->min_timestamp,
|
||||
@@ -2724,12 +2725,54 @@ void DBImpl::MaybeSyncLastSequenceWithAllocatedForRecovery(
|
||||
}
|
||||
}
|
||||
|
||||
Status DBImpl::EnterWriteThreadForNonBlockingFlush(
|
||||
WriteThread::Writer* w, WriteThread::Writer* nonmem_w) {
|
||||
mutex_.AssertHeld();
|
||||
if (!write_thread_.EnterUnbatchedNonBlocking(w, &mutex_)) {
|
||||
return Status::Incomplete(
|
||||
"Write stall in progress, unable to join the write thread to switch "
|
||||
"memtables");
|
||||
}
|
||||
if (two_write_queues_ &&
|
||||
!nonmem_write_thread_.EnterUnbatchedNonBlocking(nonmem_w, &mutex_)) {
|
||||
write_thread_.ExitUnbatched(w);
|
||||
return Status::Incomplete(
|
||||
"Write stall in progress, unable to join the write thread to switch "
|
||||
"memtables");
|
||||
}
|
||||
if (!error_handler_.IsBGWorkStopped() && !write_controller_.IsStopped() &&
|
||||
!WouldBlockJoiningWriteThread()) {
|
||||
return Status::OK();
|
||||
}
|
||||
write_thread_.ExitUnbatched(w);
|
||||
if (two_write_queues_) {
|
||||
nonmem_write_thread_.ExitUnbatched(nonmem_w);
|
||||
}
|
||||
return Status::Incomplete(
|
||||
"Write stop started while joining the write thread to switch memtables");
|
||||
}
|
||||
|
||||
Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
|
||||
const FlushOptions& flush_options,
|
||||
FlushReason flush_reason,
|
||||
bool entered_write_thread) {
|
||||
return FlushMemTableImpl(cfd, flush_options, flush_reason,
|
||||
entered_write_thread, WriteThreadJoinMode::kBlocking,
|
||||
nullptr /* made_progress */);
|
||||
}
|
||||
|
||||
Status DBImpl::FlushMemTableImpl(ColumnFamilyData* cfd,
|
||||
const FlushOptions& flush_options,
|
||||
FlushReason flush_reason,
|
||||
bool entered_write_thread,
|
||||
WriteThreadJoinMode join_mode,
|
||||
bool* made_progress) {
|
||||
if (made_progress != nullptr) {
|
||||
*made_progress = false;
|
||||
}
|
||||
// This method should not be called if atomic_flush is true.
|
||||
assert(!immutable_db_options_.atomic_flush);
|
||||
assert(!IsRecoveryFlush(flush_reason));
|
||||
if (!flush_options.wait && write_controller_.IsStopped()) {
|
||||
std::ostringstream oss;
|
||||
oss << "Writes have been stopped, thus unable to perform manual flush. "
|
||||
@@ -2763,13 +2806,45 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
|
||||
WriteThread::Writer w;
|
||||
WriteThread::Writer nonmem_w;
|
||||
if (needs_to_join_write_thread) {
|
||||
write_thread_.EnterUnbatched(&w, &mutex_);
|
||||
if (two_write_queues_) {
|
||||
nonmem_write_thread_.EnterUnbatched(&nonmem_w, &mutex_);
|
||||
if (join_mode == WriteThreadJoinMode::kNonBlocking) {
|
||||
s = EnterWriteThreadForNonBlockingFlush(&w, &nonmem_w);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
} else {
|
||||
write_thread_.EnterUnbatched(&w, &mutex_);
|
||||
if (two_write_queues_) {
|
||||
nonmem_write_thread_.EnterUnbatched(&nonmem_w, &mutex_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (join_mode == WriteThreadJoinMode::kNonBlocking && cfd->IsDropped()) {
|
||||
TEST_SYNC_POINT("DBImpl::FlushMemTableImpl:Dropped");
|
||||
if (needs_to_join_write_thread) {
|
||||
write_thread_.ExitUnbatched(&w);
|
||||
if (two_write_queues_) {
|
||||
nonmem_write_thread_.ExitUnbatched(&nonmem_w);
|
||||
}
|
||||
}
|
||||
s.PermitUncheckedOk();
|
||||
return Status::ColumnFamilyDropped();
|
||||
}
|
||||
WaitForPendingWrites();
|
||||
|
||||
if (join_mode == WriteThreadJoinMode::kNonBlocking &&
|
||||
flush_reason == FlushReason::kWriteBufferManager &&
|
||||
cfd->imm()->IsFlushPendingOrRunning()) {
|
||||
TEST_SYNC_POINT("DBImpl::FlushMemTableImpl:AlreadyFlushing");
|
||||
if (needs_to_join_write_thread) {
|
||||
write_thread_.ExitUnbatched(&w);
|
||||
if (two_write_queues_) {
|
||||
nonmem_write_thread_.ExitUnbatched(&nonmem_w);
|
||||
}
|
||||
}
|
||||
return Status::Incomplete(
|
||||
"Column family started flushing before the WBM handoff completed");
|
||||
}
|
||||
|
||||
// Recovery may have released `mutex_` after the earlier `ResumeImpl()`
|
||||
// sync. Refresh sequence state at the actual memtable-switch fence, after
|
||||
// both write queues are drained and before `SwitchMemtable()` consumes
|
||||
@@ -2779,6 +2854,9 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
|
||||
if (!cfd->mem()->IsEmpty() || !cached_recoverable_state_empty_.load() ||
|
||||
IsRecoveryFlush(flush_reason)) {
|
||||
s = SwitchMemtable(cfd, &context);
|
||||
if (s.ok() && made_progress != nullptr) {
|
||||
*made_progress = true;
|
||||
}
|
||||
}
|
||||
const uint64_t flush_memtable_id = std::numeric_limits<uint64_t>::max();
|
||||
if (s.ok()) {
|
||||
@@ -2851,7 +2929,11 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
|
||||
req.cfd_to_max_mem_id_to_persist.begin()->first;
|
||||
bool already_queued_for_flush = loop_cfd->queued_for_flush();
|
||||
bool flush_req_enqueued = EnqueuePendingFlush(req);
|
||||
if (already_queued_for_flush || flush_req_enqueued) {
|
||||
if (flush_req_enqueued && made_progress != nullptr) {
|
||||
*made_progress = true;
|
||||
}
|
||||
if (flush_reason != FlushReason::kWriteBufferManager &&
|
||||
(already_queued_for_flush || flush_req_enqueued)) {
|
||||
loop_cfd->SetFlushSkipReschedule();
|
||||
}
|
||||
}
|
||||
@@ -2866,7 +2948,9 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
|
||||
}
|
||||
}
|
||||
|
||||
NotifyOnManualFlushScheduled({cfd}, flush_reason);
|
||||
if (flush_reason != FlushReason::kWriteBufferManager) {
|
||||
NotifyOnManualFlushScheduled({cfd}, flush_reason);
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::FlushMemTable:AfterScheduleFlush");
|
||||
TEST_SYNC_POINT("DBImpl::FlushMemTable:BeforeWaitForBgFlush");
|
||||
if (s.ok() && flush_options.wait) {
|
||||
@@ -2895,6 +2979,20 @@ Status DBImpl::AtomicFlushMemTables(
|
||||
const FlushOptions& flush_options, FlushReason flush_reason,
|
||||
const autovector<ColumnFamilyData*>& provided_candidate_cfds,
|
||||
bool entered_write_thread) {
|
||||
return AtomicFlushMemTablesImpl(flush_options, flush_reason,
|
||||
provided_candidate_cfds, entered_write_thread,
|
||||
WriteThreadJoinMode::kBlocking,
|
||||
nullptr /* made_progress */);
|
||||
}
|
||||
|
||||
Status DBImpl::AtomicFlushMemTablesImpl(
|
||||
const FlushOptions& flush_options, FlushReason flush_reason,
|
||||
const autovector<ColumnFamilyData*>& provided_candidate_cfds,
|
||||
bool entered_write_thread, WriteThreadJoinMode join_mode,
|
||||
bool* made_progress) {
|
||||
if (made_progress != nullptr) {
|
||||
*made_progress = false;
|
||||
}
|
||||
if (!flush_options.wait && write_controller_.IsStopped()) {
|
||||
std::ostringstream oss;
|
||||
oss << "Writes have been stopped, thus unable to perform manual flush. "
|
||||
@@ -2925,32 +3023,30 @@ Status DBImpl::AtomicFlushMemTables(
|
||||
candidate_cfds = provided_candidate_cfds;
|
||||
}
|
||||
|
||||
const auto unref_generated_candidates = [&]() {
|
||||
if (!provided_candidate_cfds.empty()) {
|
||||
return;
|
||||
}
|
||||
for (auto candidate_cfd : candidate_cfds) {
|
||||
candidate_cfd->UnrefAndTryDelete();
|
||||
}
|
||||
candidate_cfds.clear();
|
||||
};
|
||||
|
||||
if (!flush_options.allow_write_stall) {
|
||||
int num_cfs_to_flush = 0;
|
||||
for (auto cfd : candidate_cfds) {
|
||||
bool flush_needed = true;
|
||||
s = WaitUntilFlushWouldNotStallWrites(cfd, &flush_needed);
|
||||
if (!s.ok()) {
|
||||
// Unref the newly generated candidate cfds (when not provided) in
|
||||
// `candidate_cfds`
|
||||
if (provided_candidate_cfds.empty()) {
|
||||
for (auto candidate_cfd : candidate_cfds) {
|
||||
candidate_cfd->UnrefAndTryDelete();
|
||||
}
|
||||
}
|
||||
unref_generated_candidates();
|
||||
return s;
|
||||
} else if (flush_needed) {
|
||||
++num_cfs_to_flush;
|
||||
}
|
||||
}
|
||||
if (0 == num_cfs_to_flush) {
|
||||
// Unref the newly generated candidate cfds (when not provided) in
|
||||
// `candidate_cfds`
|
||||
if (provided_candidate_cfds.empty()) {
|
||||
for (auto candidate_cfd : candidate_cfds) {
|
||||
candidate_cfd->UnrefAndTryDelete();
|
||||
}
|
||||
}
|
||||
unref_generated_candidates();
|
||||
return s;
|
||||
}
|
||||
}
|
||||
@@ -2965,39 +3061,54 @@ Status DBImpl::AtomicFlushMemTables(
|
||||
WriteThread::Writer w;
|
||||
WriteThread::Writer nonmem_w;
|
||||
if (needs_to_join_write_thread) {
|
||||
write_thread_.EnterUnbatched(&w, &mutex_);
|
||||
if (two_write_queues_) {
|
||||
nonmem_write_thread_.EnterUnbatched(&nonmem_w, &mutex_);
|
||||
if (join_mode == WriteThreadJoinMode::kNonBlocking) {
|
||||
s = EnterWriteThreadForNonBlockingFlush(&w, &nonmem_w);
|
||||
if (!s.ok()) {
|
||||
unref_generated_candidates();
|
||||
return s;
|
||||
}
|
||||
} else {
|
||||
write_thread_.EnterUnbatched(&w, &mutex_);
|
||||
if (two_write_queues_) {
|
||||
nonmem_write_thread_.EnterUnbatched(&nonmem_w, &mutex_);
|
||||
}
|
||||
}
|
||||
}
|
||||
WaitForPendingWrites();
|
||||
|
||||
// Keep atomic recovery flushes consistent with the single-CF path: the
|
||||
// sequence sync must happen after both write queues are drained and before
|
||||
// any recovery memtable switch reads `LastSequence()`.
|
||||
// Recovery may have released `mutex_` after the earlier `ResumeImpl()`
|
||||
// sync. Refresh sequence state at the atomic memtable-switch fence, after
|
||||
// both write queues are drained and before `SwitchMemtable()` consumes
|
||||
// `LastSequence()`.
|
||||
MaybeSyncLastSequenceWithAllocatedForRecovery(flush_reason);
|
||||
|
||||
SelectColumnFamiliesForAtomicFlush(&cfds, candidate_cfds, flush_reason);
|
||||
|
||||
// Unref the newly generated candidate cfds (when not provided) in
|
||||
// `candidate_cfds`
|
||||
if (provided_candidate_cfds.empty()) {
|
||||
for (auto candidate_cfd : candidate_cfds) {
|
||||
candidate_cfd->UnrefAndTryDelete();
|
||||
}
|
||||
}
|
||||
unref_generated_candidates();
|
||||
|
||||
const bool defer_wbm_accounting_refresh = cfds.size() > 1;
|
||||
bool switch_attempted = false;
|
||||
for (auto cfd : cfds) {
|
||||
if (cfd->mem()->IsEmpty() && cached_recoverable_state_empty_.load() &&
|
||||
!IsRecoveryFlush(flush_reason)) {
|
||||
continue;
|
||||
}
|
||||
cfd->Ref();
|
||||
s = SwitchMemtable(cfd, &context);
|
||||
switch_attempted = true;
|
||||
s = defer_wbm_accounting_refresh
|
||||
? SwitchMemtableImpl(cfd, &context, nullptr, 0,
|
||||
false /* refresh_wbm_accounting */)
|
||||
: SwitchMemtable(cfd, &context);
|
||||
cfd->UnrefAndTryDelete();
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
if (made_progress != nullptr) {
|
||||
*made_progress = true;
|
||||
}
|
||||
}
|
||||
if (defer_wbm_accounting_refresh && switch_attempted) {
|
||||
UpdateWBMAccountingAfterMemtableSwitch(true /* refresh_now */);
|
||||
}
|
||||
if (s.ok()) {
|
||||
AssignAtomicFlushSeq(cfds);
|
||||
@@ -3014,7 +3125,9 @@ Status DBImpl::AtomicFlushMemTables(
|
||||
}
|
||||
}
|
||||
GenerateFlushRequest(cfds, flush_reason, &flush_req);
|
||||
EnqueuePendingFlush(flush_req);
|
||||
if (EnqueuePendingFlush(flush_req) && made_progress != nullptr) {
|
||||
*made_progress = true;
|
||||
}
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
|
||||
@@ -3025,14 +3138,39 @@ Status DBImpl::AtomicFlushMemTables(
|
||||
}
|
||||
}
|
||||
}
|
||||
NotifyOnManualFlushScheduled(cfds, flush_reason);
|
||||
if (flush_reason != FlushReason::kWriteBufferManager) {
|
||||
NotifyOnManualFlushScheduled(cfds, flush_reason);
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::AtomicFlushMemTables:AfterScheduleFlush");
|
||||
TEST_SYNC_POINT("DBImpl::AtomicFlushMemTables:BeforeWaitForBgFlush");
|
||||
if (s.ok() && flush_options.wait) {
|
||||
// Walk cfds and look each one up, so the ids line up with cfds by
|
||||
// construction. Iterating the map instead pairs ids with the wrong CF
|
||||
// (its order is unrelated to cfds), and a CF that dropped out of the map
|
||||
// (an idle CF de-selected during the switch loop, or a duplicate handle
|
||||
// collapsed by the map) would make the two vectors different lengths.
|
||||
autovector<const uint64_t*> flush_memtable_ids;
|
||||
for (auto& iter : flush_req.cfd_to_max_mem_id_to_persist) {
|
||||
flush_memtable_ids.push_back(&(iter.second));
|
||||
flush_memtable_ids.reserve(cfds.size());
|
||||
for (auto* cfd : cfds) {
|
||||
auto iter = flush_req.cfd_to_max_mem_id_to_persist.find(cfd);
|
||||
flush_memtable_ids.push_back(
|
||||
iter == flush_req.cfd_to_max_mem_id_to_persist.end()
|
||||
? nullptr
|
||||
: &(iter->second));
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
// WaitForFlushMemTables pairs flush_memtable_ids[i] with cfds[i], so each
|
||||
// entry must point at this cfd's own map id (or be null when absent). Fires
|
||||
// if the ids are ever rebuilt in the request map's order.
|
||||
assert(flush_memtable_ids.size() == cfds.size());
|
||||
for (size_t i = 0; i < cfds.size(); ++i) {
|
||||
auto iter = flush_req.cfd_to_max_mem_id_to_persist.find(cfds[i]);
|
||||
assert(flush_memtable_ids[i] ==
|
||||
(iter == flush_req.cfd_to_max_mem_id_to_persist.end()
|
||||
? nullptr
|
||||
: &(iter->second)));
|
||||
}
|
||||
#endif // NDEBUG
|
||||
s = WaitForFlushMemTables(
|
||||
cfds, flush_memtable_ids,
|
||||
flush_reason == FlushReason::kErrorRecovery /* resuming_from_bg_err */,
|
||||
@@ -3072,9 +3210,29 @@ Status DBImpl::RetryFlushesForErrorRecovery(FlushReason flush_reason,
|
||||
flush_req.atomic_flush = true;
|
||||
GenerateFlushRequest(cfds, flush_reason, &flush_req);
|
||||
EnqueuePendingFlush(flush_req);
|
||||
for (auto& iter : flush_req.cfd_to_max_mem_id_to_persist) {
|
||||
flush_memtable_ids.push_back(iter.second);
|
||||
// Keep flush_memtable_ids aligned with cfds by walking cfds, since the
|
||||
// request map's order is unrelated to cfds. A CF not in the map waits on
|
||||
// its whole imm list, matching the max-bound the non-atomic branch uses.
|
||||
for (auto* cfd : cfds) {
|
||||
auto iter = flush_req.cfd_to_max_mem_id_to_persist.find(cfd);
|
||||
flush_memtable_ids.push_back(
|
||||
iter == flush_req.cfd_to_max_mem_id_to_persist.end()
|
||||
? std::numeric_limits<uint64_t>::max()
|
||||
: iter->second);
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
// WaitForFlushMemTables pairs flush_memtable_ids[i] with cfds[i], so each
|
||||
// entry must be this cfd's own max id. Fires if the ids are ever rebuilt in
|
||||
// the request map's order, which is unrelated to cfds order.
|
||||
assert(flush_memtable_ids.size() == cfds.size());
|
||||
for (size_t i = 0; i < cfds.size(); ++i) {
|
||||
auto iter = flush_req.cfd_to_max_mem_id_to_persist.find(cfds[i]);
|
||||
assert(flush_memtable_ids[i] ==
|
||||
(iter == flush_req.cfd_to_max_mem_id_to_persist.end()
|
||||
? std::numeric_limits<uint64_t>::max()
|
||||
: iter->second));
|
||||
}
|
||||
#endif // NDEBUG
|
||||
} else {
|
||||
for (auto cfd : cfds) {
|
||||
flush_memtable_ids.push_back(
|
||||
@@ -3835,11 +3993,34 @@ bool DBImpl::EnqueuePendingFlush(const FlushRequest& flush_req) {
|
||||
enqueued = true;
|
||||
}
|
||||
} else {
|
||||
// Atomic flush requests bypass the queued_for_flush() deduplication guard.
|
||||
// This means an atomic request and a non-atomic request for the same CF
|
||||
// can coexist in the flush queue. This is safe because PickMemtablesToFlush
|
||||
// uses flush_in_progress_ to prevent double-picking of memtables, and
|
||||
// BackgroundFlush filters out CFs where !IsFlushPending().
|
||||
if (flush_req.flush_reason == FlushReason::kWriteBufferManager &&
|
||||
write_buffer_manager_->ShouldTrackFlushInitiator()) {
|
||||
for (const FlushRequest& queued : flush_queue_) {
|
||||
if (!queued.atomic_flush) {
|
||||
continue;
|
||||
}
|
||||
bool covered = true;
|
||||
for (const auto& requested : flush_req.cfd_to_max_mem_id_to_persist) {
|
||||
const auto queued_entry =
|
||||
queued.cfd_to_max_mem_id_to_persist.find(requested.first);
|
||||
if (queued_entry == queued.cfd_to_max_mem_id_to_persist.end() ||
|
||||
queued_entry->second < requested.second) {
|
||||
covered = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (covered) {
|
||||
TEST_SYNC_POINT(
|
||||
"DBImpl::EnqueuePendingFlush:AtomicWBMRequestDeduplicated");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Atomic flush requests normally bypass the per-CF queued_for_flush()
|
||||
// guard. Cross-DB WBM requests are deduplicated above because its
|
||||
// autonomous retry loop must not repeatedly enqueue the same atomic cut
|
||||
// while HIGH workers are unavailable. Other atomic requests retain their
|
||||
// existing behavior.
|
||||
for (auto& iter : flush_req.cfd_to_max_mem_id_to_persist) {
|
||||
ColumnFamilyData* cfd = iter.first;
|
||||
cfd->Ref();
|
||||
@@ -3964,6 +4145,178 @@ void DBImpl::UnscheduleFlushCallback(void* arg) {
|
||||
TEST_SYNC_POINT("DBImpl::UnscheduleFlushCallback");
|
||||
}
|
||||
|
||||
void DBImpl::RefreshFlushableMemAccounting() {
|
||||
mutex_.AssertHeld();
|
||||
versions_->GetColumnFamilySet()->RefreshFlushableMemAccounting();
|
||||
}
|
||||
|
||||
bool DBImpl::TryRefreshFlushableMemAccounting() {
|
||||
if (!mutex_.TryLock()) {
|
||||
return false;
|
||||
}
|
||||
Defer unlock_mutex([this] { mutex_.Unlock(); });
|
||||
RefreshFlushableMemAccounting();
|
||||
return wbm_flush_initiator_ != nullptr &&
|
||||
wbm_flush_initiator_->HasAccurateFlushableMemUsage();
|
||||
}
|
||||
|
||||
DBImpl::FlushableCFs DBImpl::CollectFlushableCFs(
|
||||
bool include_waiting_immutable) {
|
||||
mutex_.AssertHeld();
|
||||
FlushableCFs result;
|
||||
SequenceNumber oldest_seq = kMaxSequenceNumber;
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (cfd->IsDropped() || !cfd->initialized()) {
|
||||
continue;
|
||||
}
|
||||
const size_t mutable_mem =
|
||||
cfd->mem()->IsEmpty() ? 0 : cfd->mem()->WBMTrackedMemoryUsage();
|
||||
size_t waiting_immutable_mem = 0;
|
||||
if (include_waiting_immutable) {
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
waiting_immutable_mem =
|
||||
cfd->imm()->WBMTrackedUnstartedMemTablesMemoryUsage();
|
||||
} else if (!cfd->imm()->IsFlushPendingOrRunning()) {
|
||||
waiting_immutable_mem =
|
||||
cfd->imm()->WBMTrackedUnflushedMemTablesMemoryUsage();
|
||||
}
|
||||
}
|
||||
result.total_mem += mutable_mem + waiting_immutable_mem;
|
||||
// Non-atomic flushes skip CFs already flushing.
|
||||
if (cfd->imm()->IsFlushPendingOrRunning()) {
|
||||
continue;
|
||||
}
|
||||
size_t mem = mutable_mem;
|
||||
if (include_waiting_immutable) {
|
||||
mem += waiting_immutable_mem;
|
||||
}
|
||||
if (mem == 0) {
|
||||
continue;
|
||||
}
|
||||
if (result.largest == nullptr || mem > result.largest_mem) {
|
||||
result.largest = cfd;
|
||||
result.largest_mem = mem;
|
||||
}
|
||||
const SequenceNumber seq = cfd->mem()->GetCreationSeq();
|
||||
if (result.oldest == nullptr || seq < oldest_seq) {
|
||||
result.oldest = cfd;
|
||||
oldest_seq = seq;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool DBImpl::ScheduleWriteBufferManagerFlush() {
|
||||
{
|
||||
if (!mutex_.TryLock()) {
|
||||
return false;
|
||||
}
|
||||
Defer unlock_mutex([this] { mutex_.Unlock(); });
|
||||
// Recheck bid eligibility because DB state may have changed.
|
||||
if (!write_buffer_manager_->ShouldTrackFlushInitiator() ||
|
||||
shutdown_initiated_.load(std::memory_order_acquire) ||
|
||||
shutting_down_.load(std::memory_order_acquire) ||
|
||||
reject_new_background_jobs_ || !opened_successfully_ || read_only_ ||
|
||||
error_handler_.IsBGWorkStopped() || write_controller_.IsStopped() ||
|
||||
// Reject both the pause drain and fully paused states.
|
||||
bg_work_paused_ > 0 || bg_compaction_paused_ > 0 ||
|
||||
// Never queue work that would wait behind this DB's stall.
|
||||
WouldBlockJoiningWriteThread()) {
|
||||
return false;
|
||||
}
|
||||
// A queued or running job does not count as new progress. Returning false
|
||||
// lets the coordinator try another DB instead of queuing more work behind
|
||||
// a busy LOW pool.
|
||||
if (bg_wbm_flush_scheduled_ > 0) {
|
||||
return false;
|
||||
}
|
||||
const FlushableCFs flushable =
|
||||
CollectFlushableCFs(true /* include_waiting_immutable */);
|
||||
const bool has_flushable_cf = immutable_db_options_.atomic_flush
|
||||
? flushable.total_mem > 0
|
||||
: flushable.largest != nullptr;
|
||||
if (!has_flushable_cf) {
|
||||
wbm_flush_initiator_->SetHasFlushableCF(false);
|
||||
write_buffer_manager_->NotifyFlushInitiatorChanged();
|
||||
return false;
|
||||
}
|
||||
wbm_flush_initiator_->SetHasFlushableCF(false);
|
||||
write_buffer_manager_->NotifyFlushInitiatorChanged();
|
||||
++bg_wbm_flush_scheduled_;
|
||||
}
|
||||
|
||||
// The WBM-owned executor provides bounded capacity independent of Env::LOW.
|
||||
// Run outside mutex_ so DB-local work cannot block other DB mutex users.
|
||||
BackgroundCallWBMFlush();
|
||||
return true;
|
||||
}
|
||||
|
||||
void DBImpl::BackgroundCallWBMFlush() {
|
||||
const bool atomic_flush = immutable_db_options_.atomic_flush;
|
||||
ColumnFamilyData* cfd_to_flush = nullptr;
|
||||
bool has_flushable_cf = false;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
if (!shutdown_initiated_.load(std::memory_order_acquire) &&
|
||||
!shutting_down_.load(std::memory_order_acquire) &&
|
||||
!reject_new_background_jobs_ && opened_successfully_ && !read_only_ &&
|
||||
!error_handler_.IsBGWorkStopped() && !write_controller_.IsStopped() &&
|
||||
bg_work_paused_ == 0 && bg_compaction_paused_ == 0 &&
|
||||
!WouldBlockJoiningWriteThread()) {
|
||||
const FlushableCFs flushable =
|
||||
CollectFlushableCFs(true /* include_waiting_immutable */);
|
||||
has_flushable_cf =
|
||||
atomic_flush ? flushable.total_mem > 0 : flushable.largest != nullptr;
|
||||
if (!atomic_flush) {
|
||||
cfd_to_flush = flushable.largest;
|
||||
if (cfd_to_flush != nullptr) {
|
||||
cfd_to_flush->Ref();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCallWBMFlush:AfterPick");
|
||||
|
||||
FlushOptions flush_options;
|
||||
flush_options.wait = false;
|
||||
flush_options.allow_write_stall = true;
|
||||
bool made_progress = false;
|
||||
if (has_flushable_cf) {
|
||||
Status s;
|
||||
if (atomic_flush) {
|
||||
s = AtomicFlushMemTablesImpl(
|
||||
flush_options, FlushReason::kWriteBufferManager,
|
||||
{} /* provided_candidate_cfds */, false /* entered_write_thread */,
|
||||
WriteThreadJoinMode::kNonBlocking, &made_progress);
|
||||
} else {
|
||||
s = FlushMemTableImpl(cfd_to_flush, flush_options,
|
||||
FlushReason::kWriteBufferManager,
|
||||
false /* entered_write_thread */,
|
||||
WriteThreadJoinMode::kNonBlocking, &made_progress);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(
|
||||
immutable_db_options_.info_log,
|
||||
"Flush on behalf of a shared WriteBufferManager failed: %s",
|
||||
s.ToString().c_str());
|
||||
}
|
||||
s.PermitUncheckedError();
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCallWBMFlush:MadeProgress",
|
||||
&made_progress);
|
||||
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
if (cfd_to_flush != nullptr) {
|
||||
cfd_to_flush->UnrefAndTryDelete();
|
||||
}
|
||||
RefreshFlushableMemAccounting();
|
||||
--bg_wbm_flush_scheduled_;
|
||||
write_buffer_manager_->NotifyFlushInitiatorFlushCompleted(
|
||||
wbm_flush_initiator_.get(), made_progress);
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::BGWorkWBMFlush:done", this);
|
||||
bg_cv_.SignalAll();
|
||||
}
|
||||
|
||||
Status DBImpl::BackgroundFlush(bool* made_progress, JobContext* job_context,
|
||||
LogBuffer* log_buffer, FlushReason* reason,
|
||||
bool* flush_rescheduled_to_retain_udt,
|
||||
@@ -4194,6 +4547,9 @@ void DBImpl::BackgroundCallFlush(Env::Priority thread_pri) {
|
||||
assert(num_running_flushes_ > 0);
|
||||
num_running_flushes_--;
|
||||
bg_flush_scheduled_--;
|
||||
if (write_buffer_manager_->ShouldTrackFlushInitiator()) {
|
||||
RefreshFlushableMemAccounting();
|
||||
}
|
||||
// See if there's more work to be done
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
|
||||
@@ -4852,7 +5208,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
false /* marked_for_compact */,
|
||||
c->GetOutputTemperature() /* temperature */,
|
||||
in_file->oldest_blob_file_number,
|
||||
in_file->oldest_ancester_time,
|
||||
in_file->oldest_ancestor_time,
|
||||
out_file_creation_time,
|
||||
c->MinInputFileEpochNumber(),
|
||||
dest_writer->GetFileChecksum(),
|
||||
@@ -5658,15 +6014,47 @@ Status DBImpl::WaitForCompact(
|
||||
const auto deadline = immutable_db_options_.clock->NowMicros() +
|
||||
wait_for_compact_options.timeout.count();
|
||||
for (;;) {
|
||||
// The coordinator reserves this DB before invoking its non-blocking
|
||||
// scheduling callback. Drain that DB-specific reservation without holding
|
||||
// mutex_; successful callbacks publish bg_wbm_flush_scheduled_ before
|
||||
// releasing the reservation.
|
||||
if (write_buffer_manager_ != nullptr &&
|
||||
write_buffer_manager_->ShouldTrackFlushInitiator() &&
|
||||
wbm_flush_initiator_ != nullptr) {
|
||||
// Fence all coordinator requests visible before this local quiescence
|
||||
// check. Candidate retrieval either observes this generation and skips
|
||||
// the DB, or atomically publishes a handoff reservation first.
|
||||
write_buffer_manager_->FenceFlushInitiator(wbm_flush_initiator_.get());
|
||||
}
|
||||
if (wbm_flush_initiator_ != nullptr &&
|
||||
wbm_flush_initiator_->HasPendingFlushHandoff()) {
|
||||
std::chrono::microseconds handoff_timeout{0};
|
||||
if (wait_for_compact_options.timeout.count() != 0) {
|
||||
const uint64_t now = immutable_db_options_.clock->NowMicros();
|
||||
if (now >= deadline) {
|
||||
return Status::TimedOut();
|
||||
}
|
||||
handoff_timeout = std::chrono::microseconds(deadline - now);
|
||||
}
|
||||
mutex_.Unlock();
|
||||
const bool handoff_finished =
|
||||
wbm_flush_initiator_->WaitForPendingFlushHandoff(handoff_timeout);
|
||||
mutex_.Lock();
|
||||
if (!handoff_finished) {
|
||||
return Status::TimedOut();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (shutting_down_.load(std::memory_order_acquire)) {
|
||||
return Status::ShutdownInProgress();
|
||||
}
|
||||
if (bg_work_paused_ && wait_for_compact_options.abort_on_pause) {
|
||||
return Status::Aborted();
|
||||
}
|
||||
// A WBM job can enqueue a regular flush, so wait for both to quiesce.
|
||||
if ((bg_bottom_compaction_scheduled_ || bg_compaction_scheduled_ ||
|
||||
bg_flush_scheduled_ || unscheduled_compactions_ ||
|
||||
!parked_compaction_cfds_.empty() ||
|
||||
bg_flush_scheduled_ || bg_wbm_flush_scheduled_ ||
|
||||
unscheduled_compactions_ || !parked_compaction_cfds_.empty() ||
|
||||
(wait_for_compact_options.wait_for_purge && bg_purge_scheduled_) ||
|
||||
unscheduled_flushes_ || error_handler_.IsRecoveryInProgress()) &&
|
||||
(error_handler_.GetBGError().ok())) {
|
||||
|
||||
@@ -144,6 +144,18 @@ Status DBImpl::TEST_SwitchMemtable(ColumnFamilyData* cfd) {
|
||||
return s;
|
||||
}
|
||||
|
||||
size_t DBImpl::TEST_GetWBMFlushableMemUsage() {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
assert(wbm_flush_initiator_ != nullptr);
|
||||
RefreshFlushableMemAccounting();
|
||||
return wbm_flush_initiator_->GetFlushableMemUsage();
|
||||
}
|
||||
|
||||
bool DBImpl::TEST_IsWBMFlushInitiatorFlushable() const {
|
||||
assert(wbm_flush_initiator_ != nullptr);
|
||||
return wbm_flush_initiator_->IsFlushable();
|
||||
}
|
||||
|
||||
Status DBImpl::TEST_FlushMemTable(bool wait, bool allow_write_stall,
|
||||
ColumnFamilyHandle* cfh) {
|
||||
FlushOptions fo;
|
||||
@@ -182,9 +194,31 @@ Status DBImpl::TEST_FlushMemTableWithListenerWait(bool allow_write_stall,
|
||||
|
||||
Status DBImpl::TEST_AtomicFlushMemTables(
|
||||
const autovector<ColumnFamilyData*>& provided_candidate_cfds,
|
||||
const FlushOptions& flush_opts) {
|
||||
return AtomicFlushMemTables(flush_opts, FlushReason::kTest,
|
||||
provided_candidate_cfds);
|
||||
const FlushOptions& flush_opts, bool non_blocking_write_thread) {
|
||||
const auto join_mode = non_blocking_write_thread
|
||||
? WriteThreadJoinMode::kNonBlocking
|
||||
: WriteThreadJoinMode::kBlocking;
|
||||
return AtomicFlushMemTablesImpl(
|
||||
flush_opts, FlushReason::kTest, provided_candidate_cfds,
|
||||
false /* entered_write_thread */, join_mode, nullptr /* made_progress */);
|
||||
}
|
||||
|
||||
void DBImpl::TEST_BeginWriteStall() {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
write_thread_.BeginWriteStall();
|
||||
if (wbm_flush_initiator_ != nullptr &&
|
||||
write_buffer_manager_->ShouldTrackFlushInitiator()) {
|
||||
wbm_flush_initiator_->SetFlushable(false);
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::TEST_EndWriteStall() {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
write_thread_.EndWriteStall();
|
||||
if (wbm_flush_initiator_ != nullptr &&
|
||||
write_buffer_manager_->ShouldTrackFlushInitiator()) {
|
||||
wbm_flush_initiator_->SetFlushable(true);
|
||||
}
|
||||
}
|
||||
|
||||
Status DBImpl::TEST_WaitForBackgroundWork() {
|
||||
|
||||
@@ -140,7 +140,7 @@ Status DBImpl::PromoteL0(ColumnFamilyHandle* column_family, int target_level) {
|
||||
f->fd.GetFileSize(), f->smallest, f->largest,
|
||||
f->fd.smallest_seqno, f->fd.largest_seqno,
|
||||
f->marked_for_compaction, f->temperature,
|
||||
f->oldest_blob_file_number, f->oldest_ancester_time,
|
||||
f->oldest_blob_file_number, f->oldest_ancestor_time,
|
||||
f->file_creation_time, f->epoch_number, f->file_checksum,
|
||||
f->file_checksum_func_name, f->unique_id,
|
||||
f->compensated_range_deletion_size, f->tail_size,
|
||||
|
||||
@@ -153,6 +153,7 @@ Status DBImplFollower::TryCatchUpWithLeader() {
|
||||
cfd->InstallSuperVersion(&sv_context, &mutex_);
|
||||
sv_context.NewSuperVersion();
|
||||
}
|
||||
PublishSecondaryReadView();
|
||||
}
|
||||
|
||||
for (auto& file : files_to_delete) {
|
||||
@@ -164,6 +165,7 @@ Status DBImplFollower::TryCatchUpWithLeader() {
|
||||
}
|
||||
}
|
||||
}
|
||||
CleanupRetiredSecondaryReadViews();
|
||||
job_context.Clean();
|
||||
|
||||
// Cleanup unused, obsolete files.
|
||||
@@ -296,7 +298,8 @@ Status DB::OpenAsFollower(
|
||||
dbname, &impl->immutable_db_options_, impl->mutable_db_options_,
|
||||
impl->file_options_, impl->table_cache_.get(),
|
||||
impl->write_buffer_manager_, &impl->write_controller_, impl->io_tracer_,
|
||||
impl->db_id_, impl->db_session_id_));
|
||||
impl->db_id_, impl->db_session_id_,
|
||||
/*defer_sst_file_opening=*/false));
|
||||
impl->column_family_memtables_.reset(
|
||||
new ColumnFamilyMemTablesImpl(impl->versions_->GetColumnFamilySet()));
|
||||
impl->wal_in_db_path_ = impl->immutable_db_options_.IsWalDirSameAsDBPath();
|
||||
@@ -322,9 +325,11 @@ Status DB::OpenAsFollower(
|
||||
sv_context.NewSuperVersion();
|
||||
cfd->InstallSuperVersion(&sv_context, &impl->mutex_);
|
||||
}
|
||||
impl->PublishSecondaryReadView();
|
||||
impl->MarkAsyncFileOpenNotNeeded();
|
||||
}
|
||||
impl->mutex_.Unlock();
|
||||
impl->CleanupRetiredSecondaryReadViews();
|
||||
sv_context.Clean();
|
||||
if (s.ok()) {
|
||||
dbptr->reset(impl);
|
||||
|
||||
+32
-13
@@ -683,7 +683,7 @@ Status DBImpl::Recover(
|
||||
f->marked_for_compaction,
|
||||
f->temperature, // this can be different from
|
||||
// `last_level_temperature`
|
||||
f->oldest_blob_file_number, f->oldest_ancester_time,
|
||||
f->oldest_blob_file_number, f->oldest_ancestor_time,
|
||||
f->file_creation_time, f->epoch_number,
|
||||
f->file_checksum, f->file_checksum_func_name,
|
||||
f->unique_id, f->compensated_range_deletion_size,
|
||||
@@ -2143,7 +2143,7 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
immutable_db_options_.clock->GetCurrentTime(&_current_time)
|
||||
.PermitUncheckedError(); // ignore error
|
||||
const uint64_t current_time = static_cast<uint64_t>(_current_time);
|
||||
meta.oldest_ancester_time = current_time;
|
||||
meta.oldest_ancestor_time = current_time;
|
||||
meta.epoch_number = cfd->NewEpochNumber();
|
||||
{
|
||||
auto write_hint = cfd->current()->storage_info()->CalculateSSTWriteHint(
|
||||
@@ -2277,7 +2277,7 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
meta.fd.GetFileSize(), meta.smallest, meta.largest,
|
||||
meta.fd.smallest_seqno, meta.fd.largest_seqno,
|
||||
meta.marked_for_compaction, meta.temperature,
|
||||
meta.oldest_blob_file_number, meta.oldest_ancester_time,
|
||||
meta.oldest_blob_file_number, meta.oldest_ancestor_time,
|
||||
meta.file_creation_time, meta.epoch_number,
|
||||
meta.file_checksum, meta.file_checksum_func_name,
|
||||
meta.unique_id, meta.compensated_range_deletion_size,
|
||||
@@ -2791,8 +2791,9 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
s = WritableFileWriter::PrepareIOOptions(write_options, opts);
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = log_writer->file()->Sync(opts,
|
||||
impl->immutable_db_options_.use_fsync);
|
||||
s = impl->immutable_db_options_.use_fsync
|
||||
? log_writer->file()->Fsync(opts)
|
||||
: log_writer->file()->Sync(opts);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2979,8 +2980,9 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
IOOptions opts;
|
||||
s = WritableFileWriter::PrepareIOOptions(write_options, opts);
|
||||
if (s.ok()) {
|
||||
s = log_writer->file()->Sync(opts,
|
||||
impl->immutable_db_options_.use_fsync);
|
||||
s = impl->immutable_db_options_.use_fsync
|
||||
? log_writer->file()->Fsync(opts)
|
||||
: log_writer->file()->Sync(opts);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3013,6 +3015,8 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
// Register only after open succeeds and without holding the DB mutex.
|
||||
impl->MaybeRegisterFlushInitiator();
|
||||
*dbptr = std::move(impl);
|
||||
} else {
|
||||
for (auto* h : *handles) {
|
||||
@@ -3070,8 +3074,18 @@ void DBImpl::ScheduleAsyncFileOpening() {
|
||||
bg_async_file_open_state_ = AsyncFileOpenState::kScheduled;
|
||||
|
||||
// since this is a one time job, best to schedule it with high priority
|
||||
auto unschedule_callback = [](void* arg) {
|
||||
auto* unscheduled_ctx = static_cast<AsyncFileOpenContext*>(arg);
|
||||
DBImpl* db = unscheduled_ctx->db;
|
||||
db->mutex_.AssertHeld();
|
||||
delete unscheduled_ctx;
|
||||
db->bg_async_file_open_state_ = AsyncFileOpenState::kComplete;
|
||||
db->bg_cv_.SignalAll();
|
||||
TEST_SYNC_POINT("DBImpl::UnscheduleAsyncFileOpenCallback");
|
||||
TEST_SYNC_POINT("DBImpl::BGWorkAsyncFileOpen:Done");
|
||||
};
|
||||
env_->Schedule(&DBImpl::BGWorkAsyncFileOpen, ctx, Env::Priority::HIGH,
|
||||
nullptr);
|
||||
&bg_async_file_open_state_, unschedule_callback);
|
||||
}
|
||||
|
||||
void DBImpl::MarkAsyncFileOpenNotNeeded() {
|
||||
@@ -3101,8 +3115,9 @@ void DBImpl::BGWorkAsyncFileOpen(void* arg) {
|
||||
auto* version = ctx->versions[i];
|
||||
ColumnFamilyData* cfd = version->cfd();
|
||||
|
||||
// Skip column families that were dropped after scheduling
|
||||
if (cfd->IsDropped()) {
|
||||
// Retained secondary handles remain readable after the primary drops the
|
||||
// column family, so their captured Versions still need pinned readers.
|
||||
if (cfd->IsDropped() && !db->read_only_) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -3132,11 +3147,15 @@ void DBImpl::BGWorkAsyncFileOpen(void* arg) {
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_ERROR(
|
||||
db->immutable_db_options_.info_log,
|
||||
"BGWorkAsyncFileOpen: LoadTableHandlers failed for CF %s: "
|
||||
"%s",
|
||||
"BGWorkAsyncFileOpen: file validation/opening failed for CF %s: %s",
|
||||
cfd->GetName().c_str(), s.ToString().c_str());
|
||||
InstrumentedMutexLock l(&db->mutex_);
|
||||
db->error_handler_.SetBGError(s, BackgroundErrorReason::kAsyncFileOpen);
|
||||
if (db->read_only_) {
|
||||
db->error_handler_.SetBGErrorNoAutoRecovery(
|
||||
s, BackgroundErrorReason::kAsyncFileOpen);
|
||||
} else {
|
||||
db->error_handler_.SetBGError(s, BackgroundErrorReason::kAsyncFileOpen);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
+308
-27
@@ -23,6 +23,7 @@
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/utilities/options_util.h"
|
||||
#include "util/atomic.h"
|
||||
#include "util/cast_util.h"
|
||||
#include "util/write_batch_util.h"
|
||||
|
||||
@@ -33,12 +34,229 @@ DBImplSecondary::DBImplSecondary(const DBOptions& db_options,
|
||||
std::string secondary_path)
|
||||
: DBImpl(db_options, dbname, false, true, true),
|
||||
secondary_path_(std::move(secondary_path)) {
|
||||
secondary_read_view_reclaimer_.reset(new port::Thread(
|
||||
&DBImplSecondary::ReclaimRetiredSecondaryReadViewsLoop, this));
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Opening the db in secondary mode");
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
}
|
||||
|
||||
DBImplSecondary::~DBImplSecondary() = default;
|
||||
DBImplSecondary::~DBImplSecondary() {
|
||||
ResetSecondaryReadView();
|
||||
StopSecondaryReadViewReclaimer();
|
||||
}
|
||||
|
||||
// REQUIRES: mutex_ not held. CleanupSuperVersion() locks mutex_ to release a
|
||||
// SuperVersion's last reference, so dropping the last reference to a view while
|
||||
// mutex_ is held self-deadlocks on the non-reentrant mutex.
|
||||
DBImplSecondary::SecondaryReadView::~SecondaryReadView() {
|
||||
for (const SuperVersions::value_type& entry : super_versions) {
|
||||
db->CleanupSuperVersion(entry.second);
|
||||
}
|
||||
}
|
||||
|
||||
void DBImplSecondary::PublishSecondaryReadView() {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
if (secondary_read_view_closed_) {
|
||||
// A catch-up that raced ResetSecondaryReadView() must not republish, or the
|
||||
// new view would outlive the drain that close waits on.
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_ptr<SecondaryReadView> new_view =
|
||||
std::make_shared<SecondaryReadView>(this, versions_->LastSequence());
|
||||
for (ColumnFamilyData* cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (cfd->IsDropped() || !cfd->initialized()) {
|
||||
continue;
|
||||
}
|
||||
SuperVersion* super_version = cfd->GetSuperVersion();
|
||||
if (super_version == nullptr) {
|
||||
continue;
|
||||
}
|
||||
new_view->super_versions.emplace(cfd->GetID(), super_version->Ref());
|
||||
}
|
||||
|
||||
// Load the outgoing view before the store, and keep that reference until it
|
||||
// reaches the retirement queue. The store drops the published reference, so
|
||||
// without this local it could be the last one and ~SecondaryReadView() would
|
||||
// run here, under mutex_. Retiring before the store would leave the view
|
||||
// reachable by readers after it was queued for reclamation.
|
||||
std::shared_ptr<const SecondaryReadView> previous_view =
|
||||
AtomicSharedPtrLoad(&secondary_read_view_, std::memory_order_acquire);
|
||||
AtomicSharedPtrStore(
|
||||
&secondary_read_view_,
|
||||
std::shared_ptr<const SecondaryReadView>(std::move(new_view)),
|
||||
std::memory_order_release);
|
||||
RetireSecondaryReadView(std::move(previous_view));
|
||||
}
|
||||
|
||||
void DBImplSecondary::RetireSecondaryReadView(
|
||||
std::shared_ptr<const SecondaryReadView> read_view) {
|
||||
if (read_view == nullptr) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(retired_secondary_read_views_mutex_);
|
||||
retired_secondary_read_views_.emplace_back(std::move(read_view));
|
||||
}
|
||||
// Publish and close synchronously reclaim after retiring a view. Do not wake
|
||||
// the background reclaimer here: it could take a reader-free view first and
|
||||
// let catch-up scan for obsolete files before releasing the view's
|
||||
// SuperVersions. A reader that makes a retained view reclaimable later wakes
|
||||
// the reclaimer in ReleaseSecondaryReadView().
|
||||
}
|
||||
|
||||
bool DBImplSecondary::HasReclaimableSecondaryReadView() const {
|
||||
for (const auto& read_view : retired_secondary_read_views_) {
|
||||
if (read_view.use_count() == 1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DBImplSecondary::AllSecondaryReadViewsReclaimed() const {
|
||||
return retired_secondary_read_views_.empty() &&
|
||||
secondary_read_view_reclaims_in_flight_ == 0;
|
||||
}
|
||||
|
||||
bool DBImplSecondary::ReclaimRetiredSecondaryReadViews() {
|
||||
// Keep moved views alive until after the retirement lock is released. Their
|
||||
// destructors call CleanupSuperVersion(), which can lock mutex_.
|
||||
std::vector<std::shared_ptr<const SecondaryReadView>> reclaimable_views;
|
||||
size_t num_reclaiming;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(retired_secondary_read_views_mutex_);
|
||||
for (auto iter = retired_secondary_read_views_.begin();
|
||||
iter != retired_secondary_read_views_.end();) {
|
||||
// The view is no longer published, so its reference count can only
|
||||
// decrease. A count of one means the retirement queue is the sole owner.
|
||||
if (iter->use_count() == 1) {
|
||||
reclaimable_views.emplace_back(std::move(*iter));
|
||||
iter = retired_secondary_read_views_.erase(iter);
|
||||
} else {
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
num_reclaiming = reclaimable_views.size();
|
||||
secondary_read_view_reclaims_in_flight_ += num_reclaiming;
|
||||
if (num_reclaiming == 0) {
|
||||
return AllSecondaryReadViewsReclaimed();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT("DBImplSecondary::ReclaimRetiredReadViews:BeforeReclaim");
|
||||
reclaimable_views.clear();
|
||||
bool all_reclaimed;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(retired_secondary_read_views_mutex_);
|
||||
assert(secondary_read_view_reclaims_in_flight_ >= num_reclaiming);
|
||||
secondary_read_view_reclaims_in_flight_ -= num_reclaiming;
|
||||
all_reclaimed = AllSecondaryReadViewsReclaimed();
|
||||
}
|
||||
retired_secondary_read_views_cv_.notify_all();
|
||||
TEST_SYNC_POINT("DBImplSecondary::ReclaimRetiredReadViews:AfterReclaim");
|
||||
return all_reclaimed;
|
||||
}
|
||||
|
||||
void DBImplSecondary::ReclaimRetiredSecondaryReadViewsLoop() {
|
||||
while (true) {
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(retired_secondary_read_views_mutex_);
|
||||
retired_secondary_read_views_cv_.wait(lock, [this]() {
|
||||
if (secondary_read_view_reclaimer_stopping_ &&
|
||||
AllSecondaryReadViewsReclaimed()) {
|
||||
return true;
|
||||
}
|
||||
return HasReclaimableSecondaryReadView();
|
||||
});
|
||||
if (secondary_read_view_reclaimer_stopping_ &&
|
||||
AllSecondaryReadViewsReclaimed()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
TEST_SYNC_POINT(
|
||||
"DBImplSecondary::ReclaimRetiredReadViewsLoop:BeforeReclaim");
|
||||
ReclaimRetiredSecondaryReadViews();
|
||||
}
|
||||
}
|
||||
|
||||
void DBImplSecondary::CleanupRetiredSecondaryReadViews() {
|
||||
ReclaimRetiredSecondaryReadViews();
|
||||
std::unique_lock<std::mutex> lock(retired_secondary_read_views_mutex_);
|
||||
retired_secondary_read_views_cv_.wait(
|
||||
lock, [this]() { return secondary_read_view_reclaims_in_flight_ == 0; });
|
||||
}
|
||||
|
||||
void DBImplSecondary::DrainRetiredSecondaryReadViews() {
|
||||
while (!ReclaimRetiredSecondaryReadViews()) {
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(retired_secondary_read_views_mutex_);
|
||||
TEST_SYNC_POINT("DBImplSecondary::DrainRetiredReadViews:Wait");
|
||||
retired_secondary_read_views_cv_.wait(lock, [this]() {
|
||||
if (AllSecondaryReadViewsReclaimed()) {
|
||||
return true;
|
||||
}
|
||||
return HasReclaimableSecondaryReadView();
|
||||
});
|
||||
}
|
||||
TEST_SYNC_POINT("DBImplSecondary::DrainRetiredReadViews:AfterWait");
|
||||
}
|
||||
}
|
||||
|
||||
void DBImplSecondary::ReleaseSecondaryReadView(
|
||||
std::shared_ptr<const SecondaryReadView>* read_view) {
|
||||
read_view->reset();
|
||||
{
|
||||
// Synchronize the reference-count change with the wait predicate so its
|
||||
// notification cannot be lost.
|
||||
std::lock_guard<std::mutex> lock(retired_secondary_read_views_mutex_);
|
||||
retired_secondary_read_views_cv_.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
void DBImplSecondary::StopSecondaryReadViewReclaimer() {
|
||||
if (secondary_read_view_reclaimer_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(retired_secondary_read_views_mutex_);
|
||||
secondary_read_view_reclaimer_stopping_ = true;
|
||||
}
|
||||
retired_secondary_read_views_cv_.notify_all();
|
||||
TEST_SYNC_POINT("DBImplSecondary::StopSecondaryReadViewReclaimer:BeforeJoin");
|
||||
secondary_read_view_reclaimer_->join();
|
||||
secondary_read_view_reclaimer_.reset();
|
||||
}
|
||||
|
||||
void DBImplSecondary::ResetSecondaryReadView() {
|
||||
std::shared_ptr<const SecondaryReadView> read_view;
|
||||
{
|
||||
// mutex_ makes this read-modify-write atomic against
|
||||
// PublishSecondaryReadView(), which holds it too. Without that, a catch-up
|
||||
// racing close can read the same outgoing view and retire it twice, leaving
|
||||
// two queue entries that each see use_count() == 2 so the drain below never
|
||||
// finishes.
|
||||
InstrumentedMutexLock lock_guard(&mutex_);
|
||||
secondary_read_view_closed_ = true;
|
||||
read_view =
|
||||
AtomicSharedPtrLoad(&secondary_read_view_, std::memory_order_acquire);
|
||||
AtomicSharedPtrStore(&secondary_read_view_,
|
||||
std::shared_ptr<const SecondaryReadView>(),
|
||||
std::memory_order_release);
|
||||
}
|
||||
RetireSecondaryReadView(std::move(read_view));
|
||||
DrainRetiredSecondaryReadViews();
|
||||
}
|
||||
|
||||
Status DBImplSecondary::CloseImpl() {
|
||||
// The view's SuperVersions pin their column families, so release them before
|
||||
// DBImpl::CloseImpl() destroys the VersionSet.
|
||||
ResetSecondaryReadView();
|
||||
StopSecondaryReadViewReclaimer();
|
||||
return DBImpl::CloseImpl();
|
||||
}
|
||||
|
||||
Status DBImplSecondary::Recover(
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
@@ -738,23 +956,44 @@ Status DBImplSecondary::NewIteratorsImpl(
|
||||
iterators->clear();
|
||||
iterators->reserve(column_families.size());
|
||||
|
||||
SequenceNumber read_seq;
|
||||
std::shared_ptr<const SecondaryReadView> read_view =
|
||||
AtomicSharedPtrLoad(&secondary_read_view_, std::memory_order_acquire);
|
||||
if (read_view == nullptr) {
|
||||
return Status::InvalidArgument("Secondary read view is not initialized");
|
||||
}
|
||||
TEST_SYNC_POINT("DBImplSecondary::NewIterators:AfterLoadReadView");
|
||||
|
||||
const SequenceNumber read_seq = read_view->sequence;
|
||||
autovector<ColumnFamilySuperVersionPair, MultiGetContext::MAX_BATCH_SIZE>
|
||||
cf_sv_pairs;
|
||||
{
|
||||
InstrumentedMutexLock lock_guard(&mutex_);
|
||||
read_seq = versions_->LastSequence();
|
||||
for (auto* cf : column_families) {
|
||||
auto* cfh = static_cast_with_check<ColumnFamilyHandleImpl>(cf);
|
||||
cf_sv_pairs.emplace_back(cfh, cfh->cfd()->GetSuperVersion()->Ref());
|
||||
}
|
||||
}
|
||||
|
||||
const auto cleanup_super_versions = [&]() {
|
||||
for (const auto& cf_sv_pair : cf_sv_pairs) {
|
||||
CleanupSuperVersion(cf_sv_pair.super_version);
|
||||
}
|
||||
};
|
||||
for (ColumnFamilyHandle* cf : column_families) {
|
||||
ColumnFamilyHandleImpl* cfh =
|
||||
static_cast_with_check<ColumnFamilyHandleImpl>(cf);
|
||||
const SecondaryReadView::SuperVersions::const_iterator sv_iter =
|
||||
read_view->super_versions.find(cfh->GetID());
|
||||
if (sv_iter == read_view->super_versions.end()) {
|
||||
if (!cfh->cfd()->IsDropped()) {
|
||||
cleanup_super_versions();
|
||||
ReleaseSecondaryReadView(&read_view);
|
||||
return Status::InvalidArgument(
|
||||
"Column family is not part of the secondary read view");
|
||||
}
|
||||
// A retained handle remains readable after its column family is dropped.
|
||||
// Its immutable final SuperVersion is intentionally not published, since
|
||||
// doing so would keep the dropped column family alive indefinitely.
|
||||
cf_sv_pairs.emplace_back(cfh,
|
||||
cfh->cfd()->GetReferencedSuperVersion(this));
|
||||
} else {
|
||||
cf_sv_pairs.emplace_back(cfh, sv_iter->second->Ref());
|
||||
}
|
||||
}
|
||||
ReleaseSecondaryReadView(&read_view);
|
||||
TEST_SYNC_POINT("DBImplSecondary::NewIterators:AfterReleaseReadView");
|
||||
for (size_t i = 0; i < cf_sv_pairs.size(); ++i) {
|
||||
const auto& cf_sv_pair = cf_sv_pairs[i];
|
||||
const ReadOptions& options = normalized_read_options[i];
|
||||
@@ -771,7 +1010,8 @@ Status DBImplSecondary::NewIteratorsImpl(
|
||||
const auto& cf_sv_pair = cf_sv_pairs[i];
|
||||
iterators->push_back(NewIteratorImpl(
|
||||
normalized_read_options[i], cf_sv_pair.cfh, cf_sv_pair.super_version,
|
||||
read_seq, nullptr /* read_callback */));
|
||||
read_seq, nullptr /* read_callback */, false /* expose_blob_index */,
|
||||
false /* allow_refresh */));
|
||||
TEST_SYNC_POINT("DBImplSecondary::NewIterators:AfterCreateIterator");
|
||||
}
|
||||
return Status::OK();
|
||||
@@ -881,8 +1121,14 @@ Status DBImplSecondary::TryCatchUpWithPrimary() {
|
||||
// completions; an outstanding prepare stops the scan.
|
||||
(void)logs_with_prep_tracker_.FindMinLogContainingOutstandingPrep();
|
||||
}
|
||||
TEST_SYNC_POINT(
|
||||
"DBImplSecondary::TryCatchUpWithPrimary:BeforePublishReadView");
|
||||
TEST_SYNC_POINT(
|
||||
"DBImplSecondary::TryCatchUpWithPrimary:AllowPublishReadView");
|
||||
PublishSecondaryReadView();
|
||||
}
|
||||
}
|
||||
CleanupRetiredSecondaryReadViews();
|
||||
job_context.Clean();
|
||||
|
||||
// Cleanup unused, obsolete files.
|
||||
@@ -937,6 +1183,24 @@ Status DBImplSecondary::OpenAsSecondaryImpl(
|
||||
std::vector<ColumnFamilyHandle*>* handles, std::unique_ptr<DB>* dbptr,
|
||||
bool recover_wal, bool trust_manifest_recovery) {
|
||||
*dbptr = nullptr;
|
||||
// Only normal DB::OpenAsSecondary recovery can defer SST opening.
|
||||
// `recover_wal` excludes OpenAndCompact even when no snapshot floor is
|
||||
// supplied, `open_files_async` is the explicit opt-in, and trust-MANIFEST
|
||||
// recovery must only open its protected compaction inputs.
|
||||
const bool defer_sst_file_opening =
|
||||
recover_wal && db_options.open_files_async && !trust_manifest_recovery;
|
||||
if (defer_sst_file_opening && !db_options.skip_stats_update_on_db_open) {
|
||||
return Status::InvalidArgument(
|
||||
"open_files_async requires skip_stats_update_on_db_open = true.");
|
||||
}
|
||||
if (defer_sst_file_opening) {
|
||||
for (const ColumnFamilyDescriptor& column_family : column_families) {
|
||||
if (column_family.options.compaction_style == kCompactionStyleFIFO) {
|
||||
return Status::NotSupported(
|
||||
"FIFO compaction is not supported with open_files_async = true.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DBOptions tmp_opts(db_options);
|
||||
Status s;
|
||||
@@ -949,7 +1213,7 @@ Status DBImplSecondary::OpenAsSecondaryImpl(
|
||||
}
|
||||
|
||||
assert(tmp_opts.info_log != nullptr);
|
||||
if (db_options.max_open_files != -1) {
|
||||
if (db_options.max_open_files != -1 || defer_sst_file_opening) {
|
||||
std::ostringstream oss;
|
||||
oss << "The primary instance may delete all types of files after they "
|
||||
"become obsolete. The application can coordinate the primary and "
|
||||
@@ -957,15 +1221,24 @@ Status DBImplSecondary::OpenAsSecondaryImpl(
|
||||
"currently being used by the secondary. Alternatively, a custom "
|
||||
"Env/FS can be provided such that files become inaccessible only "
|
||||
"after all primary and secondaries indicate that they are obsolete "
|
||||
"and deleted. If the above two are not possible, you can open the "
|
||||
"secondary instance with `max_open_files==-1` so that secondary "
|
||||
"will eagerly keep all table files open. Even if a file is deleted, "
|
||||
"its content can still be accessed via a prior open file "
|
||||
"descriptor. This is a hacky workaround for only table files. If "
|
||||
"none of the above is done, then point lookup or "
|
||||
"and deleted. ";
|
||||
if (defer_sst_file_opening) {
|
||||
oss << "With `open_files_async=true`, `max_open_files==-1` does not pin "
|
||||
"all table files before OpenAsSecondary returns. ";
|
||||
} else {
|
||||
oss << "If the above two are not possible, you can open the secondary "
|
||||
"instance with `max_open_files==-1` so that secondary will "
|
||||
"eagerly keep all table files open. Even if a file is deleted, "
|
||||
"its content can still be accessed via a prior open file "
|
||||
"descriptor. This is a hacky workaround for only table files. ";
|
||||
}
|
||||
oss << "If none of the above is done, then point lookup or "
|
||||
"range scan via the secondary instance can result in IOError: file "
|
||||
"not found. This can be resolved by retrying "
|
||||
"TryCatchUpWithPrimary().";
|
||||
"not found. TryCatchUpWithPrimary() can resolve a transient, "
|
||||
"non-latched error if a newer primary version no longer references "
|
||||
"the missing file. A latched asynchronous-open background error "
|
||||
"requires closing and reopening the secondary after fixing the "
|
||||
"underlying problem.";
|
||||
ROCKS_LOG_WARN(tmp_opts.info_log, "%s", oss.str().c_str());
|
||||
}
|
||||
|
||||
@@ -975,9 +1248,10 @@ Status DBImplSecondary::OpenAsSecondaryImpl(
|
||||
dbname, &impl->immutable_db_options_, impl->mutable_db_options_,
|
||||
impl->file_options_, impl->table_cache_.get(),
|
||||
impl->write_buffer_manager_, &impl->write_controller_, impl->io_tracer_,
|
||||
impl->db_id_, impl->db_session_id_));
|
||||
static_cast_with_check<ReactiveVersionSet>(impl->versions_.get())
|
||||
->SetTrustManifestRecovery(trust_manifest_recovery);
|
||||
impl->db_id_, impl->db_session_id_, defer_sst_file_opening));
|
||||
ReactiveVersionSet* reactive_versions =
|
||||
static_cast_with_check<ReactiveVersionSet>(impl->versions_.get());
|
||||
reactive_versions->SetTrustManifestRecovery(trust_manifest_recovery);
|
||||
impl->column_family_memtables_.reset(
|
||||
new ColumnFamilyMemTablesImpl(impl->versions_->GetColumnFamilySet()));
|
||||
impl->wal_in_db_path_ = impl->immutable_db_options_.IsWalDirSameAsDBPath();
|
||||
@@ -1015,9 +1289,15 @@ Status DBImplSecondary::OpenAsSecondaryImpl(
|
||||
sv_context.NewSuperVersion();
|
||||
cfd->InstallSuperVersion(&sv_context, &impl->mutex_);
|
||||
}
|
||||
impl->MarkAsyncFileOpenNotNeeded();
|
||||
impl->PublishSecondaryReadView();
|
||||
if (defer_sst_file_opening) {
|
||||
impl->ScheduleAsyncFileOpening();
|
||||
} else {
|
||||
impl->MarkAsyncFileOpenNotNeeded();
|
||||
}
|
||||
}
|
||||
impl->mutex_.Unlock();
|
||||
impl->CleanupRetiredSecondaryReadViews();
|
||||
sv_context.Clean();
|
||||
job_context.Clean();
|
||||
if (s.ok()) {
|
||||
@@ -1986,8 +2266,9 @@ Status DBImplSecondary::PersistInitialCompactionProgress(
|
||||
return s;
|
||||
}
|
||||
|
||||
s = compaction_progress_writer->file()->Sync(opts,
|
||||
immutable_db_options_.use_fsync);
|
||||
s = immutable_db_options_.use_fsync
|
||||
? compaction_progress_writer->file()->Fsync(opts)
|
||||
: compaction_progress_writer->file()->Sync(opts);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -5,14 +5,19 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "logging/logging.h"
|
||||
#include "port/port.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -288,6 +293,8 @@ class DBImplSecondary : public DBImpl {
|
||||
#endif // NDEBUG
|
||||
|
||||
protected:
|
||||
Status CloseImpl() override;
|
||||
|
||||
Status FlushForGetLiveFiles(bool /*force_atomic_flush*/) override {
|
||||
// No-op for read-only DB
|
||||
return Status::OK();
|
||||
@@ -322,9 +329,50 @@ class DBImplSecondary : public DBImpl {
|
||||
std::unique_ptr<log::Reader::Reporter> manifest_reporter_;
|
||||
std::unique_ptr<Status> manifest_reader_status_;
|
||||
|
||||
// Publishes a view of every initialized, non-dropped column family while
|
||||
// mutex_ protects their SuperVersions. The previous view is retained for
|
||||
// cleanup outside mutex_ and outside reader threads. No-op once the view has
|
||||
// been torn down for close.
|
||||
// REQUIRES: mutex_ held
|
||||
void PublishSecondaryReadView();
|
||||
|
||||
// Reclaims retired views that have no readers. This must be called without
|
||||
// mutex_ because releasing a view's final SuperVersion can lock it.
|
||||
void CleanupRetiredSecondaryReadViews();
|
||||
|
||||
private:
|
||||
friend class DB;
|
||||
|
||||
struct SecondaryReadView {
|
||||
using SuperVersions = std::unordered_map<uint32_t, SuperVersion*>;
|
||||
|
||||
SecondaryReadView(DBImplSecondary* db_arg, SequenceNumber sequence_arg)
|
||||
: db(db_arg), sequence(sequence_arg) {}
|
||||
~SecondaryReadView();
|
||||
SecondaryReadView(const SecondaryReadView&) = delete;
|
||||
SecondaryReadView& operator=(const SecondaryReadView&) = delete;
|
||||
SecondaryReadView(SecondaryReadView&&) = delete;
|
||||
SecondaryReadView& operator=(SecondaryReadView&&) = delete;
|
||||
|
||||
DBImplSecondary* db;
|
||||
SequenceNumber sequence;
|
||||
SuperVersions super_versions;
|
||||
};
|
||||
|
||||
void RetireSecondaryReadView(
|
||||
std::shared_ptr<const SecondaryReadView> read_view);
|
||||
// REQUIRES: retired_secondary_read_views_mutex_ held
|
||||
bool HasReclaimableSecondaryReadView() const;
|
||||
// REQUIRES: retired_secondary_read_views_mutex_ held
|
||||
bool AllSecondaryReadViewsReclaimed() const;
|
||||
bool ReclaimRetiredSecondaryReadViews();
|
||||
void ReclaimRetiredSecondaryReadViewsLoop();
|
||||
void DrainRetiredSecondaryReadViews();
|
||||
void StopSecondaryReadViewReclaimer();
|
||||
void ReleaseSecondaryReadView(
|
||||
std::shared_ptr<const SecondaryReadView>* read_view);
|
||||
void ResetSecondaryReadView();
|
||||
|
||||
Status NewIteratorsImpl(
|
||||
const std::vector<ReadOptions>& read_options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
@@ -519,6 +567,22 @@ class DBImplSecondary : public DBImpl {
|
||||
std::unordered_map<uint32_t, std::pair<uint64_t, int>>
|
||||
cf_id_to_retention_warning_;
|
||||
|
||||
// Mutated only under mutex_, so that load-then-store stays atomic between
|
||||
// publication and teardown. Readers load it without mutex_, so every access
|
||||
// must still go through AtomicSharedPtrLoad/Store.
|
||||
std::shared_ptr<const SecondaryReadView> secondary_read_view_;
|
||||
// Set by ResetSecondaryReadView() so a later catch-up cannot publish a view
|
||||
// that would outlive the drain close waits on.
|
||||
// Guarded by mutex_, which every mutation of secondary_read_view_ holds.
|
||||
bool secondary_read_view_closed_ = false;
|
||||
std::mutex retired_secondary_read_views_mutex_;
|
||||
std::condition_variable retired_secondary_read_views_cv_;
|
||||
std::vector<std::shared_ptr<const SecondaryReadView>>
|
||||
retired_secondary_read_views_;
|
||||
size_t secondary_read_view_reclaims_in_flight_ = 0;
|
||||
bool secondary_read_view_reclaimer_stopping_ = false;
|
||||
std::unique_ptr<port::Thread> secondary_read_view_reclaimer_;
|
||||
|
||||
const std::string secondary_path_;
|
||||
|
||||
CompactionProgress compaction_progress_;
|
||||
|
||||
+244
-63
@@ -380,15 +380,26 @@ Status DBImpl::IngestWBWIAsMemtable(
|
||||
|
||||
// Switch memtable and add WBWIMemTables
|
||||
Status s;
|
||||
const bool defer_wbm_accounting_refresh =
|
||||
immutable_db_options_.atomic_flush && cfds.size() > 1;
|
||||
bool switch_attempted = false;
|
||||
for (size_t i = 0; i < cfds.size(); ++i) {
|
||||
WriteContext write_context;
|
||||
switch_attempted = true;
|
||||
// TODO: not switch on empty memtable, may need to update metadata
|
||||
// like NextLogNumber(), earliest_seqno and memtable id.
|
||||
if (i < memtables.size()) {
|
||||
s = SwitchMemtable(cfds[i], &write_context, memtables[i],
|
||||
last_seqno_after_ingest);
|
||||
s = defer_wbm_accounting_refresh
|
||||
? SwitchMemtableImpl(cfds[i], &write_context, memtables[i],
|
||||
last_seqno_after_ingest,
|
||||
false /* refresh_wbm_accounting */)
|
||||
: SwitchMemtable(cfds[i], &write_context, memtables[i],
|
||||
last_seqno_after_ingest);
|
||||
} else {
|
||||
s = SwitchMemtable(cfds[i], &write_context);
|
||||
s = defer_wbm_accounting_refresh
|
||||
? SwitchMemtableImpl(cfds[i], &write_context, nullptr, 0,
|
||||
false /* refresh_wbm_accounting */)
|
||||
: SwitchMemtable(cfds[i], &write_context);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
// SwitchMemtable() can only fail if a new WAL is to be created, this
|
||||
@@ -420,6 +431,9 @@ Status DBImpl::IngestWBWIAsMemtable(
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (defer_wbm_accounting_refresh && switch_attempted) {
|
||||
UpdateWBMAccountingAfterMemtableSwitch(true /* refresh_now */);
|
||||
}
|
||||
for (size_t i = 0; i < cfds.size(); ++i) {
|
||||
if (cfds[i]->UnrefAndTryDelete()) {
|
||||
cfds[i] = nullptr;
|
||||
@@ -1201,6 +1215,7 @@ Status DBImpl::WriteImpl(
|
||||
IOStatus io_s;
|
||||
Status pre_release_cb_status;
|
||||
size_t seq_inc = 0;
|
||||
uint64_t wal_write_number = 0;
|
||||
if (status.ok()) {
|
||||
if (deferred_put_entities != nullptr) {
|
||||
my_batch->Clear();
|
||||
@@ -1362,6 +1377,7 @@ Status DBImpl::WriteImpl(
|
||||
if (!two_write_queues_) {
|
||||
if (status.ok() && !write_options.disableWAL) {
|
||||
assert(wal_context.wal_file_number_size);
|
||||
wal_write_number = wal_context.wal_file_number_size->number;
|
||||
wal_context.prev_size = wal_context.writer->file()->GetFileSize();
|
||||
PERF_TIMER_GUARD(write_wal_time);
|
||||
io_s = WriteGroupToWAL(write_group, wal_context.writer, wal_used,
|
||||
@@ -1486,7 +1502,9 @@ Status DBImpl::WriteImpl(
|
||||
|
||||
if (!io_s.ok()) {
|
||||
// Check WriteToWAL status
|
||||
WALIOStatusCheck(io_s);
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::WriteImpl:BeforeWALIOStatusCheck", &io_s);
|
||||
WALIOStatusCheck(io_s, wal_write_number, last_sequence,
|
||||
last_sequence + 1 - seq_inc);
|
||||
}
|
||||
if (!w.CallbackFailed()) {
|
||||
if (!io_s.ok()) {
|
||||
@@ -2069,7 +2087,10 @@ void DBImpl::WriteStatusCheck(const Status& status) {
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::WALIOStatusCheck(const IOStatus& io_status) {
|
||||
void DBImpl::WALIOStatusCheck(const IOStatus& io_status,
|
||||
uint64_t failed_wal_number,
|
||||
SequenceNumber failed_wal_sequence,
|
||||
SequenceNumber failed_wal_sequence_start) {
|
||||
// Is setting bg_error_ enough here? This will at least stop
|
||||
// compaction and fail any further writes.
|
||||
if ((immutable_db_options_.paranoid_checks && !io_status.ok() &&
|
||||
@@ -2077,8 +2098,33 @@ void DBImpl::WALIOStatusCheck(const IOStatus& io_status) {
|
||||
io_status.IsIOFenced()) {
|
||||
mutex_.Lock();
|
||||
// Maybe change the return status to void?
|
||||
error_handler_.SetBGError(io_status, BackgroundErrorReason::kWriteCallback,
|
||||
/*wal_related=*/true);
|
||||
if (io_status.IsIOError() && !io_status.IsIOFenced()) {
|
||||
DBRecoverContext context;
|
||||
context.failed_wal_sequence = failed_wal_sequence;
|
||||
context.failed_wal_sequence_start = failed_wal_sequence_start;
|
||||
const uint64_t resolved_failed_wal_number =
|
||||
failed_wal_number != 0 ? failed_wal_number : cur_wal_number_;
|
||||
if (io_status.GetScope() == IOStatus::IOErrorScope::kIOErrorScopeFile) {
|
||||
context.failed_wal_number = resolved_failed_wal_number;
|
||||
if (failed_wal_sequence_start != 0 &&
|
||||
failed_wal_sequence_start <= failed_wal_sequence) {
|
||||
// Successful live resumption deliberately omits this write from the
|
||||
// database even if the physical WAL record is complete. Keep a
|
||||
// DB-lifetime fence so TransactionLogIterator does not expose it.
|
||||
wal_manager_.RecordIndeterminateWriteSequence(
|
||||
resolved_failed_wal_number, failed_wal_sequence_start,
|
||||
failed_wal_sequence);
|
||||
}
|
||||
ExtendWALRecoveryCutoff(context.failed_wal_number);
|
||||
}
|
||||
error_handler_.SetBGError(io_status,
|
||||
BackgroundErrorReason::kWriteCallback,
|
||||
/*wal_related=*/true, context);
|
||||
} else {
|
||||
error_handler_.SetBGError(io_status,
|
||||
BackgroundErrorReason::kWriteCallback,
|
||||
/*wal_related=*/true);
|
||||
}
|
||||
mutex_.Unlock();
|
||||
} else {
|
||||
// Force writable file to be continue writable.
|
||||
@@ -2086,6 +2132,16 @@ void DBImpl::WALIOStatusCheck(const IOStatus& io_status) {
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::ExtendWALRecoveryCutoff(uint64_t failed_wal_number) {
|
||||
assert(failed_wal_number != 0);
|
||||
uint64_t current = wal_recovery_cutoff_.load(std::memory_order_relaxed);
|
||||
while (current < failed_wal_number &&
|
||||
!wal_recovery_cutoff_.compare_exchange_weak(
|
||||
current, failed_wal_number, std::memory_order_release,
|
||||
std::memory_order_relaxed)) {
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::HandleMemTableInsertFailure(const Status& status) {
|
||||
assert(!status.ok());
|
||||
// A non-OK status on memtable insert indicates that the state implied by the
|
||||
@@ -2127,14 +2183,11 @@ Status DBImpl::PreprocessWrite(const WriteOptions& write_options,
|
||||
}
|
||||
|
||||
if (UNLIKELY(status.ok() && write_buffer_manager_->ShouldFlush())) {
|
||||
// Before a new memtable is added in SwitchMemtable(),
|
||||
// write_buffer_manager_->ShouldFlush() will keep returning true. If another
|
||||
// thread is writing to another DB with the same write buffer, they may also
|
||||
// be flushed. We may end up with flushing much more DBs than needed. It's
|
||||
// suboptimal but still correct.
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
WaitForPendingWrites();
|
||||
status = HandleWriteBufferManagerFlush(write_context);
|
||||
if (write_buffer_manager_->TryAcquireLocalFlush()) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
WaitForPendingWrites();
|
||||
status = HandleWriteBufferManagerFlush(write_context);
|
||||
}
|
||||
}
|
||||
|
||||
if (UNLIKELY(status.ok() && !trim_history_scheduler_.Empty())) {
|
||||
@@ -2178,7 +2231,7 @@ Status DBImpl::PreprocessWrite(const WriteOptions& write_options,
|
||||
status = Status::Incomplete("Write stall");
|
||||
} else {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
WriteBufferManagerStallWrites();
|
||||
status = WriteBufferManagerStallWrites(write_context);
|
||||
}
|
||||
}
|
||||
InstrumentedMutexLock l(&wal_write_mutex_);
|
||||
@@ -2287,10 +2340,13 @@ IOStatus DBImpl::WriteToWAL(const WriteBatch& merged_batch,
|
||||
}
|
||||
IOStatus io_s = log_writer->MaybeAddUserDefinedTimestampSizeRecord(
|
||||
write_options, versions_->GetColumnFamiliesTimestampSizeForRecord());
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"DBImpl::WriteToWAL:AfterMaybeAddUserDefinedTimestampSizeRecord", &io_s);
|
||||
if (!io_s.ok()) {
|
||||
return io_s;
|
||||
}
|
||||
io_s = log_writer->AddRecord(write_options, log_entry, sequence);
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::WriteToWAL:AfterAddRecord", &io_s);
|
||||
|
||||
if (UNLIKELY(needs_locking)) {
|
||||
wal_write_mutex_.Unlock();
|
||||
@@ -2379,7 +2435,8 @@ IOStatus DBImpl::WriteGroupToWAL(const WriteThread::WriteGroup& write_group,
|
||||
// and closed WAL that just needs to be recorded as synced in the
|
||||
// manifest.
|
||||
if (auto* f = log.writer->file()) {
|
||||
io_s = f->Sync(opts, immutable_db_options_.use_fsync);
|
||||
io_s =
|
||||
immutable_db_options_.use_fsync ? f->Fsync(opts) : f->Sync(opts);
|
||||
if (!io_s.ok()) {
|
||||
break;
|
||||
}
|
||||
@@ -2671,14 +2728,24 @@ Status DBImpl::SwitchWAL(WriteContext* write_context) {
|
||||
nonmem_write_thread_.EnterUnbatched(&nonmem_w, &mutex_);
|
||||
}
|
||||
|
||||
const bool defer_wbm_accounting_refresh =
|
||||
immutable_db_options_.atomic_flush && cfds.size() > 1;
|
||||
bool switch_attempted = false;
|
||||
for (const auto cfd : cfds) {
|
||||
cfd->Ref();
|
||||
status = SwitchMemtable(cfd, write_context);
|
||||
switch_attempted = true;
|
||||
status = defer_wbm_accounting_refresh
|
||||
? SwitchMemtableImpl(cfd, write_context, nullptr, 0,
|
||||
false /* refresh_wbm_accounting */)
|
||||
: SwitchMemtable(cfd, write_context);
|
||||
cfd->UnrefAndTryDelete();
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (defer_wbm_accounting_refresh && switch_attempted) {
|
||||
UpdateWBMAccountingAfterMemtableSwitch(true /* refresh_now */);
|
||||
}
|
||||
if (two_write_queues_) {
|
||||
nonmem_write_thread_.ExitUnbatched(&nonmem_w);
|
||||
}
|
||||
@@ -2722,25 +2789,17 @@ Status DBImpl::HandleWriteBufferManagerFlush(WriteContext* write_context) {
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
SelectColumnFamiliesForAtomicFlush(&cfds);
|
||||
} else {
|
||||
ColumnFamilyData* cfd_picked = nullptr;
|
||||
SequenceNumber seq_num_for_cf_picked = kMaxSequenceNumber;
|
||||
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
if (!cfd->mem()->IsEmpty() && !cfd->imm()->IsFlushPendingOrRunning()) {
|
||||
// We only consider flush on CFs with bytes in the mutable memtable,
|
||||
// and no immutable memtables for which flush has yet to finish. If
|
||||
// we triggered flush on CFs already trying to flush, we would risk
|
||||
// creating too many immutable memtables leading to write stalls.
|
||||
uint64_t seq = cfd->mem()->GetCreationSeq();
|
||||
if (cfd_picked == nullptr || seq < seq_num_for_cf_picked) {
|
||||
cfd_picked = cfd;
|
||||
seq_num_for_cf_picked = seq;
|
||||
}
|
||||
}
|
||||
}
|
||||
const WriteBufferFlushPolicy policy = write_buffer_manager_->flush_policy();
|
||||
// Cross-DB selection still flushes the largest CF within the chosen DB.
|
||||
const bool flush_largest =
|
||||
policy == WriteBufferFlushPolicy::kFlushLargest ||
|
||||
policy == WriteBufferFlushPolicy::kFlushLargestAcrossDBs;
|
||||
const bool include_waiting_immutable =
|
||||
policy == WriteBufferFlushPolicy::kFlushLargestAcrossDBs;
|
||||
const FlushableCFs flushable =
|
||||
CollectFlushableCFs(include_waiting_immutable);
|
||||
ColumnFamilyData* cfd_picked =
|
||||
flush_largest ? flushable.largest : flushable.oldest;
|
||||
if (cfd_picked != nullptr) {
|
||||
cfds.push_back(cfd_picked);
|
||||
}
|
||||
@@ -2760,12 +2819,17 @@ Status DBImpl::HandleWriteBufferManagerFlush(WriteContext* write_context) {
|
||||
if (two_write_queues_) {
|
||||
nonmem_write_thread_.EnterUnbatched(&nonmem_w, &mutex_);
|
||||
}
|
||||
const bool defer_wbm_accounting_refresh =
|
||||
immutable_db_options_.atomic_flush && cfds.size() > 1;
|
||||
for (const auto cfd : cfds) {
|
||||
if (cfd->mem()->IsEmpty()) {
|
||||
continue;
|
||||
}
|
||||
cfd->Ref();
|
||||
status = SwitchMemtable(cfd, write_context);
|
||||
status = defer_wbm_accounting_refresh
|
||||
? SwitchMemtableImpl(cfd, write_context, nullptr, 0,
|
||||
false /* refresh_wbm_accounting */)
|
||||
: SwitchMemtable(cfd, write_context);
|
||||
cfd->UnrefAndTryDelete();
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
@@ -2794,6 +2858,11 @@ Status DBImpl::HandleWriteBufferManagerFlush(WriteContext* write_context) {
|
||||
GenerateFlushRequest(cfds, FlushReason::kWriteBufferManager, &flush_req);
|
||||
EnqueuePendingFlush(flush_req);
|
||||
}
|
||||
}
|
||||
if (write_buffer_manager_->ShouldTrackFlushInitiator()) {
|
||||
RefreshFlushableMemAccounting();
|
||||
}
|
||||
if (status.ok()) {
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
return status;
|
||||
@@ -2914,25 +2983,63 @@ Status DBImpl::DelayWrite(uint64_t num_bytes, WriteThread& write_thread,
|
||||
|
||||
// REQUIRES: mutex_ is held
|
||||
// REQUIRES: this thread is currently at the front of the writer queue
|
||||
void DBImpl::WriteBufferManagerStallWrites() {
|
||||
Status DBImpl::WriteBufferManagerStallWrites(WriteContext* write_context) {
|
||||
mutex_.AssertHeld();
|
||||
// First block future writer threads who want to add themselves to the queue
|
||||
// of WriteThread.
|
||||
write_thread_.BeginWriteStall();
|
||||
mutex_.Unlock();
|
||||
for (;;) {
|
||||
// First block future writer threads who want to add themselves to the
|
||||
// queue of WriteThread.
|
||||
write_thread_.BeginWriteStall();
|
||||
const bool hid_flush_initiator =
|
||||
wbm_flush_initiator_ != nullptr &&
|
||||
write_buffer_manager_->ShouldTrackFlushInitiator();
|
||||
if (hid_flush_initiator) {
|
||||
wbm_flush_initiator_->SetFlushable(false);
|
||||
}
|
||||
mutex_.Unlock();
|
||||
|
||||
// Change the state to State::Blocked.
|
||||
static_cast<WBMStallInterface*>(wbm_stall_.get())
|
||||
->SetState(WBMStallInterface::State::BLOCKED);
|
||||
// Then WriteBufferManager will add DB instance to its queue
|
||||
// and block this thread by calling WBMStallInterface::Block().
|
||||
write_buffer_manager_->BeginWriteStall(wbm_stall_.get());
|
||||
wbm_stall_->Block();
|
||||
WBMStallInterface* const stall =
|
||||
static_cast<WBMStallInterface*>(wbm_stall_.get());
|
||||
stall->SetState(WBMStallInterface::State::BLOCKED);
|
||||
write_buffer_manager_->BeginWriteStall(wbm_stall_.get());
|
||||
bool retry_local_flush = false;
|
||||
if (write_buffer_manager_->ShouldTrackFlushInitiator()) {
|
||||
for (;;) {
|
||||
if (stall->BlockFor(
|
||||
write_buffer_manager_->GetLocalFlushRetryMicros())) {
|
||||
break;
|
||||
}
|
||||
if (write_buffer_manager_->TryAcquireLocalFlush(wbm_stall_.get())) {
|
||||
write_buffer_manager_->RemoveDBFromQueue(wbm_stall_.get());
|
||||
retry_local_flush = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stall->Block();
|
||||
}
|
||||
|
||||
mutex_.Lock();
|
||||
// Stall has ended. Signal writer threads so that they can add
|
||||
// themselves to the WriteThread queue for writes.
|
||||
write_thread_.EndWriteStall();
|
||||
mutex_.Lock();
|
||||
// Allow this front writer to retry the manager-wide emergency lease. It
|
||||
// must not let its write through while the hard limit is still exceeded.
|
||||
write_thread_.EndWriteStall();
|
||||
// Restore the state hidden by this stall even if the runtime policy
|
||||
// changed while the writer was blocked. Otherwise switching back to the
|
||||
// across-DB policy can leave this DB permanently ineligible.
|
||||
if (hid_flush_initiator) {
|
||||
wbm_flush_initiator_->SetFlushable(true);
|
||||
if (write_buffer_manager_->ShouldTrackFlushInitiator()) {
|
||||
write_buffer_manager_->NotifyFlushInitiatorChanged();
|
||||
}
|
||||
}
|
||||
if (!retry_local_flush || !write_buffer_manager_->ShouldStall()) {
|
||||
return Status::OK();
|
||||
}
|
||||
WaitForPendingWrites();
|
||||
Status status = HandleWriteBufferManagerFlush(write_context);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Status DBImpl::ThrottleLowPriWritesIfNeeded(const WriteOptions& write_options,
|
||||
@@ -3063,17 +3170,27 @@ Status DBImpl::ScheduleFlushes(WriteContext* context) {
|
||||
nullptr);
|
||||
autovector<FlushReason> flush_reasons;
|
||||
flush_reasons.reserve(cfds.size());
|
||||
const bool defer_wbm_accounting_refresh =
|
||||
immutable_db_options_.atomic_flush && cfds.size() > 1;
|
||||
bool switch_attempted = false;
|
||||
for (auto& cfd : cfds) {
|
||||
FlushReason flush_reason = FlushReason::kWriteBufferFull;
|
||||
if (status.ok() && !cfd->mem()->IsEmpty()) {
|
||||
flush_reason = cfd->mem()->GetFlushReason();
|
||||
status = SwitchMemtable(cfd, context);
|
||||
switch_attempted = true;
|
||||
status = defer_wbm_accounting_refresh
|
||||
? SwitchMemtableImpl(cfd, context, nullptr, 0,
|
||||
false /* refresh_wbm_accounting */)
|
||||
: SwitchMemtable(cfd, context);
|
||||
}
|
||||
flush_reasons.push_back(flush_reason);
|
||||
if (cfd->UnrefAndTryDelete()) {
|
||||
cfd = nullptr;
|
||||
}
|
||||
}
|
||||
if (defer_wbm_accounting_refresh && switch_attempted) {
|
||||
UpdateWBMAccountingAfterMemtableSwitch(true /* refresh_now */);
|
||||
}
|
||||
|
||||
if (two_write_queues_) {
|
||||
nonmem_write_thread_.ExitUnbatched(&nonmem_w);
|
||||
@@ -3118,6 +3235,31 @@ void DBImpl::NotifyOnMemTableSealed(ColumnFamilyData* /*cfd*/,
|
||||
Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
|
||||
ReadOnlyMemTable* new_imm,
|
||||
SequenceNumber last_seqno) {
|
||||
return SwitchMemtableImpl(cfd, context, new_imm, last_seqno,
|
||||
true /* refresh_wbm_accounting */);
|
||||
}
|
||||
|
||||
void DBImpl::UpdateWBMAccountingAfterMemtableSwitch(bool refresh_now) {
|
||||
mutex_.AssertHeld();
|
||||
if (wbm_flush_initiator_ == nullptr ||
|
||||
!write_buffer_manager_->ShouldTrackFlushInitiator()) {
|
||||
return;
|
||||
}
|
||||
if (refresh_now) {
|
||||
TEST_SYNC_POINT("DBImpl::UpdateWBMAccountingAfterMemtableSwitch:Refresh");
|
||||
RefreshFlushableMemAccounting();
|
||||
} else {
|
||||
// Do not let the sorter consume an intermediate snapshot while a caller is
|
||||
// switching a batch of memtables. The caller refreshes once after the
|
||||
// complete batch, including a partial-error exit.
|
||||
wbm_flush_initiator_->InvalidateLargestFlushableCFMem();
|
||||
}
|
||||
}
|
||||
|
||||
Status DBImpl::SwitchMemtableImpl(ColumnFamilyData* cfd, WriteContext* context,
|
||||
ReadOnlyMemTable* new_imm,
|
||||
SequenceNumber last_seqno,
|
||||
bool refresh_wbm_accounting) {
|
||||
mutex_.AssertHeld();
|
||||
assert(lock_wal_owner_thread_id_counts_.empty());
|
||||
|
||||
@@ -3128,6 +3270,7 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
|
||||
UnpublishedWAL prepared_wal;
|
||||
MemTable* new_mem = nullptr;
|
||||
IOStatus io_s;
|
||||
bool current_wal_write_buffer_failed = false;
|
||||
|
||||
// Recoverable state is persisted in WAL. After memtable switch, WAL might
|
||||
// be deleted, so we write the state to memtable to be persisted as well.
|
||||
@@ -3142,7 +3285,9 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
|
||||
if (two_write_queues_) {
|
||||
wal_write_mutex_.Lock();
|
||||
}
|
||||
bool creating_new_log = !wal_empty_;
|
||||
bool creating_new_log =
|
||||
!wal_empty_ || (recovering_from_wal_write_error_ &&
|
||||
cur_wal_number_ <= recover_wal_through_number_);
|
||||
if (two_write_queues_) {
|
||||
wal_write_mutex_.Unlock();
|
||||
}
|
||||
@@ -3273,13 +3418,28 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
|
||||
InstrumentedMutexLock l(&wal_write_mutex_);
|
||||
assert(new_log != nullptr);
|
||||
if (!logs_.empty()) {
|
||||
// Alway flush the buffer of the last log before switching to a new one
|
||||
// Always flush the buffer of the last log before switching to a new one,
|
||||
// except when recovery is replacing a file-scoped failed WAL. Its
|
||||
// accepted contents are persisted by the atomic recovery flush.
|
||||
log::Writer* cur_log_writer = logs_.back().writer;
|
||||
if (error_handler_.IsRecoveryInProgress()) {
|
||||
const bool abandon_current_wal =
|
||||
recovering_from_wal_write_error_ &&
|
||||
cur_log_writer->get_log_number() <= recover_wal_through_number_;
|
||||
if (abandon_current_wal) {
|
||||
TEST_SYNC_POINT("DBImpl::SwitchMemtable:AbandonWAL");
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"[WAL recovery] [%s] Replacing failed WAL #%" PRIu64
|
||||
" with WAL #%" PRIu64,
|
||||
cfd->GetName().c_str(), cur_log_writer->get_log_number(),
|
||||
new_log_number);
|
||||
} else if (error_handler_.IsRecoveryInProgress()) {
|
||||
// In recovery path, we force another try of writing WAL buffer.
|
||||
cur_log_writer->file()->reset_seen_error();
|
||||
}
|
||||
io_s = cur_log_writer->WriteBuffer(write_options);
|
||||
if (!abandon_current_wal) {
|
||||
io_s = cur_log_writer->WriteBuffer(write_options);
|
||||
current_wal_write_buffer_failed = !io_s.ok();
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = io_s;
|
||||
}
|
||||
@@ -3306,16 +3466,35 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
|
||||
delete new_mem;
|
||||
delete new_log;
|
||||
context->superversion_context.new_superversion.reset();
|
||||
// We may have lost data from the WritableFileBuffer in-memory buffer for
|
||||
// the current log, so treat it as a fatal error and set bg_error
|
||||
// Only a WriteBuffer() failure belongs to the current WAL. CreateWAL() and
|
||||
// StartWALFile() operate on an unpublished replacement, so a failure there
|
||||
// must not quarantine an otherwise healthy current WAL.
|
||||
if (!io_s.ok()) {
|
||||
error_handler_.SetBGError(io_s, BackgroundErrorReason::kMemTable,
|
||||
/*wal_related=*/true);
|
||||
if (!current_wal_write_buffer_failed) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Failed to create or start replacement WAL #%" PRIu64
|
||||
"; current WAL #%" PRIu64 " remains active: %s",
|
||||
new_log_number, cur_wal_number_,
|
||||
io_s.ToString().c_str());
|
||||
}
|
||||
if (current_wal_write_buffer_failed && io_s.IsIOError() &&
|
||||
!io_s.IsIOFenced() &&
|
||||
io_s.GetScope() == IOStatus::IOErrorScope::kIOErrorScopeFile) {
|
||||
DBRecoverContext recovery_context;
|
||||
recovery_context.failed_wal_number = cur_wal_number_;
|
||||
ExtendWALRecoveryCutoff(recovery_context.failed_wal_number);
|
||||
error_handler_.SetBGError(io_s, BackgroundErrorReason::kMemTable,
|
||||
/*wal_related=*/true, recovery_context);
|
||||
} else {
|
||||
error_handler_.SetBGError(io_s, BackgroundErrorReason::kMemTable,
|
||||
/*wal_related=*/true);
|
||||
}
|
||||
} else {
|
||||
error_handler_.SetBGError(s, BackgroundErrorReason::kMemTable);
|
||||
}
|
||||
// Read back bg_error in order to get the right severity
|
||||
s = error_handler_.GetBGError();
|
||||
UpdateWBMAccountingAfterMemtableSwitch(refresh_wbm_accounting);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -3357,6 +3536,7 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
|
||||
BackgroundErrorReason::kManifestWrite);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
UpdateWBMAccountingAfterMemtableSwitch(refresh_wbm_accounting);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -3409,6 +3589,7 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
|
||||
}
|
||||
new_mem->Ref();
|
||||
cfd->SetMemtable(new_mem);
|
||||
UpdateWBMAccountingAfterMemtableSwitch(refresh_wbm_accounting);
|
||||
InstallSuperVersionAndScheduleWork(cfd, &context->superversion_context);
|
||||
MaybeScheduleAsyncWALPrecreate(preallocate_block_size);
|
||||
|
||||
|
||||
@@ -140,7 +140,8 @@ TEST_F(DBMemTableTest, DuplicateSeq) {
|
||||
ImmutableOptions ioptions(options);
|
||||
WriteBufferManager wb(options.db_write_buffer_size);
|
||||
MemTable* mem = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb,
|
||||
kMaxSequenceNumber, 0 /* column_family_id */);
|
||||
kMaxSequenceNumber, 0 /* column_family_id */,
|
||||
nullptr /* flush_initiator */);
|
||||
|
||||
// Write some keys and make sure it returns false on duplicates
|
||||
ASSERT_OK(
|
||||
@@ -188,7 +189,8 @@ TEST_F(DBMemTableTest, DuplicateSeq) {
|
||||
new TestPrefixExtractor()); // which uses _ to extract the prefix
|
||||
ioptions = ImmutableOptions(options);
|
||||
mem = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb,
|
||||
kMaxSequenceNumber, 0 /* column_family_id */);
|
||||
kMaxSequenceNumber, 0 /* column_family_id */,
|
||||
nullptr /* flush_initiator */);
|
||||
// Insert a duplicate key with _ in it
|
||||
ASSERT_OK(
|
||||
mem->Add(seq, kTypeValue, "key_1", "value", nullptr /* kv_prot_info */));
|
||||
@@ -201,7 +203,8 @@ TEST_F(DBMemTableTest, DuplicateSeq) {
|
||||
options.allow_concurrent_memtable_write = true;
|
||||
ioptions = ImmutableOptions(options);
|
||||
mem = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb,
|
||||
kMaxSequenceNumber, 0 /* column_family_id */);
|
||||
kMaxSequenceNumber, 0 /* column_family_id */,
|
||||
nullptr /* flush_initiator */);
|
||||
MemTablePostProcessInfo post_process_info;
|
||||
ASSERT_OK(mem->Add(seq, kTypeValue, "key", "value",
|
||||
nullptr /* kv_prot_info */, true, &post_process_info));
|
||||
@@ -234,7 +237,8 @@ TEST_F(DBMemTableTest, ConcurrentMergeWrite) {
|
||||
ImmutableOptions ioptions(options);
|
||||
WriteBufferManager wb(options.db_write_buffer_size);
|
||||
MemTable* mem = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb,
|
||||
kMaxSequenceNumber, 0 /* column_family_id */);
|
||||
kMaxSequenceNumber, 0 /* column_family_id */,
|
||||
nullptr /* flush_initiator */);
|
||||
|
||||
// Put 0 as the base
|
||||
PutFixed64(&value, static_cast<uint64_t>(0));
|
||||
|
||||
+835
-15
@@ -7,6 +7,11 @@
|
||||
// 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 <chrono>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/db_impl/db_impl_secondary.h"
|
||||
#include "db/db_test_util.h"
|
||||
@@ -173,6 +178,19 @@ class DBSecondaryTestBase : public DBBasicTestWithTimestampBase {
|
||||
ASSERT_EQ(expected, iter->value().ToString());
|
||||
}
|
||||
|
||||
void VerifySecondaryBatchValue(ColumnFamilyHandle* cfh,
|
||||
const std::string& key,
|
||||
const std::string& expected) {
|
||||
std::vector<Iterator*> iterators;
|
||||
ASSERT_OK(db_secondary_->NewIterators(ReadOptions(), {cfh}, &iterators));
|
||||
ASSERT_EQ(1U, iterators.size());
|
||||
std::unique_ptr<Iterator> iterator(iterators[0]);
|
||||
iterator->Seek(key);
|
||||
ASSERT_OK(iterator->status());
|
||||
ASSERT_TRUE(iterator->Valid());
|
||||
ASSERT_EQ(expected, iterator->value());
|
||||
}
|
||||
|
||||
// Returns the path of the primary's newest table file for `cf_name`.
|
||||
std::string GetNewestTableFilePath(const std::string& cf_name) {
|
||||
std::vector<LiveFileMetaData> live_files;
|
||||
@@ -860,6 +878,336 @@ TEST_F(DBSecondaryTest, OpenAsSecondary) {
|
||||
verify_db_func("new_foo_value", "new_bar_value");
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, OpenFilesAsync) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
Reopen(options);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ASSERT_OK(Put("key", "value" + std::to_string(i)));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
Options secondary_options = options;
|
||||
secondary_options.max_open_files = -1;
|
||||
secondary_options.open_files_async = true;
|
||||
secondary_options.skip_stats_update_on_db_open = true;
|
||||
secondary_options.statistics = CreateDBStatistics();
|
||||
|
||||
std::atomic<uint32_t> file_opens = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionBuilder::Rep::LoadTableHandlers::BeforeFindTable",
|
||||
[&](void* /*arg*/) { ++file_opens; });
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBSecondaryTest::OpenFilesAsync:Release",
|
||||
"DBImpl::BGWorkAsyncFileOpen::Start"},
|
||||
{"DBImpl::BGWorkAsyncFileOpen:Done",
|
||||
"DBSecondaryTest::OpenFilesAsync:Done"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Defer cleanup_sync_point([] {
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
});
|
||||
|
||||
OpenSecondary(secondary_options);
|
||||
ASSERT_EQ(0, file_opens.load());
|
||||
ASSERT_EQ(0, TestGetTickerCount(secondary_options, NO_FILE_OPENS));
|
||||
|
||||
TEST_SYNC_POINT("DBSecondaryTest::OpenFilesAsync:Release");
|
||||
TEST_SYNC_POINT("DBSecondaryTest::OpenFilesAsync:Done");
|
||||
ASSERT_EQ(3, file_opens.load());
|
||||
ASSERT_EQ(3, TestGetTickerCount(secondary_options, NO_FILE_OPENS));
|
||||
VerifySecondaryValue("key", "value2");
|
||||
|
||||
const uint32_t initial_file_opens = file_opens.load();
|
||||
const uint64_t initial_no_file_opens =
|
||||
TestGetTickerCount(secondary_options, NO_FILE_OPENS);
|
||||
ASSERT_OK(Put("key", "value3"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
|
||||
ASSERT_GT(file_opens.load(), initial_file_opens);
|
||||
ASSERT_GT(TestGetTickerCount(secondary_options, NO_FILE_OPENS),
|
||||
initial_no_file_opens);
|
||||
VerifySecondaryValue("key", "value3");
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, OpenFilesAsyncRequiresSkipStatsUpdate) {
|
||||
Options options = CurrentOptions();
|
||||
options.open_files_async = true;
|
||||
options.skip_stats_update_on_db_open = false;
|
||||
|
||||
ASSERT_TRUE(TryOpenSecondary(options).IsInvalidArgument());
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, OpenFilesAsyncRejectsFIFOCompaction) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleFIFO;
|
||||
options.open_files_async = true;
|
||||
options.skip_stats_update_on_db_open = true;
|
||||
|
||||
const Status s = TryOpenSecondary(options);
|
||||
ASSERT_TRUE(s.IsNotSupported());
|
||||
ASSERT_NE(std::string::npos, s.ToString().find("FIFO compaction"));
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, OpenFilesAsyncQueuedJobCanceledDuringShutdown) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("key", "value"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
Options secondary_options = options;
|
||||
secondary_options.max_open_files = -1;
|
||||
secondary_options.open_files_async = true;
|
||||
secondary_options.skip_stats_update_on_db_open = true;
|
||||
|
||||
const int original_high_threads =
|
||||
env_->GetBackgroundThreads(Env::Priority::HIGH);
|
||||
Defer restore_high_threads([&] {
|
||||
env_->SetBackgroundThreads(original_high_threads, Env::Priority::HIGH);
|
||||
});
|
||||
std::atomic<uint32_t> async_open_cancellations = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::UnscheduleAsyncFileOpenCallback",
|
||||
[&](void* /*arg*/) { ++async_open_cancellations; });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Defer cleanup_sync_point([] {
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
});
|
||||
|
||||
for (int mode = 0; mode < 2; ++mode) {
|
||||
const bool cancel_directly = mode == 1;
|
||||
SCOPED_TRACE(cancel_directly ? "CancelAllBackgroundWork" : "Close");
|
||||
|
||||
env_->SetBackgroundThreads(1, Env::Priority::HIGH);
|
||||
test::SleepingBackgroundTask sleeping_task;
|
||||
env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task,
|
||||
Env::Priority::HIGH);
|
||||
sleeping_task.WaitUntilSleeping();
|
||||
|
||||
OpenSecondary(secondary_options);
|
||||
ASSERT_EQ(1U, env_->GetThreadPoolQueueLen(Env::Priority::HIGH));
|
||||
env_->SetBackgroundThreads(0, Env::Priority::HIGH);
|
||||
sleeping_task.WakeUp();
|
||||
sleeping_task.WaitUntilDone();
|
||||
|
||||
std::mutex close_mutex;
|
||||
std::condition_variable close_cv;
|
||||
bool close_done = false;
|
||||
Status close_status;
|
||||
port::Thread close_thread([&] {
|
||||
if (cancel_directly) {
|
||||
CancelAllBackgroundWork(db_secondary_.get(), /*wait=*/true);
|
||||
} else {
|
||||
close_status = db_secondary_->Close();
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(close_mutex);
|
||||
close_done = true;
|
||||
}
|
||||
close_cv.notify_all();
|
||||
});
|
||||
bool closed_without_worker;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(close_mutex);
|
||||
closed_without_worker = close_cv.wait_for(lock, std::chrono::seconds(10),
|
||||
[&] { return close_done; });
|
||||
}
|
||||
if (!closed_without_worker) {
|
||||
// Let an uncanceled opener drain so a regression fails without hanging
|
||||
// the test process.
|
||||
env_->SetBackgroundThreads(1, Env::Priority::HIGH);
|
||||
}
|
||||
close_thread.join();
|
||||
|
||||
ASSERT_TRUE(closed_without_worker);
|
||||
ASSERT_OK(close_status);
|
||||
ASSERT_EQ(static_cast<uint32_t>(mode + 1), async_open_cancellations.load());
|
||||
if (cancel_directly) {
|
||||
CloseSecondary();
|
||||
} else {
|
||||
db_secondary_.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, OpenFilesAsyncDetectsFileSizeMismatch) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("key", "value"));
|
||||
ASSERT_OK(Flush());
|
||||
const std::string table_path =
|
||||
GetNewestTableFilePath(kDefaultColumnFamilyName);
|
||||
uint64_t table_size = 0;
|
||||
ASSERT_OK(env_->GetFileSize(table_path, &table_size));
|
||||
Close();
|
||||
|
||||
Options secondary_options = options;
|
||||
secondary_options.max_open_files = -1;
|
||||
secondary_options.open_files_async = true;
|
||||
secondary_options.skip_stats_update_on_db_open = true;
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBSecondaryTest::OpenFilesAsyncDetectsFileSizeMismatch:Release",
|
||||
"DBImpl::BGWorkAsyncFileOpen::Start"},
|
||||
{"DBImpl::BGWorkAsyncFileOpen:Done",
|
||||
"DBSecondaryTest::OpenFilesAsyncDetectsFileSizeMismatch:Done"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Defer cleanup_sync_point([] {
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
});
|
||||
|
||||
OpenSecondary(secondary_options);
|
||||
std::unique_ptr<WritableFile> table_file;
|
||||
ASSERT_OK(env_->ReopenWritableFile(table_path, &table_file, EnvOptions()));
|
||||
ASSERT_OK(table_file->Truncate(table_size + 1));
|
||||
ASSERT_OK(table_file->Close());
|
||||
TEST_SYNC_POINT(
|
||||
"DBSecondaryTest::OpenFilesAsyncDetectsFileSizeMismatch:Release");
|
||||
TEST_SYNC_POINT(
|
||||
"DBSecondaryTest::OpenFilesAsyncDetectsFileSizeMismatch:Done");
|
||||
|
||||
const Status bg_error = db_secondary_full()->TEST_GetBGError();
|
||||
ASSERT_TRUE(bg_error.IsCorruption());
|
||||
ASSERT_NE(std::string::npos,
|
||||
bg_error.ToString().find("Sst file size mismatch"));
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, OpenFilesAsyncStopsOpeningDuringShutdown) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
Reopen(options);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ASSERT_OK(Put("key", "value" + std::to_string(i)));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
Options secondary_options = options;
|
||||
secondary_options.max_open_files = -1;
|
||||
secondary_options.max_file_opening_threads = 1;
|
||||
secondary_options.open_files_async = true;
|
||||
secondary_options.skip_stats_update_on_db_open = true;
|
||||
|
||||
std::mutex state_mutex;
|
||||
std::condition_variable state_cv;
|
||||
uint32_t file_opens = 0;
|
||||
bool allow_async_open = false;
|
||||
bool close_waiting = false;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionBuilder::Rep::LoadTableHandlers::BeforeFindTable",
|
||||
[&](void* /*arg*/) {
|
||||
std::unique_lock<std::mutex> lock(state_mutex);
|
||||
++file_opens;
|
||||
if (file_opens == 1) {
|
||||
state_cv.notify_all();
|
||||
state_cv.wait(lock, [&] { return allow_async_open; });
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::~DBImpl:WaitJob", [&](void* /*arg*/) {
|
||||
std::lock_guard<std::mutex> lock(state_mutex);
|
||||
close_waiting = true;
|
||||
state_cv.notify_all();
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Defer cleanup_sync_point([] {
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
});
|
||||
|
||||
OpenSecondary(secondary_options);
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(state_mutex);
|
||||
state_cv.wait(lock, [&] { return file_opens == 1; });
|
||||
}
|
||||
|
||||
Status close_status;
|
||||
port::Thread close_thread([&] { close_status = db_secondary_->Close(); });
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(state_mutex);
|
||||
state_cv.wait(lock, [&] { return close_waiting; });
|
||||
allow_async_open = true;
|
||||
state_cv.notify_all();
|
||||
}
|
||||
close_thread.join();
|
||||
|
||||
ASSERT_OK(close_status);
|
||||
ASSERT_EQ(1, file_opens);
|
||||
db_secondary_.reset();
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, OpenFilesAsyncErrorDoesNotStartRecovery) {
|
||||
class CountingListener final : public EventListener {
|
||||
public:
|
||||
void OnBackgroundError(BackgroundErrorReason /*reason*/,
|
||||
Status* bg_error) override {
|
||||
*bg_error = Status::IOError("listener-adjusted file-open error");
|
||||
++background_errors;
|
||||
}
|
||||
|
||||
void OnErrorRecoveryBegin(BackgroundErrorReason /*reason*/, Status bg_error,
|
||||
bool* /*auto_recovery*/) override {
|
||||
bg_error.PermitUncheckedError();
|
||||
++recovery_begins;
|
||||
}
|
||||
|
||||
std::atomic<uint32_t> background_errors{0};
|
||||
std::atomic<uint32_t> recovery_begins{0};
|
||||
};
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("key", "value"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
auto fault_fs = std::make_shared<FaultInjectionTestFS>(env_->GetFileSystem());
|
||||
std::unique_ptr<Env> fault_env(new CompositeEnvWrapper(env_, fault_fs));
|
||||
Options secondary_options = options;
|
||||
secondary_options.env = fault_env.get();
|
||||
secondary_options.max_open_files = -1;
|
||||
secondary_options.open_files_async = true;
|
||||
secondary_options.skip_stats_update_on_db_open = true;
|
||||
auto listener = std::make_shared<CountingListener>();
|
||||
secondary_options.listeners.emplace_back(listener);
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBSecondaryTest::OpenFilesAsyncErrorDoesNotStartRecovery:Release",
|
||||
"DBImpl::BGWorkAsyncFileOpen::Start"},
|
||||
{"DBImpl::BGWorkAsyncFileOpen:Done",
|
||||
"DBSecondaryTest::OpenFilesAsyncErrorDoesNotStartRecovery:Done"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Defer cleanup([&] {
|
||||
fault_fs->SetFilesystemActive(true);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
});
|
||||
|
||||
OpenSecondary(secondary_options);
|
||||
IOStatus error = IOStatus::IOError("injected retryable file-open error");
|
||||
error.SetRetryable(true);
|
||||
fault_fs->SetFilesystemActive(false, error);
|
||||
TEST_SYNC_POINT(
|
||||
"DBSecondaryTest::OpenFilesAsyncErrorDoesNotStartRecovery:Release");
|
||||
TEST_SYNC_POINT(
|
||||
"DBSecondaryTest::OpenFilesAsyncErrorDoesNotStartRecovery:Done");
|
||||
|
||||
const Status bg_error = db_secondary_full()->TEST_GetBGError();
|
||||
ASSERT_OK(bg_error);
|
||||
ASSERT_FALSE(db_secondary_full()->TEST_IsRecoveryInProgress());
|
||||
ASSERT_EQ(1, listener->background_errors.load());
|
||||
ASSERT_EQ(0, listener->recovery_begins.load());
|
||||
std::string value;
|
||||
ASSERT_TRUE(db_secondary_->Get(ReadOptions(), "key", &value).IsIOError());
|
||||
ASSERT_OK(db_secondary_->Resume());
|
||||
fault_fs->SetFilesystemActive(true);
|
||||
VerifySecondaryValue("key", "value");
|
||||
CloseSecondary();
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, OptionsOverrideTest) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
@@ -996,7 +1344,15 @@ TEST_F(DBSecondaryTest, SecondaryCloseFiles) {
|
||||
options1.env = traced_env.get();
|
||||
OpenSecondary(options1);
|
||||
|
||||
static const auto verify_db = [&]() {
|
||||
// `db_secondary_` holds `traced_env` as its Env and touches it during close
|
||||
// (CloseHelper unschedules background work through env_). traced_env is a
|
||||
// local here, but db_secondary_ is a fixture member torn down later in
|
||||
// ~DBSecondaryTestBase; an early return from a failed ASSERT below would free
|
||||
// traced_env first and leave that later close calling into a destroyed Env.
|
||||
// Close the secondary now, before traced_env is destroyed, on every path.
|
||||
Defer close_secondary([this]() { CloseSecondary(); });
|
||||
|
||||
const auto verify_db = [&]() {
|
||||
std::unique_ptr<Iterator> iter1(dbfull()->NewIterator(ReadOptions()));
|
||||
std::unique_ptr<Iterator> iter2(db_secondary_->NewIterator(ReadOptions()));
|
||||
for (iter1->SeekToFirst(), iter2->SeekToFirst();
|
||||
@@ -1028,7 +1384,6 @@ TEST_F(DBSecondaryTest, SecondaryCloseFiles) {
|
||||
|
||||
Status s = db_secondary_->SetDBOptions({{"max_open_files", "-1"}});
|
||||
ASSERT_TRUE(s.IsNotSupported());
|
||||
CloseSecondary();
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, OpenAsSecondaryWALTailing) {
|
||||
@@ -1215,8 +1570,11 @@ TEST_F(DBSecondaryCatchUpFaultTest, ReconcilesMemtableAfterFailedRound) {
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
|
||||
VerifySecondaryBatchValue(db_secondary_->DefaultColumnFamily(), "foo", "v1");
|
||||
|
||||
ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
|
||||
VerifySecondaryValue("foo", "v2");
|
||||
VerifySecondaryBatchValue(db_secondary_->DefaultColumnFamily(), "foo", "v2");
|
||||
}
|
||||
|
||||
// A round whose WAL discovery reports a purged path replays nothing, yet
|
||||
@@ -1775,26 +2133,25 @@ TEST_F(DBSecondaryTest, NewIteratorsConsistentViewDuringCatchUp) {
|
||||
ASSERT_OK(
|
||||
db_->CompactRange(CompactRangeOptions(), handles_[1], nullptr, nullptr));
|
||||
|
||||
bool caught_up = false;
|
||||
Status catch_up_status;
|
||||
const auto catch_up = [&](void*) {
|
||||
if (!caught_up) {
|
||||
caught_up = true;
|
||||
catch_up_status = db_secondary_->TryCatchUpWithPrimary();
|
||||
}
|
||||
};
|
||||
SyncPoint::GetInstance()->SetCallBack("DBImpl::MultiCFSnapshot::AfterRefSV",
|
||||
catch_up);
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImplSecondary::NewIterators:AfterCreateIterator", catch_up);
|
||||
SyncPoint::GetInstance()->LoadDependency({
|
||||
{"DBImplSecondary::TryCatchUpWithPrimary:BeforePublishReadView",
|
||||
"DBImplSecondary::NewIterators:AfterLoadReadView"},
|
||||
{"DBImplSecondary::NewIterators:AfterLoadReadView",
|
||||
"DBImplSecondary::TryCatchUpWithPrimary:AllowPublishReadView"},
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
port::Thread catch_up_thread(
|
||||
[&]() { catch_up_status = db_secondary_->TryCatchUpWithPrimary(); });
|
||||
|
||||
std::vector<Iterator*> iterators;
|
||||
const Status new_iterators_status = db_secondary_->NewIterators(
|
||||
ReadOptions(), handles_secondary_, &iterators);
|
||||
catch_up_thread.join();
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->ClearTrace();
|
||||
|
||||
std::vector<std::unique_ptr<Iterator>> owned_iterators;
|
||||
owned_iterators.reserve(iterators.size());
|
||||
@@ -1803,7 +2160,6 @@ TEST_F(DBSecondaryTest, NewIteratorsConsistentViewDuringCatchUp) {
|
||||
}
|
||||
|
||||
ASSERT_OK(catch_up_status);
|
||||
ASSERT_TRUE(caught_up);
|
||||
ASSERT_OK(new_iterators_status);
|
||||
ASSERT_EQ(2, owned_iterators.size());
|
||||
for (const auto& iterator : owned_iterators) {
|
||||
@@ -1817,6 +2173,97 @@ TEST_F(DBSecondaryTest, NewIteratorsConsistentViewDuringCatchUp) {
|
||||
VerifySecondaryValue(handles_secondary_[1], "key", "new");
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, RetiredReadViewIsNotReclaimedByReader) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.disable_auto_compactions = true;
|
||||
CreateAndReopenWithCF({"cf1"}, options);
|
||||
|
||||
ASSERT_OK(Put(0, "key", "old"));
|
||||
ASSERT_OK(Put(1, "key", "old"));
|
||||
ASSERT_OK(Flush(0));
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
Options secondary_options = options;
|
||||
secondary_options.max_open_files = -1;
|
||||
OpenSecondaryWithColumnFamilies({"cf1"}, secondary_options);
|
||||
|
||||
WriteOptions write_options;
|
||||
write_options.disableWAL = true;
|
||||
ASSERT_OK(db_->Put(write_options, handles_[1], "key", "new"));
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
std::mutex state_mutex;
|
||||
std::condition_variable state_cv;
|
||||
bool reader_loaded = false;
|
||||
bool allow_reader = false;
|
||||
bool second_catch_up_locked = false;
|
||||
bool reader_done = false;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImplSecondary::NewIterators:AfterLoadReadView", [&](void* /*arg*/) {
|
||||
std::unique_lock<std::mutex> lock(state_mutex);
|
||||
reader_loaded = true;
|
||||
state_cv.notify_all();
|
||||
state_cv.wait(lock, [&]() { return allow_reader; });
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Status reader_status;
|
||||
std::vector<Iterator*> iterators;
|
||||
port::Thread reader_thread([&]() {
|
||||
reader_status = db_secondary_->NewIterators(
|
||||
ReadOptions(), {handles_secondary_[0]}, &iterators);
|
||||
std::lock_guard<std::mutex> lock(state_mutex);
|
||||
reader_done = true;
|
||||
state_cv.notify_all();
|
||||
});
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(state_mutex);
|
||||
state_cv.wait(lock, [&]() { return reader_loaded; });
|
||||
}
|
||||
const Status first_catch_up_status = db_secondary_->TryCatchUpWithPrimary();
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImplSecondary::TryCatchUpWithPrimary:BeforePublishReadView",
|
||||
[&](void* /*arg*/) {
|
||||
std::unique_lock<std::mutex> lock(state_mutex);
|
||||
second_catch_up_locked = true;
|
||||
state_cv.notify_all();
|
||||
state_cv.wait(lock, [&]() { return reader_done; });
|
||||
});
|
||||
Status second_catch_up_status;
|
||||
port::Thread second_catch_up_thread([&]() {
|
||||
second_catch_up_status = db_secondary_->TryCatchUpWithPrimary();
|
||||
});
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(state_mutex);
|
||||
state_cv.wait(lock, [&]() { return second_catch_up_locked; });
|
||||
allow_reader = true;
|
||||
state_cv.notify_all();
|
||||
}
|
||||
|
||||
reader_thread.join();
|
||||
second_catch_up_thread.join();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
std::vector<std::unique_ptr<Iterator>> owned_iterators;
|
||||
owned_iterators.reserve(iterators.size());
|
||||
for (Iterator* iterator : iterators) {
|
||||
owned_iterators.emplace_back(iterator);
|
||||
}
|
||||
ASSERT_OK(first_catch_up_status);
|
||||
ASSERT_OK(second_catch_up_status);
|
||||
ASSERT_OK(reader_status);
|
||||
ASSERT_EQ(1U, owned_iterators.size());
|
||||
owned_iterators[0]->Seek("key");
|
||||
ASSERT_OK(owned_iterators[0]->status());
|
||||
ASSERT_TRUE(owned_iterators[0]->Valid());
|
||||
ASSERT_EQ("old", owned_iterators[0]->value());
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, RefreshIterator) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
@@ -2070,6 +2517,378 @@ TEST_F(DBSecondaryTest, PrimaryDropColumnFamily) {
|
||||
value.clear();
|
||||
ASSERT_OK(db_secondary_->Get(ropts, handles_secondary_[1], "foo", &value));
|
||||
ASSERT_EQ("foo_val_1", value);
|
||||
|
||||
std::vector<Iterator*> raw_iterators;
|
||||
Status new_iterators_status = db_secondary_->NewIterators(
|
||||
ropts, {handles_secondary_[1]}, &raw_iterators);
|
||||
std::vector<std::unique_ptr<Iterator>> iterators;
|
||||
for (Iterator* iterator : raw_iterators) {
|
||||
iterators.emplace_back(iterator);
|
||||
}
|
||||
ASSERT_OK(new_iterators_status);
|
||||
ASSERT_EQ(1U, iterators.size());
|
||||
iterators[0]->SeekToFirst();
|
||||
ASSERT_TRUE(iterators[0]->Valid());
|
||||
ASSERT_EQ("foo", iterators[0]->key().ToString());
|
||||
ASSERT_EQ("foo_val_1", iterators[0]->value().ToString());
|
||||
|
||||
// A later publication must keep skipping the dropped column family after
|
||||
// releasing the previous view's SuperVersion.
|
||||
ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
|
||||
|
||||
raw_iterators.clear();
|
||||
new_iterators_status = db_secondary_->NewIterators(
|
||||
ropts, {handles_secondary_[0], handles_secondary_[1]}, &raw_iterators);
|
||||
iterators.clear();
|
||||
for (Iterator* iterator : raw_iterators) {
|
||||
iterators.emplace_back(iterator);
|
||||
}
|
||||
ASSERT_OK(new_iterators_status);
|
||||
ASSERT_EQ(2U, iterators.size());
|
||||
iterators[0]->SeekToFirst();
|
||||
ASSERT_FALSE(iterators[0]->Valid());
|
||||
ASSERT_OK(iterators[0]->status());
|
||||
iterators[1]->SeekToFirst();
|
||||
ASSERT_TRUE(iterators[1]->Valid());
|
||||
ASSERT_EQ("foo", iterators[1]->key().ToString());
|
||||
ASSERT_EQ("foo_val_1", iterators[1]->value().ToString());
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, OpenFilesAsyncPrimaryDropColumnFamily) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.disable_auto_compactions = true;
|
||||
const std::string kCfName = "pikachu";
|
||||
CreateAndReopenWithCF({kCfName}, options);
|
||||
ASSERT_OK(Put(1 /*cf*/, "foo", "foo_val"));
|
||||
ASSERT_OK(Flush(1 /*cf*/));
|
||||
const std::string table_path = GetNewestTableFilePath(kCfName);
|
||||
|
||||
Options secondary_options = options;
|
||||
secondary_options.max_open_files = -1;
|
||||
secondary_options.open_files_async = true;
|
||||
secondary_options.skip_stats_update_on_db_open = true;
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBSecondaryTest::OpenFilesAsyncPrimaryDropColumnFamily:Release",
|
||||
"DBImpl::BGWorkAsyncFileOpen::Start"},
|
||||
{"DBImpl::BGWorkAsyncFileOpen:Done",
|
||||
"DBSecondaryTest::OpenFilesAsyncPrimaryDropColumnFamily:Done"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Defer cleanup_sync_point([] {
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
});
|
||||
|
||||
OpenSecondaryWithColumnFamilies({kCfName}, secondary_options);
|
||||
ASSERT_EQ(2, handles_secondary_.size());
|
||||
ASSERT_OK(db_->DisableFileDeletions());
|
||||
ASSERT_OK(db_->DropColumnFamily(handles_[1]));
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(handles_[1]));
|
||||
handles_[1] = nullptr;
|
||||
ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
|
||||
|
||||
TEST_SYNC_POINT(
|
||||
"DBSecondaryTest::OpenFilesAsyncPrimaryDropColumnFamily:Release");
|
||||
TEST_SYNC_POINT(
|
||||
"DBSecondaryTest::OpenFilesAsyncPrimaryDropColumnFamily:Done");
|
||||
ASSERT_OK(db_->EnableFileDeletions());
|
||||
dbfull()->TEST_DeleteObsoleteFiles();
|
||||
ASSERT_OK(dbfull()->TEST_WaitForPurge());
|
||||
ASSERT_TRUE(env_->FileExists(table_path).IsNotFound());
|
||||
VerifySecondaryValue(handles_secondary_[1], "foo", "foo_val");
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, ReclaimsRetiredReadViewAfterLastReader) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
CreateAndReopenWithCF({"cf1"}, options);
|
||||
|
||||
options.max_open_files = -1;
|
||||
OpenSecondaryWithColumnFamilies({"cf1"}, options);
|
||||
|
||||
std::mutex state_mutex;
|
||||
std::condition_variable state_cv;
|
||||
bool reader_loaded = false;
|
||||
bool allow_reader = false;
|
||||
bool view_reclaimed = false;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImplSecondary::NewIterators:AfterLoadReadView", [&](void* /*arg*/) {
|
||||
std::unique_lock<std::mutex> lock(state_mutex);
|
||||
reader_loaded = true;
|
||||
state_cv.notify_all();
|
||||
state_cv.wait(lock, [&]() { return allow_reader; });
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImplSecondary::ReclaimRetiredReadViews:AfterReclaim",
|
||||
[&](void* /*arg*/) {
|
||||
std::lock_guard<std::mutex> lock(state_mutex);
|
||||
view_reclaimed = true;
|
||||
state_cv.notify_all();
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Status reader_status;
|
||||
std::vector<Iterator*> iterators;
|
||||
port::Thread reader_thread([&]() {
|
||||
reader_status = db_secondary_->NewIterators(ReadOptions(),
|
||||
handles_secondary_, &iterators);
|
||||
});
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(state_mutex);
|
||||
state_cv.wait(lock, [&]() { return reader_loaded; });
|
||||
}
|
||||
|
||||
const Status catch_up_status = db_secondary_->TryCatchUpWithPrimary();
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(state_mutex);
|
||||
allow_reader = true;
|
||||
state_cv.notify_all();
|
||||
if (catch_up_status.ok()) {
|
||||
state_cv.wait(lock, [&]() { return view_reclaimed; });
|
||||
}
|
||||
}
|
||||
|
||||
reader_thread.join();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
std::vector<std::unique_ptr<Iterator>> owned_iterators;
|
||||
for (Iterator* iterator : iterators) {
|
||||
owned_iterators.emplace_back(iterator);
|
||||
}
|
||||
ASSERT_OK(catch_up_status);
|
||||
ASSERT_OK(reader_status);
|
||||
ASSERT_EQ(handles_secondary_.size(), owned_iterators.size());
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, CloseWaitsForInFlightReadViewReclamation) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
CreateAndReopenWithCF({"cf1"}, options);
|
||||
|
||||
options.max_open_files = -1;
|
||||
OpenSecondaryWithColumnFamilies({"cf1"}, options);
|
||||
|
||||
std::mutex state_mutex;
|
||||
std::condition_variable state_cv;
|
||||
std::thread::id reclaimer_thread_id;
|
||||
bool reader_loaded = false;
|
||||
bool allow_reader = false;
|
||||
bool reclaimer_ready = false;
|
||||
bool allow_reclaimer = false;
|
||||
bool reclamation_in_flight = false;
|
||||
bool allow_reclamation = false;
|
||||
bool close_waiting = false;
|
||||
bool close_reached_stop = false;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImplSecondary::NewIterators:AfterLoadReadView", [&](void* /*arg*/) {
|
||||
std::unique_lock<std::mutex> lock(state_mutex);
|
||||
reader_loaded = true;
|
||||
state_cv.notify_all();
|
||||
state_cv.wait(lock, [&]() { return allow_reader; });
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImplSecondary::ReclaimRetiredReadViewsLoop:BeforeReclaim",
|
||||
[&](void* /*arg*/) {
|
||||
std::unique_lock<std::mutex> lock(state_mutex);
|
||||
reclaimer_thread_id = std::this_thread::get_id();
|
||||
reclaimer_ready = true;
|
||||
state_cv.notify_all();
|
||||
state_cv.wait(lock, [&]() { return allow_reclaimer; });
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImplSecondary::ReclaimRetiredReadViews:BeforeReclaim",
|
||||
[&](void* /*arg*/) {
|
||||
std::unique_lock<std::mutex> lock(state_mutex);
|
||||
if (std::this_thread::get_id() != reclaimer_thread_id) {
|
||||
return;
|
||||
}
|
||||
reclamation_in_flight = true;
|
||||
state_cv.notify_all();
|
||||
state_cv.wait(lock, [&]() { return allow_reclamation; });
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImplSecondary::DrainRetiredReadViews:Wait", [&](void* /*arg*/) {
|
||||
std::lock_guard<std::mutex> lock(state_mutex);
|
||||
close_waiting = true;
|
||||
state_cv.notify_all();
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImplSecondary::StopSecondaryReadViewReclaimer:BeforeJoin",
|
||||
[&](void* /*arg*/) {
|
||||
std::lock_guard<std::mutex> lock(state_mutex);
|
||||
close_reached_stop = true;
|
||||
state_cv.notify_all();
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Status reader_status;
|
||||
std::vector<Iterator*> iterators;
|
||||
port::Thread reader_thread([&]() {
|
||||
reader_status = db_secondary_->NewIterators(ReadOptions(),
|
||||
handles_secondary_, &iterators);
|
||||
});
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(state_mutex);
|
||||
state_cv.wait(lock, [&]() { return reader_loaded; });
|
||||
}
|
||||
const Status catch_up_status = db_secondary_->TryCatchUpWithPrimary();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state_mutex);
|
||||
allow_reader = true;
|
||||
state_cv.notify_all();
|
||||
}
|
||||
reader_thread.join();
|
||||
|
||||
for (Iterator* iterator : iterators) {
|
||||
delete iterator;
|
||||
}
|
||||
iterators.clear();
|
||||
Status destroy_handles_status;
|
||||
for (ColumnFamilyHandle* handle : handles_secondary_) {
|
||||
const Status s = db_secondary_->DestroyColumnFamilyHandle(handle);
|
||||
if (!s.ok() && destroy_handles_status.ok()) {
|
||||
destroy_handles_status = s;
|
||||
}
|
||||
}
|
||||
handles_secondary_.clear();
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(state_mutex);
|
||||
state_cv.wait(lock, [&]() { return reclaimer_ready; });
|
||||
allow_reclaimer = true;
|
||||
state_cv.notify_all();
|
||||
state_cv.wait(lock, [&]() { return reclamation_in_flight; });
|
||||
}
|
||||
|
||||
Status close_status;
|
||||
port::Thread close_thread([&]() { close_status = db_secondary_->Close(); });
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(state_mutex);
|
||||
state_cv.wait(lock, [&]() { return close_waiting || close_reached_stop; });
|
||||
EXPECT_TRUE(close_waiting);
|
||||
EXPECT_FALSE(close_reached_stop);
|
||||
allow_reclamation = true;
|
||||
state_cv.notify_all();
|
||||
}
|
||||
close_thread.join();
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
ASSERT_OK(catch_up_status);
|
||||
ASSERT_OK(reader_status);
|
||||
ASSERT_OK(destroy_handles_status);
|
||||
ASSERT_OK(close_status);
|
||||
db_secondary_.reset();
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, ExplicitCloseReleasesPublishedReadView) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
CreateAndReopenWithCF({"cf1"}, options);
|
||||
|
||||
options.max_open_files = -1;
|
||||
OpenSecondaryWithColumnFamilies({"cf1"}, options);
|
||||
ASSERT_OK(Put(0, "key", "value"));
|
||||
ASSERT_OK(Put(1, "key", "value"));
|
||||
ASSERT_OK(db_->FlushWAL(/*sync=*/true));
|
||||
ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
|
||||
|
||||
for (ColumnFamilyHandle* handle : handles_secondary_) {
|
||||
ASSERT_OK(db_secondary_->DestroyColumnFamilyHandle(handle));
|
||||
}
|
||||
handles_secondary_.clear();
|
||||
ASSERT_OK(db_secondary_->Close());
|
||||
db_secondary_.reset();
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, ExplicitCloseWaitsForRetiredReadView) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
CreateAndReopenWithCF({"cf1"}, options);
|
||||
|
||||
options.max_open_files = -1;
|
||||
OpenSecondaryWithColumnFamilies({"cf1"}, options);
|
||||
|
||||
std::mutex state_mutex;
|
||||
std::condition_variable state_cv;
|
||||
bool reader_loaded = false;
|
||||
bool allow_reader = false;
|
||||
bool close_waiting = false;
|
||||
bool close_reclaiming = false;
|
||||
bool allow_close = false;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImplSecondary::NewIterators:AfterLoadReadView", [&](void* /*arg*/) {
|
||||
std::unique_lock<std::mutex> lock(state_mutex);
|
||||
reader_loaded = true;
|
||||
state_cv.notify_all();
|
||||
state_cv.wait(lock, [&]() { return allow_reader; });
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImplSecondary::DrainRetiredReadViews:Wait", [&](void* /*arg*/) {
|
||||
std::lock_guard<std::mutex> lock(state_mutex);
|
||||
close_waiting = true;
|
||||
state_cv.notify_all();
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImplSecondary::DrainRetiredReadViews:AfterWait", [&](void* /*arg*/) {
|
||||
std::unique_lock<std::mutex> lock(state_mutex);
|
||||
close_reclaiming = true;
|
||||
state_cv.notify_all();
|
||||
state_cv.wait(lock, [&]() { return allow_close; });
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Status reader_status;
|
||||
std::vector<Iterator*> iterators;
|
||||
port::Thread reader_thread([&]() {
|
||||
reader_status = db_secondary_->NewIterators(ReadOptions(),
|
||||
handles_secondary_, &iterators);
|
||||
});
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(state_mutex);
|
||||
state_cv.wait(lock, [&]() { return reader_loaded; });
|
||||
}
|
||||
|
||||
Status close_status;
|
||||
port::Thread close_thread([&]() { close_status = db_secondary_->Close(); });
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(state_mutex);
|
||||
state_cv.wait(lock, [&]() { return close_waiting; });
|
||||
allow_reader = true;
|
||||
state_cv.notify_all();
|
||||
state_cv.wait(lock, [&]() { return close_reclaiming; });
|
||||
}
|
||||
|
||||
reader_thread.join();
|
||||
const size_t num_iterators = iterators.size();
|
||||
for (Iterator* iterator : iterators) {
|
||||
delete iterator;
|
||||
}
|
||||
iterators.clear();
|
||||
Status destroy_handles_status;
|
||||
for (ColumnFamilyHandle* handle : handles_secondary_) {
|
||||
const Status s = db_secondary_->DestroyColumnFamilyHandle(handle);
|
||||
if (!s.ok() && destroy_handles_status.ok()) {
|
||||
destroy_handles_status = s;
|
||||
}
|
||||
}
|
||||
handles_secondary_.clear();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state_mutex);
|
||||
allow_close = true;
|
||||
state_cv.notify_all();
|
||||
}
|
||||
close_thread.join();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
ASSERT_OK(reader_status);
|
||||
ASSERT_EQ(2U, num_iterators);
|
||||
ASSERT_OK(destroy_handles_status);
|
||||
ASSERT_OK(close_status);
|
||||
db_secondary_.reset();
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, SwitchManifest) {
|
||||
@@ -2395,6 +3214,7 @@ TEST_F(DBSecondaryTest, NewIteratorsPerColumnFamilyOptionsConsistentView) {
|
||||
ASSERT_OK(iterators[i]->status());
|
||||
ASSERT_TRUE(iterators[i]->Valid());
|
||||
ASSERT_EQ(expected_values[i], iterators[i]->value());
|
||||
ASSERT_TRUE(iterators[i]->Refresh().IsNotSupported());
|
||||
delete iterators[i];
|
||||
}
|
||||
|
||||
|
||||
+81
-5
@@ -1183,10 +1183,43 @@ TEST_F(DBSSTTest, DeleteSchedulerMultipleDBPaths) {
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, DestroyDBWithRateLimitedDelete) {
|
||||
Destroy(last_options_);
|
||||
|
||||
int bg_delete_file = 0;
|
||||
int preexisting_trash_files_scheduled = 0;
|
||||
uint64_t preexisting_trash_penalty = 0;
|
||||
std::string file_being_deleted;
|
||||
std::string preexisting_trash_file;
|
||||
std::vector<uint64_t> penalties;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DeleteScheduler::DeleteTrashFile:DeleteFile",
|
||||
[&](void* /*arg*/) { bg_delete_file++; });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DeleteScheduler::DeleteTrashFile::cb",
|
||||
[&](void* arg) { file_being_deleted = *static_cast<std::string*>(arg); });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DeleteScheduler::BackgroundEmptyTrash:Wait", [&](void* arg) {
|
||||
uint64_t penalty = *(static_cast<uint64_t*>(arg));
|
||||
penalties.push_back(penalty);
|
||||
if (file_being_deleted == preexisting_trash_file) {
|
||||
preexisting_trash_penalty = penalty;
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"SstFileManagerImpl::ScheduleExistingTrashFileDeletion", [&](void* arg) {
|
||||
const std::string& file_path = *static_cast<std::string*>(arg);
|
||||
if (EndsWith(file_path, ".trash")) {
|
||||
preexisting_trash_files_scheduled++;
|
||||
}
|
||||
});
|
||||
std::atomic<bool> fail_preexisting_trash_delete{true};
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DeleteScheduler::DeleteTrashFile:BeforeDeleteFile", [&](void* arg) {
|
||||
if (file_being_deleted == preexisting_trash_file &&
|
||||
fail_preexisting_trash_delete.exchange(false)) {
|
||||
*static_cast<Status*>(arg) = Status::IOError("injected delete error");
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Status s;
|
||||
@@ -1196,7 +1229,7 @@ TEST_F(DBSSTTest, DestroyDBWithRateLimitedDelete) {
|
||||
options.sst_file_manager.reset(
|
||||
NewSstFileManager(env_, nullptr, "", 0, false, &s, 0));
|
||||
ASSERT_OK(s);
|
||||
DestroyAndReopen(options);
|
||||
Reopen(options);
|
||||
|
||||
// Create 4 files in L0
|
||||
for (int i = 0; i < 4; i++) {
|
||||
@@ -1211,27 +1244,70 @@ TEST_F(DBSSTTest, DestroyDBWithRateLimitedDelete) {
|
||||
|
||||
int num_sst_files = 0;
|
||||
int num_wal_files = 0;
|
||||
std::string sst_file_to_mark_trash;
|
||||
std::vector<std::string> db_files;
|
||||
ASSERT_OK(env_->GetChildren(dbname_, &db_files));
|
||||
for (const std::string& f : db_files) {
|
||||
if (f.substr(f.find_last_of('.') + 1) == "sst") {
|
||||
num_sst_files++;
|
||||
if (sst_file_to_mark_trash.empty()) {
|
||||
sst_file_to_mark_trash = dbname_;
|
||||
sst_file_to_mark_trash.append("/").append(f);
|
||||
}
|
||||
} else if (f.substr(f.find_last_of('.') + 1) == "log") {
|
||||
num_wal_files++;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
ASSERT_GT(num_sst_files, 0);
|
||||
ASSERT_GT(num_wal_files, 0);
|
||||
ASSERT_FALSE(sst_file_to_mark_trash.empty());
|
||||
uint64_t preexisting_trash_file_size = 0;
|
||||
ASSERT_OK(
|
||||
env_->GetFileSize(sst_file_to_mark_trash, &preexisting_trash_file_size));
|
||||
preexisting_trash_file = sst_file_to_mark_trash;
|
||||
preexisting_trash_file.append(".trash");
|
||||
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(options.sst_file_manager.get());
|
||||
|
||||
sfm->SetDeleteRateBytesPerSecond(1024 * 1024);
|
||||
const int64_t rate_bytes_per_sec = 1024 * 1024;
|
||||
sfm->SetDeleteRateBytesPerSecond(rate_bytes_per_sec);
|
||||
// Set an extra high trash ratio to prevent immediate/non-rate limited
|
||||
// deletions
|
||||
sfm->delete_scheduler()->SetMaxTrashDBRatio(1000.0);
|
||||
ASSERT_OK(DestroyDB(dbname_, options));
|
||||
ASSERT_OK(
|
||||
sfm->OnAddFile(sst_file_to_mark_trash, preexisting_trash_file_size));
|
||||
ASSERT_OK(sfm->ScheduleFileDeletion(sst_file_to_mark_trash, dbname_,
|
||||
/*force_bg=*/true));
|
||||
sfm->WaitForEmptyTrashInDirectory(dbname_);
|
||||
ASSERT_FALSE(fail_preexisting_trash_delete);
|
||||
ASSERT_OK(env_->FileExists(preexisting_trash_file));
|
||||
ASSERT_EQ(sfm->GetTotalSize(), preexisting_trash_file_size);
|
||||
ASSERT_EQ(sfm->GetTotalTrashSize(), preexisting_trash_file_size);
|
||||
|
||||
const std::string unaccounted_trash_file = dbname_ + "/unaccounted.sst.trash";
|
||||
ASSERT_OK(WriteStringToFile(env_, "trash", unaccounted_trash_file,
|
||||
/*should_sync=*/false));
|
||||
|
||||
std::string dbname_alias = dbname_;
|
||||
dbname_alias.insert(dbname_alias.find_last_of('/'), "/");
|
||||
ASSERT_OK(DestroyDB(dbname_alias, options));
|
||||
sfm->WaitForEmptyTrash();
|
||||
ASSERT_EQ(bg_delete_file, num_sst_files + num_wal_files);
|
||||
ASSERT_EQ(bg_delete_file, num_sst_files + num_wal_files + 2);
|
||||
ASSERT_EQ(preexisting_trash_files_scheduled, 2);
|
||||
ASSERT_GE(preexisting_trash_penalty,
|
||||
preexisting_trash_file_size * 1000000 / rate_bytes_per_sec);
|
||||
ASSERT_EQ(penalties.size(), num_sst_files + num_wal_files + 2);
|
||||
ASSERT_TRUE(sfm->GetTrackedFiles().empty());
|
||||
ASSERT_EQ(sfm->GetTotalSize(), 0);
|
||||
ASSERT_EQ(sfm->GetTotalTrashSize(), 0);
|
||||
auto bg_errors = sfm->delete_scheduler()->GetBackgroundErrors();
|
||||
ASSERT_EQ(bg_errors.size(), 1);
|
||||
for (auto& error : bg_errors) {
|
||||
ASSERT_NOK(error.second);
|
||||
}
|
||||
ASSERT_TRUE(env_->FileExists(dbname_).IsNotFound());
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, DBWithMaxSpaceAllowed) {
|
||||
@@ -1672,7 +1748,7 @@ TEST_F(DBSSTTest, GetTotalSstFilesSize) {
|
||||
// it. Should remove the workaround after we propagate the property on
|
||||
// compaction.
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"FlushJob::WriteLevel0Table:oldest_ancester_time", [&](void* arg) {
|
||||
"FlushJob::WriteLevel0Table:oldest_ancestor_time", [&](void* arg) {
|
||||
uint64_t* current_time = static_cast<uint64_t*>(arg);
|
||||
*current_time = 0;
|
||||
});
|
||||
|
||||
@@ -897,6 +897,23 @@ TEST_F(DBTablePropertiesTest, LsmInfoAtCreationPacking) {
|
||||
EXPECT_TRUE(decoded.is_bottommost);
|
||||
}
|
||||
|
||||
TEST_F(DBTablePropertiesTest, CreationTimeAliasesOldestAncestorTime) {
|
||||
TableProperties props;
|
||||
ASSERT_EQ(&props.creation_time, &props.oldest_ancestor_time);
|
||||
props.creation_time = 12345;
|
||||
ASSERT_EQ(props.oldest_ancestor_time, 12345U);
|
||||
ASSERT_EQ(&TablePropertiesNames::kCreationTime,
|
||||
&TablePropertiesNames::kOldestAncestorTime);
|
||||
}
|
||||
|
||||
TEST_F(DBTablePropertiesTest, SstFileMetaDataOldestAncesterTimeAlias) {
|
||||
// Intentionally uses the deprecated misspelled alias
|
||||
LiveFileMetaData meta;
|
||||
ASSERT_EQ(&meta.oldest_ancester_time, &meta.oldest_ancestor_time);
|
||||
meta.oldest_ancester_time = 12345;
|
||||
ASSERT_EQ(meta.oldest_ancestor_time, 12345U);
|
||||
}
|
||||
|
||||
TEST_F(DBTablePropertiesTest, UncompressedDataSizeAndLsmInfoPersisted) {
|
||||
Options options = CurrentOptions();
|
||||
options.compression = kNoCompression;
|
||||
|
||||
+4
-4
@@ -1334,16 +1334,16 @@ void CheckColumnFamilyMeta(
|
||||
file_meta_from_files.largest.user_key().ToString());
|
||||
ASSERT_EQ(file_meta_from_cf.oldest_blob_file_number,
|
||||
file_meta_from_files.oldest_blob_file_number);
|
||||
ASSERT_EQ(file_meta_from_cf.oldest_ancester_time,
|
||||
file_meta_from_files.oldest_ancester_time);
|
||||
ASSERT_EQ(file_meta_from_cf.oldest_ancestor_time,
|
||||
file_meta_from_files.oldest_ancestor_time);
|
||||
ASSERT_EQ(file_meta_from_cf.file_creation_time,
|
||||
file_meta_from_files.file_creation_time);
|
||||
ASSERT_GE(file_meta_from_cf.file_creation_time, start_time);
|
||||
ASSERT_LE(file_meta_from_cf.file_creation_time, end_time);
|
||||
ASSERT_EQ(file_meta_from_cf.epoch_number,
|
||||
file_meta_from_files.epoch_number);
|
||||
ASSERT_GE(file_meta_from_cf.oldest_ancester_time, start_time);
|
||||
ASSERT_LE(file_meta_from_cf.oldest_ancester_time, end_time);
|
||||
ASSERT_GE(file_meta_from_cf.oldest_ancestor_time, start_time);
|
||||
ASSERT_LE(file_meta_from_cf.oldest_ancestor_time, end_time);
|
||||
// More from FileStorageInfo
|
||||
ASSERT_EQ(file_meta_from_cf.file_type, kTableFile);
|
||||
ASSERT_EQ(file_meta_from_cf.name,
|
||||
|
||||
+41
-28
@@ -991,35 +991,12 @@ std::vector<std::string> DBTestBase::MultiGet(const std::vector<std::string>& k,
|
||||
bool async,
|
||||
bool optimize_multiget_for_io,
|
||||
bool use_coroutine) {
|
||||
ReadOptions options;
|
||||
options.verify_checksums = true;
|
||||
options.snapshot = snapshot;
|
||||
options.async_io = async;
|
||||
options.optimize_multiget_for_io = optimize_multiget_for_io;
|
||||
std::vector<Slice> keys;
|
||||
std::vector<std::string> result(k.size());
|
||||
std::vector<Status> statuses(k.size());
|
||||
std::vector<PinnableSlice> pin_values(k.size());
|
||||
std::vector<Status> statuses;
|
||||
std::vector<PinnableSlice> pin_values;
|
||||
MultiGet(k, snapshot, async, optimize_multiget_for_io, use_coroutine,
|
||||
pin_values, statuses);
|
||||
|
||||
for (size_t i = 0; i < k.size(); ++i) {
|
||||
keys.emplace_back(k[i]);
|
||||
}
|
||||
#if USE_COROUTINES
|
||||
if (use_coroutine) {
|
||||
std::vector<ColumnFamilyHandle*> cfs(keys.size(),
|
||||
dbfull()->DefaultColumnFamily());
|
||||
folly::coro::blockingWait(CoroDB::CoMultiGet(
|
||||
dbfull(), options, keys.size(), cfs.data(), keys.data(),
|
||||
pin_values.data(), /*timestamps=*/nullptr, statuses.data(),
|
||||
/*sorted_input=*/false));
|
||||
} else
|
||||
#else
|
||||
(void)use_coroutine;
|
||||
#endif // USE_COROUTINES
|
||||
{
|
||||
db_->MultiGet(options, dbfull()->DefaultColumnFamily(), keys.size(),
|
||||
keys.data(), pin_values.data(), statuses.data());
|
||||
}
|
||||
std::vector<std::string> result(k.size());
|
||||
for (size_t i = 0; i < statuses.size(); ++i) {
|
||||
if (statuses[i].IsNotFound()) {
|
||||
result[i] = "NOT_FOUND";
|
||||
@@ -1035,6 +1012,42 @@ std::vector<std::string> DBTestBase::MultiGet(const std::vector<std::string>& k,
|
||||
return result;
|
||||
}
|
||||
|
||||
void DBTestBase::MultiGet(const std::vector<std::string>& k,
|
||||
const Snapshot* snapshot, bool async,
|
||||
bool optimize_multiget_for_io, bool use_coroutine,
|
||||
std::vector<PinnableSlice>& values,
|
||||
std::vector<Status>& statuses) {
|
||||
ReadOptions options;
|
||||
options.verify_checksums = true;
|
||||
options.snapshot = snapshot;
|
||||
options.async_io = async;
|
||||
options.optimize_multiget_for_io = optimize_multiget_for_io;
|
||||
std::vector<Slice> keys;
|
||||
values.clear();
|
||||
statuses.resize(k.size());
|
||||
values.resize(k.size());
|
||||
|
||||
for (size_t i = 0; i < k.size(); ++i) {
|
||||
keys.emplace_back(k[i]);
|
||||
}
|
||||
#if USE_COROUTINES
|
||||
if (use_coroutine) {
|
||||
std::vector<ColumnFamilyHandle*> cfs(keys.size(),
|
||||
dbfull()->DefaultColumnFamily());
|
||||
folly::coro::blockingWait(CoroDB::CoMultiGet(
|
||||
dbfull(), options, keys.size(), cfs.data(), keys.data(), values.data(),
|
||||
/*timestamps=*/nullptr, statuses.data(),
|
||||
/*sorted_input=*/false));
|
||||
} else
|
||||
#else
|
||||
(void)use_coroutine;
|
||||
#endif // USE_COROUTINES
|
||||
{
|
||||
db_->MultiGet(options, dbfull()->DefaultColumnFamily(), keys.size(),
|
||||
keys.data(), values.data(), statuses.data());
|
||||
}
|
||||
}
|
||||
|
||||
Status DBTestBase::Get(const std::string& k, PinnableSlice* v,
|
||||
bool use_coroutine) {
|
||||
ReadOptions options;
|
||||
|
||||
@@ -1253,6 +1253,11 @@ class DBTestBase : public testing::Test {
|
||||
bool optimize_multiget_for_io = true,
|
||||
bool use_coroutine = false);
|
||||
|
||||
void MultiGet(const std::vector<std::string>& k, const Snapshot* snapshot,
|
||||
bool async, bool optimize_multiget_for_io, bool use_coroutine,
|
||||
std::vector<PinnableSlice>& values,
|
||||
std::vector<Status>& statuses);
|
||||
|
||||
Status CompactRange(const CompactRangeOptions& options,
|
||||
std::optional<Slice> begin, std::optional<Slice> end);
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1075,6 +1075,161 @@ INSTANTIATE_TEST_CASE_P(DBWriteTestInstance, DBWriteTest,
|
||||
DBTestBase::kConcurrentWALWrites,
|
||||
DBTestBase::kPipelinedWrite));
|
||||
|
||||
// A non-blocking unbatched join must refuse an active write stall.
|
||||
TEST(WriteThreadTest, EnterUnbatchedNonBlockingRefusesDuringStall) {
|
||||
Options options;
|
||||
ImmutableDBOptions db_options(options);
|
||||
WriteThread write_thread(db_options);
|
||||
InstrumentedMutex mu;
|
||||
|
||||
// No stall: joining succeeds and must be paired with ExitUnbatched().
|
||||
{
|
||||
WriteThread::Writer w;
|
||||
InstrumentedMutexLock l(&mu);
|
||||
ASSERT_TRUE(write_thread.EnterUnbatchedNonBlocking(&w, &mu));
|
||||
ASSERT_OK(w.status);
|
||||
EXPECT_FALSE(w.no_slowdown);
|
||||
EXPECT_FALSE(w.non_blocking_join);
|
||||
write_thread.ExitUnbatched(&w);
|
||||
}
|
||||
|
||||
// Stalled: refuses rather than parking on stall_cv_. Reaching the assertion
|
||||
// at all is the point -- EnterUnbatched() would never have returned.
|
||||
{
|
||||
InstrumentedMutexLock l(&mu);
|
||||
write_thread.BeginWriteStall();
|
||||
ASSERT_NE(0, write_thread.GetBegunCountOfOutstandingStall());
|
||||
|
||||
WriteThread::Writer w;
|
||||
ASSERT_FALSE(write_thread.EnterUnbatchedNonBlocking(&w, &mu));
|
||||
ASSERT_TRUE(w.status.IsIncomplete());
|
||||
EXPECT_FALSE(w.no_slowdown);
|
||||
EXPECT_FALSE(w.non_blocking_join);
|
||||
|
||||
// Not linked, so ExitUnbatched() must not be called; ending the stall is
|
||||
// enough to leave the queue clean.
|
||||
write_thread.EndWriteStall();
|
||||
ASSERT_EQ(0, write_thread.GetBegunCountOfOutstandingStall());
|
||||
}
|
||||
|
||||
{
|
||||
WriteThread::Writer w;
|
||||
InstrumentedMutexLock l(&mu);
|
||||
ASSERT_TRUE(write_thread.EnterUnbatchedNonBlocking(&w, &mu));
|
||||
write_thread.ExitUnbatched(&w);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(WriteThreadTest, EnterUnbatchedNonBlockingFinishesAfterLeadershipStall) {
|
||||
Options options;
|
||||
ImmutableDBOptions db_options(options);
|
||||
WriteThread write_thread(db_options);
|
||||
InstrumentedMutex mu;
|
||||
InstrumentedMutex signal_mu;
|
||||
InstrumentedCondVar signal_cv(&signal_mu);
|
||||
bool before_lock = false;
|
||||
bool release_joiner = false;
|
||||
bool entered = true;
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"WriteThread::EnterUnbatchedNonBlocking:BeforeLock", [&](void*) {
|
||||
InstrumentedMutexLock l(&signal_mu);
|
||||
before_lock = true;
|
||||
signal_cv.SignalAll();
|
||||
while (!release_joiner) {
|
||||
signal_cv.Wait();
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
Defer cleanup_sync_points([] {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
});
|
||||
|
||||
port::Thread joiner([&] {
|
||||
WriteThread::Writer w;
|
||||
InstrumentedMutexLock l(&mu);
|
||||
entered = write_thread.EnterUnbatchedNonBlocking(&w, &mu);
|
||||
if (entered) {
|
||||
write_thread.ExitUnbatched(&w);
|
||||
}
|
||||
});
|
||||
|
||||
{
|
||||
InstrumentedMutexLock l(&signal_mu);
|
||||
while (!before_lock) {
|
||||
signal_cv.Wait();
|
||||
}
|
||||
}
|
||||
{
|
||||
InstrumentedMutexLock l(&mu);
|
||||
write_thread.BeginWriteStall();
|
||||
}
|
||||
{
|
||||
InstrumentedMutexLock l(&signal_mu);
|
||||
release_joiner = true;
|
||||
signal_cv.SignalAll();
|
||||
}
|
||||
|
||||
joiner.join();
|
||||
EXPECT_TRUE(entered);
|
||||
{
|
||||
InstrumentedMutexLock l(&mu);
|
||||
write_thread.EndWriteStall();
|
||||
}
|
||||
}
|
||||
|
||||
// A WBM join queues behind an ordinary leader so sustained write traffic
|
||||
// cannot starve the memtable switch.
|
||||
TEST(WriteThreadTest, EnterUnbatchedNonBlockingQueuesBehindOrdinaryLeader) {
|
||||
Options options;
|
||||
ImmutableDBOptions db_options(options);
|
||||
WriteThread write_thread(db_options);
|
||||
InstrumentedMutex mu;
|
||||
InstrumentedMutex signal_mu;
|
||||
InstrumentedCondVar signal_cv(&signal_mu);
|
||||
bool queued = false;
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"WriteThread::EnterUnbatched:Wait", [&](void*) {
|
||||
InstrumentedMutexLock l(&signal_mu);
|
||||
queued = true;
|
||||
signal_cv.SignalAll();
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
Defer cleanup_sync_points([] {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
});
|
||||
|
||||
WriteThread::Writer leader;
|
||||
mu.Lock();
|
||||
write_thread.EnterUnbatched(&leader, &mu);
|
||||
mu.Unlock();
|
||||
|
||||
bool entered = false;
|
||||
port::Thread joiner([&] {
|
||||
WriteThread::Writer follower;
|
||||
InstrumentedMutexLock l(&mu);
|
||||
entered = write_thread.EnterUnbatchedNonBlocking(&follower, &mu);
|
||||
if (entered) {
|
||||
write_thread.ExitUnbatched(&follower);
|
||||
}
|
||||
});
|
||||
|
||||
{
|
||||
InstrumentedMutexLock l(&signal_mu);
|
||||
while (!queued) {
|
||||
signal_cv.Wait();
|
||||
}
|
||||
}
|
||||
mu.Lock();
|
||||
write_thread.ExitUnbatched(&leader);
|
||||
mu.Unlock();
|
||||
joiner.join();
|
||||
EXPECT_TRUE(entered);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+113
-15
@@ -5,6 +5,8 @@
|
||||
//
|
||||
#include "db/error_handler.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "file/sst_file_manager_impl.h"
|
||||
@@ -293,7 +295,9 @@ void ErrorHandler::CancelErrorRecoveryForShutDown() {
|
||||
// also track the error separately in recovery_error_ so we can tell in the
|
||||
// end whether recovery succeeded or not
|
||||
void ErrorHandler::HandleKnownErrors(const Status& bg_err,
|
||||
BackgroundErrorReason reason) {
|
||||
BackgroundErrorReason reason,
|
||||
const DBRecoverContext& context,
|
||||
bool allow_auto_recovery) {
|
||||
db_mutex_->AssertHeld();
|
||||
if (bg_err.ok()) {
|
||||
return;
|
||||
@@ -302,7 +306,6 @@ void ErrorHandler::HandleKnownErrors(const Status& bg_err,
|
||||
bool paranoid = db_options_.paranoid_checks;
|
||||
Status::Severity sev = Status::Severity::kFatalError;
|
||||
Status new_bg_err;
|
||||
DBRecoverContext context;
|
||||
bool found = false;
|
||||
|
||||
{
|
||||
@@ -338,7 +341,7 @@ void ErrorHandler::HandleKnownErrors(const Status& bg_err,
|
||||
recovery_error_ = status_to_io_status(Status(new_bg_err));
|
||||
}
|
||||
|
||||
bool auto_recovery = auto_recovery_;
|
||||
bool auto_recovery = allow_auto_recovery && auto_recovery_;
|
||||
if (new_bg_err.severity() >= Status::Severity::kFatalError && auto_recovery) {
|
||||
auto_recovery = false;
|
||||
}
|
||||
@@ -353,9 +356,13 @@ void ErrorHandler::HandleKnownErrors(const Status& bg_err,
|
||||
Status s = new_bg_err;
|
||||
EventHelpers::NotifyOnBackgroundError(db_options_.listeners, reason, &s,
|
||||
db_mutex_, &auto_recovery);
|
||||
if (!s.ok() && (s.severity() > bg_error_.severity())) {
|
||||
if (!s.ok() && s.severity() > bg_error_.severity()) {
|
||||
bg_error_ = s;
|
||||
UpdateRecoveryContext(context, /*replace_existing_context=*/true);
|
||||
} else {
|
||||
if (!s.ok()) {
|
||||
UpdateRecoveryContext(context, /*replace_existing_context=*/false);
|
||||
}
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
"ErrorHandler: Hit less severe background error\n");
|
||||
|
||||
@@ -371,7 +378,6 @@ void ErrorHandler::HandleKnownErrors(const Status& bg_err,
|
||||
"ErrorHandler: Set regular background error, auto_recovery=%d, stop=%d\n",
|
||||
int{auto_recovery}, int{stop});
|
||||
|
||||
recover_context_ = context;
|
||||
if (auto_recovery) {
|
||||
recovery_in_prog_ = true;
|
||||
|
||||
@@ -412,6 +418,27 @@ void ErrorHandler::HandleKnownErrors(const Status& bg_err,
|
||||
// such as delegating to SstFileManager to handle no space error.
|
||||
void ErrorHandler::SetBGError(const Status& bg_status,
|
||||
BackgroundErrorReason reason, bool wal_related) {
|
||||
SetBGErrorImpl(bg_status, reason, wal_related, DBRecoverContext(),
|
||||
/*allow_auto_recovery=*/true);
|
||||
}
|
||||
|
||||
void ErrorHandler::SetBGError(const Status& bg_status,
|
||||
BackgroundErrorReason reason, bool wal_related,
|
||||
DBRecoverContext context) {
|
||||
SetBGErrorImpl(bg_status, reason, wal_related, std::move(context),
|
||||
/*allow_auto_recovery=*/true);
|
||||
}
|
||||
|
||||
void ErrorHandler::SetBGErrorNoAutoRecovery(const Status& bg_status,
|
||||
BackgroundErrorReason reason) {
|
||||
SetBGErrorImpl(bg_status, reason, /*wal_related=*/false, DBRecoverContext(),
|
||||
/*allow_auto_recovery=*/false);
|
||||
}
|
||||
|
||||
void ErrorHandler::SetBGErrorImpl(const Status& bg_status,
|
||||
BackgroundErrorReason reason,
|
||||
bool wal_related, DBRecoverContext context,
|
||||
bool allow_auto_recovery) {
|
||||
db_mutex_->AssertHeld();
|
||||
Status tmp_status = bg_status;
|
||||
IOStatus bg_io_err = status_to_io_status(std::move(tmp_status));
|
||||
@@ -421,18 +448,34 @@ void ErrorHandler::SetBGError(const Status& bg_status,
|
||||
}
|
||||
ROCKS_LOG_WARN(db_options_.info_log, "Background IO error %s, reason %d",
|
||||
bg_io_err.ToString().c_str(), static_cast<int>(reason));
|
||||
if (context.failed_wal_number != 0) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"[WAL recovery] Quarantined WALs through #%" PRIu64
|
||||
" after a file-scoped error; indeterminate sequence range=[%" PRIu64
|
||||
", %" PRIu64 "], status=%s",
|
||||
context.failed_wal_number, context.failed_wal_sequence_start,
|
||||
context.failed_wal_sequence, bg_io_err.ToString().c_str());
|
||||
}
|
||||
|
||||
RecordStats({ERROR_HANDLER_BG_ERROR_COUNT, ERROR_HANDLER_BG_IO_ERROR_COUNT},
|
||||
{} /* int_histograms */);
|
||||
|
||||
Status new_bg_io_err = bg_io_err;
|
||||
DBRecoverContext context;
|
||||
if (bg_io_err.GetScope() != IOStatus::IOErrorScope::kIOErrorScopeFile &&
|
||||
bg_io_err.GetDataLoss()) {
|
||||
// First, data loss (non file scope) is treated as unrecoverable error. So
|
||||
// it can directly overwrite any existing bg_error_.
|
||||
bool auto_recovery = false;
|
||||
Status bg_err(new_bg_io_err, Status::Severity::kUnrecoverableError);
|
||||
if (!allow_auto_recovery) {
|
||||
EventHelpers::NotifyOnBackgroundError(db_options_.listeners, reason,
|
||||
&bg_err, db_mutex_, &auto_recovery);
|
||||
if (!bg_err.ok()) {
|
||||
CheckAndSetRecoveryAndBGError(bg_err, context);
|
||||
}
|
||||
return;
|
||||
}
|
||||
CheckAndSetRecoveryAndBGError(bg_err, context);
|
||||
ROCKS_LOG_INFO(
|
||||
db_options_.info_log,
|
||||
@@ -448,12 +491,11 @@ void ErrorHandler::SetBGError(const Status& bg_status,
|
||||
}
|
||||
if (db_options_.manual_wal_flush && wal_related && bg_io_err.IsIOError()) {
|
||||
// With manual_wal_flush, a WAL write failure can drop buffered WAL writes.
|
||||
// Memtables and WAL then become inconsistent. A successful memtable flush
|
||||
// on one CF can cause CFs to be inconsistent upon restart. Before we fix
|
||||
// the bug in auto recovery from WAL write failures that can flush one CF
|
||||
// at a time, we set the error severity to fatal to disallow auto recovery.
|
||||
// TODO: remove parameter `wal_related` once we can automatically recover
|
||||
// from WAL write failures.
|
||||
// Memtables and WAL can then become inconsistent. Full error recovery now
|
||||
// flushes all CFs atomically, but automatically recovering this case still
|
||||
// needs dedicated analysis and testing. Keep the error fatal until then.
|
||||
// TODO: Re-evaluate `wal_related` and automatic recovery for
|
||||
// manual_wal_flush WAL failures.
|
||||
bool auto_recovery = false;
|
||||
Status bg_err(new_bg_io_err, Status::Severity::kFatalError);
|
||||
CheckAndSetRecoveryAndBGError(bg_err, context);
|
||||
@@ -517,6 +559,15 @@ void ErrorHandler::SetBGError(const Status& bg_status,
|
||||
severity = Status::Severity::kHardError;
|
||||
}
|
||||
Status bg_err(new_bg_io_err, severity);
|
||||
if (!allow_auto_recovery) {
|
||||
bool auto_recovery = false;
|
||||
EventHelpers::NotifyOnBackgroundError(db_options_.listeners, reason,
|
||||
&bg_err, db_mutex_, &auto_recovery);
|
||||
if (!bg_err.ok()) {
|
||||
CheckAndSetRecoveryAndBGError(bg_err, context);
|
||||
}
|
||||
return;
|
||||
}
|
||||
CheckAndSetRecoveryAndBGError(bg_err, context);
|
||||
bool auto_recovery = db_options_.max_bgerror_resume_count > 0;
|
||||
EventHelpers::NotifyOnBackgroundError(db_options_.listeners, reason,
|
||||
@@ -525,7 +576,7 @@ void ErrorHandler::SetBGError(const Status& bg_status,
|
||||
StartRecoverFromRetryableBGIOError(bg_io_err);
|
||||
return;
|
||||
}
|
||||
HandleKnownErrors(new_bg_io_err, reason);
|
||||
HandleKnownErrors(new_bg_io_err, reason, context, allow_auto_recovery);
|
||||
}
|
||||
|
||||
void ErrorHandler::AddFilesToQuarantine(
|
||||
@@ -610,6 +661,10 @@ Status ErrorHandler::ClearBGError() {
|
||||
is_db_stopped_.store(false, std::memory_order_release);
|
||||
bg_error_ = Status::OK();
|
||||
recovery_error_ = IOStatus::OK();
|
||||
// Recovery facts belong to one background-error episode. Keeping a
|
||||
// retired WAL cutoff or reserved sequence here would attach it to a later,
|
||||
// unrelated error when UpdateRecoveryContext() merges the next context.
|
||||
recover_context_ = DBRecoverContext();
|
||||
bg_error_.PermitUncheckedError();
|
||||
recovery_error_.PermitUncheckedError();
|
||||
recovery_in_prog_ = false;
|
||||
@@ -657,6 +712,15 @@ Status ErrorHandler::RecoverFromBGError(bool is_manual) {
|
||||
recovery_error_.PermitUncheckedError();
|
||||
DBRecoverContext context = recover_context_;
|
||||
context.flush_after_recovery = true;
|
||||
TEST_SYNC_POINT_CALLBACK("ErrorHandler::RecoverFromBGError:Context",
|
||||
&context);
|
||||
if (context.failed_wal_number != 0 || context.failed_wal_sequence != 0) {
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
"[WAL recovery] Starting requested recovery: cutoff=%" PRIu64
|
||||
", indeterminate sequence range=[%" PRIu64 ", %" PRIu64 "]",
|
||||
context.failed_wal_number, context.failed_wal_sequence_start,
|
||||
context.failed_wal_sequence);
|
||||
}
|
||||
Status s = db_->ResumeImpl(context, Env::IOActivity::kFlush);
|
||||
if (s.ok()) {
|
||||
soft_error_no_bg_work_ = false;
|
||||
@@ -754,6 +818,15 @@ void ErrorHandler::RecoverFromRetryableBGIOError() {
|
||||
DBRecoverContext context = recover_context_;
|
||||
context.flush_after_recovery = true;
|
||||
retry_count++;
|
||||
if (context.failed_wal_number != 0 || context.failed_wal_sequence != 0) {
|
||||
ROCKS_LOG_INFO(
|
||||
db_options_.info_log,
|
||||
"[WAL recovery] Starting automatic recovery attempt #%" PRIu64
|
||||
": cutoff=%" PRIu64 ", indeterminate sequence range=[%" PRIu64
|
||||
", %" PRIu64 "]",
|
||||
retry_count, context.failed_wal_number,
|
||||
context.failed_wal_sequence_start, context.failed_wal_sequence);
|
||||
}
|
||||
Status s = db_->ResumeImpl(context, Env::IOActivity::kFlush);
|
||||
RecordStats({ERROR_HANDLER_AUTORESUME_RETRY_TOTAL_COUNT},
|
||||
{} /* int_histograms */);
|
||||
@@ -819,15 +892,40 @@ void ErrorHandler::CheckAndSetRecoveryAndBGError(
|
||||
if (recovery_in_prog_ && recovery_error_.ok()) {
|
||||
recovery_error_ = status_to_io_status(Status(bg_err));
|
||||
}
|
||||
if (bg_err.severity() > bg_error_.severity()) {
|
||||
if (!bg_err.ok() && bg_err.severity() > bg_error_.severity()) {
|
||||
bg_error_ = bg_err;
|
||||
recover_context_ = context;
|
||||
UpdateRecoveryContext(context, /*replace_existing_context=*/true);
|
||||
} else {
|
||||
UpdateRecoveryContext(context, /*replace_existing_context=*/false);
|
||||
}
|
||||
if (bg_error_.severity() >= Status::Severity::kHardError) {
|
||||
is_db_stopped_.store(true, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
void ErrorHandler::UpdateRecoveryContext(const DBRecoverContext& context,
|
||||
bool replace_existing_context) {
|
||||
db_mutex_->AssertHeld();
|
||||
const uint64_t failed_wal_number =
|
||||
std::max(recover_context_.failed_wal_number, context.failed_wal_number);
|
||||
uint64_t failed_wal_sequence = recover_context_.failed_wal_sequence;
|
||||
uint64_t failed_wal_sequence_start =
|
||||
recover_context_.failed_wal_sequence_start;
|
||||
if (context.failed_wal_sequence > failed_wal_sequence) {
|
||||
failed_wal_sequence = context.failed_wal_sequence;
|
||||
failed_wal_sequence_start = context.failed_wal_sequence_start;
|
||||
}
|
||||
if (replace_existing_context) {
|
||||
recover_context_ = context;
|
||||
}
|
||||
// WAL failures can race or be classified through different severity paths.
|
||||
// Preserve the broadest failed-file cutoff and highest sequence assigned to
|
||||
// an indeterminate write group independently of the winning Status.
|
||||
recover_context_.failed_wal_number = failed_wal_number;
|
||||
recover_context_.failed_wal_sequence = failed_wal_sequence;
|
||||
recover_context_.failed_wal_sequence_start = failed_wal_sequence_start;
|
||||
}
|
||||
|
||||
void ErrorHandler::EndAutoRecovery() {
|
||||
db_mutex_->AssertHeld();
|
||||
if (!end_recovery_) {
|
||||
|
||||
+39
-4
@@ -23,12 +23,33 @@ class DBImpl;
|
||||
struct DBRecoverContext {
|
||||
FlushReason flush_reason;
|
||||
bool flush_after_recovery;
|
||||
// A non-zero value is an inclusive upper bound on WAL file handles that
|
||||
// recovery must not reuse. Recovery must not declare success until every
|
||||
// column family has atomically advanced beyond it in the MANIFEST.
|
||||
uint64_t failed_wal_number;
|
||||
// Highest sequence assigned to the write group whose WAL outcome is
|
||||
// indeterminate. Reserving it prevents a later acknowledged write from
|
||||
// reusing a sequence that might exist in the abandoned WAL.
|
||||
uint64_t failed_wal_sequence;
|
||||
// First sequence in the same indeterminate write group. Generic errors keep
|
||||
// this evidence without reserving it, in case a retry later establishes that
|
||||
// the WAL handle is file-scoped and activates the stronger recovery path.
|
||||
uint64_t failed_wal_sequence_start;
|
||||
|
||||
DBRecoverContext()
|
||||
: flush_reason(FlushReason::kErrorRecovery),
|
||||
flush_after_recovery(false) {}
|
||||
flush_after_recovery(false),
|
||||
failed_wal_number(0),
|
||||
failed_wal_sequence(0),
|
||||
failed_wal_sequence_start(0) {}
|
||||
DBRecoverContext(FlushReason reason)
|
||||
: flush_reason(reason), flush_after_recovery(false) {}
|
||||
: flush_reason(reason),
|
||||
flush_after_recovery(false),
|
||||
failed_wal_number(0),
|
||||
failed_wal_sequence(0),
|
||||
failed_wal_sequence_start(0) {}
|
||||
|
||||
bool IsWALWriteErrorRecovery() const { return failed_wal_number != 0; }
|
||||
};
|
||||
|
||||
class ErrorHandler {
|
||||
@@ -59,6 +80,11 @@ class ErrorHandler {
|
||||
|
||||
void SetBGError(const Status& bg_err, BackgroundErrorReason reason,
|
||||
bool wal_related = false);
|
||||
void SetBGError(const Status& bg_err, BackgroundErrorReason reason,
|
||||
bool wal_related, DBRecoverContext context);
|
||||
|
||||
void SetBGErrorNoAutoRecovery(const Status& bg_err,
|
||||
BackgroundErrorReason reason);
|
||||
|
||||
Status GetBGError() const { return bg_error_; }
|
||||
|
||||
@@ -151,13 +177,22 @@ class ErrorHandler {
|
||||
// unsorted.
|
||||
autovector<uint64_t> files_to_quarantine_;
|
||||
|
||||
void HandleKnownErrors(const Status& bg_err, BackgroundErrorReason reason);
|
||||
void SetBGErrorImpl(const Status& bg_err, BackgroundErrorReason reason,
|
||||
bool wal_related, DBRecoverContext context,
|
||||
bool allow_auto_recovery);
|
||||
void HandleKnownErrors(const Status& bg_err, BackgroundErrorReason reason,
|
||||
const DBRecoverContext& context,
|
||||
bool allow_auto_recovery);
|
||||
// REQUIRES: db_mutex_ held. When replacing the general recovery context,
|
||||
// retain WAL facts accumulated from concurrent errors independently.
|
||||
void UpdateRecoveryContext(const DBRecoverContext& context,
|
||||
bool replace_existing_context);
|
||||
Status OverrideNoSpaceError(const Status& bg_error, bool* auto_recovery);
|
||||
void RecoverFromNoSpace();
|
||||
void StartRecoverFromRetryableBGIOError(const IOStatus& io_error);
|
||||
void RecoverFromRetryableBGIOError();
|
||||
// If recovery is in progress, record the first new error for the current
|
||||
// attempt. If this error is more severe, retain it and its recovery context.
|
||||
// attempt. A more severe error replaces the current background error.
|
||||
void CheckAndSetRecoveryAndBGError(const Status& bg_err,
|
||||
const DBRecoverContext& context);
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+3
-3
@@ -136,9 +136,9 @@ void EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
<< table_properties.property_collectors_names << "compression"
|
||||
<< table_properties.compression_name << "compression_options"
|
||||
<< table_properties.compression_options << "creation_time"
|
||||
<< table_properties.creation_time << "oldest_key_time"
|
||||
<< table_properties.newest_key_time << "newest_key_time"
|
||||
<< table_properties.oldest_key_time << "file_creation_time"
|
||||
<< table_properties.oldest_ancestor_time << "oldest_key_time"
|
||||
<< table_properties.oldest_key_time << "newest_key_time"
|
||||
<< table_properties.newest_key_time << "file_creation_time"
|
||||
<< table_properties.file_creation_time
|
||||
<< "slow_compression_estimated_data_size"
|
||||
<< table_properties.slow_compression_estimated_data_size
|
||||
|
||||
+1
-1
@@ -163,7 +163,7 @@ Status UpdateManifestForFilesState(
|
||||
lf->fd.GetFileSize(), lf->smallest, lf->largest,
|
||||
lf->fd.smallest_seqno, lf->fd.largest_seqno,
|
||||
lf->marked_for_compaction, temp, lf->oldest_blob_file_number,
|
||||
lf->oldest_ancester_time, lf->file_creation_time,
|
||||
lf->oldest_ancestor_time, lf->file_creation_time,
|
||||
lf->epoch_number, lf->file_checksum,
|
||||
lf->file_checksum_func_name, lf->unique_id,
|
||||
lf->compensated_range_deletion_size, lf->tail_size,
|
||||
|
||||
+253
-196
File diff suppressed because it is too large.
Load diff
@@ -114,11 +114,8 @@ Status ExternalSstFileIngestionJob::PrepareAtomicReplaceRangeTombstone(
|
||||
super_version->mutable_cf_options.default_write_temperature;
|
||||
tombstone.prefetch_lmax_index_and_filter_blocks =
|
||||
ingestion_options_.prefetch_lmax_index_and_filter_blocks;
|
||||
const PreparedFileInfo* prepared_file_info =
|
||||
ingestion_options_.write_global_seqno
|
||||
? nullptr
|
||||
: external_file_info.prepared_file_info.get();
|
||||
status = GetIngestedFileInfo(file_path, file_number, prepared_file_info,
|
||||
status = GetIngestedFileInfo(file_path, file_number,
|
||||
external_file_info.prepared_file_info.get(),
|
||||
&tombstone, super_version);
|
||||
if (!status.ok()) {
|
||||
fs_->DeleteFile(file_path, IOOptions(), nullptr).PermitUncheckedError();
|
||||
@@ -392,28 +389,23 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
}
|
||||
TEST_SYNC_POINT("ExternalSstFileIngestionJob::AfterSyncDir");
|
||||
|
||||
// Generate and check the sst file checksum. Note that, if
|
||||
// IngestExternalFileOptions::write_global_seqno is true, we will not update
|
||||
// the checksum information in the files_to_ingests_ here, since the file is
|
||||
// updated with the new global_seqno. After global_seqno is updated, DB will
|
||||
// generate the new checksum and store it in the Manifest. In all other cases
|
||||
// if ingestion_options_.write_global_seqno == true and
|
||||
// verify_file_checksum is false, we only check the checksum function name.
|
||||
// Generate and check the SST file checksum.
|
||||
if (status.ok() && db_options_.file_checksum_gen_factory != nullptr) {
|
||||
bool need_generate_file_checksum;
|
||||
if (ingestion_options_.verify_file_checksum == false &&
|
||||
files_checksums.size() == files_to_ingest_.size() &&
|
||||
files_checksum_func_names.size() == files_to_ingest_.size()) {
|
||||
// Only when verify_file_checksum == false and the checksum for ingested
|
||||
// files are provided, DB will use the provided checksum and does not
|
||||
// generate the checksum for ingested files.
|
||||
need_generate_file_checksum_ = false;
|
||||
need_generate_file_checksum = false;
|
||||
} else {
|
||||
need_generate_file_checksum_ = true;
|
||||
need_generate_file_checksum = true;
|
||||
}
|
||||
std::vector<std::string> generated_checksums;
|
||||
std::vector<std::string> generated_checksum_func_names;
|
||||
// Step 1: generate the checksum for ingested sst file.
|
||||
if (need_generate_file_checksum_) {
|
||||
if (need_generate_file_checksum) {
|
||||
for (size_t i = 0; i < files_to_ingest_.size(); i++) {
|
||||
std::string generated_checksum;
|
||||
std::string generated_checksum_func_name;
|
||||
@@ -453,11 +445,9 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
status.ToString().c_str());
|
||||
break;
|
||||
}
|
||||
if (ingestion_options_.write_global_seqno == false) {
|
||||
files_to_ingest_[i].file_checksum = generated_checksum;
|
||||
files_to_ingest_[i].file_checksum_func_name =
|
||||
generated_checksum_func_name;
|
||||
}
|
||||
files_to_ingest_[i].file_checksum = generated_checksum;
|
||||
files_to_ingest_[i].file_checksum_func_name =
|
||||
generated_checksum_func_name;
|
||||
generated_checksums.push_back(generated_checksum);
|
||||
generated_checksum_func_names.push_back(generated_checksum_func_name);
|
||||
}
|
||||
@@ -864,21 +854,13 @@ Status ExternalSstFileIngestionJob::AssignLevelsForOneBatch(
|
||||
}
|
||||
max_assigned_seqno_ = std::max(max_assigned_seqno_, assigned_seqno);
|
||||
|
||||
status = GenerateChecksumForIngestedFile(file);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to generate checksum for ingested file: %s",
|
||||
status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
|
||||
// We use the import time as the ancester time. This is the time the data
|
||||
// We use the import time as the ancestor time. This is the time the data
|
||||
// is written to the database.
|
||||
int64_t temp_current_time = 0;
|
||||
uint64_t current_time = kUnknownFileCreationTime;
|
||||
uint64_t oldest_ancester_time = kUnknownOldestAncesterTime;
|
||||
uint64_t oldest_ancestor_time = kUnknownOldestAncestorTime;
|
||||
if (clock_->GetCurrentTime(&temp_current_time).ok()) {
|
||||
current_time = oldest_ancester_time =
|
||||
current_time = oldest_ancestor_time =
|
||||
static_cast<uint64_t>(temp_current_time);
|
||||
}
|
||||
uint64_t tail_size = FileMetaData::CalculateTailSize(
|
||||
@@ -902,7 +884,7 @@ Status ExternalSstFileIngestionJob::AssignLevelsForOneBatch(
|
||||
file->fd.GetNumber(), file->fd.GetPathId(), file->fd.GetFileSize(),
|
||||
file->smallest_internal_key, file->largest_internal_key, smallest_seqno,
|
||||
largest_seqno, false, file->file_temperature, kInvalidBlobFileNumber,
|
||||
oldest_ancester_time, current_time,
|
||||
oldest_ancestor_time, current_time,
|
||||
ingestion_options_.ingest_behind
|
||||
? kReservedEpochNumberForFileIngestedBehind
|
||||
: cfd_->NewEpochNumber(), // orders files ingested to L0
|
||||
@@ -1229,17 +1211,6 @@ Status ExternalSstFileIngestionJob::SanityCheckTableProperties(
|
||||
|
||||
// Set the global sequence number
|
||||
file_to_ingest->original_seqno = DecodeFixed64(seqno_iter->second.c_str());
|
||||
file_to_ingest->global_seqno_offset =
|
||||
static_cast<size_t>(props.external_sst_file_global_seqno_offset);
|
||||
// The on-disk offset is only needed if we will write the global seqno back
|
||||
// into the file (write_global_seqno). The metadata fast-path does not open
|
||||
// the file, and its in-memory table properties do not carry the offset
|
||||
// (it is only computed while reading the file back); that is fine as long
|
||||
// as write_global_seqno is not requested.
|
||||
if (ingestion_options_.write_global_seqno &&
|
||||
file_to_ingest->global_seqno_offset == 0) {
|
||||
return Status::Corruption("Was not able to find file global seqno field");
|
||||
}
|
||||
} else if (file_to_ingest->version == 1) {
|
||||
// SST file V1 should not have global seqno field
|
||||
assert(seqno_iter == uprops.end());
|
||||
@@ -1253,7 +1224,6 @@ Status ExternalSstFileIngestionJob::SanityCheckTableProperties(
|
||||
// allow_db_generated_files is true
|
||||
assert(seqno_iter == uprops.end());
|
||||
file_to_ingest->original_seqno = 0;
|
||||
file_to_ingest->global_seqno_offset = 0;
|
||||
} else {
|
||||
return Status::InvalidArgument("External file version " +
|
||||
std::to_string(file_to_ingest->version) +
|
||||
@@ -1819,64 +1789,13 @@ Status ExternalSstFileIngestionJob::AssignGlobalSeqnoForIngestedFile(
|
||||
assert(seqno == 0);
|
||||
assert(file_to_ingest->original_seqno == 0);
|
||||
}
|
||||
if (file_to_ingest->original_seqno == seqno) {
|
||||
// This file already has the correct global seqno.
|
||||
return Status::OK();
|
||||
} else if (!ingestion_options_.allow_global_seqno) {
|
||||
return Status::InvalidArgument("Global seqno is required, but disabled");
|
||||
} else if (ingestion_options_.write_global_seqno &&
|
||||
file_to_ingest->global_seqno_offset == 0) {
|
||||
return Status::InvalidArgument(
|
||||
"Trying to set global seqno for a file that don't have a global seqno "
|
||||
"field");
|
||||
}
|
||||
|
||||
if (ingestion_options_.write_global_seqno) {
|
||||
// Determine if we can write global_seqno to a given offset of file.
|
||||
// If the file system does not support random write, then we should not.
|
||||
// Otherwise we should.
|
||||
std::unique_ptr<FSRandomRWFile> rwfile;
|
||||
Status status = fs_->NewRandomRWFile(file_to_ingest->internal_file_path,
|
||||
env_options_, &rwfile, nullptr);
|
||||
TEST_SYNC_POINT_CALLBACK("ExternalSstFileIngestionJob::NewRandomRWFile",
|
||||
&status);
|
||||
if (status.ok()) {
|
||||
FSRandomRWFilePtr fsptr(std::move(rwfile), io_tracer_,
|
||||
file_to_ingest->internal_file_path);
|
||||
std::string seqno_val;
|
||||
PutFixed64(&seqno_val, seqno);
|
||||
status = fsptr->Write(file_to_ingest->global_seqno_offset, seqno_val,
|
||||
IOOptions(), nullptr);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to write global seqno to %s: %s",
|
||||
file_to_ingest->internal_file_path.c_str(),
|
||||
status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
TEST_SYNC_POINT("ExternalSstFileIngestionJob::BeforeSyncGlobalSeqno");
|
||||
status = SyncIngestedFile(fsptr.get());
|
||||
TEST_SYNC_POINT("ExternalSstFileIngestionJob::AfterSyncGlobalSeqno");
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to sync ingested file %s after writing global "
|
||||
"sequence number: %s",
|
||||
file_to_ingest->internal_file_path.c_str(),
|
||||
status.ToString().c_str());
|
||||
}
|
||||
}
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
} else if (!status.IsNotSupported()) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"Failed to open ingested file %s for random read/write: %s",
|
||||
file_to_ingest->internal_file_path.c_str(),
|
||||
status.ToString().c_str());
|
||||
return status;
|
||||
if (file_to_ingest->original_seqno != seqno) {
|
||||
if (!ingestion_options_.allow_global_seqno) {
|
||||
return Status::InvalidArgument("Global seqno is required, but disabled");
|
||||
}
|
||||
if (file_to_ingest->original_seqno != 0) {
|
||||
return Status::InvalidArgument(
|
||||
"External file global seqno does not match assigned seqno");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1884,42 +1803,6 @@ Status ExternalSstFileIngestionJob::AssignGlobalSeqnoForIngestedFile(
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
IOStatus ExternalSstFileIngestionJob::GenerateChecksumForIngestedFile(
|
||||
IngestedFileInfo* file_to_ingest) {
|
||||
if (db_options_.file_checksum_gen_factory == nullptr ||
|
||||
need_generate_file_checksum_ == false ||
|
||||
ingestion_options_.write_global_seqno == false) {
|
||||
// If file_checksum_gen_factory is not set, we are not able to generate
|
||||
// the checksum. if write_global_seqno is false, it means we will use
|
||||
// file checksum generated during Prepare(). This step will be skipped.
|
||||
return IOStatus::OK();
|
||||
}
|
||||
std::string file_checksum;
|
||||
std::string file_checksum_func_name;
|
||||
std::string requested_checksum_func_name;
|
||||
// TODO: rate limit file reads for checksum calculation during file ingestion.
|
||||
// TODO: plumb Env::IOActivity
|
||||
ReadOptions ro;
|
||||
FileOptions gen_fopts;
|
||||
gen_fopts.file_checksum_func_name = kNoFileChecksumFuncName;
|
||||
IOStatus io_s = GenerateOneFileChecksum(
|
||||
fs_.get(), file_to_ingest->internal_file_path,
|
||||
db_options_.file_checksum_gen_factory.get(), requested_checksum_func_name,
|
||||
&file_checksum, &file_checksum_func_name,
|
||||
ingestion_options_.verify_checksums_readahead_size,
|
||||
db_options_.allow_mmap_reads, io_tracer_, db_options_.rate_limiter.get(),
|
||||
ro, db_options_.stats, db_options_.clock, gen_fopts);
|
||||
if (!io_s.ok()) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log, "Failed to generate checksum for %s: %s",
|
||||
file_to_ingest->internal_file_path.c_str(), io_s.ToString().c_str());
|
||||
return io_s;
|
||||
}
|
||||
file_to_ingest->file_checksum = std::move(file_checksum);
|
||||
file_to_ingest->file_checksum_func_name = std::move(file_checksum_func_name);
|
||||
return IOStatus::OK();
|
||||
}
|
||||
|
||||
bool ExternalSstFileIngestionJob::IngestedFileFitInLevel(
|
||||
const IngestedFileInfo* file_to_ingest, int level) {
|
||||
if (level == 0) {
|
||||
@@ -1942,16 +1825,6 @@ bool ExternalSstFileIngestionJob::IngestedFileFitInLevel(
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename TWritableFile>
|
||||
Status ExternalSstFileIngestionJob::SyncIngestedFile(TWritableFile* file) {
|
||||
assert(file != nullptr);
|
||||
if (db_options_.use_fsync) {
|
||||
return file->Fsync(IOOptions(), nullptr);
|
||||
} else {
|
||||
return file->Sync(IOOptions(), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
Status ExternalSstFileIngestionJob::GetSeqnoBoundaryForFile(
|
||||
TableReader* table_reader, SuperVersion* sv,
|
||||
IngestedFileInfo* file_to_ingest, bool allow_data_in_errors) {
|
||||
|
||||
@@ -133,9 +133,6 @@ struct IngestedFileInfo : public KeyRangeInfo {
|
||||
std::string limit_ukey;
|
||||
// Sequence number for keys in external file
|
||||
SequenceNumber original_seqno;
|
||||
// Offset of the global sequence number field in the file, will
|
||||
// be zero if version is 1 (global seqno is not supported)
|
||||
size_t global_seqno_offset;
|
||||
// External file size
|
||||
uint64_t file_size;
|
||||
// total number of keys in external file
|
||||
@@ -430,21 +427,15 @@ class ExternalSstFileIngestionJob {
|
||||
// REQUIRES: Mutex held
|
||||
Status CheckLevelForIngestedBehindFile(IngestedFileInfo* file_to_ingest);
|
||||
|
||||
// Set the file global sequence number to `seqno`
|
||||
// Set the file's assigned global sequence number to `seqno`.
|
||||
Status AssignGlobalSeqnoForIngestedFile(IngestedFileInfo* file_to_ingest,
|
||||
SequenceNumber seqno);
|
||||
// Generate the file checksum and store in the IngestedFileInfo
|
||||
IOStatus GenerateChecksumForIngestedFile(IngestedFileInfo* file_to_ingest);
|
||||
|
||||
// Check if `file_to_ingest` can fit in level `level`
|
||||
// REQUIRES: Mutex held
|
||||
bool IngestedFileFitInLevel(const IngestedFileInfo* file_to_ingest,
|
||||
int level);
|
||||
|
||||
// Helper method to sync given file.
|
||||
template <typename TWritableFile>
|
||||
Status SyncIngestedFile(TWritableFile* file);
|
||||
|
||||
// Helper function to obtain the smallest and largest sequence number from a
|
||||
// file. When OK is returned, file_to_ingest->smallest_seqno and
|
||||
// file_to_ingest->largest_seqno will be updated.
|
||||
@@ -484,9 +475,6 @@ class ExternalSstFileIngestionJob {
|
||||
// Set in ExternalSstFileIngestionJob::Prepare(), if true all files are
|
||||
// ingested in L0
|
||||
bool files_overlap_{false};
|
||||
// Set in ExternalSstFileIngestionJob::Prepare(), if true and DB
|
||||
// file_checksum_gen_factory is set, DB will generate checksum each file.
|
||||
bool need_generate_file_checksum_{true};
|
||||
std::shared_ptr<IOTracer> io_tracer_;
|
||||
|
||||
// Flag indicating whether the column family is flushed after `Prepare` and
|
||||
|
||||
+164
-160
@@ -96,14 +96,14 @@ class ExternSSTFileLinkFailFallbackTest
|
||||
|
||||
class ExternalSSTFileTest
|
||||
: public ExternalSSTFileTestBase,
|
||||
public ::testing::WithParamInterface<std::tuple<bool, bool, bool>> {
|
||||
public ::testing::WithParamInterface<std::tuple<bool, bool>> {
|
||||
public:
|
||||
ExternalSSTFileTest() = default;
|
||||
|
||||
void SetUp() override {
|
||||
const auto* info = ::testing::UnitTest::GetInstance()->current_test_info();
|
||||
if (info != nullptr && info->value_param() != nullptr) {
|
||||
two_phase_ingest_ = std::get<2>(GetParam());
|
||||
two_phase_ingest_ = std::get<1>(GetParam());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ class ExternalSSTFileTest
|
||||
Status GenerateAndAddExternalFile(
|
||||
const Options options,
|
||||
std::vector<std::pair<std::string, std::string>> data, int file_id = -1,
|
||||
bool allow_global_seqno = false, bool write_global_seqno = false,
|
||||
bool allow_global_seqno = false,
|
||||
bool verify_checksums_before_ingest = true, bool ingest_behind = false,
|
||||
bool sort_data = false,
|
||||
std::map<std::string, std::string>* true_data = nullptr,
|
||||
@@ -210,7 +210,6 @@ class ExternalSSTFileTest
|
||||
if (s.ok()) {
|
||||
IngestExternalFileOptions ifo;
|
||||
ifo.allow_global_seqno = allow_global_seqno;
|
||||
ifo.write_global_seqno = allow_global_seqno ? write_global_seqno : false;
|
||||
ifo.verify_checksums_before_ingest = verify_checksums_before_ingest;
|
||||
ifo.ingest_behind = ingest_behind;
|
||||
ifo.fill_cache = fill_cache;
|
||||
@@ -288,7 +287,6 @@ class ExternalSSTFileTest
|
||||
Status GenerateAndAddExternalFile(
|
||||
const Options options, std::vector<std::pair<int, std::string>> data,
|
||||
int file_id = -1, bool allow_global_seqno = false,
|
||||
bool write_global_seqno = false,
|
||||
bool verify_checksums_before_ingest = true, bool ingest_behind = false,
|
||||
bool sort_data = false,
|
||||
std::map<std::string, std::string>* true_data = nullptr,
|
||||
@@ -298,14 +296,14 @@ class ExternalSSTFileTest
|
||||
file_data.emplace_back(Key(entry.first), entry.second);
|
||||
}
|
||||
return GenerateAndAddExternalFile(options, file_data, file_id,
|
||||
allow_global_seqno, write_global_seqno,
|
||||
allow_global_seqno,
|
||||
verify_checksums_before_ingest,
|
||||
ingest_behind, sort_data, true_data, cfh);
|
||||
}
|
||||
|
||||
Status GenerateAndAddExternalFile(
|
||||
const Options options, std::vector<int> keys, int file_id = -1,
|
||||
bool allow_global_seqno = false, bool write_global_seqno = false,
|
||||
bool allow_global_seqno = false,
|
||||
bool verify_checksums_before_ingest = true, bool ingest_behind = false,
|
||||
bool sort_data = false,
|
||||
std::map<std::string, std::string>* true_data = nullptr,
|
||||
@@ -315,21 +313,19 @@ class ExternalSSTFileTest
|
||||
file_data.emplace_back(Key(k), Key(k) + std::to_string(file_id));
|
||||
}
|
||||
return GenerateAndAddExternalFile(
|
||||
options, file_data, file_id, allow_global_seqno, write_global_seqno,
|
||||
options, file_data, file_id, allow_global_seqno,
|
||||
verify_checksums_before_ingest, ingest_behind, sort_data, true_data,
|
||||
cfh, fill_cache);
|
||||
}
|
||||
|
||||
Status DeprecatedAddFile(const std::vector<std::string>& files,
|
||||
bool move_files = false,
|
||||
bool skip_snapshot_check = false,
|
||||
bool skip_write_global_seqno = false) {
|
||||
bool skip_snapshot_check = false) {
|
||||
IngestExternalFileOptions opts;
|
||||
opts.move_files = move_files;
|
||||
opts.snapshot_consistency = !skip_snapshot_check;
|
||||
opts.allow_global_seqno = false;
|
||||
opts.allow_blocking_flush = false;
|
||||
opts.write_global_seqno = !skip_write_global_seqno;
|
||||
return db_->IngestExternalFile(files, opts);
|
||||
}
|
||||
|
||||
@@ -374,8 +370,8 @@ TEST_F(ExternalSSTFileTest, IngestionTimingHistogram) {
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {{"k1", "v1"}, {"k2", "v2"}, {"k3", "v3"}}, /*file_id=*/-1,
|
||||
/*allow_global_seqno=*/true, /*write_global_seqno=*/false,
|
||||
/*verify_checksums_before_ingest=*/true, /*ingest_behind=*/false,
|
||||
/*allow_global_seqno=*/true, /*verify_checksums_before_ingest=*/true,
|
||||
/*ingest_behind=*/false,
|
||||
/*sort_data=*/true));
|
||||
|
||||
auto hist = [&](Histograms h) {
|
||||
@@ -395,8 +391,8 @@ TEST_F(ExternalSSTFileTest, IngestionTimingHistogram) {
|
||||
// A second call adds exactly one more sample to each histogram.
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {{"k4", "v4"}, {"k5", "v5"}}, /*file_id=*/-1,
|
||||
/*allow_global_seqno=*/true, /*write_global_seqno=*/false,
|
||||
/*verify_checksums_before_ingest=*/true, /*ingest_behind=*/false,
|
||||
/*allow_global_seqno=*/true, /*verify_checksums_before_ingest=*/true,
|
||||
/*ingest_behind=*/false,
|
||||
/*sort_data=*/true));
|
||||
ASSERT_EQ(2, hist(INGEST_EXTERNAL_FILE_PREPARE_TIME).count);
|
||||
ASSERT_EQ(2, hist(INGEST_EXTERNAL_FILE_RUN_TIME).count);
|
||||
@@ -756,8 +752,8 @@ TEST_F(ExternalSSTFileTest, IngestWithFileInfo) {
|
||||
auto ingest_fi = [&](std::vector<std::pair<std::string, std::string>> data) {
|
||||
return GenerateAndAddExternalFile(
|
||||
options, std::move(data), /*file_id=*/-1, /*allow_global_seqno=*/true,
|
||||
/*write_global_seqno=*/false, /*verify_checksums_before_ingest=*/true,
|
||||
/*ingest_behind=*/false, /*sort_data=*/true, &true_data,
|
||||
/*verify_checksums_before_ingest=*/true, /*ingest_behind=*/false,
|
||||
/*sort_data=*/true, &true_data,
|
||||
/*cfh=*/nullptr, /*fill_cache=*/false, /*ingest_with_file_info=*/true);
|
||||
};
|
||||
|
||||
@@ -777,8 +773,8 @@ TEST_F(ExternalSSTFileTest, IngestWithFileInfo) {
|
||||
// Control: ingesting the same way but WITHOUT file_infos does enter the scan.
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {{Key(8), "c8"}}, /*file_id=*/-1, /*allow_global_seqno=*/true,
|
||||
/*write_global_seqno=*/false, /*verify_checksums_before_ingest=*/true,
|
||||
/*ingest_behind=*/false, /*sort_data=*/true, &true_data));
|
||||
/*verify_checksums_before_ingest=*/true, /*ingest_behind=*/false,
|
||||
/*sort_data=*/true, &true_data));
|
||||
ASSERT_GT(read_path_count.load(), 0);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
@@ -845,25 +841,57 @@ TEST_F(ExternalSSTFileTest, IngestWithFileInfoVerifiesChecksum) {
|
||||
ASSERT_TRUE(db_->IngestExternalFiles({arg}).IsCorruption());
|
||||
}
|
||||
|
||||
// write_global_seqno is incompatible with file_infos: the file is not opened,
|
||||
// so the seqno cannot be written back into it.
|
||||
TEST_F(ExternalSSTFileTest, IngestWithFileInfoRejectsWriteGlobalSeqno) {
|
||||
TEST_F(ExternalSSTFileTest, IngestWithSerializedFileInfo) {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
const std::string f = sst_files_dir_ + "wgs.sst";
|
||||
SstFileWriter w(EnvOptions(), options);
|
||||
ASSERT_OK(w.Open(f));
|
||||
ASSERT_OK(w.Put(Key(1), "v1"));
|
||||
const std::string file_path = sst_files_dir_ + "serialized_file_info.sst";
|
||||
SstFileWriter writer(EnvOptions(), options);
|
||||
ASSERT_OK(writer.Open(file_path));
|
||||
ASSERT_OK(writer.Put(Key(10), "v10"));
|
||||
ExternalSstFileInfo file_info;
|
||||
ASSERT_OK(w.Finish(&file_info));
|
||||
ASSERT_OK(writer.Finish(&file_info));
|
||||
|
||||
std::string serialized;
|
||||
ASSERT_OK(
|
||||
SerializePreparedFileInfo(*file_info.prepared_file_info, &serialized));
|
||||
std::shared_ptr<const PreparedFileInfo> deserialized;
|
||||
ASSERT_OK(DeserializePreparedFileInfo(serialized, &deserialized));
|
||||
|
||||
IngestExternalFileArg arg;
|
||||
arg.column_family = db_->DefaultColumnFamily();
|
||||
arg.external_files = {f};
|
||||
arg.file_infos = {file_info.prepared_file_info.get()};
|
||||
arg.options.write_global_seqno = true;
|
||||
ASSERT_TRUE(db_->IngestExternalFiles({arg}).IsInvalidArgument());
|
||||
arg.external_files = {file_path};
|
||||
arg.file_infos = {deserialized.get()};
|
||||
ASSERT_OK(db_->IngestExternalFiles({arg}));
|
||||
ASSERT_EQ(Get(Key(10)), "v10");
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileTest, PreparedFileInfoRejectsInvalidSerialization) {
|
||||
std::shared_ptr<const PreparedFileInfo> prepared_file_info;
|
||||
ASSERT_TRUE(DeserializePreparedFileInfo("invalid", &prepared_file_info)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(prepared_file_info, nullptr);
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileTest, PreparedFileInfoDetectsSerializationCorruption) {
|
||||
Options options = CurrentOptions();
|
||||
const std::string file_path = sst_files_dir_ + "file_info_corruption.sst";
|
||||
SstFileWriter writer(EnvOptions(), options);
|
||||
ASSERT_OK(writer.Open(file_path));
|
||||
ASSERT_OK(writer.Put(Key(1), "v1"));
|
||||
ExternalSstFileInfo file_info;
|
||||
ASSERT_OK(writer.Finish(&file_info));
|
||||
|
||||
std::string serialized;
|
||||
ASSERT_OK(
|
||||
SerializePreparedFileInfo(*file_info.prepared_file_info, &serialized));
|
||||
ASSERT_FALSE(serialized.empty());
|
||||
serialized.back() ^= 1;
|
||||
|
||||
std::shared_ptr<const PreparedFileInfo> deserialized;
|
||||
ASSERT_TRUE(
|
||||
DeserializePreparedFileInfo(serialized, &deserialized).IsCorruption());
|
||||
ASSERT_EQ(deserialized, nullptr);
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileTest, GetPreparedFileInfoForExternalSstIngestion) {
|
||||
@@ -906,10 +934,15 @@ TEST_F(ExternalSSTFileTest, GetPreparedFileInfoForExternalSstIngestion) {
|
||||
ASSERT_EQ(prepared_file_info->table_properties.key_largest_seqno,
|
||||
live_meta[0].largest_seqno);
|
||||
|
||||
std::string serialized;
|
||||
ASSERT_OK(SerializePreparedFileInfo(*prepared_file_info, &serialized));
|
||||
std::shared_ptr<const PreparedFileInfo> deserialized;
|
||||
ASSERT_OK(DeserializePreparedFileInfo(serialized, &deserialized));
|
||||
|
||||
IngestExternalFileArg arg;
|
||||
arg.column_family = db_->DefaultColumnFamily();
|
||||
arg.external_files = {file_path};
|
||||
arg.file_infos = {prepared_file_info.get()};
|
||||
arg.file_infos = {deserialized.get()};
|
||||
arg.options.allow_db_generated_files = true;
|
||||
arg.options.snapshot_consistency = false;
|
||||
|
||||
@@ -1021,8 +1054,8 @@ TEST_F(ExternalSSTFileTest, NoBlockCache) {
|
||||
size_t usage_before_ingestion = cache->GetUsage();
|
||||
std::map<std::string, std::string> true_data;
|
||||
// Ingest with fill_cache = true
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 2}, -1, false, false, true,
|
||||
false, false, &true_data, nullptr,
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 2}, -1, false, true, false,
|
||||
false, &true_data, nullptr,
|
||||
/*fill_cache=*/true));
|
||||
ASSERT_EQ(FilesPerLevel(), "0,0,0,0,0,0,1");
|
||||
EXPECT_GT(cache->GetUsage(), usage_before_ingestion);
|
||||
@@ -1036,8 +1069,8 @@ TEST_F(ExternalSSTFileTest, NoBlockCache) {
|
||||
|
||||
usage_before_ingestion = cache->GetUsage();
|
||||
// Ingest with fill_cache = false
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {3, 4}, -1, false, false, true,
|
||||
false, false, &true_data, nullptr,
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {3, 4}, -1, false, true, false,
|
||||
false, &true_data, nullptr,
|
||||
/*fill_cache=*/false));
|
||||
EXPECT_EQ(usage_before_ingestion, cache->GetUsage());
|
||||
|
||||
@@ -2073,13 +2106,13 @@ TEST_P(ExternalSSTFileTest, PickedLevel) {
|
||||
std::map<std::string, std::string> true_data;
|
||||
|
||||
// File 0 will go to last level (L3)
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 10}, -1, false, false, true,
|
||||
false, false, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 10}, -1, false, true, false,
|
||||
false, &true_data));
|
||||
EXPECT_EQ(FilesPerLevel(), "0,0,0,1");
|
||||
|
||||
// File 1 will go to level L2 (since it overlap with file 0 in L3)
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {2, 9}, -1, false, false, true,
|
||||
false, false, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {2, 9}, -1, false, true, false,
|
||||
false, &true_data));
|
||||
EXPECT_EQ(FilesPerLevel(), "0,0,1,1");
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({
|
||||
@@ -2109,13 +2142,13 @@ TEST_P(ExternalSSTFileTest, PickedLevel) {
|
||||
// This file overlaps with file 0 (L3), file 1 (L2) and the
|
||||
// output of compaction going to L1
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {4, 7}, -1,
|
||||
true /* allow_global_seqno */, false,
|
||||
true, false, false, &true_data));
|
||||
true /* allow_global_seqno */, true,
|
||||
false, false, &true_data));
|
||||
EXPECT_EQ(FilesPerLevel(), "5,0,1,1");
|
||||
|
||||
// This file does not overlap with any file or with the running compaction
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {9000, 9001}, -1, false, false,
|
||||
false, false, false, &true_data));
|
||||
false, false, &true_data));
|
||||
EXPECT_EQ(FilesPerLevel(), "5,0,1,2");
|
||||
|
||||
// Hold compaction from finishing
|
||||
@@ -2265,13 +2298,13 @@ TEST_F(ExternalSSTFileTest, PickedLevelDynamic) {
|
||||
// This file overlaps with the output of the compaction (going to L3)
|
||||
// so the file will be added to L0 since L3 is the base level
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {31, 32, 33, 34}, -1,
|
||||
true /* allow_global_seqno */, false,
|
||||
true, false, false, &true_data));
|
||||
true /* allow_global_seqno */, true,
|
||||
false, false, &true_data));
|
||||
EXPECT_EQ(FilesPerLevel(), "5");
|
||||
|
||||
// This file does not overlap with the current running compactiong
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {9000, 9001}, -1, false, false,
|
||||
true, false, false, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {9000, 9001}, -1, false, true,
|
||||
false, false, &true_data));
|
||||
EXPECT_EQ(FilesPerLevel(), "5,0,0,1");
|
||||
|
||||
// Hold compaction from finishing
|
||||
@@ -2285,26 +2318,26 @@ TEST_F(ExternalSSTFileTest, PickedLevelDynamic) {
|
||||
options.disable_auto_compactions = true;
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 15, 19}, -1, false, false,
|
||||
true, false, false, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 15, 19}, -1, false, true,
|
||||
false, false, &true_data));
|
||||
ASSERT_EQ(FilesPerLevel(), "1,0,0,3");
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1000, 1001, 1002}, -1, false,
|
||||
false, true, false, false, &true_data));
|
||||
true, false, false, &true_data));
|
||||
ASSERT_EQ(FilesPerLevel(), "1,0,0,4");
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {500, 600, 700}, -1, false,
|
||||
false, true, false, false, &true_data));
|
||||
true, false, false, &true_data));
|
||||
ASSERT_EQ(FilesPerLevel(), "1,0,0,5");
|
||||
|
||||
// File 5 overlaps with file 2 (L3 / base level)
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {2, 10}, -1, false, false, true,
|
||||
false, false, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {2, 10}, -1, false, true, false,
|
||||
false, &true_data));
|
||||
ASSERT_EQ(FilesPerLevel(), "2,0,0,5");
|
||||
|
||||
// File 6 overlaps with file 2 (L3 / base level) and file 5 (L0)
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {3, 9}, -1, false, false, true,
|
||||
false, false, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {3, 9}, -1, false, true, false,
|
||||
false, &true_data));
|
||||
ASSERT_EQ(FilesPerLevel(), "3,0,0,5");
|
||||
|
||||
// Verify data in files
|
||||
@@ -2323,7 +2356,7 @@ TEST_F(ExternalSSTFileTest, PickedLevelDynamic) {
|
||||
|
||||
// File 7 overlaps with file 4 (L3)
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {650, 651, 652}, -1, false,
|
||||
false, true, false, false, &true_data));
|
||||
true, false, false, &true_data));
|
||||
ASSERT_EQ(FilesPerLevel(), "5,0,0,5");
|
||||
|
||||
VerifyDBFromMap(true_data, &kcnt, false);
|
||||
@@ -2741,8 +2774,7 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoRandomized) {
|
||||
options.level0_slowdown_writes_trigger = 256;
|
||||
options.level0_stop_writes_trigger = 256;
|
||||
|
||||
bool write_global_seqno = std::get<0>(GetParam());
|
||||
bool verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
bool verify_checksums_before_ingest = std::get<0>(GetParam());
|
||||
for (int iter = 0; iter < 2; iter++) {
|
||||
bool write_to_memtable = (iter == 0);
|
||||
DestroyAndReopen(options);
|
||||
@@ -2765,9 +2797,9 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoRandomized) {
|
||||
true_data[entry.first] = entry.second;
|
||||
}
|
||||
} else {
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, random_data, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, true, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, random_data, -1, true,
|
||||
verify_checksums_before_ingest,
|
||||
false, true, &true_data));
|
||||
}
|
||||
}
|
||||
size_t kcnt = 0;
|
||||
@@ -2797,11 +2829,10 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedLevel) {
|
||||
for (int i = 0; i <= 20; i++) {
|
||||
file_data.emplace_back(Key(i), "L4");
|
||||
}
|
||||
bool write_global_seqno = std::get<0>(GetParam());
|
||||
bool verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, file_data, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, false, &true_data));
|
||||
bool verify_checksums_before_ingest = std::get<0>(GetParam());
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, file_data, -1, true,
|
||||
verify_checksums_before_ingest, false,
|
||||
false, &true_data));
|
||||
|
||||
// This file don't overlap with anything in the DB, will go to L4
|
||||
ASSERT_EQ("0,0,0,0,1", FilesPerLevel());
|
||||
@@ -2811,9 +2842,9 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedLevel) {
|
||||
for (int i = 80; i <= 130; i++) {
|
||||
file_data.emplace_back(Key(i), "L0");
|
||||
}
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, file_data, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, false, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, file_data, -1, true,
|
||||
verify_checksums_before_ingest, false,
|
||||
false, &true_data));
|
||||
|
||||
// This file overlap with the memtable, so it will flush it and add
|
||||
// it self to L0
|
||||
@@ -2824,9 +2855,9 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedLevel) {
|
||||
for (int i = 30; i <= 50; i++) {
|
||||
file_data.emplace_back(Key(i), "L4");
|
||||
}
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, file_data, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, false, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, file_data, -1, true,
|
||||
verify_checksums_before_ingest, false,
|
||||
false, &true_data));
|
||||
|
||||
// This file don't overlap with anything in the DB and fit in L4 as well
|
||||
ASSERT_EQ("2,0,0,0,2", FilesPerLevel());
|
||||
@@ -2836,9 +2867,9 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedLevel) {
|
||||
for (int i = 10; i <= 40; i++) {
|
||||
file_data.emplace_back(Key(i), "L3");
|
||||
}
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, file_data, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, false, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, file_data, -1, true,
|
||||
verify_checksums_before_ingest, false,
|
||||
false, &true_data));
|
||||
|
||||
// This file overlap with files in L4, we will ingest it in L3
|
||||
ASSERT_EQ("2,0,0,1,2", FilesPerLevel());
|
||||
@@ -2848,8 +2879,7 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedLevel) {
|
||||
}
|
||||
|
||||
TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedUniversal) {
|
||||
bool write_global_seqno = std::get<0>(GetParam());
|
||||
bool verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
bool verify_checksums_before_ingest = std::get<0>(GetParam());
|
||||
Options options = CurrentOptions();
|
||||
options.num_levels = 5;
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
@@ -2883,9 +2913,9 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedUniversal) {
|
||||
file_data.emplace_back(Key(i), "L4");
|
||||
}
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, file_data, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, false, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, file_data, -1, true,
|
||||
verify_checksums_before_ingest, false,
|
||||
false, &true_data));
|
||||
|
||||
// This file don't overlap with anything in the DB, will go to L4
|
||||
ASSERT_EQ("0,0,0,0,2", FilesPerLevel());
|
||||
@@ -2895,9 +2925,9 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedUniversal) {
|
||||
for (int i = 80; i <= 130; i++) {
|
||||
file_data.emplace_back(Key(i), "L0");
|
||||
}
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, file_data, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, false, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, file_data, -1, true,
|
||||
verify_checksums_before_ingest, false,
|
||||
false, &true_data));
|
||||
|
||||
// This file overlap with the memtable, so it will flush it and add
|
||||
// it self to L0
|
||||
@@ -2908,9 +2938,9 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedUniversal) {
|
||||
for (int i = 30; i <= 50; i++) {
|
||||
file_data.emplace_back(Key(i), "L4");
|
||||
}
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, file_data, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, false, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, file_data, -1, true,
|
||||
verify_checksums_before_ingest, false,
|
||||
false, &true_data));
|
||||
|
||||
// This file don't overlap with anything in the DB and fit in L4 as well
|
||||
ASSERT_EQ("2,0,0,0,3", FilesPerLevel());
|
||||
@@ -2920,9 +2950,9 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedUniversal) {
|
||||
for (int i = 10; i <= 40; i++) {
|
||||
file_data.emplace_back(Key(i), "L3");
|
||||
}
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, file_data, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, false, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, file_data, -1, true,
|
||||
verify_checksums_before_ingest, false,
|
||||
false, &true_data));
|
||||
|
||||
// This file overlap with files in L4, we will ingest it into the closest
|
||||
// non-overlapping level, in this case, it's L3.
|
||||
@@ -2947,20 +2977,19 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoMemtableFlush) {
|
||||
&entries_in_memtable));
|
||||
ASSERT_GE(entries_in_memtable, 1);
|
||||
|
||||
bool write_global_seqno = std::get<0>(GetParam());
|
||||
bool verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
bool verify_checksums_before_ingest = std::get<0>(GetParam());
|
||||
// No need for flush
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {90, 100, 110}, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, false, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {90, 100, 110}, -1, true,
|
||||
verify_checksums_before_ingest, false,
|
||||
false, &true_data));
|
||||
ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumEntriesActiveMemTable,
|
||||
&entries_in_memtable));
|
||||
ASSERT_GE(entries_in_memtable, 1);
|
||||
|
||||
// This file will flush the memtable
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {19, 20, 21}, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, false, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {19, 20, 21}, -1, true,
|
||||
verify_checksums_before_ingest, false,
|
||||
false, &true_data));
|
||||
ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumEntriesActiveMemTable,
|
||||
&entries_in_memtable));
|
||||
ASSERT_EQ(entries_in_memtable, 0);
|
||||
@@ -2974,17 +3003,17 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoMemtableFlush) {
|
||||
ASSERT_GE(entries_in_memtable, 1);
|
||||
|
||||
// No need for flush, this file keys fit between the memtable keys
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {202, 203, 204}, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, false, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {202, 203, 204}, -1, true,
|
||||
verify_checksums_before_ingest, false,
|
||||
false, &true_data));
|
||||
ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumEntriesActiveMemTable,
|
||||
&entries_in_memtable));
|
||||
ASSERT_GE(entries_in_memtable, 1);
|
||||
|
||||
// This file will flush the memtable
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {206, 207}, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, false, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {206, 207}, -1, true,
|
||||
verify_checksums_before_ingest, false,
|
||||
false, &true_data));
|
||||
ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumEntriesActiveMemTable,
|
||||
&entries_in_memtable));
|
||||
ASSERT_EQ(entries_in_memtable, 0);
|
||||
@@ -3002,16 +3031,13 @@ TEST_P(ExternalSSTFileTest, L0SortingIssue) {
|
||||
ASSERT_OK(Put(Key(1), "memtable"));
|
||||
ASSERT_OK(Put(Key(10), "memtable"));
|
||||
|
||||
bool write_global_seqno = std::get<0>(GetParam());
|
||||
bool verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
bool verify_checksums_before_ingest = std::get<0>(GetParam());
|
||||
// No Flush needed, No global seqno needed, Ingest in L1
|
||||
ASSERT_OK(
|
||||
GenerateAndAddExternalFile(options, {7, 8}, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, false));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {7, 8}, -1, true, verify_checksums_before_ingest, false, false));
|
||||
// No Flush needed, but need a global seqno, Ingest in L0
|
||||
ASSERT_OK(
|
||||
GenerateAndAddExternalFile(options, {7, 8}, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, false));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {7, 8}, -1, true, verify_checksums_before_ingest, false, false));
|
||||
printf("%s\n", FilesPerLevel().c_str());
|
||||
|
||||
// Overwrite what we added using external files
|
||||
@@ -3282,12 +3308,11 @@ TEST_P(ExternalSSTFileTest, IngestionListener) {
|
||||
options.listeners.emplace_back(listener);
|
||||
CreateAndReopenWithCF({"koko", "toto"}, options);
|
||||
|
||||
bool write_global_seqno = std::get<0>(GetParam());
|
||||
bool verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
bool verify_checksums_before_ingest = std::get<0>(GetParam());
|
||||
// Ingest into default cf
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {1, 2}, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, true, nullptr, handles_[0]));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 2}, -1, true,
|
||||
verify_checksums_before_ingest, false,
|
||||
true, nullptr, handles_[0]));
|
||||
ASSERT_EQ(listener->ingested_files.size(), 1);
|
||||
ASSERT_EQ(listener->ingested_files.back().cf_name, "default");
|
||||
ASSERT_EQ(listener->ingested_files.back().global_seqno, 0);
|
||||
@@ -3297,9 +3322,9 @@ TEST_P(ExternalSSTFileTest, IngestionListener) {
|
||||
"default");
|
||||
|
||||
// Ingest into cf1
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {1, 2}, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, true, nullptr, handles_[1]));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 2}, -1, true,
|
||||
verify_checksums_before_ingest, false,
|
||||
true, nullptr, handles_[1]));
|
||||
ASSERT_EQ(listener->ingested_files.size(), 2);
|
||||
ASSERT_EQ(listener->ingested_files.back().cf_name, "koko");
|
||||
ASSERT_EQ(listener->ingested_files.back().global_seqno, 0);
|
||||
@@ -3309,9 +3334,9 @@ TEST_P(ExternalSSTFileTest, IngestionListener) {
|
||||
"koko");
|
||||
|
||||
// Ingest into cf2
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {1, 2}, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, true, nullptr, handles_[2]));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 2}, -1, true,
|
||||
verify_checksums_before_ingest, false,
|
||||
true, nullptr, handles_[2]));
|
||||
ASSERT_EQ(listener->ingested_files.size(), 3);
|
||||
ASSERT_EQ(listener->ingested_files.back().cf_name, "toto");
|
||||
ASSERT_EQ(listener->ingested_files.back().global_seqno, 0);
|
||||
@@ -3378,12 +3403,11 @@ TEST_P(ExternalSSTFileTest, IngestBehind) {
|
||||
|
||||
bool allow_global_seqno = true;
|
||||
bool ingest_behind = true;
|
||||
bool write_global_seqno = std::get<0>(GetParam());
|
||||
bool verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
bool verify_checksums_before_ingest = std::get<0>(GetParam());
|
||||
|
||||
// Can't ingest behind since allow_ingest_behind isn't set to true
|
||||
ASSERT_NOK(GenerateAndAddExternalFile(
|
||||
options, file_data, -1, allow_global_seqno, write_global_seqno,
|
||||
options, file_data, -1, allow_global_seqno,
|
||||
verify_checksums_before_ingest, ingest_behind, false /*sort_data*/,
|
||||
&true_data));
|
||||
|
||||
@@ -3409,13 +3433,13 @@ TEST_P(ExternalSSTFileTest, IngestBehind) {
|
||||
// Universal picker should go at second from the bottom level
|
||||
ASSERT_EQ("0,1", FilesPerLevel());
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, file_data, -1, allow_global_seqno, write_global_seqno,
|
||||
options, file_data, -1, allow_global_seqno,
|
||||
verify_checksums_before_ingest, true /*ingest_behind*/,
|
||||
false /*sort_data*/, &true_data));
|
||||
ASSERT_EQ("0,1,1", FilesPerLevel());
|
||||
// this time ingest should fail as the file doesn't fit to the bottom level
|
||||
ASSERT_NOK(GenerateAndAddExternalFile(
|
||||
options, file_data, -1, allow_global_seqno, write_global_seqno,
|
||||
options, file_data, -1, allow_global_seqno,
|
||||
verify_checksums_before_ingest, true /*ingest_behind*/,
|
||||
false /*sort_data*/, &true_data));
|
||||
ASSERT_EQ("0,1,1", FilesPerLevel());
|
||||
@@ -3459,9 +3483,8 @@ TEST_P(ExternalSSTFileTest, IngestBehind) {
|
||||
ASSERT_OK(db_->CreateColumnFamily(new_cf_option, "new_cf", &new_cfh));
|
||||
ASSERT_TRUE(GenerateAndAddExternalFile(
|
||||
new_cf_option, file_data, -1, allow_global_seqno,
|
||||
write_global_seqno, verify_checksums_before_ingest,
|
||||
true /*ingest_behind*/, false /*sort_data*/, nullptr,
|
||||
/*cfh=*/new_cfh)
|
||||
verify_checksums_before_ingest, true /*ingest_behind*/,
|
||||
false /*sort_data*/, nullptr, /*cfh=*/new_cfh)
|
||||
.IsInvalidArgument());
|
||||
ASSERT_OK(db_->DropColumnFamily(new_cfh));
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(new_cfh));
|
||||
@@ -3624,10 +3647,8 @@ TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_Success) {
|
||||
std::vector<IngestExternalFileOptions> ifos(column_families.size());
|
||||
for (auto& ifo : ifos) {
|
||||
ifo.allow_global_seqno = true; // Always allow global_seqno
|
||||
// May or may not write global_seqno
|
||||
ifo.write_global_seqno = std::get<0>(GetParam());
|
||||
// Whether to verify checksums before ingestion
|
||||
ifo.verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
ifo.verify_checksums_before_ingest = std::get<0>(GetParam());
|
||||
}
|
||||
std::vector<std::vector<std::pair<std::string, std::string>>> data;
|
||||
data.push_back(
|
||||
@@ -3698,10 +3719,8 @@ TEST_P(ExternalSSTFileTest,
|
||||
std::vector<IngestExternalFileOptions> ifos(column_families.size());
|
||||
for (auto& ifo : ifos) {
|
||||
ifo.allow_global_seqno = true; // Always allow global_seqno
|
||||
// May or may not write global_seqno
|
||||
ifo.write_global_seqno = std::get<0>(GetParam());
|
||||
// Whether to verify checksums before ingestion
|
||||
ifo.verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
ifo.verify_checksums_before_ingest = std::get<0>(GetParam());
|
||||
}
|
||||
std::vector<std::vector<std::pair<std::string, std::string>>> data;
|
||||
data.push_back(
|
||||
@@ -3806,10 +3825,8 @@ TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_PrepareFail) {
|
||||
std::vector<IngestExternalFileOptions> ifos(column_families.size());
|
||||
for (auto& ifo : ifos) {
|
||||
ifo.allow_global_seqno = true; // Always allow global_seqno
|
||||
// May or may not write global_seqno
|
||||
ifo.write_global_seqno = std::get<0>(GetParam());
|
||||
// Whether to verify block checksums before ingest
|
||||
ifo.verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
ifo.verify_checksums_before_ingest = std::get<0>(GetParam());
|
||||
}
|
||||
std::vector<std::vector<std::pair<std::string, std::string>>> data;
|
||||
data.push_back(
|
||||
@@ -3876,10 +3893,8 @@ TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_CommitFail) {
|
||||
std::vector<IngestExternalFileOptions> ifos(column_families.size());
|
||||
for (auto& ifo : ifos) {
|
||||
ifo.allow_global_seqno = true; // Always allow global_seqno
|
||||
// May or may not write global_seqno
|
||||
ifo.write_global_seqno = std::get<0>(GetParam());
|
||||
// Whether to verify block checksums before ingestion
|
||||
ifo.verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
ifo.verify_checksums_before_ingest = std::get<0>(GetParam());
|
||||
}
|
||||
std::vector<std::vector<std::pair<std::string, std::string>>> data;
|
||||
data.push_back(
|
||||
@@ -3950,10 +3965,8 @@ TEST_P(ExternalSSTFileTest,
|
||||
std::vector<IngestExternalFileOptions> ifos(column_families.size());
|
||||
for (auto& ifo : ifos) {
|
||||
ifo.allow_global_seqno = true; // Always allow global_seqno
|
||||
// May or may not write global_seqno
|
||||
ifo.write_global_seqno = std::get<0>(GetParam());
|
||||
// Whether to verify block checksums before ingestion
|
||||
ifo.verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
ifo.verify_checksums_before_ingest = std::get<0>(GetParam());
|
||||
}
|
||||
std::vector<std::vector<std::pair<std::string, std::string>>> data;
|
||||
data.push_back(
|
||||
@@ -4133,7 +4146,7 @@ TEST_F(ExternalSSTFileTest, FIFOCompaction) {
|
||||
}
|
||||
// Overlaps with memtable, will trigger flush
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, file_data, -1,
|
||||
/*allow_global_seqno=*/true, true, false,
|
||||
/*allow_global_seqno=*/true, false,
|
||||
false, false, &true_data));
|
||||
ASSERT_EQ("2", FilesPerLevel());
|
||||
|
||||
@@ -4143,10 +4156,10 @@ TEST_F(ExternalSSTFileTest, FIFOCompaction) {
|
||||
}
|
||||
// global sequence number is always assigned, so this will fail
|
||||
ASSERT_NOK(GenerateAndAddExternalFile(options, file_data, -1,
|
||||
/*allow_global_seqno=*/false, true,
|
||||
false, false, false, &true_data));
|
||||
/*allow_global_seqno=*/false, false,
|
||||
false, false, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, file_data, -1,
|
||||
/*allow_global_seqno=*/true, true, false,
|
||||
/*allow_global_seqno=*/true, false,
|
||||
false, false, &true_data));
|
||||
|
||||
// Compact to data to lower level to test multi-level FIFO later
|
||||
@@ -4167,7 +4180,7 @@ TEST_F(ExternalSSTFileTest, FIFOCompaction) {
|
||||
}
|
||||
// Files are ingested into L0 for multi-level FIFO
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, file_data, -1,
|
||||
/*allow_global_seqno=*/true, true, false,
|
||||
/*allow_global_seqno=*/true, false,
|
||||
false, false, &true_data));
|
||||
|
||||
ASSERT_EQ("1,0,0,0,0,0,1", FilesPerLevel());
|
||||
@@ -4835,8 +4848,7 @@ TEST_F(ExternalSSTFileWithTimestampTest,
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(ExternalSSTFileTest, ExternalSSTFileTest,
|
||||
testing::Combine(testing::Bool(), testing::Bool(),
|
||||
testing::Bool()));
|
||||
testing::Combine(testing::Bool(), testing::Bool()));
|
||||
|
||||
class IngestDBGeneratedFileTest
|
||||
: public ExternalSSTFileTestBase,
|
||||
@@ -4916,14 +4928,6 @@ TEST_P(IngestDBGeneratedFileTest, FailureCase) {
|
||||
std::string::npos);
|
||||
ingest_opts.fail_if_not_bottommost_level = false;
|
||||
}
|
||||
ingest_opts.write_global_seqno = true;
|
||||
s = db_->IngestExternalFile(to_ingest_files, ingest_opts);
|
||||
ASSERT_TRUE(s.ToString().find("write_global_seqno is deprecated and does "
|
||||
"not work with allow_db_generated_files") !=
|
||||
std::string::npos);
|
||||
ASSERT_NOK(s);
|
||||
ingest_opts.write_global_seqno = false;
|
||||
|
||||
// Delete the overlapping key.
|
||||
ASSERT_OK(db_->Delete(WriteOptions(), handles_[1], Key(49)));
|
||||
ASSERT_OK(db_->CompactRange(cro, handles_[1], nullptr, nullptr));
|
||||
|
||||
+14
-10
@@ -502,9 +502,13 @@ Status FlushJob::MemPurge() {
|
||||
}
|
||||
}
|
||||
|
||||
// MemPurge output is installed directly into the immutable list, so it
|
||||
// must not advertise memory that can be reclaimed by switching a mutable
|
||||
// memtable.
|
||||
new_mem = new MemTable(cfd_->internal_comparator(), cfd_->ioptions(),
|
||||
mutable_cf_options_, cfd_->write_buffer_mgr(),
|
||||
earliest_seqno, cfd_->GetID());
|
||||
earliest_seqno, cfd_->GetID(),
|
||||
nullptr /* flush_initiator */);
|
||||
assert(new_mem != nullptr);
|
||||
|
||||
Env* env = db_options_.env;
|
||||
@@ -1002,11 +1006,11 @@ Status FlushJob::WriteLevel0Table() {
|
||||
auto status = clock_->GetCurrentTime(&_current_time);
|
||||
// Safe to proceed even if GetCurrentTime fails. So, log and proceed.
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"Failed to get current time to populate creation_time property. "
|
||||
"Status: %s",
|
||||
status.ToString().c_str());
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to get current time to populate "
|
||||
"oldest_ancestor_time property. "
|
||||
"Status: %s",
|
||||
status.ToString().c_str());
|
||||
}
|
||||
const uint64_t current_time = static_cast<uint64_t>(_current_time);
|
||||
|
||||
@@ -1014,12 +1018,12 @@ Status FlushJob::WriteLevel0Table() {
|
||||
|
||||
// It's not clear whether oldest_key_time is always available. In case
|
||||
// it is not available, use current_time.
|
||||
uint64_t oldest_ancester_time = std::min(current_time, oldest_key_time);
|
||||
uint64_t oldest_ancestor_time = std::min(current_time, oldest_key_time);
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"FlushJob::WriteLevel0Table:oldest_ancester_time",
|
||||
&oldest_ancester_time);
|
||||
meta_.oldest_ancester_time = oldest_ancester_time;
|
||||
"FlushJob::WriteLevel0Table:oldest_ancestor_time",
|
||||
&oldest_ancestor_time);
|
||||
meta_.oldest_ancestor_time = oldest_ancestor_time;
|
||||
meta_.file_creation_time = current_time;
|
||||
|
||||
uint64_t memtable_payload_bytes = 0;
|
||||
|
||||
@@ -164,13 +164,13 @@ Status ImportColumnFamilyJob::Prepare(uint64_t next_file_number,
|
||||
// REQUIRES: we have become the only writer by entering both write_thread_ and
|
||||
// nonmem_write_thread_
|
||||
Status ImportColumnFamilyJob::Run() {
|
||||
// We use the import time as the ancester time. This is the time the data
|
||||
// We use the import time as the ancestor time. This is the time the data
|
||||
// is written to the database.
|
||||
int64_t temp_current_time = 0;
|
||||
uint64_t oldest_ancester_time = kUnknownOldestAncesterTime;
|
||||
uint64_t current_time = kUnknownOldestAncesterTime;
|
||||
uint64_t oldest_ancestor_time = kUnknownOldestAncestorTime;
|
||||
uint64_t current_time = kUnknownOldestAncestorTime;
|
||||
if (clock_->GetCurrentTime(&temp_current_time).ok()) {
|
||||
current_time = oldest_ancester_time =
|
||||
current_time = oldest_ancestor_time =
|
||||
static_cast<uint64_t>(temp_current_time);
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ Status ImportColumnFamilyJob::Run() {
|
||||
f.fd.GetFileSize(), f.smallest_internal_key, f.largest_internal_key,
|
||||
file_metadata.smallest_seqno, file_metadata.largest_seqno, false,
|
||||
file_metadata.temperature, kInvalidBlobFileNumber,
|
||||
oldest_ancester_time, current_time, file_metadata.epoch_number,
|
||||
oldest_ancestor_time, current_time, file_metadata.epoch_number,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName, f.unique_id, 0,
|
||||
tail_size,
|
||||
static_cast<bool>(
|
||||
|
||||
+2
-2
@@ -897,8 +897,8 @@ class RetriableLogTest : public ::testing::TestWithParam<int> {
|
||||
}
|
||||
|
||||
void Write(const Slice& data) {
|
||||
ASSERT_OK(writer_->Append(IOOptions(), data));
|
||||
ASSERT_OK(writer_->Sync(IOOptions(), true));
|
||||
ASSERT_OK(writer_->Append(data, IOOptions()));
|
||||
ASSERT_OK(writer_->Fsync(IOOptions()));
|
||||
}
|
||||
|
||||
bool TryRead(std::string* result) {
|
||||
|
||||
+5
-6
@@ -116,10 +116,9 @@ IOStatus Writer::AddRecord(const WriteOptions& write_options,
|
||||
// Fill the trailer (literal below relies on kHeaderSize and
|
||||
// kRecyclableHeaderSize being <= 11)
|
||||
assert(header_size_ <= 11);
|
||||
s = dest_->Append(opts,
|
||||
Slice("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
s = dest_->Append(Slice("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
static_cast<size_t>(leftover)),
|
||||
0 /* crc32c_checksum */);
|
||||
opts);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
@@ -350,10 +349,10 @@ IOStatus Writer::EmitPhysicalRecord(const WriteOptions& write_options,
|
||||
IOOptions opts;
|
||||
IOStatus s = WritableFileWriter::PrepareIOOptions(write_options, opts);
|
||||
if (s.ok()) {
|
||||
s = dest_->Append(opts, Slice(buf, header_size), 0 /* crc32c_checksum */);
|
||||
s = dest_->Append(Slice(buf, header_size), opts);
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = dest_->Append(opts, Slice(ptr, n), payload_crc);
|
||||
s = dest_->Append(Slice(ptr, n), opts, Crc32cChecksum(payload_crc));
|
||||
}
|
||||
block_offset_ += header_size + n;
|
||||
return s;
|
||||
@@ -387,7 +386,7 @@ IOStatus Writer::MaybeSwitchToNewBlock(const WriteOptions& write_options,
|
||||
}
|
||||
|
||||
std::vector<char> trailer(leftover, '\x00');
|
||||
s = dest_->Append(opts, Slice(trailer.data(), trailer.size()));
|
||||
s = dest_->Append(Slice(trailer.data(), trailer.size()), opts);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
+8
-4
@@ -189,11 +189,12 @@ MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
const ImmutableOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
SequenceNumber latest_seq, uint32_t column_family_id)
|
||||
SequenceNumber latest_seq, uint32_t column_family_id,
|
||||
FlushInitiator* flush_initiator)
|
||||
: comparator_(cmp),
|
||||
moptions_(ioptions, mutable_cf_options),
|
||||
kArenaBlockSize(Arena::OptimizeBlockSize(moptions_.arena_block_size)),
|
||||
mem_tracker_(write_buffer_manager),
|
||||
mem_tracker_(write_buffer_manager, flush_initiator),
|
||||
arena_(moptions_.arena_block_size,
|
||||
(write_buffer_manager != nullptr &&
|
||||
(write_buffer_manager->enabled() ||
|
||||
@@ -1165,6 +1166,7 @@ Status MemTable::Add(SequenceNumber s, ValueType type,
|
||||
}
|
||||
|
||||
Slice key_without_ts = StripTimestampFromUserKey(key, ts_sz_);
|
||||
bool activate_flush_initiator = false;
|
||||
|
||||
if (!allow_concurrent) {
|
||||
// Extract prefix for insert with hint. Hints are for point key table
|
||||
@@ -1187,7 +1189,7 @@ Status MemTable::Add(SequenceNumber s, ValueType type,
|
||||
// (non-concurrent), these counters may be concurrently modified by
|
||||
// BatchPostProcess() (e.g., from AddLogicallyRedundantRangeTombstone
|
||||
// called on a read path).
|
||||
num_entries_.FetchAddRelaxed(1);
|
||||
activate_flush_initiator = num_entries_.FetchAddRelaxed(1) == 0;
|
||||
data_size_.FetchAddRelaxed(encoded_len);
|
||||
if (type == kTypeDeletion || type == kTypeSingleDeletion ||
|
||||
type == kTypeDeletionWithTimestamp) {
|
||||
@@ -1244,6 +1246,9 @@ Status MemTable::Add(SequenceNumber s, ValueType type,
|
||||
while ((cur_earliest_seqno == kMaxSequenceNumber || s < cur_earliest_seqno) &&
|
||||
!earliest_seqno_.compare_exchange_weak(cur_earliest_seqno, s)) {
|
||||
}
|
||||
if (activate_flush_initiator) {
|
||||
mem_tracker_.ActivateFlushInitiator();
|
||||
}
|
||||
MaybeUpdateNewestUDT(key_slice);
|
||||
if (type == kTypeRangeDeletion) {
|
||||
auto new_cache = std::make_shared<FragmentedRangeTombstoneListCache>();
|
||||
@@ -1274,7 +1279,6 @@ Status MemTable::Add(SequenceNumber s, ValueType type,
|
||||
is_range_del_table_empty_.StoreRelaxed(false);
|
||||
}
|
||||
UpdateOldestKeyTime();
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("MemTable::Add:BeforeReturn:Encoded", &encoded);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
+29
-2
@@ -126,6 +126,10 @@ class ReadOnlyMemTable {
|
||||
// used by MemTableListVersion::MemoryAllocatedBytesExcludingLast
|
||||
virtual size_t MemoryAllocatedBytes() const = 0;
|
||||
|
||||
// Returns memory charged to this memtable's WriteBufferManager. Memtable
|
||||
// implementations that do not allocate through a WBM return zero.
|
||||
virtual size_t WBMTrackedMemoryUsage() const = 0;
|
||||
|
||||
// Returns a vector of unique random memtable entries of size 'sample_size'.
|
||||
//
|
||||
// Note: the entries are stored in the unordered_set as length-prefixed keys,
|
||||
@@ -597,7 +601,8 @@ class MemTable final : public ReadOnlyMemTable {
|
||||
const ImmutableOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
SequenceNumber earliest_seq, uint32_t column_family_id);
|
||||
SequenceNumber earliest_seq, uint32_t column_family_id,
|
||||
FlushInitiator* flush_initiator);
|
||||
// No copying allowed
|
||||
MemTable(const MemTable&) = delete;
|
||||
MemTable& operator=(const MemTable&) = delete;
|
||||
@@ -620,6 +625,24 @@ class MemTable final : public ReadOnlyMemTable {
|
||||
arena_.MemoryAllocatedBytes();
|
||||
}
|
||||
|
||||
size_t WBMTrackedMemoryUsage() const override {
|
||||
return mem_tracker_.allocated_bytes();
|
||||
}
|
||||
|
||||
bool IsWBMTrackingActive() const {
|
||||
return mem_tracker_.IsFlushInitiatorActive();
|
||||
}
|
||||
|
||||
void RefreshWBMTracking() {
|
||||
ReadLock rl(&immutable_mutex_);
|
||||
mem_tracker_.RefreshFlushInitiator();
|
||||
}
|
||||
|
||||
void StopWBMTracking() {
|
||||
WriteLock wl(&immutable_mutex_);
|
||||
mem_tracker_.DeactivateFlushInitiator();
|
||||
}
|
||||
|
||||
void UniqueRandomSample(const uint64_t& target_sample_size,
|
||||
std::unordered_set<const char*>* entries) override {
|
||||
// TODO(bjlemaire): at the moment, only supported by skiplistrep.
|
||||
@@ -739,7 +762,11 @@ class MemTable final : public ReadOnlyMemTable {
|
||||
// Used in concurrent memtable inserts.
|
||||
void BatchPostProcess(const MemTablePostProcessInfo& update_counters) {
|
||||
table_->BatchPostProcess();
|
||||
num_entries_.FetchAddRelaxed(update_counters.num_entries);
|
||||
const uint64_t old_num_entries =
|
||||
num_entries_.FetchAddRelaxed(update_counters.num_entries);
|
||||
if (old_num_entries == 0 && update_counters.num_entries != 0) {
|
||||
mem_tracker_.ActivateFlushInitiator();
|
||||
}
|
||||
data_size_.FetchAddRelaxed(update_counters.data_size);
|
||||
if (update_counters.num_deletes != 0) {
|
||||
num_deletes_.FetchAddRelaxed(update_counters.num_deletes);
|
||||
|
||||
@@ -794,6 +794,24 @@ size_t MemTableList::ApproximateUnflushedMemTablesMemoryUsage() {
|
||||
return total_size;
|
||||
}
|
||||
|
||||
size_t MemTableList::WBMTrackedUnflushedMemTablesMemoryUsage() const {
|
||||
size_t total_size = 0;
|
||||
for (const ReadOnlyMemTable* memtable : current_->memlist_) {
|
||||
total_size += memtable->WBMTrackedMemoryUsage();
|
||||
}
|
||||
return total_size;
|
||||
}
|
||||
|
||||
size_t MemTableList::WBMTrackedUnstartedMemTablesMemoryUsage() const {
|
||||
size_t total_size = 0;
|
||||
for (const ReadOnlyMemTable* memtable : current_->memlist_) {
|
||||
if (!memtable->flush_in_progress_) {
|
||||
total_size += memtable->WBMTrackedMemoryUsage();
|
||||
}
|
||||
}
|
||||
return total_size;
|
||||
}
|
||||
|
||||
size_t MemTableList::ApproximateMemoryUsage() { return current_memory_usage_; }
|
||||
|
||||
size_t MemTableList::MemoryAllocatedBytesExcludingLast() const {
|
||||
|
||||
@@ -399,6 +399,14 @@ class MemTableList {
|
||||
// the unflushed mem-tables.
|
||||
size_t ApproximateUnflushedMemTablesMemoryUsage();
|
||||
|
||||
// Returns the memory charged to the WriteBufferManager by unflushed
|
||||
// memtables.
|
||||
size_t WBMTrackedUnflushedMemTablesMemoryUsage() const;
|
||||
|
||||
// Returns WBM-tracked memory for immutable memtables whose flush has not
|
||||
// started. Requires the DB mutex, like the other MemTableList accessors.
|
||||
size_t WBMTrackedUnstartedMemTablesMemoryUsage() const;
|
||||
|
||||
// Returns an estimate of the timestamp of the earliest key.
|
||||
uint64_t ApproximateOldestKeyTime() const;
|
||||
|
||||
|
||||
+28
-18
@@ -267,7 +267,8 @@ TEST_F(MemTableListTest, GetTest) {
|
||||
|
||||
WriteBufferManager wb(options.db_write_buffer_size);
|
||||
MemTable* mem = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb,
|
||||
kMaxSequenceNumber, 0 /* column_family_id */);
|
||||
kMaxSequenceNumber, 0 /* column_family_id */,
|
||||
nullptr /* flush_initiator */);
|
||||
mem->Ref();
|
||||
|
||||
// Write some keys to this memtable.
|
||||
@@ -335,7 +336,8 @@ TEST_F(MemTableListTest, GetTest) {
|
||||
// Create another memtable and write some keys to it
|
||||
WriteBufferManager wb2(options.db_write_buffer_size);
|
||||
MemTable* mem2 = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb2,
|
||||
kMaxSequenceNumber, 0 /* column_family_id */);
|
||||
kMaxSequenceNumber, 0 /* column_family_id */,
|
||||
nullptr /* flush_initiator */);
|
||||
mem2->SetID(2);
|
||||
mem2->Ref();
|
||||
|
||||
@@ -432,7 +434,8 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
|
||||
WriteBufferManager wb(options.db_write_buffer_size);
|
||||
MemTable* mem = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb,
|
||||
kMaxSequenceNumber, 0 /* column_family_id */);
|
||||
kMaxSequenceNumber, 0 /* column_family_id */,
|
||||
nullptr /* flush_initiator */);
|
||||
mem->Ref();
|
||||
|
||||
// Write some keys to this memtable.
|
||||
@@ -536,7 +539,8 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
// Create another memtable and write some keys to it
|
||||
WriteBufferManager wb2(options.db_write_buffer_size);
|
||||
MemTable* mem2 = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb2,
|
||||
kMaxSequenceNumber, 0 /* column_family_id */);
|
||||
kMaxSequenceNumber, 0 /* column_family_id */,
|
||||
nullptr /* flush_initiator */);
|
||||
mem2->Ref();
|
||||
|
||||
ASSERT_OK(
|
||||
@@ -566,7 +570,8 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
// Add a third memtable to push the first memtable out of the history
|
||||
WriteBufferManager wb3(options.db_write_buffer_size);
|
||||
MemTable* mem3 = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb3,
|
||||
kMaxSequenceNumber, 0 /* column_family_id */);
|
||||
kMaxSequenceNumber, 0 /* column_family_id */,
|
||||
nullptr /* flush_initiator */);
|
||||
mem3->Ref();
|
||||
// This is to make assert(memtable->IsFragmentedRangeTombstonesConstructed())
|
||||
// in MemTableListVersion::GetFromList work.
|
||||
@@ -660,8 +665,9 @@ TEST_F(MemTableListTest, FlushPendingTest) {
|
||||
std::vector<MemTable*> tables;
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
for (int i = 0; i < num_tables; i++) {
|
||||
MemTable* mem = new MemTable(cmp, ioptions, mutable_cf_options, &wb,
|
||||
kMaxSequenceNumber, 0 /* column_family_id */);
|
||||
MemTable* mem =
|
||||
new MemTable(cmp, ioptions, mutable_cf_options, &wb, kMaxSequenceNumber,
|
||||
0 /* column_family_id */, nullptr /* flush_initiator */);
|
||||
mem->SetID(memtable_id++);
|
||||
mem->Ref();
|
||||
|
||||
@@ -935,8 +941,9 @@ TEST_F(MemTableListTest, FlushRequestPersistsAfterPartialPick) {
|
||||
0 /* max_write_buffer_size_to_maintain */);
|
||||
|
||||
for (uint64_t memtable_id = 0; memtable_id < 2; ++memtable_id) {
|
||||
MemTable* mem = new MemTable(cmp, ioptions, mutable_cf_options, &wb,
|
||||
kMaxSequenceNumber, 0 /* column_family_id */);
|
||||
MemTable* mem =
|
||||
new MemTable(cmp, ioptions, mutable_cf_options, &wb, kMaxSequenceNumber,
|
||||
0 /* column_family_id */, nullptr /* flush_initiator */);
|
||||
mem->SetID(memtable_id);
|
||||
mem->Ref();
|
||||
ASSERT_OK(mem->Add(++seq, kTypeValue, "key" + std::to_string(memtable_id),
|
||||
@@ -991,8 +998,9 @@ TEST_F(MemTableListTest, RemoveOldMemTablesTest) {
|
||||
// WAL 20.
|
||||
SequenceNumber seq = 1;
|
||||
for (uint64_t next_log_number : {10, 20}) {
|
||||
MemTable* mem = new MemTable(cmp, ioptions, mutable_cf_options, &wb,
|
||||
kMaxSequenceNumber, 0 /* column_family_id */);
|
||||
MemTable* mem =
|
||||
new MemTable(cmp, ioptions, mutable_cf_options, &wb, kMaxSequenceNumber,
|
||||
0 /* column_family_id */, nullptr /* flush_initiator */);
|
||||
mem->Ref();
|
||||
ASSERT_OK(mem->Add(++seq, kTypeValue, "key", "value",
|
||||
nullptr /* kv_prot_info */));
|
||||
@@ -1065,9 +1073,9 @@ TEST_F(MemTableListTest, AtomicFlushTest) {
|
||||
mutable_cf_options_list.emplace_back(new MutableCFOptions(options));
|
||||
uint64_t memtable_id = 0;
|
||||
for (int i = 0; i != num_tables_per_cf; ++i) {
|
||||
MemTable* mem =
|
||||
new MemTable(cmp, ioptions, *(mutable_cf_options_list.back()), &wb,
|
||||
kMaxSequenceNumber, cf_id);
|
||||
MemTable* mem = new MemTable(
|
||||
cmp, ioptions, *(mutable_cf_options_list.back()), &wb,
|
||||
kMaxSequenceNumber, cf_id, nullptr /* flush_initiator */);
|
||||
mem->SetID(memtable_id++);
|
||||
mem->Ref();
|
||||
|
||||
@@ -1218,8 +1226,9 @@ TEST_F(MemTableListWithTimestampTest, GetTableNewestUDT) {
|
||||
std::string key;
|
||||
std::string write_ts;
|
||||
for (int i = 0; i < num_tables; i++) {
|
||||
MemTable* mem = new MemTable(cmp, ioptions, mutable_cf_options, &wb,
|
||||
kMaxSequenceNumber, 0 /* column_family_id */);
|
||||
MemTable* mem =
|
||||
new MemTable(cmp, ioptions, mutable_cf_options, &wb, kMaxSequenceNumber,
|
||||
0 /* column_family_id */, nullptr /* flush_initiator */);
|
||||
mem->SetID(memtable_id++);
|
||||
mem->Ref();
|
||||
|
||||
@@ -1271,8 +1280,9 @@ TEST_F(MemTableListWithTimestampTest, ConcurrentGetTableNewestUDT) {
|
||||
WriteBufferManager wb(options.db_write_buffer_size);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
|
||||
MemTable* mem = new MemTable(cmp, ioptions, mutable_cf_options, &wb,
|
||||
kMaxSequenceNumber, 0 /* column_family_id */);
|
||||
MemTable* mem =
|
||||
new MemTable(cmp, ioptions, mutable_cf_options, &wb, kMaxSequenceNumber,
|
||||
0 /* column_family_id */, nullptr /* flush_initiator */);
|
||||
mem->Ref();
|
||||
|
||||
std::atomic<uint64_t> next_seq{1};
|
||||
|
||||
+2
-2
@@ -575,7 +575,7 @@ class Repairer {
|
||||
status =
|
||||
AddColumnFamily(props->column_family_name, t->column_family_id);
|
||||
}
|
||||
t->meta.oldest_ancester_time = props->creation_time;
|
||||
t->meta.oldest_ancestor_time = props->oldest_ancestor_time;
|
||||
t->meta.user_defined_timestamps_persisted =
|
||||
static_cast<bool>(props->user_defined_timestamps_persisted);
|
||||
}
|
||||
@@ -712,7 +712,7 @@ class Repairer {
|
||||
0, meta.fd.GetNumber(), meta.fd.GetPathId(), meta.fd.GetFileSize(),
|
||||
meta.smallest, meta.largest, meta.fd.smallest_seqno,
|
||||
meta.fd.largest_seqno, meta.marked_for_compaction, meta.temperature,
|
||||
meta.oldest_blob_file_number, meta.oldest_ancester_time,
|
||||
meta.oldest_blob_file_number, meta.oldest_ancestor_time,
|
||||
meta.file_creation_time, meta.epoch_number, meta.file_checksum,
|
||||
meta.file_checksum_func_name, meta.unique_id,
|
||||
meta.compensated_range_deletion_size, meta.tail_size,
|
||||
|
||||
+11
-8
@@ -10,6 +10,7 @@
|
||||
#include "db/table_cache.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "db/range_tombstone_fragmenter.h"
|
||||
@@ -72,7 +73,8 @@ TableCache::TableCache(const ImmutableOptions& ioptions,
|
||||
const FileOptions* file_options, Cache* const cache,
|
||||
BlockCacheTracer* const block_cache_tracer,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::string& db_session_id, bool fast_sst_open)
|
||||
const std::string& db_session_id, bool fast_sst_open,
|
||||
std::string column_family_name)
|
||||
: ioptions_(ioptions),
|
||||
file_options_(*file_options),
|
||||
cache_(cache),
|
||||
@@ -83,7 +85,8 @@ TableCache::TableCache(const ImmutableOptions& ioptions,
|
||||
block_cache_tracer_(block_cache_tracer),
|
||||
loader_mutex_(kLoadConcurency),
|
||||
io_tracer_(io_tracer),
|
||||
db_session_id_(db_session_id) {
|
||||
db_session_id_(db_session_id),
|
||||
column_family_name_(std::move(column_family_name)) {
|
||||
if (ioptions_.row_cache) {
|
||||
// If the same cache is shared by multiple instances, we need to
|
||||
// disambiguate its entries.
|
||||
@@ -171,11 +174,11 @@ Status TableCache::GetTableReader(
|
||||
}
|
||||
StopWatch sw(ioptions_.clock, ioptions_.stats, TABLE_OPEN_IO_MICROS);
|
||||
std::unique_ptr<RandomAccessFileReader> file_reader(
|
||||
new RandomAccessFileReader(std::move(file), fname, ioptions_.clock,
|
||||
io_tracer_, ioptions_.stats, SST_READ_MICROS,
|
||||
file_read_hist, ioptions_.rate_limiter.get(),
|
||||
ioptions_.listeners, file_temperature,
|
||||
level == ioptions_.num_levels - 1));
|
||||
new RandomAccessFileReader(
|
||||
std::move(file), fname, ioptions_.clock, io_tracer_,
|
||||
ioptions_.stats, SST_READ_MICROS, file_read_hist,
|
||||
ioptions_.rate_limiter.get(), ioptions_.listeners, file_temperature,
|
||||
level == ioptions_.num_levels - 1, ioptions_.fs.get()));
|
||||
UniqueId64x2 expected_unique_id;
|
||||
if (ioptions_.verify_sst_unique_id_in_manifest) {
|
||||
expected_unique_id = file_meta.unique_id;
|
||||
@@ -191,7 +194,7 @@ Status TableCache::GetTableReader(
|
||||
db_session_id_, file_meta.fd.GetNumber(), expected_unique_id,
|
||||
file_meta.fd.largest_seqno, file_meta.tail_size,
|
||||
file_meta.user_defined_timestamps_persisted,
|
||||
avoid_shared_metadata_cache);
|
||||
avoid_shared_metadata_cache, column_family_name_);
|
||||
// Route same-file ("embedded") blob reads through the CFD's BlobSource for
|
||||
// caching + stats. nullptr in non-DB contexts (e.g. repair).
|
||||
table_reader_options.blob_source = blob_source_;
|
||||
|
||||
+3
-1
@@ -67,7 +67,8 @@ class TableCache {
|
||||
const FileOptions* storage_options, Cache* cache,
|
||||
BlockCacheTracer* const block_cache_tracer,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::string& db_session_id, bool fast_sst_open = false);
|
||||
const std::string& db_session_id, bool fast_sst_open = false,
|
||||
std::string column_family_name = "");
|
||||
~TableCache();
|
||||
|
||||
// Sets the BlobSource used to route same-file ("embedded") blob reads of SSTs
|
||||
@@ -364,6 +365,7 @@ class TableCache {
|
||||
Striped<CacheAlignedWrapper<port::Mutex>> loader_mutex_;
|
||||
std::shared_ptr<IOTracer> io_tracer_;
|
||||
std::string db_session_id_;
|
||||
std::string column_family_name_;
|
||||
// Owned by the same ColumnFamilyData; see SetBlobSource(). nullptr in non-DB
|
||||
// contexts (e.g. repair).
|
||||
BlobSource* blob_source_ = nullptr;
|
||||
|
||||
@@ -1718,9 +1718,11 @@ class VersionBuilder::Rep : public MutableScalars {
|
||||
missing_blob_files_high_ != kInvalidBlobFileNumber;
|
||||
}
|
||||
|
||||
std::vector<std::string>& GetAndClearIntermediateFiles() {
|
||||
std::vector<std::string> GetAndClearIntermediateFiles() {
|
||||
assert(track_found_and_missing_files_);
|
||||
return intermediate_files_;
|
||||
std::vector<std::string> result;
|
||||
result.swap(intermediate_files_);
|
||||
return result;
|
||||
}
|
||||
|
||||
void ClearFoundFiles() {
|
||||
@@ -2073,7 +2075,7 @@ bool VersionBuilder::ValidVersionAvailable() {
|
||||
|
||||
bool VersionBuilder::HasMissingFiles() const { return rep_->HasMissingFiles(); }
|
||||
|
||||
std::vector<std::string>& VersionBuilder::GetAndClearIntermediateFiles() {
|
||||
std::vector<std::string> VersionBuilder::GetAndClearIntermediateFiles() {
|
||||
return rep_->GetAndClearIntermediateFiles();
|
||||
}
|
||||
|
||||
|
||||
Loaded 100 of 274 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user